# 1. Agent Harness 体系建设

# 1.1 原理

Agent Harness 不是某个 Agent 框架的同义词。框架通常提供 Prompt、Tool、Memory、Graph 等开发抽象;Harness 是包裹模型的完整运行与治理外壳,负责把概率模型变成可交付系统。

一个生产级 Harness 至少包含六个平面:

  1. 模型平面:模型路由、请求构造、流式响应、重试、限流、Prompt Cache。
  2. 执行平面:Agent Loop、工具注册/调用、并发、取消、超时、错误恢复。
  3. 上下文平面:系统指令、仓库状态、技能、记忆、历史、压缩、Token 预算。
  4. 状态平面:Thread/Session/Turn/Step、任务状态、工具状态、Rollout/Transcript。
  5. 安全平面:权限策略、审批、沙箱、网络策略、密钥与租户隔离。
  6. 治理平面:Trace、Metric、Log、Eval、回放、成本、延迟、成功率和失败归因。

关键设计原则:

  • 控制面与执行面分离:模型提出意图,Harness 校验并执行;模型不能直接获得 OS 权限。
  • 事件溯源优于只存最终状态:Agent 的失败往往发生在中间步骤,必须保留输入、决策、工具调用、结果和状态迁移。
  • 所有外部输入都不可信:模型输出、MCP 描述、工具结果、仓库文件、子 Agent 消息都要做大小、权限和来源约束。
  • 可恢复性是核心能力:长任务必须能从持久化的检查点和原始事件记录恢复,而不能只靠进程内对象。

# 1.2 Claude Code 源码映射

  • 工具编排把调用按并发安全性分组,再走串行或并行执行:toolOrchestration.ts:19toolOrchestration.ts:91
  • 流式执行器维护 queued/executing/completed/yielded 状态、取消控制器、进度事件和结果回传:StreamingToolExecutor.ts:19StreamingToolExecutor.ts:320
  • 工具调用链先做 schema/语义校验、PreToolUse Hook、权限决策,再执行并生成 ToolResult,最后跑 PostToolUse Hook:toolExecution.ts:337toolExecution.ts:682toolExecution.ts:916toolExecution.ts:1207

# 1.3 Codex 源码映射

  • RegularTask 管理一次用户任务,循环调用 run_turn 并消费运行期间进入的待处理输入:regular.rs:38
  • run_turn 在一次 Turn 内完成压缩、MCP 依赖解析、世界状态注入、模型采样、工具调用和后续迭代:turn.rs:150turn.rs:271
  • 会话同时维护内存历史和持久化 Rollout;记录对话项时会追加历史并写入 Rollout:session/mod.rs:2894session/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:280StreamingToolExecutor.ts:298toolExecution.ts:614。这说明 Harness 的核心是协议和状态边界,而不是把所有组件放进一个对象。

Codex 的对应主链是 RegularTask → run_turn → run_sampling_request → ToolRouter → Handler。Session 在内存中维护 History 与 Active Turn,同时把 Conversation Item 和 Event 写入 Rollout;因此模型循环、工具执行和持久化不是三个孤立模块,而是共享稳定 ID 的事件闭环:regular.rs:38turn.rs:271session/mod.rs:2953

这条调用链揭示了 Harness 的本质:六个平面不是六套彼此调用的“服务”,而是围绕同一个动作事实建立不同投影。执行平面关心动作是否运行,安全平面关心它是否被允许,状态平面关心它是否可恢复,治理平面关心它为何成功或失败。只要这些投影没有使用同一 session_id/turn_id/call_id 和同一份规范化参数,就会出现“UI 显示已取消、进程仍在运行”或“审计记录的命令与真正执行的命令不同”这类分裂。

源码层应重点检查四个 Harness 不变量:

  1. 模型只产生提议,外部副作用一定经过 Runtime 的工具入口。
  2. 模型看到的 Tool Schema 与随后执行时解析的 Tool Registry 属于同一 Step 快照。
  3. ToolResult 在进入下一次采样前已经具有稳定 call id,并完成必要持久化。
  4. 权限、沙箱、Trace 和 Rollout 使用同一规范化动作描述,否则审计看到的动作可能与实际执行不同。

面试追问“如何演进 Harness”时,可沿源码扩展点回答:Model Provider 放在采样边界,Tool/MCP 放在 Router,Memory/Compaction 放在 Context Manager,安全策略放在执行前,Eval 消费 Rollout。这样新增能力不会绕开原有治理链。

最后更新: 2026/8/5 22:05:21