我给自己的 AI 研发流程写过一套控制平面
拆解一套强调可追溯、可恢复和权限隔离的文件式 AI 研发控制平面。
本页目录 · 15 节
我给自己的 AI 研发流程写过一套完整的 Workflow Harness,名字叫 Marshall。
这里的 Harness 不是「先思考、再编码、最后测试」这种 Prompt 模板。它是一套位于代码仓库内的控制平面:用户只给出目标,系统负责选择阶段、绑定当前证据、限制执行者权限、持久化运行状态,并在每次状态推进前验证依赖与结果。
它试图回答一个比「模型会不会写代码」更难的问题:当一次任务跨过多个上下文窗口、多个执行者和多个有副作用的工具后,系统怎样证明当前结果仍然来自正确的输入、正确的权限和正确的执行顺序?
下面描述的是这套系统的完整设计与运行契约,而不是一个理想化流程图。
两个平面:规则与运行事实必须分开
Marshall 首先做了一个非常重要的拆分:
| 平面 | 位置 | 回答的问题 |
|---|---|---|
| 稳定控制平面 | 仓库内受版本控制的规则目录 | 允许怎样工作,谁拥有哪种权限,什么才算完成 |
| 单次运行平面 | 被忽略的单次证据目录 | 这一次实际读了什么、做了什么、产出了什么、停在哪里 |
稳定控制平面包含宪章、路由、阶段图、Artifact Map、Worker 契约、JSON Schema、领域规格和运行时代码。单次运行平面则保存 state.json、追加事件日志、不可变结果、Worker Report、设计或评审材料、补丁与回执。
这条边界避免了两种常见污染:
- 把某次任务生成的结论误当成长期规则;
- 把一份长期文档的更新时间误当成某次运行的实际顺序。
稳定文档定义「应该怎样」,运行包记录「实际怎样」。两者都需要,但权威不同。
整体链路
flowchart LR
U[用户目标] --> R[Route / Command]
R --> G[Stage Graph]
G --> S[StateV3 + Lease/CAS]
S --> C[Context / Evidence]
C --> X{Executor Type}
X --> P[Parent]
X --> D[Deterministic]
X --> W[Worker]
X --> E[Isolated Executor]
X --> H[Trusted Host]
X --> V[Provider Adapter]
P --> N[NodeResultV1]
D --> N
W --> N
E --> N
H --> N
V --> N
N --> B[Completion Barrier]
B --> T[StageResultV1]
T --> S
从左到右看,它有八个关键环节:
- 路由只确定目标和副作用边界;
stage-graph.yaml决定节点、依赖、执行者和完成屏障;StateV3为当前尝试分配 Lease、Generation、Revision 与 Fence;- Context 层从当前仓库、规格和 Provider 中挑选证据;
- 每个节点只能由图中声明的 Executor Type 执行;
- 结果必须发布为绑定输入与输出 Hash 的
NodeResultV1; - 完成屏障确认所有必需结果仍然有效;
StageResultV1才能授权状态推进或合法 Transition。
它不是一条 Prompt 驱动的流水线,而是一套「模型负责语义判断,Runtime 负责状态与权限」的混合系统。
控制平面里谁拥有最终解释权
复杂 Harness 最怕一条规则在多个位置各写一遍。因此成熟版明确了所有权:
| 组件 | 唯一职责 |
|---|---|
constitution.md |
不可覆盖的事实、授权与报告原则 |
routes/ 和 commands/ |
人类入口、接受的输入、阶段拒绝边界 |
stage-graph.yaml |
Node DAG、Executor、Barrier、Transition 与 Mode 上限 |
stage-artifacts.yaml |
逻辑 Artifact Role 到物理路径的映射 |
| Worker Schema 与 Runtime | 精确实例、上下文、输出、并发与汇合契约 |
templates/runtime/*.schema.json |
可由程序验证的数据形状 |
specs/ |
仓库与业务的稳定事实 |
| Runtime | Lease、CAS、事件、恢复、权限与 Provider 执行 |
路由文档可以解释阶段,但不能重新定义节点顺序;Worker Team 可以描述语义角色,但不能绕开 Worker Plan 自己增加实例;Markdown 可以展示结果,却不能替代 Hash 绑定的 Result。
这种设计本质上是在对抗「重复事实」。如果每层都能解释下一步,任何一个较少使用的分支都会逐渐漂移。
Stage 不是一个标签,而是一张可执行 DAG
每个 Stage 都包含:
purpose与可接受前序;- 输入绑定及 Freshness Policy;
nodes、depends_on与 Executor Type;- 每个节点的必需输出 Role 和确定性 Check;
completion_barrier;- 允许的后续 Transition。
例如成熟版 design 并不是「生成一个设计文档」,而是:
perception-bootstrap
-> context-selection
-> explore
-> pre-draft-specialists
-> question-gate
-> design-draft
├─> deterministic-verifier ─┐
└─> post-draft-critic ──────┤
v
repair-decision
-> final-design
确定性验证和批判性评审关注不同证据,可以并行;repair-decision 必须等待两个分支;只有 final-design 到达完成屏障,Stage 才能结束。
impl 则把代码写入明确分成计划、能力签发、低信任执行、补丁校验、可信提升和目标工作区回读。图中的顺序不是建议,而是权限链。
六种 Executor,代表六种不同信任边界
成熟版没有把所有节点都交给「一个更聪明的 Agent」。它区分六种执行者:
| Executor | 可以做什么 | 不可以做什么 |
|---|---|---|
parent |
阅读、推理、综合,生成阶段产物 | 冒充 Provider 或绕过写入边界 |
deterministic |
Schema、Hash、依赖、Freshness 等纯校验或转换 | 做开放式语义判断或外部写入 |
worker |
对一个窄问题产生只读、可归因报告 | 修改源码、完成 Stage、替主 Agent 下结论 |
provider |
执行一个已注册、已批准、可回读的外部操作 | 自由拼接未授权 Payload |
isolated-executor |
在隔离 worktree 内按一次性 Grant 修改白名单路径 | 写用户当前工作区或扩大范围 |
host |
校验补丁并提升到目标工作区 | 创作代码修改 |
Executor Type 不是角色名称,而是权限模型。同一个 LLM 即使能完成所有逻辑,也不能因此跨越信任边界。
StateV3 为什么不只是一个进度 JSON
StateV3 保存的是当前运行投影。除 stage 和 status 外,它还包含:
attempt、resume_epoch与active_generation;- CAS 使用的
revision; - 当前 Lease 与 Fencing Token;
- 前序
StageResultV1; - 输入、输出与 Artifact Hash;
- Node 与 Node Result;
- Worker Selection、Plan、Run 与 Result;
- Question、Approval、Mutation Intent、Delivery 与 Freshness;
- Late Result、Partial Commit 与下一条 Transition。
状态更新必须在锁内比较 Revision。旧 Generation 的 Worker 即使晚到,也不能把结果写进新一轮尝试;过期 Lease 不能继续发布;Question Gate 收到用户答复并恢复同一 Generation 后,新的 resume_epoch 会区分答复前后的执行身份。
这就是 Fencing 的意义:取消一个执行器,不只是告诉它「请停下」,还要让它即使没有停下,也失去提交结果的资格。
Event、State 与 Artifact 各自证明不同的事实
Marshall 的追加事件日志不是普通 Debug Log。每个有状态动作遵循:
decision -> action -> outcome
事件包含顺序号、父子关系、Correlation、前一事件 Hash 和当前事件 Hash。action 必须引用一个已存在的 decision,outcome 必须引用且只能完成一次对应的 action。Runtime 在发布结果之前预留 Decision/Action,执行后先读回结果投影,再追加 Outcome。
与此同时:
- State 回答「现在是什么状态」;
- Event 回答「为什么会到这里」;
- Artifact 回答「下游实际要消费什么内容」。
例如「设计已完成」不能由状态字符串单独证明。它至少需要一个 StageResultV1,其中包含输入指纹、输出绑定、Verification、Open Risks、Transition,以及 Decision/Action/Outcome 的 Event ID。绑定文件的字节发生变化,Hash 失效,完成状态就不能继续被复用。
Worker 不是固定团队,而是一次运行中的精确实例
成熟版虽然准备了领域、数据运行时、安全、分层、Critic、Verifier 等角色,但真正的运行权威是两份对象:
WorkerSelectionV1:为什么选或不选 Worker,当前有哪些风险轴,是否需要拆分;WorkerPlanV1:具体实例、阶段、上下文、Prompt/Model/Config Hash、只读权限、输出路径、并发与修复上限、Barrier。
Mode 分成 light、standard 和 deep。低风险、路径已知的单仓改动可以是零 Worker;迁移、Backfill、IAM、生产写入、跨仓、跨领域或来源权威不确定,会把设计推向 Deep;超过容量时先拆任务,而不是无限增加 Agent。
Worker Report 只有通过 Plan、Node、Generation、Context Hash 和 Schema 校验,才有资格进入 Barrier。主 Agent 仍然是唯一综合者,避免多个 Worker 各自把局部结论当成最终方案。
一次实现任务怎样穿过写入边界
实现阶段最能体现这套架构的信任模型:
branch-gate
-> task-trace
-> modification-plan
-> StageWriteGrantV1
-> isolated-executor
-> PatchBundleV1
-> PatchVerificationReceiptV1
-> trusted host promotion
-> PatchPromotionReceiptV1
-> post-promotion verification
-> spec-sync
StageWriteGrantV1 绑定仓库身份、Branch、Base Commit/Tree、目标文件前置状态、允许的 Create/Modify/Delete、执行实例、Generation、Fence、使用次数和过期时间。它只有一次使用机会。
Executor 在独立 worktree 内生成结构化 Patch Bundle,里面不是一段不透明 Diff,而是每个路径修改前后的存在状态、Mode、内容 Hash 和 Blob Ref。Trusted Host 重新检查目标工作区是否仍满足前置条件,才把补丁提升进用户正在使用的 checkout。
这里的 Promotion 不是上线,而是从低信任执行区进入高信任工作区。提升成功以后还必须回读目标文件并重新运行验证,因为隔离环境的成功不能证明目标环境相同。
外部写入为什么必须是 Preview、Approval、Execution、Readback
Jira、PR、评论和其他 Provider 写入不能只靠一句「用户说可以」。成熟版把一次外部动作拆成四段:
- 解析精确目标与 Payload;
- 展示当前预览并取得针对该内容的确认;
- 由注册 Provider Adapter 执行一个幂等 Intent;
- 从 Provider 重新读取 ID、字段、状态和 Revision。
如果请求超时,系统不能直接重试。它必须先查询 Provider:对象可能已经创建,只是响应丢了。否则所谓「重试」会生成第二个 Jira 或重复评论。
这条链同时区分能力和授权:工具里有 Token,只证明技术上能写;只有当前 Intent、目标、Payload 和确认仍然一致,才证明这一次被允许写。
恢复不是重新读一遍聊天
任务中断后,Runtime 会从 State、Event 与不可变结果恢复:
- 校验事件 Hash Chain 与 State Projection;
- 识别过期 Lease、旧 Generation 和不完整 Decision/Action;
- 验证已完成节点的输入、依赖与 Artifact Hash;
- 将不再新鲜的结果标为
stale,保留失效原因; - 从最近仍有效的 Barrier 前重新调度,而不是从头重复副作用。
恢复的难点不在「记住上次做到哪里」,而在判断哪些结果仍然有执行权。没有 Generation、Fence 和 Idempotency,恢复很容易变成重复执行。
成熟版重点防的失败模式
| 失败 | 机制 |
|---|---|
| 多个执行者同时推进同一 Stage | Lease + Revision CAS + Fencing Token |
| 旧 Worker 在新一轮完成后晚到 | Generation / Resume Epoch 校验,Late Result 隔离 |
| 下游读取旧尝试留下的产物 | Path + SHA-256 Binding 与 Freshness |
| Worker 数量很多但风险未覆盖 | Risk Profile、Required Role 与 Completion Barrier |
| Executor 修改范围外文件 | Write Allowlist、隔离 worktree、Patch Verification |
| Promotion 时用户工作区已经变化 | Base/Target Preconditions 与目标回读 |
| Provider 超时后重复创建 | Mutation Intent、Idempotency 与 Live Reconciliation |
| State 写了 completed 但产物不存在 | Node/Stage Result + Artifact Registry + Readback |
| Compaction 后模型凭摘要继续 | Runtime State 与 Event Chain 高于聊天记忆 |
这些都是工作流系统而非 Prompt 才能真正解决的问题。
这套架构适合什么,不适合什么
它适合:
- 跨进程运行数小时或数天;
- 外部调用不可安全重复;
- 多个执行者可能并发或晚到;
- 需要暂停、恢复、审计与责任追踪;
- 写入对象价值高、回滚困难;
- 合规要求必须证明谁在什么授权下做了什么。
它不适合默认包住每一个日常代码修改。一个当前会话内能完成、文件范围清楚、失败可用 Git 撤销的任务,如果也要承担 Lease、Schema、Artifact、Worker、Patch 与 Recovery 全套成本,控制面本身就会成为主要复杂度。
Temporal 的 Durable Execution 和 AWS Step Functions 都说明了类似边界:持久化历史、状态机、重试和暂停对真正的长运行流程非常有价值;价值来自流程需要这些性质,而不是因为「步骤多」就应该上工作流引擎。
分步骤阅读
- 目标路由:授权的是结果边界,不是「一路做到底」
- Progressive Context:怎样让高信号证据先进入上下文
- Stage Graph:什么时候一张 DAG 比模型计划更可靠
- State、Event 与 Artifact:当前投影、因果历史和结果内容怎样分工
- Worker:如何把多 Agent 变成风险驱动的证据系统
- Write Isolation:一次性写入能力与隔离 worktree
- Patch Promotion:候选修改怎样跨越信任边界
- Verification Readback:为什么测试、CI、部署和线上行为不能互证
参考资料
- Unrolling the Codex agent loop:模型、工具与环境之间的 Agent Loop。
- Unlocking the Codex harness:Thread 生命周期、配置、Sandbox 与 Harness 服务边界。
- OpenAI Agents SDK: Agent orchestration:LLM 编排与代码编排的不同取舍。
- Temporal Documentation:持久化执行、历史恢复与长运行 Workflow。
- AWS Step Functions workflow states:Task、Choice、Parallel、Wait 与终止状态的机器化表达。
相关文章
从 Workflow Harness 到轻量 Prompt:Codex 5.6 之后我删掉了什么
当模型的原生流程已经够强,Harness 会从效率放大器变成上下文负担。这次重写保留了安全边界,同时大幅精简 Prompt 和运行时。
目标路由:「帮我改一下」和「帮我发个 PR」不该走同一条路
路由不是给请求贴标签,而是先确定这次授权了哪些副作用、什么证据才算完成。