教程·阅读约 5 分钟·
Statewright 实战:用状态机给 AI 编码代理装上护栏

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 中执行:

code
/plugin marketplace add statewright/statewright
/plugin install statewright

浏览器会自动打开,跳转到 statewright.ai 注册账号并生成 API key,粘贴到终端即可完成配置。

第二步:启动一个工作流

安装完成后,你可以直接在对话中触发一个工作流:

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

实际执行过程看起来是这样的:

code
◆ 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 阶段只读文件(ReadGrepGlob),确认问题后申请转入 implementing 阶段才能编辑,编辑完成后转入 testing 阶段只允许跑测试。每个阶段的工具范围都很窄,代理没有机会去读不相关的文件或做出格的操作。

你也可以用斜杠命令直接启动:/statewright start bugfix

定义自己的工作流

Statewright 的真正威力在于你可以完全自定义工作流。工作流定义是一个 JSON 文件:

code
{
  "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% 就不允许通过:

code
"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 只允许前缀匹配的命令通过,其他任何命令都会被拒绝:

code
"command_allow_list": ["pytest", "cargo test", "npm test", "go test"]

结合 bash_discernment: true,Statewright 会在 Bash 的上下文中检测写操作(echo >sed -ipython -c 等脚本解释器),即使 Bash 本身在 allowed_tools 中,这些操作也会被拦截。

编辑守卫

编辑不能太大。max_edit_lines 限制单次编辑的最大行数,max_files_per_state 限制每个状态最多修改的文件数。这些限制防止代理一次改太多内容导致不可控。

审批门

某些高风险操作可以设置人工审核步骤:

code
"deploy": {
  "allowed_tools": ["Read", "Bash", "Edit"],
  "requires_approval": true,
  "on": { "APPROVED": "verify" }
}

当代理到达 deploy 状态时,会暂停等待人工审核。只有审批通过后才能继续。

中断

更高级的功能是中断(Interrupts)。当代理编辑的文件匹配某个 glob 模式时,自动跳转到一个验证状态,检查完后再回到原来的位置。比如编辑了类型定义文件,自动跳转到类型检查状态:

code
"interrupts": [
  { "pattern": "**/types/**", "state": "typecheck", "resume": true }
]

环境变量隔离

敏感信息保护。通过 blocked_env 屏蔽生产环境变量,通过 env_overrides 注入测试值:

code
"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 另一个很实用的设计。不同阶段对模型能力的要求不同,不需要全程使用同一个模型。

工作流可以指定每个状态使用哪个模型:

code
{
  "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 的审查场景,要求代理先理解改动、再做评审、必要时补充测试,最后生成评审报告。

code
{
  "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 阶段只能写不能读内容——确保生成的评审报告是完整的

这种分阶段的设计,本质上是把"一个人完成所有工作"变成了"多个人接力完成,每道工序都有明确的质量门"。

常见问题与排错指南

代理卡在某个状态不前进

这是最常见的问题。检查几个地方:

  1. 转移条件是否正确? on 字段中的键(如 READYDONE)是转移信号,代理需要明确发出这个信号才能转移。如果工作流定义中某状态没有定义 on,代理无法离开该状态

  2. max_iterations 太低:如果某个状态的逻辑比较复杂(比如 analyze 需要读很多文件),默认的 5-8 次迭代可能不够。先设到 12-15 测试,确认正常工作后再下调

  3. 工具不够用:确认当前状态的 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 参数指定工作流文件。

code
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任务)
gemma33.3GB
gemma4:e2b7.2GB✅ (特殊适配)
gpt-oss:20b13.8GB✅ (5/5)
gemma4:31b19.9GB✅ (5/5)
llama3.342.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 不是让模型变聪明,而是让它的活动范围变得刚好够用。状态机约束虽然多了一道定义工作流的步骤,但在需要可靠输出的场景下,这个投入很值得。

完整参考

分享到
微博Twitter

© 2026 四月 · CC BY-NC-SA 4.0

原文链接:https://aprilzz.com/tutorials/statewright-ai-agent-guardrails