patterns.md

August 12, 2026 ยท View on GitHub

Alpha: @statelyai/agent 2.0 is in alpha. APIs can change between releases; pin an exact version. Feedback: github.com/statelyai/agent.

This page maps common agent patterns to runnable examples. Each pattern is a control-flow shape such as a loop, a branch, a fan-out, or a handoff, written as an explicit XState machine. Pick a pattern, open its example, and copy the file. Each section ends with a canonical example, meaning the smallest example that demonstrates the pattern completely. Start there. See Lifting an example at the end of this page for the dependencies and TypeScript settings an example needs.

Core ideas

These examples cover text requests, decisions, messages, and JSON authoring.

  • twenty-questions: a decision loop where the model picks one legal event, ASK or GUESS, per turn. Legality is guard-enforced, the score lives in the machine, and a play-again transition resets it.
  • joke: a minimal streaming text workflow.
  • email-drafter: reusable text logic, parts-based messages, and schema-typed state and transition meta.
  • json-agent: a full workflow with a decision, a text request, and an idle human step, authored as a .json file. See Machines as data.
  • described-workflow: a plain XState machine with no invokes. Prompts live in state description and meta fields, and the machine runs through runAgent's getRequests option.

Start with twenty-questions.

Games

In a game, the machine owns turn order and move legality. The model picks among the moves the current state allows.

  • game-agent: allowedEvents narrowed as a function of input, gating moves by HP.
  • go-fish: hidden-information play with a check-win, agent, human loop. The model chooses requests and the machine enforces the rules.

Reasoning and tool loops

For tool use, start with tool calling, where your SDK runs the tool loop inside one request, in one machine state. ReAct is the same loop unrolled into explicit states. Use it when individual turns need gating, such as approval before a tool, a spend guard, or a snapshot mid-loop.

  • Tool calling (tool-calling): the SDK's loop runs inside a state you control, bounded by the request's maxSteps.
  • ReAct (react-agent): every turn can be gated, persisted, and inspected, under a step-budget guard.
  • Plan-and-execute (plan-and-execute): the planner returns structured output, and execution states iterate the plan.
  • Reflection (reflection-writer): generate and critique are two states, and a guard caps revisions.
  • Evaluator-optimizer (ai-sdk-evaluator-optimizer): the scoring gate is a guard, so the loop always terminates.
  • Self-correcting codegen (code-assistant): a sandboxed check actor and a maxAttempts bound, ending in an explicit failed outcome.
  • Tree search, LATS (lats): selection, expansion, and reflection scoring as separate states under a rollout budget.

Start with react-agent.

Retrieval

  • RAG (rag): retrieve and answer are separate typed states, and conversational memory lives in context.
  • Corrective RAG, CRAG (corrective-rag): self-correction is modeled as explicit branch states rather than nested conditionals.
  • Adaptive RAG (adaptive-rag): routing, grading, and a bounded query rewrite each get their own state.
  • Deep research (deep-research): researchers spawn per query, and a coverage reflection gates one optional follow-up.
  • SQL agent (sql-agent): query generation, database execution, and synthesis are separately testable states.

Start with corrective-rag.

Routing and chaining

Routing is one decision state whose legal events are the branches. Each branch is a real state, so an unreachable branch shows up as a lint finding.

flowchart LR
  C["classifying<br/>agent.decide"] -->|BILLING| B["billing"]
  C -->|TECHNICAL| T["technical"]
  C -->|OTHER| O["fallback"]
  B --> D["answering"]
  T --> D
  O --> D
  • Routing (ai-sdk-routing): the route is a decision over legal events, and each branch is its own state.
  • Prompt chaining (ai-sdk-marketing-chain): a linear state sequence where each link is independently typed and inspectable.
  • Parallel review (ai-sdk-parallel-review): parallel states fan out, and the join is a plain aggregation state.
  • Triage (triage): structured output validated against a schema before it leaves the state.

Start with ai-sdk-routing.

Multi-agent

A supervisor is a routing state over typed workers, so the graph matches the org chart. Hierarchical teams nest the same shape. Each team is a machine with a typed boundary, and the coordinator can send one bounded revision round back down.

flowchart TB
  S["supervisor<br/>agent.decide"] -->|RESEARCH| R["research team"]
  S -->|WRITE| W["writer"]
  R --> RW["worker loop"] --> R
  R -->|done| S
  W -->|done| S
  S --> F["final"]

Orchestrator-worker fans the same idea out in parallel and joins deterministically. Swarm handoff has no hub. Agents are peers, and a handoff is a transition that persists across turns.

