Agent patterns
Common agent patterns (ReAct, reflection, plan-and-execute, RAG, supervisor, and more) as copy-paste XState machines, one runnable file each.
Alpha:
@statelyai/agent2.0 is in alpha. APIs can change between releases; pin an exact version. Feedback: github.com/statelyai/agent.
Every well-known agent pattern is a control-flow shape: a loop, a branch, a fan-out, a handoff. This library makes each one an explicit XState machine you can read, test, and run. Below is a use-case map: pick a pattern, open its example, copy the one file.
Lifting an example
Each pattern is a single self-contained index.ts: no shared harness, no local imports. To lift one:
-
Copy the one example file into your project as
index.ts. -
Install the runtime deps (the provider package major must match your installed
aimajor; this repo is onai@6, so@ai-sdk/openai@3, not 4):pnpm add @statelyai/agent@alpha ai@^6 zod@^4 xstate@6.0.0-alpha.25 @ai-sdk/openai@^3 pnpm add -D @types/node typescript tsx -
The examples use Node globals (
process,console,import.meta.url), so give TypeScript atsconfig.jsonwith"module"/"moduleResolution"set tonodenext,"strict": true, and"types": ["node"]. -
Run it:
OPENAI_API_KEY=... npx tsx index.ts(or swap in any host).
Peer ranges from @statelyai/agent: ai@^6.0.67, xstate@>=6.0.0-alpha.25 <6.0.0, optional @opentelemetry/api@^1. The examples use Zod 4 directly. The @alpha tag floats, so pin the exact version it installs once you have a build that works.
Running from the repo
Examples live under examples/, one flat directory per example with an index.ts entrypoint. Clone the repo, install, then run any one directly:
OPENAI_API_KEY=... npx tsx examples/<name>/index.ts- Every example is dual-mode: run it against a real model as above, or drive it with injected mock executors in a test (no key, no network).
- Most expect
OPENAI_API_KEY; each file notes what it needs at the top.anthropic-sdk-hostwantsANTHROPIC_API_KEY; the two Cloudflare examples target a Workers runtime, not Node/tsx. - Swap the host without touching the machine (Use in any stack). The exhaustive index, with framework-comparison notes, is examples/README.md.
Core ideas
The core ideas: text requests, decisions, messages, and JSON authoring.
- twenty-questions: a decision loop where the model picks one legal event (ASK or GUESS) per turn; guard-enforced legality, machine-held score, play-again reset.
- joke: a minimal streaming text workflow.
- email-drafter: reusable text logic, parts-based messages, schema-typed state and transition meta.
- game-agent:
allowedEventsnarrowed as a function of input, gating moves by HP. - go-fish: hidden-information play with a checking-win → agent → human loop; the model chooses requests, the machine enforces the rules.
- json-agent: a full workflow (decision, text request, idle human step) authored as a real
.jsonfile. See Machines as data. - described-workflow: a plain XState machine with zero invokes (prompts live in state
descriptions andmeta), run viarunAgent'sgetRequestsoption.
Canonical example: twenty-questions.
Reasoning and tool loops
For tool use, start with Tool calling: your SDK runs the tool loop inside one request, in one machine state. ReAct is the same loop unrolled into explicit states, for when individual turns need gating (approval before a tool, a spend guard, a snapshot mid-loop).
- Tool calling (tool-calling): the SDK's loop runs inside a state you control, bounded by
metadata.maxSteps. - ReAct (react-agent): every turn is gateable, persistable, and inspectable, under a step-budget guard.
- Plan-and-execute (plan-and-execute): structured planner output; execution states iterate the plan.
- Reflection (reflection-writer): generate and critique are two states; a guard caps revisions.
- Evaluator-optimizer (ai-sdk-evaluator-optimizer): the scoring gate is a guard, so the loop terminates by construction.
- 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.
Canonical example: react-agent.
Retrieval
- RAG (rag): retrieve and answer are separate typed states; conversational memory lives in context.
- Corrective RAG (CRAG) (corrective-rag): self-correction as explicit branch states, not buried 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; coverage reflection gates one optional follow-up.
- SQL agent (sql-agent): query generation, DB execution, and synthesis are separately testable states.
Canonical example: 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 is a lint finding, not a silent dead end.
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; each branch is its own state.
- Prompt chaining (ai-sdk-marketing-chain): a linear state sequence, each link independently typed and inspectable.
- Parallel review (ai-sdk-parallel-review): parallel states fan out; the join is a plain aggregation state.
- Triage (triage): structured output validated against a schema before it leaves the state.
Canonical example: ai-sdk-routing.
Multi-agent
A supervisor is a routing state over typed workers, and the graph is the org chart. One level up, 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 drops the hub entirely: agents are peers and a handoff is a transition, persisted 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; the graph is the org chart.
- Swarm handoff (swarm-handoff): handoffs are transitions between typed child actors, persisted across turns.
- Orchestrator-worker (ai-sdk-orchestrator-worker): fan-out and join are plain
Promise.allover host actors. - Fan-out (map-reduce) (fan-out): dynamic parallelism from 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 states, not retries.
- Sub-agents (subflows, ai-sdk-sub-agents, debate-sub-agents): each child keeps its own executor binding; parents stay typed against results.
Canonical example: supervisor. See 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; the snapshot is plain JSON you store anywhere.
- Guardrails (guardrails): gate states with guards, not prompt pleading; an illegal path is unreachable.
- Context compaction (context-compaction): a
compactingstate folds old history into a running summary past a threshold. - Customer support (customer-support): sensitive actions gate on an idle state; the model can't act past the guard.
Longer pauses and durable threads:
- long-running-onboarding: a multi-day coordinator with durable typed state, two idle states, delegated IT provisioning, JSON snapshot resume.
- file-snapshot-store: a file-backed snapshot store for durable threads across processes.
Canonical example: human-in-the-loop. See Human in the loop for the idle-first pause and snapshot resume.
Hosts and runtimes
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 (Chat Completions), no AI SDK in between. - anthropic-sdk-host: the same contract against the raw
@anthropic-ai/sdkpackage (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.
- parallel-streams: fan-out over parallel worker streams relayed through a side channel.
- sse-transport: relaying provider stream chunks over an SSE transport.
Canonical example: ai-sdk-host.
Evaluation, migration, observability
- simulated-user-evaluation: a target chatbot and simulated user alternate under a turn bound, then an independent judge scores the transcript.
- retrofit: a tangled hand-rolled agent (
before.ts) refactored stepwise into a machine, each step shippable, withsimulateAgenttests pinning before/after behavior. The worked proof for Migrating from a loop. - langsmith-otel:
createOtelTraceHandlerfrom@statelyai/agent/otelexporting real spans over OTLP to LangSmith (LangChain's hosted tracing product); keyless it exports to memory and prints the span tree. See Observability.
Canonical example: retrofit.
Related
- examples/README.md: the exhaustive example index, including framework-comparison notes.
- Use in any stack: take any of these machines from local
runAgentto your server or edge runtime, unchanged. - Migrating from a hand-rolled loop: already have a
whileloop? Convert it step by step. - Thinking in state machines: how to find the states before you pick a pattern.