statewright:用状态机给 AI Agent 装护栏

Agents are suggestions, states are laws.
代理提出的是建议,状态制定的是法律。

statewright 的核心很硬:用有限状态机控制 AI agent 在每个阶段能用哪些工具、能跑哪些命令。工作流定义一次,可在 Claude Code、Codex、Cursor、opencode、Pi 上强制执行。完整文档:docs.statewright.ai

这是典型的 状态机护栏(state machine guardrails):不靠更大模型和更长 prompt,而是把问题空间做小。

问题

给模型 40+ 工具和一个开放式问题,它经常出不了门。常见「药方」是更大模型、更长提示——有时有效。可观测性只能事后告诉你哪里炸了,不能预防。

这在 Coding Agent 场景尤其刺眼:一次 bugfix 里混杂读代码、改文件、跑测试、甚至碰数据库。若全程开放同一工具面,模型容易陷入「再读一遍同一文件」或在错误阶段执行破坏性命令。

做法

状态机在每个步骤约束工具空间解空间,让模型在聚焦上下文里推理:

  • planning:只读工具
  • implementing:解锁编辑,shell 受限(即便允许 Bash,重定向写入与破坏性操作仍可被拦)
  • testing:只允许指定测试命令

调用当前阶段不该有的工具 → 被拒,并提示现在可用什么、如何转移状态

注意:这不是「在系统提示里写请先规划」——那仍是建议。这里是协议层或 hook 层的硬拦截,属于 工具调用 的执行面约束。

效果:前沿模型 token 更省;本地 13B+ 模型开始能完成原先做砸的任务。

研究侧信号

在本地模型与 5-task SWE-bench 子集上,两个约 13.8GB / 19.9GB 的模型在 statewright 约束下可从 2/10 提到 10/10(同任务同硬件)。低于约 13GB 时,模型能发工具调用,但留不住足够文件内容做准编辑——那是能力地板,不是 statewright 独有。

结构性收益还包括:

  • 打断「反复读同一文件 5 次却从不 edit」的死循环
  • 工具集足够小,模型真在推理,而不是乱挥

详情见 Research brief。注意:子集实验,不是完整 2294-instance SWE-bench。对外引用时务必写清样本规模,避免把局部增益说成基准榜通杀。

对日常工程的启示更朴素:很多「agent 蠢」其实是工具面过大 + 阶段目标模糊。先把任务切成 planning / implementing / testing,再谈要不要上更大模型。

快速上手(Claude Code)

1
/plugin marketplace add statewright/statewright
2
/plugin install statewright
3
/reload-plugins

浏览器打开 statewright.ai 注册并生成 key → 粘贴到 agent。然后:

1
start the bugfix workflow — fix the failing tests in calc.py

或:/statewright start bugfix

典型轨迹:planning → implementing → testing → completed,全程按状态解锁工具。若测试失败,状态机可回到 implementing,而不是在 completed 硬装成功——这正是「环与重试」比纯 DAG 更贴近 agent 工作的原因。

工作原理

  • 核心:Rust 引擎评估状态机定义(states、transitions、guards、tool restrictions)——确定性,环路里没有 LLM
  • 插件层:经 MCP / hooks 接入 coding agent;激活 workflow 后,按状态自动强制工具限制。
  • 模型看到的可能是 5 个工具而不是 30 个,并得到当前阶段清晰指令。

主要护栏

| Guardrail | 作用 | | --- | --- | | Per-state tool enforcement | 不在 allowed_tools 的工具对 agent 不可见 / 不可用 | | Bash discernment | 非写状态拦 >>rm 等与脚本解释器 | | Edit guards | max_edit_lines、每状态可改文件数上限 | | Command allow-lists | 前缀匹配 allowed_commands | | Conditional transitions | 守卫谓词(eq、gt、exists…) | | Approval gates | requires_approval 高风险转移前等人 | | Environment scoping | 按状态 blocked_env / env_overrides | | Session isolation | 按 session 隔离状态 |

这些护栏可以叠加:例如 implementing 允许 Edit,但仍限制单次 diff 行数与可改文件数;testing 允许 Bash,但命令前缀必须是测试 runner。目标不是把 agent 关进死胡同,而是把错误动作的成本前移到协议层

自定义 workflow 示例

1
{
2
"id": "bugfix",
3
"initial": "planning",
4
"states": {
5
"planning": {
6
"allowed_tools": ["Read", "Grep", "Glob"],
7
"max_iterations": 8,
8
"on": { "READY": "implementing" }
9
},
10
"implementing": {
11
"allowed_tools": ["Read", "Edit", "Write"],
12
"max_edit_lines": 20,
13
"max_files_per_state": 3,
14
"on": { "DONE": "testing" }
15
},
16
"testing": {
17
"allowed_tools": ["Read", "Bash"],
18
"allowed_commands": ["pytest", "cargo test", "npm test"],
19
"on": {
20
"PASS": { "target": "completed", "guard": "tests_passed" },
21
"FAIL_TEST": "implementing"
22
}
23
},
24
"completed": { "type": "final" }
25
},
26
"guards": {
27
"tests_passed": { "field": "test_result", "op": "eq", "value": "pass" }
28
}
29
}

状态机不是 DAG——可以环与重试,这才是 agent 工作的真实形状。也可指向 JSON schema 让 agent 生成 workflow,再在 visual editor 微调。

支持的 Agent

| Agent | 集成 | 强制程度 | | --- | --- | --- | | Claude Code | Hooks + MCP | Hard(协议层) | | Codex | Hooks | Hard(alpha) | | opencode | TypeScript plugin | Hard(alpha) | | Pi | Skills extension | Hard(alpha) | | Cursor | MCP + rules | Advisory(alpha) |

Hard = 工具调用在协议层拦截;Advisory = 注入规则但不硬拦。Cursor 架构下 MCP alone 无法完全 gate 工具调用。

定价与自托管(摘要)

个人开发者有 Free 档;Pro / Team / Enterprise 在 workflow 数、转移次数、run history 上分级(以官网为准)。引擎 crates/engine 为 Apache 2.0,可嵌入;全栈单人/单团队自托管见 FSL 条款。另有 patent pledge

取舍

  • 需要 agent 支持 MCP 或 hooks
  • workflow 多半手写(虽可 agent 生成)
  • 过严会卡住,escape hatch 是 statewright_deactivate
  • 研究数字来自小子集,解读要克制

和 Harness 轴的关系

Agent Harness 管「怎么跑」;statewright 把其中 权限与阶段 做成可复用、可跨 harness 的硬约束。它不替代记忆或上下文工程,但补了「模型想做的 ≠ 允许做的」这一刀。

和 Skills 的分工也清晰:Skills 告诉模型 怎么做某类任务;状态机告诉运行时 现在允许做什么。两者叠用时,planning 状态配只读 + 加载调查类 skill,implementing 再放开 edit,比「一锅端系统提示」干净得多。

落地建议

  1. 先从 bugfix / review 这类短闭环 workflow 开始,别一上来建模整条发布流水线。
  2. 每个状态写清 allowed_tools 与 allowed_commands,用真实失败补守卫,而不是先堆复杂并行状态。
  3. 保留 deactivate 逃生口,并在团队规范里说明何时允许绕过。
  4. Cursor 等 advisory 集成上,不要假设「写了 rules 就等于硬拦」——关键路径仍要沙箱与 git 边界。
  5. 评测时对照「同模型无约束」,否则你无法判断收益来自状态机还是 prompt 运气。

Liked this note? Share it on Twitter / X, or browse more writing from the home page. Feedback and pointers welcome via @qianyuhe.

Thanks for reading.

– 千羽鹤