AgentNet 1.0.0-alpha.2
dotnet add package AgentNet --version 1.0.0-alpha.2
NuGet\Install-Package AgentNet -Version 1.0.0-alpha.2
<PackageReference Include="AgentNet" Version="1.0.0-alpha.2" />
<PackageVersion Include="AgentNet" Version="1.0.0-alpha.2" />
<PackageReference Include="AgentNet" />
paket add AgentNet --version 1.0.0-alpha.2
#r "nuget: AgentNet, 1.0.0-alpha.2"
#:package AgentNet@1.0.0-alpha.2
#addin nuget:?package=AgentNet&version=1.0.0-alpha.2&prerelease
#tool nuget:?package=AgentNet&version=1.0.0-alpha.2&prerelease
Agent.NET
Elegant agent workflows for .NET, designed in F#.
The Pitch
What if building AI agents looked like this?
// Your existing .NET service for tooling
public class StockService
{
public static string GetQuote(string symbol) => ...
}
Wrapped elegantly in an F# function with metadata for the LLM:
/// <summary>Gets current stock information</summary>
/// <param name="symbol">The stock ticker symbol (e.g., AAPL)</param>
let getStockInfo (symbol: string) =
StockService.GetQuote(symbol) // Call your existing C# or implement here in F#.
let tool = Tool.createWithDocs <@ getStockInfo @>
let agent =
ChatAgent.create "You are a helpful stock assistant."
|> ChatAgent.withTools [tool]
|> ChatAgent.build chatClient
That's it. The function name becomes the tool name. The XML docs become the description. The parameter names and types are extracted automatically. No attributes. No magic strings. No sync issues.
And when you need to orchestrate multiple agents?
let analysisWorkflow = workflow {
step loadData
fanOut technicalAnalyst fundamentalAnalyst sentimentAnalyst
fanIn summarize
retry 3
timeout (TimeSpan.FromMinutes 5.0)
}
Agent.NET wraps the Microsoft Agent Framework with a clean, idiomatic F# API that makes building AI agents a joy.
🚀 Durable Workflows in Azure (Minimal Example)
Agent.NET workflows run anywhere — in‑memory for local execution, or durably on Azure using Durable Functions.
This is the entire hosting surface:
/// A durable workflow defined with Agent.NET
let tradeApprovalWorkflow =
workflow {
name "TradeApprovalWorkflow"
step analyzeStock
step sendForApproval
awaitEvent "TradeApproval" eventOf<ApprovalDecision>
step executeTrade
}
/// Azure Durable Functions orchestrator hosting the workflow
[<Function("TradeApprovalOrchestrator")>]
let orchestrator ([<OrchestrationTrigger>] ctx) =
let request = ctx.GetInput<TradeRequest>()
tradeApprovalWorkflow
|> Workflow.Durable.run ctx request
This is the final shape:
- Declarative workflow definition
- Typed steps (plain .NET functions)
- Explicit suspension via
awaitEvent - Durable execution powered by MAF and Azure Durable Functions
- Minimal host surface — the orchestrator simply runs the workflow
What's Included
| Feature | Description |
|---|---|
| ChatAgent | AI agents with automatic tool metadata extraction from F# quotations |
| TypedAgent | Structured I/O wrapper for type-safe agent integration in workflows |
| workflow CE | Composable pipelines with SRTP type inference, fan-out/fan-in, routing |
| resultWorkflow CE | Railway-oriented programming with automatic error short-circuiting |
| Resilience | Built-in retry, backoff strategies, timeout, and fallback |
All with clean F# syntax - no attributes, no magic strings, no ceremony.
Installation
dotnet add package AgentNet
Quick Start
1. Define Your Tools
Write normal F# functions with XML documentation (summary only or summary and params):
open AgentNet
/// Gets the current weather for a city
let getWeather (city: string) = task {
let! weather = WeatherApi.fetch city
return $"The weather in {city} is {weather}"
}
/// <summary>Gets the current time in a timezone</summary>
/// <param name="timezone">The timezone (e.g., America/New_York)</param>
let getTime (timezone: string) =
let time = TimeApi.now timezone
$"The current time is {time}"
// Create tools - metadata extracted automatically!
let weatherTool = Tool.createWithDocs <@ getWeather @>
let timeTool = Tool.createWithDocs <@ getTime @>
2. Create an Agent
let assistant =
ChatAgent.create "You are a helpful assistant that provides weather and time information."
|> ChatAgent.withName "WeatherBot"
|> ChatAgent.withTools [weatherTool; timeTool]
|> ChatAgent.build chatClient
// Chat with your agent
let! response = assistant.Chat("What's the weather like in Seattle?")
3. Orchestrate with Workflows
let researchWorkflow = workflow {
step researcher
step analyst
step writer
}
let! result = Workflow.runInProcess "Research AI trends" researchWorkflow
Features
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:
// 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.runInProcess 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 is where Agent.NET really shines. 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
}
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]
Conditional Routing
Route to different agents based on content:
type Priority =
| Urgent of string
| Normal of string
| LowPriority of string
let triageWorkflow = workflow {
step classifier
route (function
| Urgent msg -> urgentHandler
| Normal msg -> standardHandler
| LowPriority msg -> batchHandler)
}
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
}
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
}
Running Workflows
// Synchronous
let result = Workflow.runSync "initial input" myWorkflow
// Asynchronous
let! result = Workflow.runInProcess "initial input" myWorkflow
<details> <summary><strong>Complete Workflow Reference (with C# comparison)</strong></summary>
All Workflow Patterns
| Pattern | Agent.NET | Description |
|---|---|---|
| Sequential | step a ➔ step b ➔ step c |
Chain steps in order |
| Parallel | fanOut [a; b; c] |
Execute multiple steps simultaneously |
| Aggregate | fanIn combiner |
Combine parallel results |
| Routing | route (function \| Case1 -> a \| Case2 -> b) |
Conditional branching |
| Retry | retry 3 |
Retry on failure |
| Backoff | backoff Backoff.Exponential |
Delay strategy between retries |
| Timeout | timeout (TimeSpan.FromSeconds 30.) |
Fail if too slow |
| Fallback | fallback backupStep |
Alternative on failure |
| Compose | step innerWorkflow |
Nest workflows directly |
Side-by-Side: Agent.NET Syntax → MAF Output
Agent.NET's workflow CE compiles to MAF's graph structure. Here's what you write vs what gets generated:
Sequential Pipeline
You write (F#):
let pipeline = workflow {
step researcher
step analyst
step writer
}
// Run in-memory for testing
let! result = Workflow.runInProcess input pipeline
// Or compile to MAF for durability
let mafWorkflow = Workflow.toMAF pipeline
Compiles to (MAF equivalent):
var builder = new WorkflowBuilder(researcherExecutor);
builder.AddEdge(researcherExecutor, analystExecutor);
builder.AddEdge(analystExecutor, writerExecutor);
builder.WithOutputFrom(writerExecutor);
var workflow = builder.Build();
Parallel Fan-Out / Fan-In
You write (F#):
let analysis = workflow {
step loader
fanOut technical fundamental sentiment
fanIn summarizer
}
Compiles to (MAF equivalent):
var builder = new WorkflowBuilder(loaderExecutor);
builder.AddEdge(loaderExecutor, technicalExecutor);
builder.AddEdge(loaderExecutor, fundamentalExecutor);
builder.AddEdge(loaderExecutor, sentimentExecutor);
builder.AddEdge(technicalExecutor, summarizerExecutor);
builder.AddEdge(fundamentalExecutor, summarizerExecutor);
builder.AddEdge(sentimentExecutor, summarizerExecutor);
builder.WithOutputFrom(summarizerExecutor);
var workflow = builder.Build();
Resilience
You write (F#):
let resilient = workflow {
step unreliableService
retry 3
backoff Backoff.Exponential
timeout (TimeSpan.FromSeconds 30.)
fallback cachedResult
}
Compiles to (MAF + Polly equivalent):
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...
The patterns are the same. The ceremony is not.
</details>
Result Workflows: Railway-Oriented Programming
For workflows where any step can fail, use resultWorkflow for automatic short-circuit handling of errors:
// Model custom error types for your workflow (or use a simple string error).
type ValidationError =
| ParseError of string
| ValidationFailed of string
| SaveError of string
// Functions that return Task<Result<...>> work directly (bind semantics)
let parseDocument (raw: string) : Task<Result<Document, ValidationError>> = task { ... }
let validateSchema (doc: Document) : Task<Result<ValidatedDoc, ValidationError>> = task { ... }
let addMetadata (doc: ValidatedDoc) : Task<Result<EnrichedDoc, ValidationError>> = task { ... }
// Functions that DON'T return Result use 'ok' wrapper (map semantics)
let saveToDatabase (doc: EnrichedDoc) : Task<SavedDoc> = task { ... }
let documentWorkflow = resultWorkflow {
step parseDocument // Task<Result<...>> - auto bind semantics
step validateSchema // Task<Result<...>> - auto bind semantics
step addMetadata // Task<Result<...>> - auto bind semantics
step (ok saveToDatabase) // Task<...> - wrapped in Ok via 'ok' wrapper
}
let result = ResultWorkflow.runSync rawInput documentWorkflow
// Returns: Result<SavedDoc, ValidationError>
// Short-circuits on first Error, no manual error checking needed!
Step types:
- Functions returning
Task<Result<'o, 'e>>orAsync<Result<'o, 'e>>- auto bind semantics - Functions returning
Task<'o>orAsync<'o>- useokwrapper for map semantics ResultExecutor<'i, 'o, 'e>- direct passthrough (for explicit naming or backwards compatibility)TypedAgent<'i, 'o>- auto wrapped in Ok
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 |
ResultExecutor<'i,'o,'e> |
Executor returning Result<'o,'e> |
ResultWorkflowDef<'i,'o,'e> |
Workflow with error short-circuiting |
Tool Functions
Tool.create: Expr<'a -> 'b> -> ToolDef
Tool.createWithDocs: Expr<'a -> 'b> -> ToolDef // Extracts XML docs
Tool.describe: string -> ToolDef -> ToolDef
Agent Functions
// ChatAgent - for interactive chat
ChatAgent.create: string -> ChatAgentConfig // Instructions
ChatAgent.withName: string -> ChatAgentConfig -> ChatAgentConfig
ChatAgent.withTool: ToolDef -> ChatAgentConfig -> ChatAgentConfig
ChatAgent.withTools: ToolDef list -> ChatAgentConfig -> ChatAgentConfig
ChatAgent.build: IChatClient -> ChatAgentConfig -> ChatAgent
// TypedAgent - for structured workflows
TypedAgent.create: ('i -> string) -> ('i -> string -> 'o) -> ChatAgent -> TypedAgent<'i,'o>
TypedAgent.invoke: 'i -> TypedAgent<'i,'o> -> Task<'o>
Workflow CE Keywords
| Keyword | Description |
|---|---|
step |
Add a step to the workflow |
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
Workflow.runInProcess: 'i -> WorkflowDef<'i,'o> -> Task<'o>
Workflow.toExecutor: WorkflowDef<'i,'o> -> Executor<'i,'o>
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 stockAnalysis = workflow {
step fetchStockData
fanOut technicalAnalysis fundamentalAnalysis sentimentAnalysis
fanIn synthesizeReports
step generateRecommendation
}
// In-memory execution (quick-running workflows)
let! result = Workflow.runInProcess input stockAnalysis
// MAF durable execution (long-running, durable workflows)
let mafWorkflow = Workflow.toMAF stockAnalysis
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.runInProcess |
Used for short-lived workflows execut within the current process. |
| MAF Durable | Workflow.toMAF |
Compiles to MAF's durable runtime (backed by Azure Durable Functions) with automatic checkpointing, replay, and fault tolerance. |
Prefer explicit control? Use Workflow.runInProcess and integrate with your own persistence layer - databases, queues, event stores, whatever fits your architecture.
Want durable orchestration? Use Workflow.toMAF to get enterprise-grade durability with one line of code. You can still use Workflow.run for local testing without installing the durable runtime.
Same workflow. Your choice of execution model.
Roadmap
v1.0.0-alpha (Current)
- In-memory workflow execution (
Workflow.run) - MAF graph compilation (
Workflow.toMAF) - Sequential pipelines, parallel fan-out/fan-in
- Conditional routing (
route) - Resilience (
retry,timeout,fallback,backoff) - Result workflows (railway-oriented error handling)
Upcoming: AgentNet.Durable
- Durable-only operations:
awaitEvent<'T>,delay - Integration with Azure Durable Functions via
Microsoft.Agents.AI.DurableTask - Human-in-the-loop workflows with durable event waiting
Dependencies
- Microsoft.Agents.AI - Microsoft Agent Framework
- Microsoft.Extensions.AI - AI abstractions for .NET
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
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.100)
- 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.100)
- 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.100)
- 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 (3)
Showing the top 3 NuGet packages that depend on AgentNet:
| Package | Downloads |
|---|---|
|
AgentNet.InProcess
In-process workflow execution for AgentNet. Compiles F# workflow definitions to Microsoft Agent Framework (MAF) format and executes them in-process. |
|
|
AgentNet.Durable
Durable workflow compilation for AgentNet. Compiles F# workflow definitions to Microsoft Azure Durable Functions (MAF) format for production deployment. |
|
|
AgentNet.InProcess.Polly
Polly resilience pipeline decorators for AgentNet in-process workflows. Wraps workflow steps with Polly ResiliencePipeline for retry, timeout, circuit-breaker and other resilience strategies. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 81 | 6/15/2026 |