StateM: A CLI State Machine for Long-Running AI Agents
CLI runbook for agent long run.
At a glance
- What is it?
- StateM keeps an agent's procedural state in a versioned runbook instead of the model context, gating each phase transition on executable checks. It is a small Python CLI, not a workflow engine, and the README is thinner on failure recovery than on the happy path.
- Who is it for?
- StateM suits engineers running long, cyclic coding or terminal-agent tasks who want phase boundaries and evidence gates stored in files rather than in a prompt. It is the wrong tool if you need a general workflow engine with a scheduler, retries and a web UI, or if you want a library API rather than a CLI.
- Can I use it commercially?
- Yes. Apache-2.0 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 16 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The failure StateM targets: procedural state living in the prompt
The README names four ordinary reasons long agent runs fail: the original goal fades from attention, progress lives only in chat history, verification is postponed, and a new session cannot reconstruct what happened. None of these are model capability problems. They are bookkeeping problems, and StateM's answer is to move the bookkeeping out of the context window and into files.
The intended user is someone running an agent across many turns on a task with a recognizable shape: plan, implement, verify, hand off, and loop back when verification finds a gap. The README's own example is a coding agent, and the repository ships runbooks for Terminal-Bench 2.1 task families, so the project is aimed at terminal and coding agents rather than chat assistants or single-shot generation.
What StateM is not is equally clear from the README. It describes itself as "deliberately smaller than a workflow engine" and as a state-aware runbook that an agent can read, author, inspect and repair from the command line. There is no scheduler, no daemon and no server in the README. The unit of work is a run identified by a run ID, advanced by explicit commands.
How a transition works: a five-step transaction with gates
StateM separates two layers. The static runbook holds nodes, edges, prompts, hooks and gates and is meant to be committed to git. Runtime state holds the current node, history, results and timestamps and is not. A third layer, dynamic checks, lets an agent register verification specific to the current entry without mutating the shared runbook.
The README describes a transition as a transaction with an ordered sequence. The tool resolves the requested outgoing edge, runs the current node's before_transfer checks, loads and runs current-entry dynamic checks, evaluates the edge's condition, then runs the current node's out_hook and the edge hook. Only after that does it record the transition, create a new target entry and run the target node's in_hook.
The ordering matters in practice. Because the edge condition is evaluated after the blocking checks, a condition can assume the evidence exists. And because the target node's in_hook runs only after the transition is recorded, a crash between recording and entering leaves a trace rather than a silent gap. If a blocking check fails, the README states the agent remains in the current state with the failure recorded for repair. That is the repair loop in the diagram, not an exception path.
Gates themselves are typed: checklists, commands, predicates, manual approval and LLM review. A command gate such as python3 -m pytest -q is the most concrete of these, since its exit status is the evidence.
Installing StateM and running the bundled coding-agent runbook
The README gives one installation path: clone the repository and install it in editable mode. The core package declares no runtime dependencies and requires Python 3.11 or newer, so there is nothing to resolve beyond setuptools at build time.
git clone https://github.com/henryqin1997/statem.git
cd statem
python3 -m pip install -e .After that, the CLI should respond to a help flag. The pyproject file registers the entry point as statem = "statem.cli:main", so the command name is statem.
statem --helpThe quick start validates and starts the included coding-agent runbook, then inspects the current node and the next legal move. Note that each command takes --run-id, which is how a run is identified across invocations.
statem validate examples/coding-agent.yaml
statem start examples/coding-agent.yaml --run-id demo
statem cur --run-id demo
statem next --run-id demoAdvancing is a separate, deliberate command. The README shows goto with a target node and then history to inspect what happened.
statem goto plan --run-id demo
statem history --run-id demoRuntime data defaults to .statem/ inside the working tree. For state that should survive a disposable checkout, the README recommends exporting STATEM_STATE_DIR to a path outside the repository before starting the run.
export STATEM_STATE_DIR="$HOME/.local/state/statem/my-project"
statem start examples/coding-agent.yaml --run-id demoA minimal runbook is short enough to read in one sitting. It declares a name, an initial node, the nodes with their prompts and before_transfer gates, and the directed edges with conditions.
name: implementation-loop
initial: plan
nodes:
plan:
prompt: |
Read the task and write a concrete implementation plan.
before_transfer:
type: checklist
items:
- Scope and constraints are recorded
- Verification steps are definedThe execute node in the README's example combines a command gate with a checklist, so a test failure blocks the transition on its own. The edges form the loop: plan to execute, execute back to plan when verification found a fixable gap, and execute to handoff when implementation and verification are complete.
Where StateM is the wrong tool, and what the README leaves open
The comparison table in the README puts CI pipelines and general workflow engines in the same frame as StateM, and the honest reading is that StateM loses on most operational features they have. A CI pipeline is triggered by repository events and runs to completion without an agent in the loop. A general workflow engine usually brings scheduling, retries and a UI. StateM brings none of that. It is a CLI that an agent calls, and the agent is what decides when to call it. If your process needs to run unattended on a schedule, StateM is the wrong layer.
The bigger gap is recovery documentation. The README states that a failed blocking check leaves the agent in the current state with the failure recorded for repair, and it mentions generating resume and compaction prompts for long cyclic runs. It does not document rollback of a partially applied transition, nor what happens if the process is killed between recording a transition and running the target node's in_hook. The transaction ordering suggests the design anticipates this, but the README does not describe the recovery procedure. Treat that as unverified until you read the source.
Dynamic checks are the other place to be careful. Letting an agent register checks for the current entry without mutating the shared runbook is useful for task-specific verification, but it also means the checks that gated a given transition are not fully captured in the committed runbook. The runtime history is where they live, and runtime state is explicitly not meant to be committed. Reproducing a run later therefore depends on state you chose not to version.
The published accuracy figure, 88.8% descriptive accuracy on Terminal-Bench 2.1 across 395 of 445 trials, comes from the project's own release notes for the DeepSeek-V4-Flash runbook. It is a result for one policy and one benchmark, not a general measure of the tool.
StateM against a general workflow engine: definition versus runtime
The closest alternative in the README's own framing is a general workflow engine. The difference is where the graph lives and who advances it. In a workflow engine, the engine owns the graph and the runtime: it schedules steps, handles retries and exposes the run through an API or UI. In StateM, the graph is a YAML file in your repository and the agent owns the advancing, calling statem goto when it believes a transition is warranted. The engine's job shrinks to enforcing the gates and recording what happened.
That inversion is the whole design. It means StateM can be edited by the agent mid-run, a property the README's comparison table marks as rarely true of workflow engines. It also means StateM cannot enforce anything the agent does not ask for. A gate only fires on a transition attempt, so an agent that never calls goto simply stays put. There is no timeout, no watchdog and no external trigger in the README.
A TODO list is the other comparison the README makes, and the distinction is that a TODO list records intent while StateM records intent plus the evidence required to leave a phase. A checklist item such as "Relevant tests pass" is a claim; the command gate running pytest is the check. StateM's contribution is putting both in the same transition.
Maintenance, versioning and the Apache-2.0 licence
The repository is not archived and the last push was on 2026-09-02, roughly two weeks before this writing. The latest release, deepseek-policy9-tb21-artifacts-20260818, is dated 2026-08-18 and ships the DeepSeek-V4-Flash runbook plus Terminal-Bench 2.1 artifacts. The package version in pyproject.toml is 0.1.0, which is consistent with a young project whose API surface, the runbook schema in particular, has had little time to settle.
Upgrade cost is unusually low on the dependency side: the core package declares an empty dependencies list, so installing a new version does not pull a transitive tree with it. The cost sits in the runbook schema instead. Any change to node fields, gate types or the edge format can invalidate runbooks you have already committed, and the README's runbook reference is truncated, so the full field set is not visible there. Pin the version you validate against and re-run statem validate on your runbooks after an upgrade rather than assuming compatibility.
The project is Apache-2.0, which permits commercial use and modification and includes an explicit patent grant. The usual obligations apply: keep the licence and notice files with redistributed copies, and state significant changes. Nothing here is legal advice; if you embed StateM in a product, read LICENSE in the repository rather than this paragraph.
Editorial conclusion
StateM suits engineers running long, cyclic coding or terminal-agent tasks who want phase boundaries and evidence gates stored in files rather than in a prompt. It is the wrong tool if you need a general workflow engine with a scheduler, retries and a web UI, or if you want a library API rather than a CLI. Before adopting it, validate one of the bundled examples with statem validate examples/coding-agent.yaml, then decide where STATEM_STATE_DIR should point, because the default .statem/ directory sits inside the repository and does not survive a disposable checkout.
Frequently asked questions
What is StateM?
StateM is a command-line state machine for reliable, long-running AI agents, written in Python. It turns an agent workflow into a graph of states, transitions and executable checks, and stores the procedural state in files and runtime history instead of the model context.
How do I install StateM?
The README gives one path: clone the repository, change into it, and run python3 -m pip install -e . to install it in editable mode. The core package requires Python 3.11 or newer and declares no runtime dependencies.
How do I start a StateM run?
Validate the runbook first with statem validate examples/coding-agent.yaml, then run statem start examples/coding-agent.yaml --run-id demo. The run ID identifies the run across later commands such as statem cur and statem next.
Where does StateM store runtime data?
Runtime data defaults to the .statem/ directory. The README notes that for durable machine-local state which survives disposable checkouts, you can set the STATEM_STATE_DIR environment variable to a path outside the repository.
What happens when a StateM transition check fails?
The README states that if a blocking check fails, the agent remains in the current state and the failure is recorded for repair. The document does not describe rollback of a partially applied transition.
Community notes