
Statewright 实战:用状态机给 AI 编码代理装上护栏
手把手教你用 Statewright 的状态机框架为 AI 编码代理定义工作流,让 Claude Code、Codex 等工具按你设定的阶段逐步执行任务
原创。AI 编码代理很强,但你给它 40 多个工具和一个开放式问题,它就会开始重复读同一个文件、在审核阶段调用编辑、测试还没跑就部署。Statewright 用状态机给每个阶段限定工具集合——规划阶段只能读代码,实现阶段才能编辑,测试阶段只允许跑测试。这篇教程带你从安装到自定义工作流,一步步搭建自己的代理护栏。
为什么 AI 代理需要护栏
如果你用过 Claude Code 或 Codex 做复杂任务,大概率遇到过这种情况:你让它修复一个 bug,它先读了一遍代码,没找到问题,又读了一遍,然后开始怀疑是配置文件的问题,又跑去读配置,读完之后回来改了一段完全不相干的代码,最后跑测试发现更糟了。
这不是模型不够聪明。问题出在工具空间太大。
给一个模型 40 多个工具和一个开放式目标,它就像走进了一家有 40 道门的超市,每道门后面又有 40 条走廊。它会在合理决策和随机试探之间反复摇摆。更大的模型和更长的提示词能缓解一部分,但不是根本解法。
状态机提供了一个完全不同的思路——不是让模型更聪明,而是让问题空间更小。
—— 广告 ——
Statewright 是什么
Statewright 是一个开源的 Rust 框架(GitHub 400+ stars),用状态机来控制 AI 编码代理在每个阶段能使用哪些工具。核心思想可以用一句话概括:
代理是建议,状态是法律。
你定义一个工作流(workflow),包含若干个状态(state),每个状态限定一组可用的工具。代理只能在当前状态下使用允许的工具。想调用不在当前阶段的工具?会被拒绝,并返回一条消息告诉你当前能用什么、如何切换到下一阶段。
Statewright 支持 Claude Code、Codex、Cursor、Opencode 和 Pi 等多种编码代理,同一个工作流可以跨工具复用。
截至 2026 年 6 月,项目发布仅一个多月,GitHub 上已有 400+ stars,社区活跃度很高。
架构拆解:三层设计
Statewright 的架构分三层,每层独立可用:
1. 引擎层(Engine)
纯 Rust 的状态机评估器,位于 crates/engine。负责状态、转移、守卫条件和工具限制的评估逻辑。这一层是确定性的——没有 LLM 参与,没有运行时依赖。可以把它理解为一个纯粹的状态转换规则引擎。
2. 代理二进制(Agent Binary)
位于 crates/cli,编译产物是 sw-agent。这是一个直接连接 Ollama 的代理执行器。它能加载一个工作流,在受限循环中运行 LLM,强制执行工具访问权限,并输出结构化的 JSONL 事件流。
支持通过 --config 按状态配置模型路由,通过 --state 执行单个状态(TUI 或 MCP 网关负责编排,sw-agent 每次执行一个状态后退出)。
3. 插件层(Plugin Layer)
位于 crates/mcp-gateway + plugins/。通过 MCP 网关集成到 Claude Code、Codex、Pi 等编码代理中。激活工作流后,hook 会在每个状态强制执行工具限制。代理看到的是 5 个工具而非 30 个,每个阶段做什么一目了然。
终端界面(TUI)
crates/tui 提供一个基于 ratatui 的终端界面,它启动 sw-agent 作为子进程,实时渲染 JSONL 事件流。支持键盘输入、演示模式和 fixture 选择。
快速开始:在 Claude Code 中安装
第一步:安装插件
在 Claude Code 中执行:
/plugin marketplace add statewright/statewright
/plugin install statewright
浏览器会自动打开,跳转到 statewright.ai 注册账号并生成 API key,粘贴到终端即可完成配置。
第二步:启动一个工作流
安装完成后,你可以直接在对话中触发一个工作流:
start the bugfix workflow — fix the failing tests in calc.py
实际执行过程看起来是这样的:
◆ statewright — statewright_start (workflow: bugfix)
◆ [statewright] Workflow activated: bugfix
◆ statewright — statewright_get_state (MCP)
◆ Current phase: planning. Let me read the code first.
Read 2 files
[statewright] planning => implementing
◆ statewright — statewright_transition (READY)
Edit calc.py: 1 line changed
[statewright] implementing => testing
◆ statewright — statewright_transition (DONE)
Bash: pytest -x — 7 passed
[statewright] testing => completed
◆ [statewright] Workflow complete. 46 seconds.
注意整个过程只用了 46 秒。代理在 planning 阶段只读文件(Read、Grep、Glob),确认问题后申请转入 implementing 阶段才能编辑,编辑完成后转入 testing 阶段只允许跑测试。每个阶段的工具范围都很窄,代理没有机会去读不相关的文件或做出格的操作。
你也可以用斜杠命令直接启动:/statewright start bugfix。
定义自己的工作流
Statewright 的真正威力在于你可以完全自定义工作流。工作流定义是一个 JSON 文件:
{
"id": "bugfix",
"initial": "planning",
"meta": {
"default_model": "claude-sonnet-4-20250514"
},
"states": {
"planning": {
"allowed_tools": ["Read", "Grep", "Glob"],
"model": "claude-haiku-4-5-20251001",
"thinking_level": "low",
"max_iterations": 8,
"on": { "READY": "implementing" }
},
"implementing": {
"allowed_tools": ["Read", "Edit", "Write"],
"max_edit_lines": 20,
"max_files_per_state": 3,
"on": { "DONE": "testing" }
},
"testing": {
"allowed_tools": ["Read", "Bash"],
"command_allow_list": ["pytest", "cargo test", "npm test"],
"bash_discernment": true,
"on": {
"pass": "completed",
"fail": "implementing"
}
},
"completed": {
"allowed_tools": ["Read"]
}
}
}这个工作流定义了一个典型的 bug 修复流程:
planning(规划):只允许读取类工具(Read、Grep、Glob),使用 Haiku 模型(便宜、快速),思考深度设为 low,最多迭代 8 步(防止死循环)。代理只能在读模式下分析问题,确认修复方案后通过 READY 信号转入 implementing。
implementing(实现):允许 Read、Edit、Write,但限制每次编辑不超过 20 行,每个状态最多改 3 个文件。这么做有两个好处:一是防止代理一次性改太多引入连锁问题,二是强制代理做小步提交。完成后通过 DONE 信号转入 testing。
testing(测试):允许 Read 和 Bash,但 Bash 只能执行白名单中的命令(pytest、cargo test、npm test)。通过 bash_discernment 阻止代理用 echo、sed 等命令绕过编辑限制。测试通过则转入 completed,测试失败则回到 implementing 继续修改。
completed(完成):只读模式,代理不能做任何修改,适合做最终审查。
条件转移
Statewright 支持基于上下文数据的条件转移。比如测试覆盖率低于 80% 就不允许通过:
"testing": {
"allowed_tools": ["Read", "Bash"],
"command_allow_list": ["pytest", "cargo test"],
"on": {
"pass": "review",
"fail": "implementing"
}
},
"review": {
"allowed_tools": ["Read"],
"on": {
"coverage_gt_80": "completed",
"coverage_lte_80": "implementing"
}
}这个例子中,测试通过后先进入 review 状态检查覆盖率,只有达到 80% 以上才允许完成,否则回到 implementing 继续补充测试。
护栏功能详解
Statewright 提供了一整套护栏机制,下面挑几个最实用的展开说。
按状态限定工具
这是最基础的护栏,也是 Statewright 的核心功能。每个状态通过 allowed_tools 字段限定可用的工具集合。代理根本看不到列表之外的工具,也就没有机会误调用。
命令白名单
Bash 工具是最危险的——代理可以用 echo > file 覆盖文件,用 sed -i 直接修改,用 rm -rf 删除内容。command_allow_list 只允许前缀匹配的命令通过,其他任何命令都会被拒绝:
"command_allow_list": ["pytest", "cargo test", "npm test", "go test"]结合 bash_discernment: true,Statewright 会在 Bash 的上下文中检测写操作(echo >、sed -i、python -c 等脚本解释器),即使 Bash 本身在 allowed_tools 中,这些操作也会被拦截。
编辑守卫
编辑不能太大。max_edit_lines 限制单次编辑的最大行数,max_files_per_state 限制每个状态最多修改的文件数。这些限制防止代理一次改太多内容导致不可控。
审批门
某些高风险操作可以设置人工审核步骤:
"deploy": {
"allowed_tools": ["Read", "Bash", "Edit"],
"requires_approval": true,
"on": { "APPROVED": "verify" }
}当代理到达 deploy 状态时,会暂停等待人工审核。只有审批通过后才能继续。
中断
更高级的功能是中断(Interrupts)。当代理编辑的文件匹配某个 glob 模式时,自动跳转到一个验证状态,检查完后再回到原来的位置。比如编辑了类型定义文件,自动跳转到类型检查状态:
"interrupts": [
{ "pattern": "**/types/**", "state": "typecheck", "resume": true }
]环境变量隔离
敏感信息保护。通过 blocked_env 屏蔽生产环境变量,通过 env_overrides 注入测试值:
"implementing": {
"allowed_tools": ["Read", "Edit", "Bash"],
"blocked_env": ["PROD_DB_URL", "AWS_SECRET_KEY"],
"env_overrides": {
"PROD_DB_URL": "postgres://test:test@localhost:5432/test"
}
}代理在 implementing 状态下运行 Bash 时,环境变量已被替换,不可能意外访问生产环境。
按状态路由模型
这是 Statewright 另一个很实用的设计。不同阶段对模型能力的要求不同,不需要全程使用同一个模型。
工作流可以指定每个状态使用哪个模型:
{
"meta": { "default_model": "claude-sonnet-4-20250514" },
"states": {
"diagnose": {
"model": "claude-haiku-4-5-20251001",
"allowed_tools": ["Read", "Bash"]
},
"propose_fix": {
"model": "claude-opus-4-6",
"allowed_tools": ["Read"]
},
"execute": {
"allowed_tools": ["Read", "Edit", "Bash"]
}
}
}diagnose 阶段用 Haiku 做快速排查——便宜、速度快,适合扫日志、查配置。propose_fix 阶段换 Opus 做高价值推理——修复方案的质量直接影响最终结果,值得用最好的模型。execute 阶段继承 default_model(Sonnet),平衡质量和成本。
sw-agent 还支持通过 --config 配置每个状态的 Ollama URL、温度和上下文窗口覆盖,适合在本地运行不同模型。
对比现有方案
和其他 AI 代理工具相比,Statewright 的定位很独特:
vs. 传统 prompt engineering:写更长的系统提示词要求代理"先规划再执行"是最常见的做法,但模型不一定遵守。Statewright 在技术层面强制执行——代理不遵守就调用不了工具。
vs. 观察性工具(如 LangSmith、Arize):这些工具告诉你代理做了什么(事后追责),但不阻止错误行为。Statewright 在事前预防——代理尝试越界操作时会被拒绝,不是被记录。
vs. DAG 工作流框架:DAG(有向无环图)是单向的,走完就结束了。状态机可以循环和重试——测试失败后回到实现阶段重新修改,这是真正的代理式工作流需要的。
vs. 拒绝侵入式工具:Statewright 通过 MCP 集成,不需要修改你的编辑器或终端——它在工具调用层面做拦截,对正常使用几乎无感知。
实战案例一:从零搭建代码评审工作流
让我们搭建一个比 bugfix 更完整的场景——代码评审工作流。这个工作流适用于 Open PR 的审查场景,要求代理先理解改动、再做评审、必要时补充测试,最后生成评审报告。
{
"id": "code-review",
"initial": "understand",
"meta": {
"default_model": "claude-sonnet-4-20250514"
},
"states": {
"understand": {
"allowed_tools": ["Read", "Grep", "Glob", "Bash"],
"command_allow_list": ["git diff", "git log", "git show"],
"max_iterations": 6,
"on": { "READY": "analyze" }
},
"analyze": {
"allowed_tools": ["Read", "Grep", "Glob"],
"model": "claude-opus-4-6",
"thinking_level": "high",
"max_iterations": 12,
"on": { "NEEDS_TESTS": "supplement_tests", "DONE": "report" }
},
"supplement_tests": {
"allowed_tools": ["Read", "Edit", "Write", "Bash"],
"command_allow_list": ["pytest", "npm test", "go test"],
"max_edit_lines": 30,
"max_files_per_state": 2,
"on": { "DONE": "report", "FAIL": "analyze" }
},
"report": {
"allowed_tools": ["Read", "Write"],
"on": { "DONE": "completed" }
},
"completed": {
"allowed_tools": ["Read"]
}
}
}这个工作流的特点是:
- understand 阶段用 Sonnet 配合 Git 命令理解改动范围,只看 git diff 和 git log,不做任何修改
- analyze 阶段换 Opus 做深度评审,思考深度设为 high。发现了需要补充测试的场景,转入 supplement_tests;没问题就直接出报告
- supplement_tests 阶段可以有限度地写代码,但只能写测试文件(通过 max_files_per_state: 2 限制),且必须先跑测试确认通过
- report 阶段只能写不能读内容——确保生成的评审报告是完整的
这种分阶段的设计,本质上是把"一个人完成所有工作"变成了"多个人接力完成,每道工序都有明确的质量门"。
常见问题与排错指南
代理卡在某个状态不前进
这是最常见的问题。检查几个地方:
-
转移条件是否正确?
on字段中的键(如READY、DONE)是转移信号,代理需要明确发出这个信号才能转移。如果工作流定义中某状态没有定义on,代理无法离开该状态 -
max_iterations太低:如果某个状态的逻辑比较复杂(比如 analyze 需要读很多文件),默认的 5-8 次迭代可能不够。先设到 12-15 测试,确认正常工作后再下调 -
工具不够用:确认当前状态的
allowed_tools包含了转移所需的工具。比如你想让代理在 planning 阶段执行git log,但 allowed_tools 只有["Read"],它调不了 Bash
代理能访问不该访问的文件
Statewright 控制的是工具级别,不是文件级别。如果你需要更细粒度的文件访问控制,需要配合操作系统的文件权限或工具的沙箱机制。Statewright 的定位是在工具调用层面做约束,而不是取代安全沙箱。
模型路由不生效
检查客户端是否支持程序化模型切换。Pi 和 Rust 执行器(sw-agent)支持即时代码切换。Claude Code 和 Codex 的模型路由是建议性的(advisory),代理可能忽略。如果模型路由是硬需求,建议使用 sw-agent 执行器。
工作流文件放哪里
工作流 JSON 文件默认放在项目根目录的 .statewright/workflows/ 下。你也可以通过环境变量 STATEWRIGHT_WORKFLOWS_DIR 指定其他路径。安装插件时,可以通过 --workflow-path 参数指定工作流文件。
export STATEWRIGHT_WORKFLOWS_DIR=./ai-workflows
# 之后启动工作流
/statewright start code-review小模型场景下的实际效果
很多人想知道 Statewright 对小模型是否有用。官方的研究数据给出了明确的答案:
在 5 个 SWE-bench 子集任务上,不加护栏时 13GB 以下的模型完全无法完成任何任务(0/10 success)。加了护栏后,7.2GB 的 gemma4:e2b 在有特殊适配的情况下能完成部分任务,但小模型的核心瓶颈不在工具选择而在输出精度。
3.3GB 的 gemma3 即使加了护栏也失败,不是因为选错了工具,而是因为它会重写整个文件而非做精确编辑。
实际的结论:Statewright 对小模型有结构性帮助(避免工具选择漂移),但无法弥补模型自身的生成能力不足。如果你的设备只能运行 3-7GB 的模型,Statewright 能帮你减少"代理乱逛"的问题,但无法让模型完成精确的文件编辑。
对于 13GB 以上的模型,效果就完全不同了。官方的 5 任务测试中,不加护栏时 10 次只成功 2 次,加了护栏后 10 次全部成功。提升主要来自两方面:一是打破读文件的死循环(代理不再反复读同一个文件),二是工具空间缩小到 3-5 个后,模型的决策质量显著提升。
硬件要求
Statewright 对模型大小有一个明确的结论:门槛大约在 13GB 左右。
| 模型 | 大小 | Bug修复 (26行) | SWE-bench (5任务) |
|---|---|---|---|
| gemma3 | 3.3GB | ❌ | ❌ |
| gemma4:e2b | 7.2GB | ✅ (特殊适配) | ❌ |
| gpt-oss:20b | 13.8GB | ✅ | ✅ (5/5) |
| gemma4:31b | 19.9GB | ✅ | ✅ (5/5) |
| llama3.3 | 42.5GB | ✅ | ✅ (2/2) |
13GB 以下的小模型能正确识别 bug,但无法做精确的文件编辑(会重写整个文件)。13GB 以上的模型配合护栏后,效果提升非常显著——在 5 个 SWE-bench 任务上,两个本地模型从 10 次尝试仅 2 次成功提升到 10 次全部成功。
适用场景与选型建议
适合用 Statewright 的场景:
- 你的团队使用 Claude Code 或 Codex 做代码开发,希望控制代理的行为范围
- 你需要一个工作流在多种编码代理间复用(比如团队同时用 Cursor 和 Claude Code)
- 你想实现"小模型做快速排查,大模型做关键决策"的分级路线
- 你在做一个涉及多步骤的复杂任务,需要确保每个步骤的输入输出可控
- 你的 CI/CD 流程中有 AI 代理参与,需要部署前做安全检查
不太适合的场景:
- 只是偶尔用 AI 代理写几行代码,不需要复杂的工作流控制
- 你的工作流非常简单(读代码 → 改代码 → 完成),不需要多状态编排
- 你需要的是 Agent 的观测和调试工具,而非事前约束
一句话总结:如果你发现 Claude Code 或 Codex 在做复杂任务时经常"跑偏",Statewright 不是让模型变聪明,而是让它的活动范围变得刚好够用。状态机约束虽然多了一道定义工作流的步骤,但在需要可靠输出的场景下,这个投入很值得。
完整参考
© 2026 四月 · CC BY-NC-SA 4.0
原文链接:https://aprilzz.com/tutorials/statewright-ai-agent-guardrails