Agents
Author AI agents as typed XState state machines, where the machine decides and the host executes.
Alpha:
@statelyai/agent2.0 is in alpha. APIs can change between releases; pin an exact version. Feedback: github.com/statelyai/agent.
Overview
@statelyai/agent lets you author an AI agent as a typed XState state machine. The machine is a portable blueprint of what your agent can do; it never talks to a model directly. The core idea: the machine decides, the host executes.
The machine is the decision layer. It declares:
- which states exist
- which transitions are legal (enforced by guards)
- which model calls (text requests and decisions) each state makes
- which events the model may choose right now
The host is the execution layer. It supplies an executor set: three plain functions (generateText, streamText, decide) that take plain request objects and return plain results. The runAgent driver calls them whenever the machine needs a model. Because the machine only knows the executor contract, the same machine runs unchanged against the Vercel AI SDK, Cloudflare Workers AI, a raw provider fetch, or anything else.
A decision is where this matters most: the model chooses exactly one currently-legal machine event, not free text and not an arbitrary tool call. An illegal choice is rejected before it takes effect, so illegal behavior is impossible by construction, not discouraged by a prompt.
Why state machines for agents
- Legal by construction. The machine and its guards define every path. The model cannot drive the agent into a state you did not author.
- Portable. No dependency on any model SDK. Swap hosts, not agents.
- Inspectable. States, transitions, and requests are data you can read, diagram, and reason about before anything runs.
- Serializable. Every settle point produces a plain, JSON-serializable snapshot. Persist it anywhere and resume later.
Unlike a hand-rolled while loop or a graph framework, control flow is a state machine you can inspect, test, and resume, and the model can only ever pick a legal event.
Three ways in
- Author a new agent. Describe states, decisions, and typed requests, run locally with
runAgent, then test and inspect it with no API key. Use it in any framework or runtime viaprovideExecutorswith zero machine changes. Start at the Quickstart, then Use in any stack. - Retrofit an existing agent. Have a
whileloop or tangled control flow? Your existing SDK calls, tools, and retry code become the executors; the machine replaces only the control flow and runs in your existing setup. See Migrating from a loop. - Copy a known pattern. ReAct, reflection, plan-and-execute, RAG, supervisor, swarm handoff, and more, each a single runnable file you lift in 60 seconds. Browse Agent patterns.
Example
import { z } from "zod";
import { runAgent, setupAgent } from "@statelyai/agent";
import { createAiSdkExecutors, defineModels } from "@statelyai/agent/ai-sdk";
import { openai } from "@ai-sdk/openai";
const models = defineModels({ quick: openai("gpt-5.4-mini") });
const answerSchema = z.object({ answer: z.string() });
const agentSetup = setupAgent({
models,
context: z.object({ prompt: z.string(), answer: z.string().nullable() }),
input: z.object({ prompt: z.string() }),
output: answerSchema,
requests: {
answerQuestion: {
schemas: { input: z.object({ prompt: z.string() }), output: answerSchema },
model: "quick",
prompt: ({ input }) => input.prompt,
},
},
});
const machine = agentSetup.createMachine({
context: ({ input }) => ({ prompt: input.prompt, answer: null }),
initial: "answering",
states: {
answering: {
invoke: {
src: "answerQuestion",
input: ({ context }) => ({ prompt: context.prompt }),
onDone: ({ output }) => ({ target: "done", context: { answer: output.answer } }),
},
},
done: { type: "final", output: ({ context }) => ({ answer: context.answer ?? "" }) },
},
});
const result = await runAgent(machine, {
input: { prompt: "Why state machines?" },
executors: createAiSdkExecutors({ models }),
});
if (result.status === "done") console.log(result.output.answer);See the Quickstart for a step-by-step walkthrough.
Alpha status
The API changed completely in 2.0 and is still settling. Expect breaking changes before 2.0 stable.
Explicitly not shipped yet:
- Database storage adapters. Core ships persistence contracts and an in-memory event-log store, but no SQLite, Postgres, or Redis adapter packages.
- OpenTelemetry exporter. Build your own from the observation callbacks on
runAgent. - SSE/WebSocket transport helpers. Host your own stream over what
onChunkgives you. - Agent-specific dynamic fan-out helper. Dynamic fan-out works today through XState
spawn(...)orPromise.all(...)inside a host actor; core has no higher-level helper for branch binding and progress. - Visualization tooling. Stately Studio and a VS Code extension own diagramming and inspection.
If something here blocks you, or the API surface feels wrong, open an issue. This alpha exists to find that out before 2.0 stable.
Documentation map
- Quickstart: install and run your first agent machine end to end.
- Use in any stack: one machine runs locally, behind an HTTP route, or on the edge, with zero machine changes.
- Scope and ecosystem boundaries: what portable machine logic owns, what the host owns, and where specialized libraries fit.
- Express host: drive an agent machine from an HTTP route.
- Cloudflare host: run an agent machine on the edge with Durable Object persistence.
- Agent machines:
setupAgent, states, invokes, typed context, and guards. - Decisions: the model choosing exactly one currently-legal machine event.
- Plans: the multi-event decision,
agent.plan. - Text requests: typed text and structured-output model calls.
- Messages: the parts-based
AgentMessagemodel. - Human in the loop: idle-first pauses and resuming by snapshot.
- Hosts: executors, the AI SDK adapter, and writing your own.
- Observability: the Stately Inspector locally, and the trace stream to OpenTelemetry in production.
- Steps: the lower-level step path for durable hosts.
- Machines as data: authoring an agent machine as JSON.
- Multi-agent: sub-agents and child actors.
- Migrating from a loop: convert a hand-rolled
whileloop into an agent machine. - Agent patterns: ReAct, reflection, RAG, supervisor, and more as copy-paste machines.
- Examples: a curated index of runnable examples.