Workflows

Table of contents

  1. Defining a Workflow
  2. Running a Workflow
  3. WorkflowStep Reference
  4. WorkflowContext
  5. WorkflowResult
  6. Guardrail Patterns
  7. Example: Document Processing Pipeline

Workflows let you break a complex task into a sequence of verified steps. The agent cannot skip ahead — each step has an optional async guardrail that must pass before the next step begins.

Defining a Workflow

var workflow = new Workflow("Process Invoice")
    .Step(
        name:        "Extract header",
        instruction: "Extract the invoice number, date, vendor name, currency, and total amount.",
        verify:      ctx => ctx.ToolInvocations.Any(t => t.Name == "save_invoice_header"))

    .Step(
        name:        "Extract line items",
        instruction: "Extract every line item into the database.",
        verify:      ctx => ctx.ToolInvocations.Any(t => t.Name == "save_invoice_lines"))

    .Step(
        name:        "Confirm totals",
        instruction: "Sum the line item values and confirm they match the invoice total.",
        verify:      ctx => ctx.ResponseText.Contains("match", StringComparison.OrdinalIgnoreCase));

Steps are verified in order. If a guardrail fails for step N, execution stops at that step even if later steps would have passed.

Running a Workflow

var result = await agent.RunWorkflowAsync(
    workflow,
    input:        "Process the attached invoice.",
    mcpServerUrl: "http://localhost:5100/mcp",
    maxRounds:    10);

if (result.Completed)
    Console.WriteLine("All steps completed.");
else
{
    var pending = result.Steps.Where(s => s.Status == WorkflowStepStatus.Pending);
    Console.WriteLine($"Incomplete steps: {string.Join(", ", pending.Select(s => s.Name))}");
}

WorkflowStep Reference

PropertyTypeDescription
NamestringShort label used in prompts and StepCompleted events
InstructionstringText appended to the system prompt for this step
VerifyFunc<WorkflowContext, Task<bool>>?Async guardrail; null means the step auto-completes after each round
StatusWorkflowStepStatusPending or Completed — updated by the agent after each round

WorkflowContext

Available inside a verify callback:

PropertyDescription
ToolInvocationsAll tool calls made so far in this workflow run
ResponseTextAccumulated model output across all rounds

WorkflowResult

PropertyDescription
Completedtrue when every step reached Completed
TextCombined model response text from all rounds
StepsFinal state of each step
ToolInvocationsAll tool calls made during the run, in order

Guardrail Patterns

// Tool was called at least once
verify: ctx => ctx.ToolInvocations.Any(t => t.Name == "save_data")

// Tool was called with a specific argument value
verify: ctx => ctx.ToolInvocations
    .Any(t => t.Name == "save_data" && t.Arguments.Contains("\"status\":\"ok\""))

// Model confirmed something in its output
verify: ctx => ctx.ResponseText.Contains("confirmed", StringComparison.OrdinalIgnoreCase)

// Async database check
verify: async ctx =>
{
    await using var db = await dbFactory.CreateDbContextAsync();
    return await db.Invoices.AnyAsync(i => i.Id == invoiceId && i.LineItems.Count > 0);
}

Example: Document Processing Pipeline

A complete workflow that processes a multi-page document:

var workflow = new Workflow("Document Intake")
    .Step(
        name:        "Classify document",
        instruction: "Identify the document type (invoice, contract, report, or other).",
        verify:      ctx => ctx.ToolInvocations.Any(t => t.Name == "set_document_type"))

    .Step(
        name:        "Extract key fields",
        instruction: "Extract all key metadata fields appropriate for this document type.",
        verify:      ctx => ctx.ToolInvocations.Any(t => t.Name == "save_metadata"))

    .Step(
        name:        "Generate summary",
        instruction: "Write a 2–3 sentence summary of the document's content.",
        verify:      ctx => ctx.ResponseText.Length > 100)

    .Step(
        name:        "Flag for review",
        instruction: "If the document contains any anomalies or requires human review, flag it.",
        verify:      null);  // auto-completes — no specific check needed

var result = await agent.RunWorkflowAsync(
    workflow,
    input:        "Process document: " + documentPath,
    mcpServerUrl: "http://localhost:5100/mcp",
    maxRounds:    20);

Console.WriteLine(result.Completed ? "Pipeline complete." : "Pipeline incomplete.");