patterns.md
August 12, 2026 ยท View on GitHub
Alpha:
@statelyai/agent2.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
.jsonfile. See Machines as data. - described-workflow: a plain XState machine with no invokes. Prompts live in state
descriptionandmetafields, and the machine runs throughrunAgent'sgetRequestsoption.
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:
allowedEventsnarrowed 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
maxAttemptsbound, ending in an explicitfailedoutcome. - 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.allover 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
compactingstate 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
openaipackage and its Chat Completions API, with no AI SDK in between. - anthropic-sdk-host: the same contract against the raw
@anthropic-ai/sdkpackage 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.tsrefactored step by step into a machine, with each step shippable andsimulateAgenttests pinning behavior before and after. This is the worked example for Migrating from a loop. - langsmith-otel:
createOtelTraceHandlerfrom@statelyai/agent/otelexporting 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:
-
Copy the example file into your project as
index.ts. -
Install the runtime dependencies. The provider package major version must match your installed
aimajor version. This repo usesai@6, so it uses@ai-sdk/openai@3rather 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 -
The examples use the Node globals
process,console, andimport.meta.url. Give TypeScript atsconfig.jsonwith"module"and"moduleResolution"set tonodenext,"strict": true, and"types": ["node"]. -
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-hostneedsANTHROPIC_API_KEY. The two Cloudflare examples target a Workers runtime rather than Node andtsx. - 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.
Related
- examples/README.md: the full example index, including framework-comparison notes.
- Use in any stack: run any of these machines from local
runAgenton your server or edge runtime, unchanged. - Migrating from a hand-rolled loop: convert an existing
whileloop step by step. - Thinking in state machines: how to find the states before you pick a pattern.