lolspider / notes
Clipping · 11 分钟

Agent State Machines: Building Deterministic Scaffolding for Agents

If you're not using some type of agent state machine, you're completely at the mercy of the nondeterministic nature of models. Are you reall...

x.com @JoshARosen

📝 摘要

文章指出,如果不用状态机,Agent 就会完全受模型非确定性支配——由模型自行决定何时采取行动、何时算完成任务。作者把 Agent 状态机按作用层级分为四类:控制 Agent 内部执行循环(如基于 XState 的 Stately Agent)、编排多阶段工作流(LangGraph / Mastra / n8n)、监督多个独立 Agent 会话的生命周期(OpenAI Symphony、Pi Missions),以及独立于执行方式、建模「工作本身」状态与依赖(ThruWire)。尽管 LangGraph、XState 等已提供原语,开发者仍因简单性、想保留现有 harness、以及与既有数据库/队列/调度系统集成而自建状态机;代价是最终可能不得不重新实现 checkpointing、分布式锁、可观测性和崩溃恢复。核心做法是把权威状态放在状态机一侧,Agent 只能请求转移,由软件校验并执行。

核心概念: 把「什么状态下允许什么动作、何时算完成」从提示词移到模型之外的确定性软件层;模型可以提议,但不能单方面决定状态转移。

关键技术栈 / 关键要点:

  1. 内部执行循环状态机 — 显式建模 reasoning / tool / approval / error / complete 等状态,Stately Agent 用 XState 在 TypeScript 中定义状态、事件与守卫,由框架校验模型提出的事件。
  2. 工作流状态机 — 状态机位于单次模型调用之上,编排研究→起草→审批→发布等阶段;LangGraph 用 StateGraph + 条件边,Mastra 用 createStep/createWorkflow,n8n 提供可视化编排。
  3. Agent 监督状态机 — 位于 Agent 之外,管理跨多个独立会话/进程的任务派发与重试,如 Symphony 调度 Codex、Pi Missions 通过扩展 API 派发 worker。
  4. 「工作本身」的状态机 — ThruWire 用带证据的 AI checkpoint 图建模工作依赖,要求依赖完成才接受后续工作;这与 LangGraph 仅保存执行状态的 checkpointing 本质不同。
  5. 自建的三点理由 — 只需少量定制状态时的简单性、保留 Claude Code / Codex / Pi 等现有 harness、复用既有数据库/队列/调度基础设施。
  6. 自建的代价 — 简单状态机最终可能演化成 checkpointing、分布式锁、调度、可观测性、崩溃恢复的重造;持久化状态不等于持久化执行,需 Temporal / Restate / DBOS 等补足。
  7. 实现形态 — 状态存 JSON 或数据库,转移函数校验事件;接入方式包括外部 orchestrator 启动 SDK/子进程、harness 扩展暴露工具,或 MCP server 提供 claim_task / complete_task。

关键结论 / 实践建议: 不需要一个状态机控制整个系统——内部循环、跨会话监督、工作验收可以各用一层状态机并协同工作。状态机必须持有权威状态并强制执行转移规则,Agent 只能通过工具调用「申请」转移;随着 Agent 承担更长周期、更高后果的工作,这些确定性层会成为 Agent 基础设施的标准组成部分。


If you're not using some type of agent state machine, you're completely at the mercy of the nondeterministic nature of models. Are you really okay with models deciding what actions to take and when their work is complete?

This question is the driving force behind agent state machines, which define explicit execution states, the events that move an agent between them, and the rules governing those transitions.

State machines have been used in software for decades, but they are finding new applications in agent infrastructure. Lately, they're being used to control an agent's internal execution loop, manage workflows, and supervise work delegated across multiple agent sessions.

Existing frameworks, such as LangGraph and XState, already provide many of the primitives for managing state and controlling execution. Yet homegrown implementations are becoming increasingly common, often with surprisingly different architectures and relatively little code.

So what are developers actually building when they talk about state machines for agents? How do these implementations interact with existing agent harnesses, and why are developers building their own?

Why Agents Need State Machines

Most agents are built around a relatively simple execution loop. The model receives a prompt, decides whether to call a tool, processes the result, and repeats until it decides the task is finished. This is, after all, the magic of agents, and it works well when the agent can complete its work within a single session.

Simple looping is fine until an agent must follow a particular process, handle external events, or continue working across multiple sessions. An agent might need approval before taking an action, recover from a failed tool call, or perform validation before reporting completion. Encoding these requirements entirely in prompts or skills leaves the model responsible for following operational rules that could instead be enforced by software.

A state machine moves those rules outside the model. The model can request an action or propose a transition, but the surrounding software determines whether it is permitted.

State Machines Inside the Agent Loop

One class of implementations controls the agent's internal execution loop. Instead of allowing the model to continuously call tools until it stops, developers explicitly represent states such as reasoning, executing tools, waiting for approval, handling errors, and completing the run.

In a sense, this externalizes and governs the behavior already present in an agent harness. The harness manages model requests, executes tools, handles their results, and determines when execution should continue. Making the state machine a first-class construct converts these behaviors into defined states and transitions.

Stately Agent takes this approach using XState. Developers define states, permitted events, and transition guards in TypeScript. The application starts the machine using runAgent(), supplying executors that connect to the model provider through an SDK. When a state requests a model decision, the executor calls the model and returns a proposed event. XState validates the event and applies the transition.

For example, a customer service agent might enter a drafting state, invoke a model to generate a response, and move into an approval state. A human approval event advances it to sending, while rejection returns it to drafting. The model writes the response, but the application controls which transitions are permitted.

