青雲的博客

Article

Harness 实战:让 Agent 流水线可信的五层工程骨架

· 10 分钟阅读

结论先行

Harness 不是让 Agent 更聪明,而是让 Agent 的工作更可信

这次变更给一条 Agent 研发流水线加了一层工程化骨架。它管五件事:

  1. 流程怎么定义
  2. 当前跑到哪
  3. 产物以什么为准
  4. 什么时候允许继续
  5. 失败后怎么恢复

这篇文章基于一个真实的 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流程定义的指纹
packHashPipeline Pack 配置模板是否变了
artifactDagHashartifact-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.nowMath.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 生命周期的每个环节:PipelineInitializedStageStartedAgentCompletedGateEvaluatedWaveVerified……

为什么不直接读聊天记录?

因为聊天记录会丢、会截断、会被模型幻觉覆盖。事件流是结构化的、只追加的、可重放的。这才是”跑到哪了”的真相。

第三层:产物层 — 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 2Lighthouse、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 };
}

恢复决策逻辑:

  1. 找到该 Agent 上次 AgentCompleted 事件
  2. promptFingerprint 变了?→ 必须重跑(指令变了)
  3. inputDigest 变了?→ 必须重跑(依赖产物变了)
  4. DAG 被修改了?→ 不可复用
  5. 都没变?→ 跳过,复用上次结果

Gate 节点始终重新评估。 因为门禁的意义就是”每次都验”。

命令行:boss runtime resume <feature> --from-run <run-id>

目标不是炫技,而是:中断后不用全量重跑,也不用靠人脑回忆上下文。


渐进式披露:SKILL.md 从 474 行到 99 行

之前主 SKILL.md 太像一个巨型总控 prompt,什么都写在里面。越复杂,越依赖模型一次性记住所有规则,最后又回到”让模型自己记流程”的老路。

现在主 SKILL.md 只保留三类内容:

  1. 入口:触发条件和模式切换
  2. 不变量:七条不可违反的工程约束
  3. 索引:指向 references/ 的路由表
场景读取的 reference
进入编排模式orchestration-loop.md
需要 runtime 命令runtime-surface.md
派发 code Agentevidence-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 行规则,只需要知道”什么时候去读哪个文件”。


七条不变量

无论怎么拆分,这些规则始终成立:

  1. 编排不替代 Agent 写产物 — Boss 负责调度,产物由专职 Agent 写
  2. 状态真相源是 runtime 事件流 — 不是聊天记录,不是 Agent 自述
  3. 产物驱动 — 没有落盘的产物就不存在
  4. 质量门禁不可绕过 — Gate 失败就是失败,不存在”差不多就行”
  5. 渐进式披露 — 不要一次性加载所有规则
  6. 平台适配不改变状态语义 — 不管底层是 Claude、Codex 还是别的,状态模型不变
  7. 中文交付 — 所有面向人的产出用中文

总结

Harness 本质上在做一件事:把”Agent 自己管理自己”变成”工程系统管理 Agent”

  • 定义层让流程可审计
  • 运行层让状态可追溯
  • 产物层让完成可验证
  • 门禁层让质量有保障
  • 恢复层让中断可续接

Agent 会幻觉、会遗忘、会自信地声称完成了没完成的事。Harness 的存在就是为了不让这些问题变成生产事故。

不是让 Agent 更聪明,而是让整条流水线即使 Agent 犯错也能兜住。

Keep Reading

相关文章

评论