Agent = Model + Harness
Agent Model Harness从通用视角拆解 Agent 的完整工程链路。本文不做框架对比只回答一个问题Agent 真正的工程在哪里读完应当能在自己的 Agent 实现里识别出对应的层次并判断哪一段最值得优化。图 0 · 全链路总览入口路由 → 意图澄清 → 任务规划 → 执行 → 工具治理 → 上下文构造 → 缓存 → 压缩 → 多 Agent。后续章节按此顺序展开。0. 起点Agent Model Harness业内对「Agent 是什么」有过两个递进的等式。Agent Model Prompt。第一代说法。把模型当作能听指令的执行器所有「智能」集中在 prompt 里。这种构造能解决一类问题但很快触及边界模型无法持续工作、不能维护状态、不会主动澄清、没有副作用边界prompt 写得再精巧也无法跨越这些缺口。Agent Model Harness。第二代说法。把模型当作循环里的决策核把真正决定 Agent 能否用于生产的能力放到 harness外壳里一个循环让模型能持续工作一组工具与治理让模型能作用于世界且不闯祸一套上下文构造让模型每一轮看到正确的世界一套状态管理让长任务不失忆、不偏移一套协作机制让多个 Agent 能分工。选择哪种视角决定了优化方向把 Agent 视作model prompt优化方向是改提示把 Agent 视作model harness优化方向是改结构。主流的现代 Agent 框架几乎都是第二种视角的落地。后续每一节都是 harness 的一个组件 —— 容易出错、且容易被低估的远比想象的多。1. Agent LoopHarness 的骨架一切从用户的一次输入开始。这次输入不会直接送达模型而是先穿过一层入口路由命令式输入斜杠命令、提及、引用走确定性分支不进入Agent Loop自由文本进入循环。Loop 的本质极简whileTrue:decisionmodel(messages,tools)ifdecision.stop_reasonend_turn:returndecision.textelifdecision.stop_reasonmax_tokens:handle_truncation(decision)break# stop_reason tool_use继续执行resultrun_tool(decision.tool_call)messages.append(result)harness 必须显式处理三种 stop_reason把它们映射到「退出」「继续循环」「截断恢复」。这条循环可以用四个状态描述Observe → Decide → Act → Record—— 看 / 想 / 做 / 记依次往复。图 1 · Agent Loop 四态切换 —— Observe / Decide / Act / Record 形成闭环四种退出条件决定循环何时收尾。此外harness 必须维护两个一等公民计数器max_steps步数上限与consecutive_failure连续失败上限。前者防止 Agent 在自我循环里耗尽预算后者防止单一工具反复报错把循环锁死。少了任何一个Loop 在生产里都不可托付。骨架本身只有十几行但 harness 的全部复杂度都源自一个要求让这四步在长任务下依然可控。后续章节依次回答这个要求的每一面。2. 意图识别把「澄清」做成一等公民工程实践中反复出现一种行为模式模型倾向立即响应而非主动确认。面对歧义输入模型大概率会沿着一种合理猜测继续推进最终交付一个方向正确但偏离意图的结果。这一模式的成因在于训练目标偏向「完成任务」而非「澄清意图」。Harness 对此的回应是把澄清动作提升为显式工具调用—— 引入一个专门的「问用户」工具典型形态如AskUserQuestion把澄清从「文本回复」抽象为「结构化交互」结构化问题、选项、默认值受 schema 约束前端可渲染为卡片或按钮可触发模型可在循环任意一轮主动发起无需绕过对话协议可中断用户可否决、可补充澄清以独立消息形式注入不污染主流。但澄清是有成本的过度问询会破坏体验。Harness 需要一条隐性策略资料齐全、需求明确→ 不问自行判断信息缺失、决策后果大→ 问仅是偏好确认命名、风格→ 用「默认值 可调」不打断。落到具体场景「帮我优化这个函数」—— 目标缺失优化速度内存可读性决策后果大 →应问「把这个函数从 O(n²) 优化为 O(n log n)」—— 目标明确 →直接执行「变量名用驼峰还是下划线」—— 仅是偏好 → 选默认 留可调不打断。意图层的工程本质是在「自主判断」与「让用户判断」之间不断校准。把澄清做成工具是这个校准能成立的前提 —— 否则模型只剩一种选择以猜测代替判断。3. 任务规划Todo 作为外显的工作记忆意图层校准了「做不做、做什么」接下来的问题是「怎么做、按什么顺序做」—— 答案不是直接进入执行而是先显式规划。成熟的 Agent 实现普遍引入一个 todo / task 列表承载这一层。它不是 UI 上的进度条而是Agent 自身的工作记忆解决两个核心问题。3.1 长任务下的注意力衰减随着上下文累积模型对早期目标的关注度会被后续内容稀释 —— 这是 long-context 场景下被广泛报告的lost-in-the-middle现象放在上下文中段的信息召回率明显低于头尾。并非所有任务都会触发但任务越长、轮数越多触发概率越高工程层必须主动应对。三轮任务无需规划三十轮任务若无外部锚点模型会沿着最近一次出现的子问题持续偏移最终忘记最初目标。显式 todo 提供了一个始终可见的目标锚点。每一轮 Observe 阶段模型都能在固定位置看到最初承诺与剩余工作[x] 解析需求 [x] 拉取相关代码 [ ] 实现 X ← 当前 [ ] 写测试 [ ] 回归验证3.2 规划与执行的解耦不显式规划时规划与执行混在同一段自由文本里。最终结果出错时无法判断是「规划错」还是「执行错」。todo 把规划落到结构里分解颗粒度本身可被审查每个条目状态pending / in_progress / done可被观测条目可被新增、修改、放弃而不污染主对话流。3.3 颗粒度过粗「完成需求」 —— 等同于没有规划过细「打开文件 / 找到函数 / 加一行 / 保存」 —— 模型陷入过程汇报合适以「可独立验证」为边界。判断颗粒度是否合适的操作标准完成后能以一两句话说明做了什么、何以为成。Todo 的真正价值不是计划本身而是让 Agent 的内部状态外显—— 可监督、可评测、可回放。这三件事藏在 prompt 里的状态一件都做不到。一个常见的替代方案是「每轮在 system prompt 里写当前目标」。它解决了「目标可见」这一点但不能解决另外三件事目标是工程注入的固定值模型无法自己更新进度状态不可被观测任务列表的演化没有事件流可回放。Todo 把目标变成了 Agent 可读可写的结构化状态这正是 system prompt 里的静态文本做不到的。4. 执行让模型选对工具执行层的核心问题不是「调用哪个工具」而是「怎样让模型在正确的时刻选择正确的工具」。工具描述存在一对结构性张力描述过于抽象模型选不准倾向滥用通用工具描述过于具体模型在边界场景死板错失可用工具。工程上的四条经验命名望文生义读文件就叫read_file不要叫fs_op或io_handler正反双向描述同时写明「适用场景」与「不适用场景」单向描述会导致过度泛化 —— 典型表现是有专门工具时模型仍用通用工具兜底明明有search_code却继续用grep因为通用工具的描述「能搜任何东西」让模型觉得它总是可用示例贴近典型用法给出一个最常见的调用样例模型对「正常调用」的认知会显著清晰职责互斥避免两个工具能力高度重叠 —— 差异不足三成模型在选择上会反复横跳。此外单轮多工具调用是被低估的能力。模型可以在一次响应中并发地发出多个无依赖的工具调用是否真正并行由编排层决定。这一能力一旦放开Agent 的端到端延迟会显著下降。工具选择的稳定性来自工具定义的清晰度而非提示工程的修辞。5. 工具治理从「可调用」到「可信赖」工具是 Agent 唯一作用于外部世界的通路。一旦工具数量增加、参数复杂化模型生成的工具调用就不再天然可信需要一层独立的治理。5.1 用 Schema 把工具调用变成数据契约最基础的治理每个工具拥有严格的输入 schema。无论使用 JSON Schema 还是 Python 侧的 Pydantic目的一致 —— 将模型的自由文本生成收敛为结构化数据classReadFile(BaseModel):path:strmax_lines:int200四项直接收益解析零负担无需正则提取参数错误前置模型臆造的字段在 schema 校验时即被拦截错误信号闭环校验失败时以结构化形式回灌模型可在下一轮自我修正工具升级的契约参数增加、类型变更schema 更新一处模型行为同步收敛。Pydantic 在 Python 侧的价值不止校验强类型表达枚举、Union、可选字段、字段间约束可直接表达JSON Schema 自动导出模型看到的 schema 与代码实现保持一致。工具治理由此从「提示工程」升级为「类型系统约束」。5.2 工具数量与粒度工具数量并非越多越好。经验观察数量阈值工具数量越过某个量级后选错率显著上升具体阈值因模型与任务而异应以实测为准不应依赖经验数字能力重叠相似职责的工具会导致选择振荡低频长尾极少使用的工具持续占用 token 预算纯粹浪费。对应策略场景化分组按任务类型动态加载工具集而非常驻全集能力收敛可合并即合并一个edit_file优于insert_linedelete_linereplace_line副作用分层只读默认放行可逆写需确认不可逆写需显式授权。5.3 工具输出的形态工具的返回值同样需要治理。模型面对 200KB 原始日志与一段结构化摘要时行为差异显著。大输出必须截断并附「已截断可重读」的明确信号失败应返回结构化错误错误码 / 原因 / 建议而非裸抛堆栈成功应返回「模型用得上」的字段子集而非数据库原始行。工具是模型「感知世界」与「作用世界」的两端。两端的形态都必须由 harness 主动定义而非由模型与下游系统自行约定。6. 上下文构造Harness 的真正算法模型每一轮看到的不是「对话历史」而是 harness拼装出的一段上下文。这一拼装过程是 Agent 工程最值得投入的地方。6.1 分层结构长任务下的上下文不应是单一消息流而应有明确分层图 2 · 上下文分层结构 —— 越靠左越稳定越靠右越易变缓存边界由 §7 进一步说明。这种分层不是审美选择它直接决定缓存命中率见 §7。6.2 注入策略「能塞就塞」是错误的直觉。上下文越冗余模型越容易跑偏—— 模型会试图「用上」所有提供的信息从而被噪声牵引。判断标准当前任务用得上→ 注入用户显式引用→ 注入仅为兜底→ 不注入。长期记忆同理记忆系统的价值是「可召回」而非「持续在场」。6.3 临时上下文 vs 工具自查一个常见的设计争议当前打开的文件、目录结构、git 状态等临时信息是否应默认注入 system prompt更稳健的工程选择是只注入最小定位信号cwd、git 状态摘要具体内容由模型在需要时通过工具自取。理由有三模型主动 read 比在 80KB 预注入内容中检索更准「按需读取」是天然的剪枝临时内容若位于稳定区会破坏缓存见下节。上下文设计的两条主线是「最小必要信息」与「分层稳定性」。前者控制模型的注意力分布后者控制成本与延迟。7. Prefix Cache从理解到压榨主流推理后端包括 Anthropic普遍提供前缀缓存相同前缀的 token 序列在重复出现时可复用已计算的 KV跳过重复推理。理解这一机制会重写对 Agent 成本与延迟的认知。7.1 命中的物理约束Anthropic prefix cache 以约 1024 token 的 cache block 为粒度匹配不足该长度的前缀不会写入缓存前缀一旦在某个 token 位置发生变化哪怕只是空白或格式微调该位置之后的所有 block 都会失效。图 3 · Prefix Cache 命中模型 —— 共同前缀跳过 prefill新增尾部增量计算。由此推出三条强约束越不变的部分越靠前越频繁变动的部分越靠后不允许在稳定区插入易变内容—— 一个时间戳就足以让前缀整体失效。§6.1 的分层正是按这一约束构造的。7.2 「伪稳定」的常见来源以下都会让看似稳定的前缀实际上每次都变轻微的字符级变化时间戳、浮点精度、空白字符、换行风格等任何微小差异都会使后续 block 失效工具顺序序列化时顺序不固定token 序列不一致随机注入A/B 测试、随机示例采样等。治理思路是把所有「本次任务才有」的动态信息归集到独立段落task-state明确放置在缓存命中区之后。稳定区一旦确定工程上应将其视作不可变量。7.3 缓存改变 Agent 的经济学理解前缀缓存后长 Agent 任务的成本曲线会发生本质变化无缓存每一轮按完整上下文计费 —— 成本随轮数近似线性增长命中缓存每一轮仅为新增尾部计费 —— 成本曲线被显著压平。延迟逻辑相同命中的前缀部分跳过 prefill 计算TTFT首 token 延迟显著下降但总延迟仍由 decoding 阶段决定并非趋近于零。在长任务与多 Agent 场景下前缀缓存不是优化项而是是否经济可行的分水岭。上下文结构稳定性是 Agent 在生产环境可持续运行的物理基础。8. Context Compact撞上窗口边界后的策略即便上下文窗口达到 200K 乃至 1M长任务仍会撞墙。原因不在单条消息长度而在轮数累积每一轮的工具输出与模型回复都需保留几十轮后总量可观。三种主流策略工程上通常组合使用。8.1 滑动窗口messagesmessages[-N:]最简方案但会丢失早期决策。仅适用于「持续编辑同一对象」这类天然局部化的任务。长程任务调研、规划、执行、复盘会因此失忆。8.2 摘要压缩接近阈值时将前半段对话压缩为结构化摘要替换原始消息原始: [t1][t2]...[t20][t21][t22] 压缩: [summary(t1..t20)][t21][t22]要点由模型生成摘要模型最了解哪些是关键决策、哪些是过程噪声硬规则裁剪会丢真正重要的信息摘要结构化固定字段目标 / 关键决策 / 已完成 / 未完成 / 待办 / 风险不允许自由散文todo 必须保留作为高密度状态锚点不参与压缩频率受控过于频繁的压缩会导致信息逐层蒸发。成熟 Agent 框架普遍提供显式的压缩入口如/compact一类的指令按上述结构化摘要路径触发具体实现细节因框架而异但思路高度趋同。8.3 结构化裁剪不动消息结构仅替换其中的大对象大型工具输出 → 替换为「结果 X 可重读」的指针整文件内容 → 替换为路径与摘要长 log → 替换为关键行加行号。需要时模型会通过工具重新读取。这一过程对模型推理透明。8.4 三策略协同实际工程通常这样组合常态结构化裁剪持续生效避免单轮工具输出膨胀接近阈值触发摘要压缩前半段保留近端原始消息极端情况滑动窗口兜底宁可丢失早期细节也避免 Agent 中断。图 4 · Compact 三策略协同 —— 按上下文使用率分档触发常态裁剪持续生效摘要与滑动只在边界激活。Agent 的有效工作时长等同于其上下文管理策略的有效寿命。能跑 50 轮的 Agent 与跑 10 轮即失稳的 Agent处理的任务复杂度不在同一量级。9. 多 AgentSubagent 与 Agent Teams单 Agent 即便做到极致也存在天花板上下文有限单一上下文承载所有信息终将撞墙角色混淆同时承担规划、执行、审查会产生内耗。Subagent 与 Agent Teams 是 harness 在水平方向上的扩展 —— 把单条 Loop 复制、隔离、再组合。9.1 Subagent 的本质隔离Subagent 的核心不是「让另一个模型帮忙」而是显式的隔离resultsubagent(rolereviewer,task...,tools[read_file,grep],)# 父 Agent 仅获取 result不接触执行过程三个维度的隔离上下文隔离父 Agent 不需承载子任务全部细节只接收结构化结论工具隔离可配置只读 Agent、只测试 Agent、只写文档 Agent —— 工具集按职责裁剪失败隔离子 Agent 失败可触发重试、降级或换策略不污染主对话。图 5 · Subagent 隔离模型 —— 上下文、工具、失败边界三重隔离父 Agent 仅见结论不见过程。这套机制本质上把「函数调用」抽象推广到了 Agent 层 —— 父 Agent 调用 Subagent 如同调用工具输入是任务描述输出是结构化结果。成熟实现中的 Subagent 有几个值得注意的设计选择独立上下文每次调用持有独立上下文不共享父 Agent 的消息历史任务目标通过结构化输入传入本身就是显式状态交接输入是描述而非上下文父 Agent 须明确目标不可 dump 历史输出对父是黑盒父只见结论不见过程 —— 这正是上下文隔离的全部价值所在。9.2 Subagent 的适用边界判断是否适合下放给 Subagent子任务独立可验证→ 适合子任务消耗大量 token / 上下文→ 适合子任务需要独立视角独立审查、独立测试 → 适合子任务强依赖父对话上下文→ 不适合应当继续留在主 Agent 内。反向也成立过度使用 Subagent 比不使用更糟。每开一个就额外承担对齐成本与整合成本。Subagent 失败同样需要显式处理路径。父 Agent 须区分「子任务失败是否阻塞全局」设定重试上限并要求 Subagent 返回结构化错误错误码 原因 建议而非裸异常——否则失败隔离的价值无法兑现子任务的失败会以不可解析的形式向上传播父 Agent 既无法重试也无法降级。9.3 从 Subagent 到 Agent TeamsSubagent 是「一次性派工」。Agent Teams 进一步多个长期存在的 Agent 按角色协作完成单一目标。典型角色拆分Planner拆解任务、维护优先级与 todoWorker承担具体任务可并行多实例Reviewer独立审查产出不参与执行避免自审Orchestrator消化所有 Agent 输出统一决策下一步。图 6 · Agent Team 角色拓扑 —— 消息按结构化结论流向 Orchestrator由其单点收口。设计 Agent Team 时有三个常被低估的认知。多 Agent 并不必然优于单 Agent。并行有成本对齐、整合、冲突调解都需要消耗。多 Agent 的真正胜场是任务沿模块 / 文件 / 职责边界天然可拆子任务间几乎无共享状态每个子任务具备独立验收标准。紧密协同、共享上下文的任务强行拆分反而退化。通信协议的重要性超过模型本身。Agent Team 的瓶颈往往不在单个 Agent 的能力而在 Agent 之间的消息结构传什么传结构化结论而非对话历史传给谁明确指向而非广播谁收口必须存在一个 Orchestrator 对最终结果负责。缺少明确收口结果将是「多 Agent 各自表演无人对结果负责」。角色定下后工具必须分家。每个角色应仅获得职责所需的工具子集Reviewer 不应有写权限Worker 不应有发布权限Planner 不应有删除权限。权限分离收缩了错误的爆炸半径是 Agent Team 可放心运行的前提。一个能写文件的 Reviewer 等价于让监督者掌控被监督对象一个能合并 PR 的 Worker 等价于让执行者跳过审查。错误的工具配置不会立刻爆炸但会让 Team 的失败半径不可控。9.4 Subagent / Team 与 Harness 的关系Subagent 与 Agent Team 不是新增概念而是把 §1–§8 的 harness 在另一个维度上复用因为有Loop每个 Subagent 能独立持续工作因为有意图与规划父 Agent 才能写出清晰的子任务描述因为有工具治理与权限分层角色拆分才有边界因为有结构化上下文与 prefix cache多 Agent 并行才经济可行因为有compact长期存在的 Agent Team 才能跑够长任务。把单 Agent 的 harness 做扎实Subagent 与 Agent Team 自然成立反之缺哪一层多 Agent 就在哪一层上失败。10. 结语Agent 工程就是 Harness 工程回到开篇的两个等式Agent Model Prompt把希望寄托在模型与提示上Agent Model Harness把工程能力放在循环、工具、上下文、状态与协作上。把整条链路走一遍会得到一个清晰的结论Agent 的工程难点不在模型本身。模型不会主动拼装上下文需要 harness 决定每一轮看到什么模型不会主动澄清与收口需要 harness 提供澄清与退出协议模型不会自我控制成本与时长需要 harness 通过缓存、压缩与多 Agent 把它分摊。一句话总结Agent 是一个有状态的循环。让循环可控、让状态外显、让边界清晰 —— 剩下的交给模型。Harness 是 Agent 工程的全部底层。逐层对齐之后回看自己的 Agent会发现值得优化的从来不是 prompt 中的几句修辞而是 harness 上某一段松垮的结构。