Agent工程三层架构:Harness、Loop与Graph的生产实践指南

发布时间:2026/10/1 13:39:57
Agent工程三层架构:Harness、Loop与Graph的生产实践指南 前一阵子帮团队重构了一个 Agent 项目代码从最开始的两千行左右膨胀到了差不多一万行。业务方倒是很高兴因为多 Agent 协作、工具权限、失败重放这些需求都上了但我自己清楚真正让这个项目活下来的不是功能变多而是我们把整个 Agent 的工程结构强制拆成了三层Harness、Loop、Graph。这篇文章想把这三层到底各管什么、在真实生产里怎么配合、有哪些从事故里换来的经验一次讲清楚。1. 先把混乱的 Agent 拆成三层Harness、Loop、Graph 到底指什么大部分团队的 Agent 项目都是这么起步的一个主循环一段长长的 system prompt再加上十几个工具函数全部堆在一个文件里。Demo 阶段很正常跑起来也挺像回事。但业务需求一变多问题就全冒出来了——有人要加权限控制有人要限制工具调用次数有人要让多个 Agent 接力干活。你用最朴素的while True去满足这些需求的时候代码会迅速变成一坨没法改的面条。我自己踩过这个大坑之后才真正认同一个观点Agent 工程不是写一个聪明的循环而是把复杂度分到不同的层里去消化。我后来遵循的分层方式是这样的Harness 层给 Agent 提供运行环境。管的是工具怎么注册、权限边界在哪、上下文怎么组装、插件怎么加载、错误怎么统一暴露。Loop 层管单个 Agent 的思考—行动—观察循环。目标怎么拆、步骤怎么规划、工具调用的结果如何影响下一步决策、什么时候该停下来。Graph 层管多个 Loop、多个 Agent 之间的编排。节点怎么连接、分支走到哪、失败怎么回退、任务状态怎么持久化和重放。打个比方Harness 是给赛车手准备的座舱和仪表盘Loop 是赛车手在赛道上的每一脚油门刹车Graph 是整场比赛的路线图和进站策略。你可能只需要一辆能开的车但真上了赛道、还要争成绩的时候这三样东西缺一不可。这三层强制切分之后最大的收益不是代码变漂亮了而是问题的归属权变清晰了。插件加载不了去 Harness 里查模型陷入死循环去 Loop 的状态机里看多 Agent 之间的任务衔接出问题去 Graph 的节点流转记录里找。排查效率完全不是一个量级。2. Harness让 Agent 在“可控环境”里行动2.1 工具注册不是写个 List而是给每个工具建立运行契约很多初学者容易低估 Harness 的复杂度觉得我聚合了十个工具函数就是有 harness 了。但真正能进生产的 Harness工具注册这件事本身就是一道完整的工程。我的做法是每个工具进来都要带一份运行契约。什么是契约就是一组声明参数类型和必填项、返回值的格式、超时时间、是否有副作用、是否允许在同一次任务里被重复调用、调用成本估算。这些信息不只是给开发者看的更是给上层的 Loop 做规划和决策用的。举个真实例子。我们有段时间在 Harness 里接了一个搜索接口没声明重复调用会触发配额计费。结果某个任务里模型对同一个关键词反复搜索几十次调用直接把当天的 API 配额打完了。后来我们给工具注册表加了两个字段max_repeat_count和cost_estimate。Loop 在规划阶段看到同一参数已经被连续调用过两次就会主动切换到另一个工具或者换一个搜索词这类事故基本绝迹。2.2 权限边界默认拒绝而不是默认放行权限是 Harness 层最容易出安全事故的地方也是最容易被开发同学忽略的地方。大多数人觉得工具反正都是我们自己写的有什么权限好控的。但别忘了真正调用工具的是 LLM而 LLM 的单次输出永远有可能偏离预期。我的经验是把每个工具按风险等级分成三类等级含义示例allow低风险、高频、无副作用查天气、读文档、向量检索confirm有一定副作用需要用户确认发邮件、改配置、写数据库deny明确禁止任何情况下都不放行删除生产数据、提现、修改权限这个分类不能只写在系统 Prompt 里。Prompt 只是给模型的建议代码强制拦截才是真正的安全边界。所有工具调用统一走 Harness 的调度器调度器会先查权限再决定是直接执行、弹确认框还是拒绝。经验是默认拒绝原则永远比默认放行但事后审计安全得多。2.3 上下文组装好结构比大塞量重要一万倍Harness 的另一项核心工作是把上下文组装好再交给 Loop。大家都懂模型上下文窗口有限这个道理但实际做上下文管理的时候很多人犯的错是只关心装了够不够多不关心装进去之后长什么样。我做过一个对照实验。同一批对话历史和工具返回结果一种做法是全部按时间顺序拼接成一个大字符串塞进 Prompt另一种做法是按时间线 角色来源 工具返回摘要整理成结构化的段落再塞进去。在复杂任务上用同样的模型跑 20 轮测试后者的任务成功率大概高了三成左右。后来我们把上下文管理的逻辑统一收口到 Harness 的 Context Assembler 组件里。所有工具返回先做摘要摘要按来源和类型分类存放Prompt 里只放摘要和引用 ID。模型如果觉得某个摘要不够详细可以主动调用一个read_full_result的工具去拉完整内容。这样做的好处是token 控制住了同时因为每一步都有引用 ID后面 Graph 做重放和审计也有了锚点。2.4 插件机制每个插件都必须是封闭的扩展点很多团队都有过harness failed to load plugins之类的报错。插件加载失败的原因五花八门依赖版本冲突、命名空间覆盖、生命周期回调没绑定、插件 A 和插件 B 注册了同名的工具 ID。我自己的血的教训是插件不能想怎么写就怎么写必须实现固定的生命周期接口。我们后来强制要求每个插件实现load、validate、register、unload四个阶段。加载顺序是先验证依赖和命名空间再静态注册工具签名再动态绑定事件回调。任何一步失败Harness 都能把整个插件从运行环境中安全移除不影响主进程和其他插件。这套机制虽然让插件开发者多写了几个函数但在生产环境里换来的收益是决定性的——插件更新不用重启主服务单个插件崩溃不会拖垮整个循环。3. Loop单个 Agent 的“大脑节奏”控制3.1 一个循环的五个环节缺一不可Harness 把环境和工具准备好了接下来真正产生智能的是 Loop 层。不管你的 Agent 对外表现是会写代码还是会做研究内部跑的都是同一个模式分析目标、拆解步骤、调用工具、观察结果、反思调整。我这里特别想强调反思调整这一步。很多团队的 Loop 结构是执行—返回—再执行没有对比预期与实际差异这个动作。结果就是模型在一个错误方向上越走越远。比如明明已经拿到了完整的答案它还在继续搜索更多资料因为它的循环里根本没有够了该停了这个判断。好的 Loop 必须在每轮结束后做一次自我校验当前结果是否足够回答最初的目标还有哪些关键信息缺失下一步是继续深入还是切换到新方向或者直接收尾。这一步做不做直接决定了 Agent 像不像一个靠谱的同事。3.2 为什么循环要状态机而不是 while True如果你直接把 Agent 的主循环写成while True那只是轮询。真正的 Agent 循环是有状态的。它处于正在规划、正在调用工具、正在观察结果、正在反思这些状态时对 Prompt 结构、可用工具列表、拦截规则的要求完全不一样。比如在正在调用工具的状态你可以直接允许工具执行如果它还处于正在规划阶段你就不想让一些高成本工具白白触发。生产环境里我采用的是有限状态机的结构IDLE → PLAN → ACT → OBSERVE → REFLECT → (回到 PLAN) 或进入 DONE / FAILED / NEED_HUMAN。每个状态有独立的处理函数状态之间的跳转条件由上一状态的输出决定。状态机带来的最大好处是可观测性。任务跑了两小时还没结束打开状态日志一眼就能看出它是不是在 PLAN 和 ACT 之间反复空转。没有状态机的时候你只能从一坨日志里靠猜。3.3 循环里的三个保命参数如果你问我 Loop 层最值钱的工程参数是什么我会说三个最大步数、重复结果检测、单步超时。最大步数不同的任务类型给不同的额度。我们内部的经验是简单问答 8 步文档处理 30 步复杂的研究类任务 80 步。超过步数直接进入 FAILED 状态转人工处理。重复结果检测把每一步的工具返回结果做哈希连续几步哈希相同判定为无效循环。这个机制救过我好几次。有一次模型反复调用同一个报错接口返回的错误信息一模一样它硬是拿同样参数重试了十几次有了检测之后走到第 3 次就能主动跳出并反思。单步超时每个工具调用必须设置独立的超时时间超时后 Harness 返回一个统一的 timeout 错误码Loop 捕获后可以选择换一个工具或者换一种调用方式。这三个参数单独看都很简单但放在一起就构成了 Loop 层的安全网。没有它们Agent 的聪明可能会变成灾难性的耗钱机器。4. Graph从单线程到多 Agent 协作编排4.1 单个 Loop 的边界就是 Graph 的起点单 Loop 做得再完善也只能处理一个人从头干到尾的任务。可真实业务里的任务经常需要多个 Agent 角色接力一个做需求拆解一个写实现方案一个做结果验证甚至还要分两条线并行调研最后把结论合成。把这些前后依赖、并行分支、条件跳转画成一张有向图就是 Graph 层要做的事。Graph 层一个经常被误解的地方是使用图不是为了看起来高级而是为了引入确定性。Loop 层充满随机性模型每次输出都可能不一样。但图本身是确定的——A 状态之后到达 B 还是 C由程序根据明确条件决定不会凭空跳到 D。这个特性让测试回归变得可行也让生产链路可以被审计。4.2 节点状态持久化中断了从哪里继续做多 Agent 协作的时候一个复杂任务可能要跑几十分钟甚至更久。中途任何一个环节挂了你的选择只有两个从最开始重新跑或者从最后一个完成节点接着跑。显然后者才是人该干的事。我的设计要求每个 Graph 节点在完成后把一个完整的节点快照写入持久化存储。快照内容包括当前节点的输入、输出、涉及的工具调用 ID、消耗的 token 数、时间戳。这样任务中断之后恢复程序可以扫描最后一个未完成节点的上游从那个位置继续。这个设计最初只是为了容灾但后来发现它还是审计利器。线上出现用户纠纷说Agent 动了不该动的数据你可以直接从持久化快照里还原出当时每一步的操作序列。这种能力不是产品需求评审时你能想到的但真出事的时候它就是救命稻草。4.3 分支和回退图的拓扑结构要固定不要交给 LLMGraph 层最容易犯的错是让 LLM 来决定图的结构。比如说模型觉得任务太复杂就动态插入一个新节点。听起来灵活实际完全不可控。你那不是编排是让演员自己改剧本。我的铁律是图拓扑固定节点内自由。运行前Graph 已经把整条路径以及所有可能的分支都定义好了。模型只能在某个节点内部做决策比如在方案设计节点里决定用方案 A 还是方案 B节点之间走哪条边是程序根据确定性条件判定的不由模型临时发挥。回退策略也一样要提前画好。比如验证失败 → 退回方案设计节点 → 最多重试两次 → 再失败转人工。在 Graph 里这就是一条带重试计数的回边。重试次数达到上限后状态自动迁移到 NEED_HUMAN等待人工接手。这两条规则保住了生产系统的可预测性让排障和演练都成为可能。5. 生产环境里的联动、并发与事故复盘5.1 三层的接缝处才是真正的工程难点架构图看起来清爽但真实系统的复杂度和坑几乎都藏在层与层的接缝里。我自己对分工有一个明确的约定Harness 管能不能权限、上下文、工具合法性Loop 管怎么做规划、执行、反思Graph 管该不该继续节点跳转、分支、回退、转人工。边界清楚了遇到问题才不会互相甩锅。一个特别容易踩的接缝是工具返回结果的格式。如果有的工具返回 Markdown有的返回 JSON有的返回纯文本Loop 层的解析逻辑会变成一团乱麻。后来我强制规定所有工具必须返回带 schema 的结构化 JSON。出错时统一返回error_code error_message模型永远看到的是干净的数据结构解析成功率显著提升。5.2 并发到底在哪一层扛AI Agent 怎么扛并发是我见过被问得最多的问题之一。很多团队的直觉是让 Loop 层支持多线程同一个模型实例同时处理多个任务。这个做法后患无穷上下文串线、工具调用顺序混乱、token 成本失控。我的答案是Loop 层保持串行Graph 层做并发。单个 Loop 内部严格串行保证一个任务流里的上下文和工具调用顺序完全可控。多个互相独立的 Loop 节点在 Graph 层被并行调度每个 Loop 实例拥有独立的上下文和状态存储跑完之后把结果汇总给父节点。这样并发度上去了但每个并发单元内部依然是单线程的Bug 边界非常清晰。5.3 记忆架构工作记忆和长期记忆要分开关于记忆我也经常被问。指望单个 Loop 里的上下文承载所有记忆是反工程的。上下文窗口是稀缺资源随着任务推进会不断被消耗。我采用的方式是双层记忆。工作记忆跟着当前 Loop 走只保留当前任务必需的信息任务结束就归档或清空。长期记忆存在 Graph 层的全局存储里按任务维度和实体维度建索引。新任务启动时Graph 会把相关的长期记忆预取到 Harness 的上下文组装器里让模型想起历史对话的关键结论。这个设计把记忆问题从模型能力问题变成了存储检索问题一旦完成这个转换可用性和可控性都大幅提升。5.4 一次插件加载失败事故的完整复盘写这篇文章之前我刚处理过一起 harness failed to load plugins 的事故正好拿来复盘。现象是线上服务报错说有两个插件条目没有激活。第一反应是检查插件文件都在第二反应是看依赖版本也都满足要求。最后排查到根因时有点意外插件 A 在注册时绑定了一个全局事件监听插件 B 加载时也注册了同一个事件后加载的 B 把 A 的监听函数覆盖掉了。于是 A 的某些功能在代码层面看起来一切正常但运行时已经静默失效。这个坑给我们带来的改进是插件加载必须做静态冲突检测。加载前扫描所有已注册的工具 ID、事件监听器、命名空间发现有冲突直接拒绝加载并给出明确的冲突报告。从那次之后我才算真正把插件机制从能用变成了稳。5.5 状态双写与审计日志Graph 层状态该放内存还是放磁盘我的答案是都要。热状态放内存供调度器快速读取全量快照写持久化供审计和重放。很多团队只做内存态觉得重启就重启吧从最开始重新跑。但一个跑了 40 分钟的复杂任务一旦重启就要从头来这代价是用户接受不了的。快照的粒度也要讲究。不必每个步骤都写盘我通常是在节点级别打点也就是某一整个环节完成后再写一次快照。这样磁盘压力不大中断恢复的精度也够用。6. 选型建议你的项目到底需不需要三层架构说了这么多我必须强调一句公道话不是所有 Agent 项目都该一上来就上三层架构。如果你只是做一个单轮问答 Demo、工具不超过五个、流程完全固定的小工具一个写得很干净的循环加一个工具列表完全够用。强行上 Graph 只会增加维护负担让本来简单的事变得复杂。但也别走另一个极端。我的建议是在项目第一天就把目录结构划成三块harness/、loop/、graph/。哪怕里面现在各只有一个文件这个目录本身就是一种工程契约它强迫你在写代码的时候思考这个逻辑该属于哪一层。如果你打算从零搭建我推荐的落地顺序和大多数人想的正好相反先做 Loop再做 Harness最后上 Graph。顺序很重要。先跑通单个 Loop让它能在给定目标和工具的情况下稳定完成单任务然后补 Harness把工具权限、上下文结构、错误码体系这些基础设施夯实最后再加 Graph把多步骤编排和任务状态管理做起来。Graph 的引入是渐进式无痛改造——Loop 还是那个 Loop只是调用方从主函数变成了图节点。另外有几个加分项是我在实际项目中反复确认过的给每次模型调用记录 token 成本和耗时按任务维度汇总。没有成本数据你判断这个反思步骤到底值不值就没有依据。给每个工具调用打 trace_id把 Harness、Loop、Graph 三层日志串起来。线上排障时这个 ID 让你能在三层的日志之间来回跳转而不是在几个系统里搜半天。把暂停、恢复当作一等公民来设计。用户随时可能打断一个长任务过几天再继续你的架构必须支持在 Graph 的任意节点上暂停和恢复。对模型输出做 schema 校验不要默认模型每次都会输出合法 JSON。校验失败就让它重试一次连续几次失败就降级给人工处理。聊到这里其实已经把三层架构的核心讲得差不多了。最后说一点我个人的体会Agent 工程的上限确实取决于模型的聪明程度但它的下限也就是稳定性和可控性几乎完全是由工程架构决定的。Harness、Loop、Graph 这套分层真正解决的是模型不可控这个先天问题——把疯狂的部分关进笼子里把确定的部分用工程手段固化下来。这也是我做 Agent 项目以来觉得最值得分享的一条经验。