flowchart LR
  subgraph OW["Orchestrator-worker"]
    P["plan"] --> W1["worker 1"] --> J["join"]
    P --> W2["worker 2"] --> J
  end
  subgraph SW["Swarm handoff"]
    A["triage agent"] -->|HANDOFF| B["refunds agent"]
    B -->|HANDOFF| A
  end
  • Supervisor (supervisor): a routing request's structured output hands off to a typed worker.
  • Swarm handoff (swarm-handoff): handoffs are transitions between typed child actors, persisted across turns.
  • Orchestrator-worker (ai-sdk-orchestrator-worker): fan-out and join use Promise.all over host actors.
  • Fan-out, or map-reduce (fan-out): dynamic parallelism driven by a planner, then a deterministic reduce state.
  • Hierarchical teams (hierarchical-teams): each team is a nested machine with a typed boundary.
  • Whole-org workflow (trading-team): one composite workflow whose reject-and-revise loop is modeled as states rather than retries.
  • Sub-agents (subflows, ai-sdk-sub-agents, debate-sub-agents): each child keeps its own executor binding, and parents stay typed against the results.

Start with supervisor. Read more about Multi-agent for sub-agents and child actors.

Control and safety

  • Human in the loop (human-in-the-loop): an idle state is a durable pause, and the snapshot is plain JSON you can store anywhere.
  • Guardrails (guardrails): guards gate states, so an illegal path is unreachable rather than discouraged in the prompt.
  • Context compaction (context-compaction): a compacting state folds old history into a running summary once history passes a threshold.
  • Customer support (customer-support): sensitive actions are gated behind an idle state, so the model cannot act past the guard.

These examples cover longer pauses and durable threads.

  • long-running-onboarding: a multi-day coordinator with durable typed state, two idle states, delegated IT provisioning, and JSON snapshot resume.
  • file-snapshot-store: a file-backed snapshot store for durable threads across processes.

Start with human-in-the-loop. Read more about the idle pause and snapshot resume in Human in the loop.

Hosts and runtimes

These examples run the same machines against different SDKs and runtimes. See Hosts and Event log.

  • ai-sdk-host: running with Vercel AI SDK host actors.
  • ai-sdk-game-host: a step-path Vercel AI SDK runner that appends every model call to the event log.
  • openai-sdk-host: the same executor contract against the raw openai package and its Chat Completions API, with no AI SDK in between.
  • anthropic-sdk-host: the same contract against the raw @anthropic-ai/sdk package and its Messages API.
  • cloudflare-workers-ai-host: a Workers AI host that persists only the event log and resumes by replay.
  • cloudflare-agent-host: a Cloudflare Agents host persisting snapshots in Durable Object state.

The two Cloudflare examples target the Workers runtime, not Node, so tsx does not run them. Each is its own package with a wrangler dev server and a vitest suite. Run them from the repo with pnpm --filter @statelyai/example-cloudflare-workers-ai-host test and pnpm --filter @statelyai/example-cloudflare-agent-host test, or pnpm run test:cloudflare for both. See Running from the repo.

  • parallel-streams: fan-out over parallel worker streams relayed through a side channel.
  • sse-transport: relaying provider stream chunks over an SSE transport.

Start with ai-sdk-host.

Evaluation, migration, observability

  • simulated-user-evaluation: a target chatbot and a simulated user alternate under a turn bound, then an independent judge scores the transcript.
  • retrofit: a hand-rolled agent in before.ts refactored step by step into a machine, with each step shippable and simulateAgent tests pinning behavior before and after. This is the worked example for Migrating from a loop.
  • langsmith-otel: createOtelTraceHandler from @statelyai/agent/otel exporting spans over OTLP to LangSmith, LangChain's hosted tracing product. Without a key it exports to memory and prints the span tree. See Observability.

Start with retrofit.

Lifting an example

Most patterns are one self-contained index.ts, with no shared harness and no local imports. A few ship as a small directory, such as email-drafter and retrofit. To lift one into your project:

  1. Copy the example file into your project as index.ts.

  2. Install the runtime dependencies. The provider package major version must match your installed ai major version. This repo uses ai@6, so it uses @ai-sdk/openai@3 rather than 4.

    pnpm add @statelyai/agent@alpha ai@^6.0.67 zod@^4 xstate@6.0.0-alpha.25 @ai-sdk/openai@^3
    pnpm add -D @types/node typescript tsx
    
  3. The examples use the Node globals process, console, and import.meta.url. Give TypeScript a tsconfig.json with "module" and "moduleResolution" set to nodenext, "strict": true, and "types": ["node"].

  4. Run it with OPENAI_API_KEY=... npx tsx index.ts, or swap in any host.

@statelyai/agent declares these peer ranges: ai@^6.0.67, xstate@>=6.0.0-alpha.25 <6.0.0, and optionally @opentelemetry/api@^1. The examples use Zod 4 directly. The @alpha tag floats, so pin the exact version it installs once you have a working build.

Running from the repo

Examples live under examples/, with one flat directory per example and an index.ts entrypoint. Clone the repo, install dependencies, then run any example directly.

OPENAI_API_KEY=... npx tsx examples/<name>/index.ts
  • Every example runs in two modes. Run it against a real model as shown above, or drive it with injected mock executors in a test, with no key and no network.
  • Most examples expect OPENAI_API_KEY. Each file notes its requirements at the top. anthropic-sdk-host needs ANTHROPIC_API_KEY. The two Cloudflare examples target a Workers runtime rather than Node and tsx.
  • You can swap the host without changing the machine. See Use in any stack. The full index, with framework-comparison notes, is examples/README.md.