Model or dataset
statelyai/agent avatar
statelyai/agent

stately agent and the guard that rejects the model's choice

Create state-machine-powered LLM agents using XState

504 stars27 forksTypeScriptMIT

At a glance

What is it?
An XState-based agent framework where the state machine owns control flow and the model may only propose an event that guards have already allowed. The interesting parts are the retry on a rejected decision, the executors that stay outside the core, and a peer dependency hazard called out in its own install notes.
Who is it for?
It fits a team whose agent failures are control flow failures, where an illegal tool call or an unbounded loop is the actual problem rather than model quality, and who already think in statecharts. It does not fit someone who wants a prompt and a tool list working this afternoon, since version 2 is alpha on a non-default branch and the install needs matching prerelease peers.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • statelyai/agent504 ★ · +48
Star history
Repository2026-09-112026-11-14
statelyai/agent456504

48 Stars in the last 64 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The machine owns the flow, the model only picks an event

The claim in one line is that invalid agent actions become impossible, and the mechanism is a division of labour with three parts. The machine defines what the agent is allowed to do. The application chooses the model, runs the requests, and stores the state. The model proposes an event, and the machine decides whether it is allowed and what happens next. That last clause is the whole idea: a model does not get to call a tool, it gets to select an event, and if that event has no legal transition from the current state the request simply fails. The stated benefits follow from representing control flow that way rather than parsing it out of prose, namely determinism, inspectability, resumability, and running anywhere. Testing, inspection and visualisation are described as falling out for free, which is the claim to test first: XState already has a snapshot model, so the hard part was never proving a machine is correct.

A guard rejects the choice and the decision is retried

The quick start is a refund reviewer, and it is worth reading closely because the constraint is the point. The agent reviews refund requests, and the model is allowed to propose an automatic refund, but the state machine owns the hundred dollar limit. Context and input are typed schemas holding a request string and an amount, the output is an enum of refunded or review, and the events are an automatic refund with no payload plus a review carrying a reason. The model chooses between whichever events are legal in the deciding state, and the automatic refund transition only works when the amount is at most one hundred dollars. If the model picks it for a larger amount, the guard rejects the choice and the decision is tried again, which is the behaviour that distinguishes this from a prompt that asks the model nicely to check the amount. Reaching the refunded state yields an outcome of refunded, and the same machine then runs against a real model with no changes to it:

ts
import { openai } from "@ai-sdk/openai";
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";

const liveResult = await runAgent(refundMachine, {
  input: { request: "I was charged twice for the same order.", amount: 75 },
  executors: createAiSdkExecutors({ models: { fast: openai("gpt-5.4-mini") } }),
});

Executors stay outside the core, which never imports the AI SDK

The package boundary is deliberate and worth naming, because it decides how much you inherit. Passing the same models map into the setup call types the machine's model references, so a typo in a model name is a type error rather than a runtime surprise. Executors are always explicit at run time, and the core does not import the AI SDK at all, which means swapping the provided executors for your own functions changes nothing about the agent's behaviour. Two executor packages ship for the two common shapes: one built on the AI SDK for providers that already speak it, and a raw OpenAI SDK executor exposed at its own export path for people who do not. There is also a routing rule worth knowing, since a machine with several requests has the executor dispatch on the request name, so one executor can serve generate, stream and decide calls in the same run. For tests, a mock language model from the AI SDK's own test entry point stands in for the provider, which is what makes the no-API-key path in the documentation possible at all. One example is built specifically around this boundary, keeping tools and message history in the AI SDK's own native format instead of translating them, which is the cheapest way to see what the framework does and does not take over.

Snapshots and resumption are XState's own, not a reimplementation

Resumability is the claim a sceptic will test first, and the answer is that this project does not invent a persistence format. It uses XState's native snapshot contract, including the library's own version and migrate mechanism, and the examples lean on that deliberately. One example is dedicated to snapshot migration, another runs an artifact through native transitions and effects as a portable XState loop, and a third persists native snapshots inside application code rather than providing a storage abstraction. The call path is drawn as a chain in the documentation and it is short: the machine issues a request, the agent runner hands it to host executors that cover text generation, streaming and decisions, those make the API call, and the result travels back as an event or an output. The machine never talks to a model directly, and the stated consequence is that replacing the provided executors with your own functions changes nothing about the agent. The core concepts behind that chain are listed as machines owning control flow, bounded model decisions, typed requests, host-run executors and native snapshots. That last one is the important design signal: storage is framework-owned, so a Rails app writes its own snapshot store and the framework never assumes a database. It also means the parts of XState you already know keep working, since the migration path for an existing agent is described as turning a while loop into a machine where the SDK calls, tools and retry code become the executors and the machine replaces only the control flow. Long-running waits, retries, parallel work, child agents and approval states are all additions to a machine rather than a different way to represent control flow.