This approach can replace some of the control logic inside a conventional agent harness or operate as a separate execution layer around it.

State Machines for Agent Workflows

Another class uses state machines to guide agents through a workflow by controlling a sequence of activities. Here the state machine operates above individual model calls, determining which stage of a workflow should execute next.

Consider an agent responsible for researching a company, preparing a report, and obtaining approval before publication. Each stage of the workflow can invoke an LLM, execute conventional code, or delegate work to another agent. Review might return the report to drafting, while certain outcomes could require human intervention.

These systems are commonly implemented as execution graphs. Developers define nodes that perform operations, shared state that carries information between them, and transitions that determine which nodes execute next.

LangGraph implements this through its StateGraph API. Developers register functions as nodes, connect them with conditional edges, and invoke the compiled graph using graph.invoke() or graph.stream().

Mastra provides a TypeScript alternative using createStep() and createWorkflow() to compose executable steps.

n8n offers a visual approach, connecting agent nodes, conditional branches, and conventional application integrations into executable workflows.

The workflow runtime typically initiates execution, either through an application API call or an external trigger. Developers can also expose an entire workflow as a tool, allowing an agent to invoke it while the workflow runtime controls its internal steps.

LangGraph also supports durable execution through checkpointing, which saves the state of the execution graph as it progresses. This allows an interrupted workflow to resume from a saved state, supports human intervention, and provides a history of execution.

State Machines for Supervising Agents

A third class operates outside the agent entirely. Instead of controlling model calls or prescribing a workflow, these state machines manage the lifecycle of work delegated to independent agents.

OpenAI's Symphony provides a concrete example. Symphony monitors an issue tracker and dispatches Codex sessions to work on eligible tasks. It runs Codex through its app-server interface, allowing each agent to operate independently while Symphony monitors execution and handles retries. The state machine governs which work can run and what happens when an agent finishes.

A related approach appears in Pi Missions, which integrates directly into the Pi harness through its extension API. The extension exposes tools that the supervising agent can call to dispatch workers, validate results, and advance the mission. Each worker runs in a fresh Pi process, while the extension maintains the mission state and enforces transitions.

Whereas Symphony operates as an external supervisor, Pi Missions embeds the orchestration tools inside the supervising agent's harness. Neither needs to control the internal execution of individual workers. Instead, the state machine governs work across multiple agent runs, allowing the agents to decide how to accomplish their assigned tasks.

State Machines for the Work Itself

Another approach is to model the state of the work independently of how agents execute it. Rather than controlling an agent loop or scheduling a workflow, the system defines a state machine of work where work isn't accepted until dependent work is completed.

ThruWire takes this approach through a graph of AI checkpoints. Each checkpoint defines a requirement that must be satisfied, with evidence supporting its completion. Dependencies between checkpoints establish which conditions must be met before others can be accepted. Agents are free to determine how to accomplish the work, while the system evaluates whether the resulting evidence satisfies the requirements.

Note that these checkpoints are fundamentally different from execution checkpoints in systems like LangGraph. LangGraph saves execution state so an interrupted process can resume. ThruWire maintains the state of the work itself, including its requirements, dependencies, and validation evidence.

Why Developers Keep Building Their Own

Given the available infrastructure, why are developers still implementing their own state machines?

The first reason is simplicity. A developer who only needs to track five bespoke task states and dispatch a coding agent may not need a general-purpose graph runtime. A collection of transition functions and a polling loop might be sufficient.

The second is that many applications already have an agent execution environment. Developers building around Claude Code, Codex, or Pi can preserve the existing harness and manage task lifecycles externally. Symphony communicates with Codex through its app-server protocol, while Pi Missions integrates through Pi's extension system. Neither requires replacing the underlying harness.

A third reason is integration with existing infrastructure. Teams may already have databases, queues, schedulers, and application-specific state machines. Adding agent execution to these systems can be easier than adopting a separate runtime, particularly when rules for task ownership, concurrency, and recovery already exist.

Of course, there is a tradeoff. Developers who start with simple state machines can eventually find themselves implementing checkpointing, distributed locking, scheduling, observability, and crash recovery. At that point, they may have rebuilt much of the infrastructure they initially avoided.

How Developers Are Building Their Own

Building a custom state machine doesn't necessarily require much code. Developers can represent state in a JSON file or database and implement transitions as functions that validate events and update the state. The complexity comes from integrating those transitions with agent execution and ensuring they are enforced consistently.

The main implementation choice is how the state machine interacts with the agent harness. An external orchestrator might launch agents through an SDK or subprocess, while a harness extension can expose tools that agents call to request transitions. Alternatively, developers can implement an MCP server with tools like claim_task and complete_task, allowing agents using different harnesses to interact with the same state machine.

In each case, the state machine maintains the authoritative state and enforces the transition rules. An agent might report completion through a tool call or return a result to its orchestrator, which then validates the request and updates the state. The state is typically persisted in files or a database so it can be accessed across agent runs.

More sophisticated implementations also need to handle recovery and interrupted executions. Persisting state alone does not make execution durable, so developers may use frameworks such as Temporal, Restate, or DBOS to provide durable execution around their state machines.

Deterministic Scaffolding

What's interesting about these implementations is that developers are applying state machines at different levels of agent infrastructure. There isn't necessarily one state machine that needs to control the entire system.

These different layers of control can also work together. An agent's internal execution loop might be governed by one state machine, while another supervises its work across multiple sessions. A separate system might then determine whether that work satisfies its requirements.

As agents take on longer-running and more consequential work, I expect these deterministic layers to become a standard part of agent infrastructure.