Building Workflows
Entry Point
Use AgentWorkflow.Create() to start the fluent builder:
var workflow = await AgentWorkflow.Create()
.WithName("ResearchPipeline")
.AddAgent(...)
.From(...).To(...)
.BuildAsync();Or load from a JSON file:
var workflow = await AgentWorkflow.FromJson("./pipeline.json").BuildAsync();Note:
FromJsonaccepts a file path string. UseAgentWorkflow.FromConfig(config)when you already have aMultiAgentWorkflowConfigobject (e.g. loaded from a database or API) — see Loading from a Config Object below.
Adding Agents
Three ways to add an agent node:
From AgentConfig (recommended)
.AddAgent("writer", new AgentConfig
{
SystemInstructions = "Write clearly and concisely."
})Agents built this way are deferred — they're built at execution time and automatically inherit the parent agent's chat client if no Provider is configured.
From a pre-built Agent instance
var myAgent = await new AgentBuilder()
.WithToolkit<SearchTools>()
.BuildAsync();
.AddAgent("researcher", myAgent)Via inline AgentBuilder lambda
.AddAgent("analyzer", builder =>
{
builder.WithToolkit<AnalysisTools>();
builder.WithInstructions("Analyze data carefully.");
})Defining Edges
Connect agents with .From().To():
// Linear chain
.From("researcher").To("writer")
// Fan-out: one feeds two (parallel)
.From("triage").To("researcher", "factChecker")
// Multiple sources into one
.From("researcher", "factChecker").To("writer")Add conditions on edges — see 06.4 Routing & Edges.
Workflow Settings
AgentWorkflow.Create()
.WithName("MyWorkflow")
.WithMaxIterations(10) // For cyclic graphs
.WithTimeout(TimeSpan.FromMinutes(5))
.BuildAsync()WorkflowResult
RunAsync() returns a WorkflowResult:
var result = await workflow.RunAsync("input text");
result.FinalAnswer // string? — value of the "answer" key from the last node that produced one
result.Outputs // Dictionary<string, object> — all node outputs, keyed as "{nodeId}.{key}"
result.Success // bool
result.Duration // TimeSpan
result.Error // string? — error message if failed
result.Exception // Exception? — underlying exceptionNote:
FinalAnsweris set from the"answer"output key. If nodes useWithOutputKey()to name their output differently (e.g."report","summary"),FinalAnswerwill benull. In that case read fromresult.Outputsdirectly:csharpvar report = result.Outputs["writer.report"] as string;
JSON Configuration
Define a workflow entirely in JSON:
{
"name": "ResearchPipeline",
"version": "1.0.0",
"agents": {
"researcher": {
"agent": {
"systemInstructions": "Research thoroughly.",
"name": "Researcher"
},
"timeout": "00:00:30",
"retry": {
"maxAttempts": 3,
"strategy": "Exponential"
}
},
"writer": {
"agent": {
"systemInstructions": "Write clearly."
},
"outputKey": "report"
}
},
"edges": [
{ "from": "researcher", "to": "writer" }
],
"settings": {
"maxIterations": 10,
"streamingMode": "PerNode",
"enableMetrics": true
}
}var workflow = await AgentWorkflow.FromJson("./pipeline.json").BuildAsync();Settings reference
| Setting | Type | Default | Description |
|---|---|---|---|
maxIterations | int | 25 | Max loop iterations for cyclic graphs. |
streamingMode | PerNode | PerLayer | PerNode | When to emit streaming events. |
enableMetrics | bool | true | Collect execution metrics (see 06.7 Observability). |
enableCheckpointing | bool | false | Enable durability checkpoints. |
defaultTimeout | TimeSpan? | null | Default timeout applied to all nodes. |
iterationOptions | object | null | Advanced loop control (see below). |
Iteration options (cyclic graphs)
For cyclic workflows, you can enable change-aware iteration to stop looping early when outputs stabilise:
"settings": {
"maxIterations": 10,
"iterationOptions": {
"useChangeAwareIteration": true,
"enableAutoConvergence": true,
"ignoreFieldsForChangeDetection": ["timestamp"],
"alwaysDirtyNodes": ["monitor"]
}
}| Field | Type | Default | Description |
|---|---|---|---|
useChangeAwareIteration | bool | false | Hash node outputs each iteration; only re-run nodes whose inputs changed. |
enableAutoConvergence | bool | true | Stop iterating automatically when no outputs changed between iterations. |
ignoreFieldsForChangeDetection | string[] | null | Output fields to exclude from change detection (e.g. timestamps). |
alwaysDirtyNodes | string[] | null | Nodes that always re-execute regardless of input changes. |
Mermaid Diagram
Every built workflow can export its structure:
Console.WriteLine(workflow.ToDiagram());
// graph TD
// START --> researcher
// researcher --> writer
// writer --> ENDExport the built workflow back to JSON (round-trip from programmatic builds):
string json = workflow.ExportConfigJson();
File.WriteAllText("./exported.json", json);Warning:
ExportConfigJsonreconstructs configuration from the runtime graph. The following agents will not export a meaningful config and will fall back to an emptyAgentConfig:
- Agents added as pre-built
Agentinstances, unlessAgent.Configis set on them.- Agents added via the inline builder lambda (
AddAgent(id, builder => ...)) — the lambda is not serializable.Type names for
StructuredOutput<T>andUnionOutput<T>are serialized as assembly-qualified names.
Loading from a Config Object
In addition to FromJson(), you can build a workflow from a programmatic MultiAgentWorkflowConfig — useful when config is loaded from a database or API:
var config = new MultiAgentWorkflowConfig
{
Name = "ResearchPipeline",
Agents = { ... },
Edges = { ... }
};
var workflow = await AgentWorkflow.FromConfig(config).BuildAsync();