Stately
PackagesAgent

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/agent 2.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:

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

  2. Install the runtime deps (the provider package major must match your installed ai major; this repo is on ai@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
  3. The examples use Node globals (process, console, import.meta.url), so give TypeScript a tsconfig.json with "module"/"moduleResolution" set to nodenext, "strict": true, and "types": ["node"].

  4. 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-host wants ANTHROPIC_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: allowedEvents narrowed 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 .json file. See Machines as data.
  • described-workflow: a plain XState machine with zero invokes (prompts live in state descriptions and meta), run via runAgent's getRequests option.

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 maxAttempts bound ending in an explicit failed outcome.
  • 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.all over 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 compacting state 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 openai package (Chat Completions), no AI SDK in between.
  • anthropic-sdk-host: the same contract against the raw @anthropic-ai/sdk package (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, with simulateAgent tests pinning before/after behavior. The worked proof for Migrating from a loop.
  • langsmith-otel: createOtelTraceHandler from @statelyai/agent/otel exporting 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.

On this page