AgentNet 1.0.0-alpha.3
dotnet add package AgentNet --version 1.0.0-alpha.3
NuGet\Install-Package AgentNet -Version 1.0.0-alpha.3
<PackageReference Include="AgentNet" Version="1.0.0-alpha.3" />
<PackageVersion Include="AgentNet" Version="1.0.0-alpha.3" />
<PackageReference Include="AgentNet" />
paket add AgentNet --version 1.0.0-alpha.3
#r "nuget: AgentNet, 1.0.0-alpha.3"
#:package AgentNet@1.0.0-alpha.3
#addin nuget:?package=AgentNet&version=1.0.0-alpha.3&prerelease
#tool nuget:?package=AgentNet&version=1.0.0-alpha.3&prerelease
Agent.NET
Elegant agent workflows for .NET, designed in F#.
What is Agent.NET?
Agent.NET is an F#‑native authoring layer built on top of the Microsoft Agent Framework.
MAF provides a powerful, low‑level foundation for building agent systems — durable state, orchestration primitives, tool execution, and a flexible runtime model.
Agent.NET builds on those capabilities with a higher‑level, ergonomic workflow DSL designed for clarity, composability, and developer experience.
Where MAF offers the essential building blocks, Agent.NET provides the expressive authoring model that makes agent workflows feel natural to write and reason about.
What can you do with Agent.NET?
1. Create chat agents with tools (ChatAgent)
Simple interface: string -> Task<string>. Tools are plain F# functions with metadata from XML docs.
let agent =
ChatAgent.create "You are a helpful assistant."
|> ChatAgent.withTools [searchTool; calculatorTool]
|> ChatAgent.build chatClient
let! text = agent.Chat("Summarize the latest quarterly report.")
2. Create typed agents as functions (TypedAgent<'input,'output>)
Wrap a ChatAgent with format/parse functions for use in workflows or anywhere you'd call a service.
let analyzeAgent: TypedAgent<CustomerMessage, SentimentResult> =
TypedAgent.create formatCustomerMessage parseSentimentResult chatAgent
let! sentiment = analyzeAgent.Invoke message
3. Create workflows (workflow)
Strongly typed orchestration mixing deterministic .NET code with LLM calls. Run in-process or on Azure Durable Functions.
let myWorkflow = workflow {
step loadData
fanOut analyst1 analyst2 analyst3
fanIn summarize
}
🚀 Durable Workflows in Azure (Minimal Example)
Agent.NET workflows run anywhere — in‑memory for local execution, or durably on Azure using Durable Functions.
From the Samples.DurableFunctions project:
/// A durable trade approval workflow defined with Agent.NET
let tradeApprovalWorkflow =
workflow {
name "TradeApprovalWorkflow"
step analyzeStock
step sendForApproval
awaitEvent "TradeApproval" eventOf<ApprovalDecision>
step executeTrade
}
You can host this workflow inside an Azure Durable Functions orchestrator written in F# or C#.
<details> <summary>F# orchestrator</summary>
module TradeApprovalWorkflow
open Microsoft.DurableTask
open Microsoft.Azure.Functions.Worker
open AgentNet.Durable
[<Function("TradeApprovalOrchestrator")>]
let orchestrator ([<OrchestrationTrigger>] ctx: TaskOrchestrationContext) =
let request = ctx.GetInput<TradeRequest>()
Workflow.Durable.run ctx request tradeApprovalWorkflow
</details>
<details open> <summary>C# orchestrator</summary>
Call a workflow defined in your F# project:
using Microsoft.DurableTask;
using Microsoft.Azure.Functions.Worker;
using AgentNet.Durable;
public static class TradeApprovalOrchestrator
{
[Function("TradeApprovalOrchestrator")]
public static Task<TradeResult> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
var request = context.GetInput<TradeRequest>();
return Workflow.Durable.run(context, request, tradeApprovalWorkflow);
}
}
</details>
This is the final shape:
- Declarative workflow definition — one expression per workflow
- Typed steps — plain .NET functions (with or without agents)
- Explicit suspension via
awaitEvent(human-in-the-loop, external events) - Durable execution powered by MAF and Azure Durable Functions
- Minimal host surface — the orchestrator simply runs the workflow
Installation
AgentNet — Agents and in-process workflows
dotnet add package AgentNet
AgentNet.Durable — Azure Durable Function workflows
dotnet add package AgentNet.Durable
Features
Railway‑Oriented Programming (tryStep)
tryStep brings typed early‑exit semantics into the workflow DSL.
Use it when a step may return a Result and you want the workflow to short‑circuit on Error.
Tools: Quotation-Based Metadata Extraction
The <@ @> quotation syntax captures your function and extracts all metadata automatically:
/// <summary>Searches the knowledge base</summary>
/// <param name="query">The search query</param>
/// <param name="maxResults">Maximum number of results to return</param>
let searchKnowledge (query: string) (maxResults: int) : string =
KnowledgeBase.search query maxResults
let searchTool = Tool.createWithDocs <@ searchKnowledge @>
// Extracts:
// Name: "searchKnowledge"
// Description: "Searches the knowledge base"
// Parameters: [{Name="query"; Description="The search query"; Type=string}
// {Name="maxResults"; Description="Maximum number of results"; Type=int}]
Why quotations?
- Function name becomes tool name (rename the function, tool updates automatically)
- Parameter names preserved (no "arg0", "arg1")
- XML docs become descriptions (documentation lives with the code)
- Type information retained for schema generation
If you prefer manual descriptions:
let searchTool =
Tool.create <@ searchKnowledge @>
|> Tool.describe "Searches the knowledge base for relevant documents"
ChatAgent: Pipeline-Style Configuration
Build agents using a clean pipeline:
let stockAdvisor =
ChatAgent.create """
You are a stock market analyst. You help users understand
stock performance, analyze trends, and compare investments.
Always provide balanced, factual analysis.
"""
|> ChatAgent.withName "StockAdvisor"
|> ChatAgent.withTool getStockInfoTool
|> ChatAgent.withTool getHistoricalPricesTool
|> ChatAgent.withTool calculateVolatilityTool
|> ChatAgent.withTools [compareTool; analysisTool] // Add multiple at once
|> ChatAgent.build chatClient
Use your agent:
// Async chat
let! response = stockAdvisor.Chat("Compare AAPL and MSFT performance")
// Access the underlying config if needed
printfn $"Agent: {stockAdvisor.Config.Name}"
TypedAgent: Structured Input/Output for Workflows
While ChatAgent works with strings (string -> Task<string>), workflows often need typed data flowing between steps. TypedAgent wraps a ChatAgent with format/parse functions to enable strongly-typed workflows.
For a minimal example of wrapping a
ChatAgentinto aTypedAgentand using it in a workflow, see the Quick Start. The example below shows a more involved stock comparison scenario.
// Domain types for your workflow
type StockPair = { Stock1: StockData; Stock2: StockData }
type AnalysisResult = { Pair: StockPair; Analysis: string }
// Define a function to format the typed input into a prompt.
let formatStockPair (pair: StockPair) =
$"""Compare these two stocks:
{pair.Stock1.Symbol}: {pair.Stock1.Info}
{pair.Stock2.Symbol}: {pair.Stock2.Info}"""
// Define a function that the AI can use to return a typed output.
let parseAnalysisResult (pair: StockPair) (response: string) =
{ Pair = pair; Analysis = response }
// Create the typed agent
let typedAnalyst = TypedAgent.create formatStockPair parseAnalysisResult stockAnalystAgent
Using TypedAgent standalone:
// Invoke with typed input, get typed output
let! result = typedAnalyst.Invoke(stockPair)
printfn $"Analysis: {result.Analysis}"
Using TypedAgent in workflows:
The real power is using TypedAgent as a strongly-typed step in a workflow:
let comparisonWorkflow = workflow {
step fetchBothStocks // async/task function
step typedAnalyst // TypedAgent works directly
step generateReport // sync function (returns Task.fromResult)
}
let input = { Symbol1 = "AAPL"; Symbol2 = "MSFT" }
let! report = Workflow.InProcess.run input comparisonWorkflow
The workflow is fully type-safe: the compiler ensures each step's output type matches the next step's input type. Steps can be named for debugging/logging, or unnamed for brevity.
Workflows: Computation Expression for Orchestration
The workflow CE generalizes the patterns from the Quick Start to more complex multi-agent scenarios. Orchestrate complex multi-agent scenarios with elegant, readable syntax.
The step operation directly accepts:
- Task functions (
'a -> Task<'b>) - Async functions (
'a -> Async<'b>) - TypedAgents (
TypedAgent<'a,'b>) - Other workflows (
WorkflowDef<'a,'b>)
No wrapping required—just pass them in. The compiler ensures each step's output type matches the next step's input type, catching mismatches at compile time.
Sequential Pipelines
let reportWorkflow = workflow {
step researcher // Gather information
step analyst // Analyze findings
step writer // Write the report
step editor // Polish and refine
}
<details> <summary>C# MAF equivalent</summary>
var builder = new WorkflowBuilder(researcherExecutor);
builder.AddEdge(researcherExecutor, analystExecutor);
builder.AddEdge(analystExecutor, writerExecutor);
builder.AddEdge(writerExecutor, editorExecutor);
builder.WithOutputFrom(editorExecutor);
var workflow = builder.Build();
</details>
Parallel Fan-Out / Fan-In
Process data in parallel, then combine results:
let claimsWorkflow = workflow {
step extractClaims
fanOut
checkPolicy
assessRisk
detectFraud
fanIn aggregateResults
step generateReport
}
let report = Workflow.runSync claimData claimsWorkflow
Note:
fanOutsupports 2-5 direct arguments. For 6+ branches, use list syntax with the+operator, which converts each item to a unifiedSteptype (enabling mixed executors, functions, angents, and workflows in the same list):fanOut [+analyst1; +analyst2; +analyst3; +analyst4; +analyst5; +analyst6]
<details> <summary>C# MAF equivalent</summary>
var builder = new WorkflowBuilder(extractClaimsExecutor);
builder.AddEdge(extractClaimsExecutor, checkPolicyExecutor);
builder.AddEdge(extractClaimsExecutor, assessRiskExecutor);
builder.AddEdge(extractClaimsExecutor, detectFraudExecutor);
builder.AddEdge(checkPolicyExecutor, aggregateResultsExecutor);
builder.AddEdge(assessRiskExecutor, aggregateResultsExecutor);
builder.AddEdge(detectFraudExecutor, aggregateResultsExecutor);
builder.AddEdge(aggregateResultsExecutor, generateReportExecutor);
builder.WithOutputFrom(generateReportExecutor);
var workflow = builder.Build();
</details>
Conditional Routing
Route to different agents based on content:
type Priority = Urgent | Normal | LowPriority
let triageWorkflow = workflow {
step classifier
route (function
| Urgent -> urgentHandler
| Normal -> standardHandler
| LowPriority -> batchHandler)
}
<details> <summary>C# MAF equivalent</summary>
// Create transitions with explicit filters
var urgentTransition = new Transition(
classifierExecutor,
urgentHandlerExecutor
);
urgentTransition.Filter = result => result is Urgent;
var normalTransition = new Transition(
classifierExecutor,
standardHandlerExecutor
);
normalTransition.Filter = result => result is Normal;
var lowTransition = new Transition(
classifierExecutor,
batchHandlerExecutor
);
lowTransition.Filter = result => result is LowPriority;
// Add transitions to the workflow
builder.AddTransition(urgentTransition);
builder.AddTransition(normalTransition);
builder.AddTransition(lowTransition);
// Declare possible outputs
builder.WithOutputFrom(
urgentHandlerExecutor,
standardHandlerExecutor,
batchHandlerExecutor
);
var workflow = builder.Build();
</details>
Resilience: Retry, Timeout, Fallback
Build fault-tolerant workflows:
let resilientWorkflow = workflow {
step primaryAgent
retry 3 // Retry up to 3 times
timeout (TimeSpan.FromSeconds 30.0) // Timeout after 30s
fallback backupAgent // Use backup if all else fails
}
Combine resilience with other operations:
let robustAnalysis = workflow {
step loadData
fanOut analyst1 analyst2 analyst3
retry 2
fanIn combiner
timeout (TimeSpan.FromMinutes 5.0)
fallback cachedResults
}
<details> <summary>C# MAF + Polly equivalent</summary>
var retryPolicy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(3, attempt =>
TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var timeoutPolicy = Policy
.TimeoutAsync(TimeSpan.FromSeconds(30));
var fallbackPolicy = Policy<string>
.Handle<Exception>()
.FallbackAsync(cachedResult);
var combinedPolicy = Policy.WrapAsync(fallbackPolicy, timeoutPolicy, retryPolicy);
// Then wire into your executor...
</details>
Composition: Nest Workflows
Workflows are composable - nest them freely:
let innerWorkflow = workflow {
step stepA
step stepB
}
// Direct nesting - just pass the workflow!
let outerWorkflow = workflow {
step preprocess
step innerWorkflow
step postprocess
}
// Or use toExecutor when you want explicit naming
let namedOuter = workflow {
step preprocess
step (Workflow.toExecutor "InnerStep" innerWorkflow)
step postprocess
}
<details> <summary>C# MAF equivalent</summary>
// Build the inner workflow
var innerBuilder = new WorkflowBuilder(stepAExecutor);
innerBuilder.AddEdge(stepAExecutor, stepBExecutor);
innerBuilder.WithOutputFrom(stepBExecutor);
var innerWorkflow = innerBuilder.Build();
// Convert the inner workflow into an executor
var innerExecutor = new WorkflowExecutor("InnerStep", innerWorkflow);
// Build the outer workflow
var outerBuilder = new WorkflowBuilder(preprocessExecutor);
outerBuilder.AddEdge(preprocessExecutor, innerExecutor);
outerBuilder.AddEdge(innerExecutor, postprocessExecutor);
outerBuilder.WithOutputFrom(postprocessExecutor);
var outerWorkflow = outerBuilder.Build();
</details>
Running Workflows
// Synchronous
let result = Workflow.runSync "initial input" myWorkflow
// Asynchronous
let! result = Workflow.InProcess.run "initial input" myWorkflow
Railway-Oriented Programming with tryStep
Workflows often need to perform validation or business‑rule checks that may fail.
tryStep brings Railway-Oriented Programming directly into the main workflow DSL, giving you short‑circuiting error handling without switching to a different computation expression or monadic style.
When a tryStep returns:
Ok value→ the workflow continues withvalueError err→ the workflow exits immediately, returningError errfromtryRun
This gives you the classic “railway switch” behavior with minimal ceremony.
Example
// Custom error type for the workflow
type ProcessingError =
| ParseError of string
| ValidationError of string
| SaveError of int
let parse (raw: string) =
if raw.Length > 0 then Ok { Id = "doc"; Content = raw }
else Error (ParseError "Empty input")
|> Task.fromResult
let validate (doc: Document) =
if doc.Content.Contains("valid") then
Ok { Doc = doc; IsValid = true; Errors = [] }
else
Error (ValidationError "Missing 'valid' keyword")
|> Task.fromResult
let save (validated: ValidatedDoc) =
{ Doc = validated; WordCount = 1; Summary = "Saved" }
|> Ok
|> Task.fromResult
let documentWorkflow = workflow {
tryStep parse
tryStep validate
step save
}
Running the workflow
let! result = Workflow.InProcess.tryRun documentWorkflow
- If any
tryStepreturnsError, the workflow stops immediately tryRunreturnsResult<'ok, 'err>run(withoutResult) throws an internal early‑exit signal instead — useful for Durable orchestrators
Why tryStep feels so natural
- No monadic boilerplate — you stay in the main
workflowCE - No type contagion — only the steps that need
Resultuse it - Clear, predictable control flow — early exit is explicit and typed
- Works everywhere — InProcess and Durable runners share the same semantics
Step types supported by tryStep
- Functions returning
Task<Result<'o,'e>>orAsync<Result<'o,'e>> TypedAgent<'i,'o>(automatically wrapped inOk)- Any normal step can follow a
tryStep— the workflow only exits when atryStepreturnsError
API Reference
Types
| Type | Description |
|---|---|
ToolDef |
Tool definition with name, description, parameters, and MethodInfo |
ParamInfo |
Parameter metadata: name, description, and type |
ChatAgentConfig |
Agent configuration: name, instructions, and tools |
ChatAgent |
Built agent with Chat: string -> Task<string> and ChatFull: string -> Task<ChatResponse> |
TypedAgent<'i,'o> |
Typed wrapper around ChatAgent with format/parse functions |
ChatResponse |
Full response with Text and Messages list |
ChatMessage |
Message with Role and Content |
ChatRole |
Union type: User, Assistant, System, Tool |
Executor<'i,'o> |
Workflow step that transforms input to output |
WorkflowDef<'i,'o> |
Composable workflow definition |
Tool Functions
| Function | Description |
|---|---|
Tool.create |
Creates a tool from an F# function using a quotation. |
Tool.createWithDocs |
Creates a tool and extracts XML documentation from the quoted function. |
Tool.describe |
Overrides or adds a description for a tool. |
Agent Functions
| Function | Description |
|---|---|
ChatAgent.create |
Creates a chat agent configuration with the given instruction string. |
ChatAgent.withName |
Assigns a display name to the agent. |
ChatAgent.withTool |
Adds a single tool to the agent configuration. |
ChatAgent.withTools |
Adds multiple tools to the agent configuration. |
ChatAgent.build |
Builds a ChatAgent using an IChatClient and the configuration. |
TypedAgent.create |
Wraps a ChatAgent with format/parse functions for typed input/output. |
TypedAgent.invoke |
Invokes a typed agent: structured input in, structured output out. |
Workflow CE Keywords
| Keyword | Description |
|---|---|
step |
Add a step to the workflow |
tryStep |
Execute a step that returns Result; short‑circuits the workflow on Error |
fanOut |
Execute multiple executors in parallel |
fanIn |
Combine parallel results into one |
route |
Conditional routing based on pattern matching |
retry |
Retry failed steps N times |
timeout |
Fail if step exceeds duration |
fallback |
Use alternative executor on failure |
backoff |
Set retry delay strategy |
Workflow Functions
| Function | Description |
|---|---|
Workflow.InProcess.run |
Runs a workflow in‑process and returns the final output. Throws on tryStep errors. |
Workflow.InProcess.tryRun |
Runs a workflow in‑process and returns Result<'output,'error> with early‑exit handling. |
Workflow.Durable.run |
Runs a workflow inside a Durable Functions orchestrator. Throws on tryStep errors. |
Workflow.Durable.tryRun |
Durable‑safe version of tryRun; returns Result<'output,'error> instead of throwing. |
Workflow.Durable.tryRunIgnore |
Use when your Durable orchestrator delegates to a workflow but doesn’t need to return a value. This avoids Durable’s restriction on serializing F# discriminated unions. |
XML Documentation Format
For Tool.createWithDocs to extract parameter descriptions, use explicit <summary> tags:
/// <summary>Searches for documents matching the query</summary>
/// <param name="query">The search query string</param>
/// <param name="limit">Maximum results to return</param>
let search (query: string) (limit: int) : string = ...
Note: F# requires
<summary>tags when using<param>tags. Without<summary>, the param tags become part of the summary text.
Examples
See the StockAdvisorFS sample project for a complete example including:
- Multiple tools with XML documentation
- Agent configuration
- Real-world usage patterns
Workflow: A Semantic Layer for MAF
Agent.NET's workflow CE is a semantic layer for Microsoft Agent Framework (MAF). Define your workflow once in expressive F#, then choose how to run it:
let stockAnalysisWF = workflow {
step fetchStockData
fanOut technicalAnalysis fundamentalAnalysis sentimentAnalysis
fanIn synthesizeReports
step generateRecommendation
}
// In-memory execution (quick-running workflows)
let! result = Workflow.InProcess.run input stockAnalysisWF
// MAF durable execution (long-running, durable workflows)
let! result = Workflow.Durable.run ctx input stockAnalysisWF
Why a Semantic Layer?
| Direct MAF (C#) | Agent.NET Workflow (F#) |
|---|---|
| Verbose executor classes | Normal F# functions |
| Manual graph wiring | Declarative step, fanOut, fanIn |
| Stringly-typed edges | Compiler-enforced type transitions |
| Resilience via Polly boilerplate | Built-in retry, timeout, fallback |
Two Execution Modes
Agent.NET supports both execution models from a single workflow definition:
| Mode | API | Description |
|---|---|---|
| In-memory | Workflow.InProcess.run |
Used for short-lived workflows executed within the current process. |
| MAF Durable | Workflow.Durable.run |
Runs on MAF's durable runtime (backed by Azure Durable Functions) with automatic checkpointing, replay, and fault tolerance. |
Same workflow. Your choice of execution model.
Dependencies
Both packages depend only on abstractions, keeping Agent.NET lightweight, platform‑agnostic, and free of Azure‑specific hosting requirements.
AgentNet
Built on the core Microsoft Agent Framework abstractions.
- Microsoft.Agents.AI — agent primitives
- Microsoft.Agents.AI.Workflows — workflow graph + in‑memory execution
- Microsoft.Extensions.AI.Abstractions — AI service abstractions for .NET
AgentNet.Durable
Adds durable execution by targeting the Durable Task abstractions.
- Microsoft.DurableTask.Abstractions — durable orchestration primitives
- Microsoft.Agents.AI.Workflows — shared workflow graph model
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
Roadmap
Workflow State Management
The current WorkflowContext.State mechanism needs rework — each step currently receives a fresh context, so state changes don't propagate between steps. A future release will provide proper state management that integrates with MAF's serialization for durable workflows.
| Feature | Status | Description |
|---|---|---|
| State propagation fix | Planned | Rework WorkflowContext so state set in one step is available to subsequent steps |
| Strongly-typed state | Planned | Workflow.InProcess.runWithState and Workflow.Durable.runWithState for passing typed state between steps with a friendlier API. Also allows initializing workflows with predefined state. |
Built with F# and a belief that AI tooling should be elegant.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Core (>= 10.0.102)
- Microsoft.Agents.AI (>= 1.0.0-preview.260108.1)
- Microsoft.Agents.AI.Workflows (>= 1.0.0-preview.260108.1)
- Microsoft.Extensions.AI.Abstractions (>= 10.2.0)
-
net8.0
- FSharp.Core (>= 10.0.102)
- Microsoft.Agents.AI (>= 1.0.0-preview.260108.1)
- Microsoft.Agents.AI.Workflows (>= 1.0.0-preview.260108.1)
- Microsoft.Extensions.AI.Abstractions (>= 10.2.0)
-
net9.0
- FSharp.Core (>= 10.0.102)
- Microsoft.Agents.AI (>= 1.0.0-preview.260108.1)
- Microsoft.Agents.AI.Workflows (>= 1.0.0-preview.260108.1)
- Microsoft.Extensions.AI.Abstractions (>= 10.2.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on AgentNet:
| Package | Downloads |
|---|---|
|
AgentNet.Durable
Durable workflow compilation for AgentNet. Compiles F# workflow definitions to Microsoft Azure Durable Functions (MAF) format for production deployment. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-alpha.3 | 44 | 1/21/2026 |
| 1.0.0-alpha.2.1 | 51 | 1/16/2026 |
| 1.0.0-alpha.2 | 45 | 1/14/2026 |
| 1.0.0-alpha.1 | 47 | 1/5/2026 |