Article
Harness 实战:让 Agent 流水线可信的五层工程骨架
结论先行
Harness 不是让 Agent 更聪明,而是让 Agent 的工作更可信。
这次变更给一条 Agent 研发流水线加了一层工程化骨架。它管五件事:
- 流程怎么定义
- 当前跑到哪
- 产物以什么为准
- 什么时候允许继续
- 失败后怎么恢复
这篇文章基于一个真实的 Agent 编排系统(Boss),拆解 Harness 如何从架构层面解决”Agent 自说自话”的问题。
最关键的变化:workflow-plan.json
过去系统里已经有 pipeline pack(流水线模板)、artifact DAG(产物依赖图)、runtime commands(运行时命令)。DAG 能表达”哪个产物依赖哪个产物”,runtime 能记录阶段和 Agent 状态。
但它们之间缺一层明确的执行定义。
现在初始化时,会把 pipeline pack 和 artifact DAG 编译成一份 workflow plan:
graph LR
A[Pipeline Pack] --> C[compileWorkflowPlan]
B[Artifact DAG] --> C
C --> D[workflow-plan.json]
D --> E[phases / agent nodes / gate nodes / 依赖关系]
编译出的 workflow-plan.json 描述这条流水线有哪些 phase、哪些 agent node、哪些 gate node,以及节点间的依赖关系。
与此同时,三个哈希锚定了”流程定义是什么”:
| Hash | 来源 | 含义 |
|---|---|---|
workflowHash | 整个 WorkflowPlan 的 sha256 | 流程定义的指纹 |
packHash | Pipeline Pack 配置 | 模板是否变了 |
artifactDagHash | artifact-dag.json 内容 | 依赖图是否变了 |
而 runId 描述的是”这一次具体执行”。
这个拆分很重要。
因为流程定义和运行实例不是一回事。定义可以被审计、比较、缓存;运行实例可以暂停、恢复、失败、重试。以前这些东西混在一起,恢复逻辑只能靠约定。现在它们有了明确边界。
五层架构
我把 Harness 分成五层来看。
第一层:定义层 — 这条流水线到底是什么
核心产物:pack、DAG、workflow-plan.json、各种 hash。
interface WorkflowPlan {
schemaVersion: '1.0.0';
feature: string;
source: {
pack: { name: string; version: string; hash: RuntimeHashDescriptor };
artifactDag: { path: string; version: string; hash: RuntimeHashDescriptor };
};
phases: WorkflowPlanPhase[];
nodes: WorkflowPlanNode[];
validation: { deterministic: boolean; errors: string[] };
}
注意 validation.deterministic。编译过程会主动检查 DAG 中是否存在 Date.now、Math.random 等不确定性调用。定义层必须稳定、可比较。
第二层:运行层 — 这一次跑到哪了
这里靠的是事件流和 execution.json,而不是聊天上下文。
graph TD
A[Agent/Gate 动作] -->|append| B[events.jsonl]
B -->|projectState 左折叠| C[execution.json]
C -->|read model| D[调度器 / CLI / UI]
关键设计:
events.jsonl是唯一写入点(append-only)execution.json是只读的 read model,由 projector 物化- 聊天记录不可靠,事件流才是状态真相源
事件类型有 26 种,覆盖 pipeline 生命周期的每个环节:PipelineInitialized、StageStarted、AgentCompleted、GateEvaluated、WaveVerified……
为什么不直接读聊天记录?
因为聊天记录会丢、会截断、会被模型幻觉覆盖。事件流是结构化的、只追加的、可重放的。这才是”跑到哪了”的真相。
第三层:产物层 — Agent 说完成了,不等于完成了
PRD、架构文档、任务拆解、QA 报告、部署报告——这些落盘并被 runtime 记录后,才算正式产物。
产物层依赖 Artifact DAG 做拓扑排序:
{
"nodes": [
{ "id": "prd", "stage": "planning", "agent": "pm", "inputs": [] },
{ "id": "architecture", "stage": "planning", "agent": "architect", "inputs": ["prd"] },
{ "id": "tasks", "stage": "planning", "agent": "architect", "inputs": ["architecture"] },
{ "id": "code", "stage": "implementation", "agent": "developer", "inputs": ["tasks"] },
{ "id": "qa-report", "stage": "verification", "agent": "qa", "inputs": ["code"] }
]
}
只有 recordArtifact() 被调用、事件被写入、状态被物化后,产物才存在。Agent 在对话中说”我写好了”但没有落盘——那就不算。
第四层:门禁层 — 凭什么继续
测试、Evidence Wave、QA、final gate,本质上都在问同一个问题:凭什么继续?
门禁分四级:
| 门禁 | 检查项 | 通不过的后果 |
|---|---|---|
| Gate 0 | 编译、lint、密钥泄露、不安全模式 | 阻塞 |
| Gate 1 | 单元测试、覆盖率 ≥ 70%、E2E | 阻塞 |
| Gate 2 | Lighthouse、API P99(仅 Web) | 阻塞 |
| Final Gate | 必需产物齐全、无活跃 Agent、无未修复失败、QA 攻击无 open 问题 | 阻塞交付 |
Evidence Wave 是额外的一层验证:
graph LR
R[Red Phase: 测试必须失败] --> G[Green Phase: 测试必须通过]
G --> V[Wave Verified ✓]
Red phase 要求在实现之前,测试先跑一遍并失败——证明测试真的在检测新功能。Green phase 在实现之后跑一遍并通过——证明实现确实修复了测试。
这层是防止”看起来完成了”的关键。
第五层:恢复层 — 中断后不靠人脑捡现场
核心机制:promptFingerprint + inputDigest + resume --from-run。
function buildAgentFingerprints(agent, stage, prompt, dependencyArtifacts, cwd, feature) {
const promptHash = sha256Hex(prompt);
const dependencies = dependencyArtifacts.sort().map(artifact => ({
artifact, hash: readArtifactDigest(cwd, feature, artifact)
}));
const inputDigest = hashRuntimeValue({ agent, stage, promptFingerprint: promptHash, dependencies });
return { promptFingerprint: promptHash, inputDigest };
}
恢复决策逻辑:
- 找到该 Agent 上次
AgentCompleted事件 promptFingerprint变了?→ 必须重跑(指令变了)inputDigest变了?→ 必须重跑(依赖产物变了)- DAG 被修改了?→ 不可复用
- 都没变?→ 跳过,复用上次结果
Gate 节点始终重新评估。 因为门禁的意义就是”每次都验”。
命令行:boss runtime resume <feature> --from-run <run-id>
目标不是炫技,而是:中断后不用全量重跑,也不用靠人脑回忆上下文。
渐进式披露:SKILL.md 从 474 行到 99 行
之前主 SKILL.md 太像一个巨型总控 prompt,什么都写在里面。越复杂,越依赖模型一次性记住所有规则,最后又回到”让模型自己记流程”的老路。
现在主 SKILL.md 只保留三类内容:
- 入口:触发条件和模式切换
- 不变量:七条不可违反的工程约束
- 索引:指向 references/ 的路由表
| 场景 | 读取的 reference |
|---|---|
| 进入编排模式 | orchestration-loop.md |
| 需要 runtime 命令 | runtime-surface.md |
| 派发 code Agent | evidence-waves.md |
| 质量门禁 | quality-gate.md |
| 平台适配 | platform-drivers.md |
| Hook 调试 | hooks-runtime.md |
| 记录产物 | artifact-guide.md |
| 测试相关 | testing-standards.md |
长流程、runtime 命令、Evidence Wave、platform driver、hooks——都拆到 references 里按需读取。模型不需要一次性记住 474 行规则,只需要知道”什么时候去读哪个文件”。
七条不变量
无论怎么拆分,这些规则始终成立:
- 编排不替代 Agent 写产物 — Boss 负责调度,产物由专职 Agent 写
- 状态真相源是 runtime 事件流 — 不是聊天记录,不是 Agent 自述
- 产物驱动 — 没有落盘的产物就不存在
- 质量门禁不可绕过 — Gate 失败就是失败,不存在”差不多就行”
- 渐进式披露 — 不要一次性加载所有规则
- 平台适配不改变状态语义 — 不管底层是 Claude、Codex 还是别的,状态模型不变
- 中文交付 — 所有面向人的产出用中文
总结
Harness 本质上在做一件事:把”Agent 自己管理自己”变成”工程系统管理 Agent”。
- 定义层让流程可审计
- 运行层让状态可追溯
- 产物层让完成可验证
- 门禁层让质量有保障
- 恢复层让中断可续接
Agent 会幻觉、会遗忘、会自信地声称完成了没完成的事。Harness 的存在就是为了不让这些问题变成生产事故。
不是让 Agent 更聪明,而是让整条流水线即使 Agent 犯错也能兜住。
Keep Reading