# 1. Agent Harness 体系建设
# 1.1 原理
Agent Harness 不是某个 Agent 框架的同义词。框架通常提供 Prompt、Tool、Memory、Graph 等开发抽象;Harness 是包裹模型的完整运行与治理外壳,负责把概率模型变成可交付系统。
一个生产级 Harness 至少包含六个平面:
- 模型平面:模型路由、请求构造、流式响应、重试、限流、Prompt Cache。
- 执行平面:Agent Loop、工具注册/调用、并发、取消、超时、错误恢复。
- 上下文平面:系统指令、仓库状态、技能、记忆、历史、压缩、Token 预算。
- 状态平面:Thread/Session/Turn/Step、任务状态、工具状态、Rollout/Transcript。
- 安全平面:权限策略、审批、沙箱、网络策略、密钥与租户隔离。
- 治理平面:Trace、Metric、Log、Eval、回放、成本、延迟、成功率和失败归因。
关键设计原则:
- 控制面与执行面分离:模型提出意图,Harness 校验并执行;模型不能直接获得 OS 权限。
- 事件溯源优于只存最终状态:Agent 的失败往往发生在中间步骤,必须保留输入、决策、工具调用、结果和状态迁移。
- 所有外部输入都不可信:模型输出、MCP 描述、工具结果、仓库文件、子 Agent 消息都要做大小、权限和来源约束。
- 可恢复性是核心能力:长任务必须能从持久化的检查点和原始事件记录恢复,而不能只靠进程内对象。
# 1.2 Claude Code 源码映射
- 工具编排把调用按并发安全性分组,再走串行或并行执行:
toolOrchestration.ts:19、toolOrchestration.ts:91。 - 流式执行器维护
queued/executing/completed/yielded状态、取消控制器、进度事件和结果回传:StreamingToolExecutor.ts:19、StreamingToolExecutor.ts:320。 - 工具调用链先做 schema/语义校验、PreToolUse Hook、权限决策,再执行并生成 ToolResult,最后跑 PostToolUse Hook:
toolExecution.ts:337、toolExecution.ts:682、toolExecution.ts:916、toolExecution.ts:1207。
# 1.3 Codex 源码映射
RegularTask管理一次用户任务,循环调用run_turn并消费运行期间进入的待处理输入:regular.rs:38。run_turn在一次 Turn 内完成压缩、MCP 依赖解析、世界状态注入、模型采样、工具调用和后续迭代:turn.rs:150、turn.rs:271。- 会话同时维护内存历史和持久化 Rollout;记录对话项时会追加历史并写入 Rollout:
session/mod.rs:2894、session/mod.rs:3595。
# 1.4 Harness 主流程伪代码
function handleUserTurn(input): // 定义一次用户输入对应的完整 Turn 处理入口
turn = session.beginTurn(input) // 创建 Turn,并为后续事件分配稳定的 turn id
context = contextManager.captureSnapshot(turn) // 捕获本 Turn 使用的指令、权限、环境和历史快照
while not turn.done: // 只要 Turn 未进入终态,就继续模型—工具循环
if context.tokenBudget.nearLimit: // 在每次采样前判断上下文是否接近模型窗口上限
context = compact(context) // 压缩历史并保留目标、计划、证据和未完成工具状态
responseStream = model.sample(context, toolSchemas) // 将当前上下文和工具 Schema 发送给模型并获取流式响应
emitVisibleDeltas(responseStream) // 把可展示的文本增量及时推送给 UI
calls = collectToolCalls(responseStream) // 从完整响应中收集并校验结构化 ToolCall
if calls.empty: // 没有工具调用通常表示模型准备给出本轮最终回答
turn.finish(responseStream.finalText) // 持久化最终文本,并把 Turn 标记为完成
break // 跳出 Agent Loop,避免对终态响应再次采样
batches = partitionByConcurrencySafety(calls) // 按只读性、共享状态和依赖关系划分串并行批次
for batch in batches: // 按确定顺序逐批处理工具调用
results = executeBatch(batch, permissionManager, sandbox) // 在权限检查和沙箱约束下执行当前批次
session.persist(results) // 先将工具结果写入可恢复的 Session/Rollout
observability.record(results) // 记录工具延迟、状态、错误和 Trace 关联信息
context.append(results) // 把结果追加为下一次模型采样可见的观察信息
session.flushDurably() // Turn 结束前等待队列内容写入持久化存储,缩小崩溃丢失窗口
# 1.5 高频面试题
问:Harness 和 LangGraph/LangChain 的区别是什么?
答题要点:后者偏应用开发抽象;Harness 覆盖 Runtime、状态、权限、沙箱、恢复、可观测、评测和开发者体验。可以用 LangGraph 实现 Harness 的部分 Loop,但不能因此得到完整生产治理能力。
问:从 0 到 1 建设 Harness,第一阶段做什么?
答题要点:先建立可回放的单 Agent 闭环——统一事件模型、工具协议、权限、持久化和 Trace;再做压缩、恢复、MCP/Skill;最后扩展 Multi-Agent 和 Eval 数据飞轮。没有事件与回放,后续优化无法量化。
# 1.6 原理深化:Harness 是共享不变量组成的闭环
Claude Code 的主链不是一个 Agent 类,而是多层协作:query() 负责模型迭代和恢复状态,StreamingToolExecutor 管 ToolUse 并发与取消,executeToolCall() 统一做校验、Hook、权限和结果包装,Session Storage 保存可恢复事实,Telemetry 再观察同一执行链:query.ts:280、StreamingToolExecutor.ts:298、toolExecution.ts:614。这说明 Harness 的核心是协议和状态边界,而不是把所有组件放进一个对象。
Codex 的对应主链是 RegularTask → run_turn → run_sampling_request → ToolRouter → Handler。Session 在内存中维护 History 与 Active Turn,同时把 Conversation Item 和 Event 写入 Rollout;因此模型循环、工具执行和持久化不是三个孤立模块,而是共享稳定 ID 的事件闭环:regular.rs:38、turn.rs:271、session/mod.rs:2953。
这条调用链揭示了 Harness 的本质:六个平面不是六套彼此调用的“服务”,而是围绕同一个动作事实建立不同投影。执行平面关心动作是否运行,安全平面关心它是否被允许,状态平面关心它是否可恢复,治理平面关心它为何成功或失败。只要这些投影没有使用同一 session_id/turn_id/call_id 和同一份规范化参数,就会出现“UI 显示已取消、进程仍在运行”或“审计记录的命令与真正执行的命令不同”这类分裂。
源码层应重点检查四个 Harness 不变量:
- 模型只产生提议,外部副作用一定经过 Runtime 的工具入口。
- 模型看到的 Tool Schema 与随后执行时解析的 Tool Registry 属于同一 Step 快照。
- ToolResult 在进入下一次采样前已经具有稳定 call id,并完成必要持久化。
- 权限、沙箱、Trace 和 Rollout 使用同一规范化动作描述,否则审计看到的动作可能与实际执行不同。
面试追问“如何演进 Harness”时,可沿源码扩展点回答:Model Provider 放在采样边界,Tool/MCP 放在 Router,Memory/Compaction 放在 Context Manager,安全策略放在执行前,Eval 消费 Rollout。这样新增能力不会绕开原有治理链。