Three ways in, and one of them is copy and paste

The project describes three starting points rather than one tutorial. The first is authoring a new agent from states, decisions and typed requests, running it locally, testing it with no API key, and then dropping it into any framework or runtime with no machine changes. The second is the retrofit path for the very common shape of a hand-rolled loop, where the existing SDK calls, tool wrappers and retry logic stay put and are re-declared as executors. The third is the cheapest one: a set of known patterns, with ReAct, reflection, plan-and-execute, retrieval augmented generation and a supervisor pattern named explicitly, each described as a single runnable file you can lift in about a minute. Read the pattern list as the project's own view of what agent architectures are worth encoding. The reference documentation covers roughly the same ground from the other direction, with separate pages for machines, text requests, decisions, human in the loop, testing and verification, persistence, observability, usage and budgets, generating machines with an LLM, and a page aimed at people arriving from LangGraph. That last one and the migration page from a hand-rolled loop are the two most useful documents for deciding whether this suits you, because they state the differences instead of implying them. The remaining example set shows the range the authors consider interesting, from a guessing game where the model picks legal events in a loop, through hidden information with a human vote, to a ticket triage machine that returns structured data and a JSON agent whose machine is defined as data rather than code.

Peer version pairs are the install hazard, and they are documented

Everything about the install is prerelease, which makes the version pairing the part worth reading:

sh
pnpm add @statelyai/agent@alpha xstate@alpha zod

Requirements are Node 22.18 or newer and XState v6 alpha.46 or newer, and the schema library is a hard part of the quick start rather than an optional extra. The package is ESM-first, though CommonJS builds are published as well so a require call still resolves. The executors are optional and installed separately, one pair for the AI SDK and one for the raw OpenAI SDK. The specific warning is about provider packages rather than this library: they must match your AI SDK major, with the OpenAI provider at version four pairing with the AI SDK at version seven, and a bare provider install resolving to latest, which can mismatch the peer the SDK declares. Judgments are handled separately again, through an experimental evaluation call with a type-safe wrapper, invoked from an ordinary actor rather than from inside a machine.

Alpha on a next branch, published with schemas and skills

The packaging details are worth reading before you pin anything. The package name is scoped, the version is an alpha build in a series where three releases landed in under three weeks in September, and the documentation states plainly that version 2 is in alpha and that APIs may change before the stable release. The default branch is not the usual one, which matters if you clone and expect a stable line. The published file list includes the compiled output, a schemas directory and a skills directory, so machine schemas and agent skills travel inside the package rather than being fetched at run time. Exports are split by concern, with separate entry points for the AI SDK, logging, machines and the OpenAI executor, each shipping both ESM and CommonJS builds with their own type declarations. Licensing is MIT in a single file, and the author is the founder of the company behind the underlying state machine library. Around the package, the repository keeps a changeset directory for releases, a plans directory, a patches directory, fixtures, a dependency-graph check, a skills directory and a text file describing the project for language models, plus a demo application with its own manifest, product document and results directory. That demo is a separate application rather than a test fixture, complete with its own environment template, component configuration and build and test configs, which suggests the project treats a running product surface as part of its own verification.

Editorial conclusion

It fits a team whose agent failures are control flow failures, where an illegal tool call or an unbounded loop is the actual problem rather than model quality, and who already think in statecharts. It does not fit someone who wants a prompt and a tool list working this afternoon, since version 2 is alpha on a non-default branch and the install needs matching prerelease peers. Before you build on it, pin the pairs the documentation names, since a bare provider package resolving to latest is exactly the mismatch that turns into a peer error at install time rather than a logic bug you can debug.

Frequently asked questions

What does @statelyai/agent need before it installs?

Node 22.18 or newer and XState v6 alpha.46 or newer, plus a schema library. The package is ESM-first, though CommonJS builds are published so require() works.

How does the state machine stop a model taking an illegal action?

The machine owns the transitions and guards. In the refund example the model may propose the automatic refund event, but that transition only works when the amount is at most $100, and for a larger amount the guard rejects the choice and the decision is tried again.

Can I test a Stately Agent machine without an API key?

Yes. You run the machine locally, and a mock language model from the AI SDK's test entry point can stand in for the provider behind the AI SDK executors.

Is @statelyai/agent stable yet?

No. Version 2 is in alpha and the documentation warns that APIs may change before the stable release. Recent published versions are alpha builds released on a rapid cadence.

How do model providers get attached to a Stately Agent machine?

Pass the same models map to setupAgent to type the machine's model references, and pass executors explicitly at run time. The core does not import the AI SDK, so swapping the provided executors for your own functions changes nothing about the machine.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. statelyai/agent on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/statelyai-agent.svg)](https://hysenlabs.com/projects/statelyai-agent)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/statelyai-agent.svg)](https://hysenlabs.com/projects/statelyai-agent)

Was this useful?