
做 Agent 工程这几年我最大的感受是demo 和成品之间隔着的是 Harness、Loop、Graph 这三层。Agent 单次调用谁都会写但要把一个 Agent 变成能上生产、能被团队维护、能扛住真实业务流量的系统绕不开这三层怎么设计、怎么调优、怎么兜底。这篇文章就围绕这个三层架构展开把我做过的几个真实项目里的设计思路、参数选择和排坑过程完整写出来适合正在搭 Agent 框架、或者准备把 Agent 接到现有业务里的开发者参考。打个比方比较好理解Harness 像车的驾驶舱和外壳决定你能碰哪些按钮、仪表显示什么、安全带系在哪Loop 是发动机让模型反复走“观察—决策—行动—看结果”这个过程Graph 是导航图告诉我们在哪个路口转弯、哪些路段可以并行、哪些地方必须停车等人。三层叠加在一起Agent 才不是一个拿着锤子见啥都敲的黑盒而是一个可以被设计、被测试、被监控的系统。下面我把三个词先掰开再分别讲每一层怎么搭、怎么调、怎么排坑。1. 先把三个词掰开揉碎Harness、Loop、Graph 到底在说什么1.1 Harness 不是“提示词包装”是 Agent 的生产级操作台很多人第一次接触 Harness以为它就是写个 system prompt把模型“框住”。这个理解差得有点远。Harness 这个词在工程领域一直有“装配、捆扎”的意思比如altium harness指的是电子设计里的线束物理世界里把电源线、信号线、地线规整地捆在一起让设备能可靠工作。Agent 领域的 Harness 干的是同一件事把模型、工具、数据、策略、权限这些原本松散的部件规整地装配到一个可控的运行环境里。我自己的定义很简单Harness 是模型之外的“整套运行框架”决定模型能用什么工具、能看什么信息、能做什么动作。一个生产级 Harness 至少负责工具注册与 schema 管理、上下文组装与裁剪、调用前权限校验、调用中沙箱隔离、调用后结果过滤、全链路日志与计费。这些事全部写在模型提示词里是做不到的因为提示词只是给文本模型看的文本而 Harness 是真正执行代码、捏着资源、握着开关的那层系统。很多人搞不清 Harness 和 Agent 的区别。Agent 是一个执行体它有目标、有行动能力Harness 是承载这个执行体的环境。同一个 Agent 逻辑放进不同 Harness表现会是两个东西一个可能什么都不让碰另一个可能让模型随意执行 shell 命令。生产系统选 Harness 时本质是在选“谁能拥有哪些特权”。1.2 Loop 是 Agent 的心脏Graph 是 Loop 的升级形态Loop 说的是 agent loop也就是 Agent 里最核心的重复过程每一步把当前状态交给模型模型决定是调用工具还是输出结果然后再把工具返回的观察结果带回去继续让模型决策。这个过程循环往复直到模型认为任务完成、或者达到预设终止条件。它是 Agent 产生“自主性”的来源。Graph 则把 Loop 从“一条直线”升级成了“一张有分叉、有汇合、有等待的地图”。Loop 只有一个隐含的反复过程Graph 则显式定义节点和边节点是任务步骤边是流转条件。比如一个节点执行成功后走到下一步失败则走降级分支需要人工确认时挂起等待。很多真实业务并不能用一条直线 Loop 跑完必须有分支、并行、人工审批这些结构Graph 就是把这些结构显式化。三层不是替代关系是叠加关系。Graph 的每个节点内部可能依然在跑一个 LoopHarness 为所有 Loop 和 Graph 提供统一的运行时能力比如工具权限、日志、计费埋点。所以正确理解是Harness 是运行基座Loop 是最小执行单元Graph 是执行拓扑。1.3 三层架构各自解决什么问题用一个表格对照看得更清楚层次核心问题主要职责典型事故Harness它能干什么、不能干什么工具装配、权限控制、上下文管理、安全护栏、可观测性模型乱调工具、越权访问、预算失控Loop它怎么一步一步把事干完迭代调度、记忆管理、终止检测、重试恢复无限循环、重复操作、上下文爆炸Graph事多了之后路线怎么编排分支、并行、人工介入、状态流转流程僵化、任务状态丢失、结果不可复现我见过很多初学 Agent 的人只搭了一个 Loop 就开始跑业务结果遇到两个问题模型在没人管的情况下反复调用同一个工具把账号余额刷光或者流程走到一半模型自己发明了一个不存在的环节。这些都是因为上层的 Harness 没兜底、Graph 没约束。下面我按层拆开讲。2. Harness 层边界、安全与工具装配决定 Agent 能不能上生产2.1 工具装配的本质把模型伸进世界的“手”和“眼睛”模型本身不具备真正调用工具的能力它只会“请求调用”。Harness 要做的是把模型发出的调用请求翻译成真实的函数执行。这个翻译质量直接决定 Agent 好不好用。我用过一个天气查询工具一开始 schema 里对参数 city 的解释写得太简单模型总是传拼音或者英文名导致查询失败。后来在参数描述里加了明确的示例“city 必须是中文城市名例如‘北京’‘上海’不要传拼音或英文”问题立刻消失了。工具描述不是给程序员看的注释是给模型看的说明书必须把边界、格式、错误情况写得清清楚楚。工具注册也不是一个简单的字典。每个工具在 Harness 层应该带五类元信息名称、版本、参数 schema、权限级别、成本预估。权限级别决定哪些用户/哪些场景下模型可以碰这个工具成本预估用来在调用前做预算校验。工具多了以后还要考虑分组一个模型请求里塞上百个工具 schema 是很占 token 的而且模型会“挑花眼”。我实测超过 80 个工具的时候需要按场景分组Harness 根据当前任务先选出一个工具子集再拿去组装请求。2.2 安全护栏越狱防护、危险操作拦截、预算熔断Agent 安全是上生产之前最容易被忽略的一环。原理很简单模型是无状态的逻辑体它不管你的钱、不管你的权限它只知道“继续干下去”。所以所有跟风险相关的事都要下沉到 Harness 层实现。我习惯做三道防线第一道是调用前规则校验。比如禁止执行某些系统命令、禁止读写某些敏感路径、外部请求只放行白名单域名。这些规则用代码写死不吃模型判断。第二道是调用时隔离。工具函数尽量跑在受限账号或容器里加上超时限制防止一个工具把整个服务拖垮。第三道是调用后结果过滤。工具返回值在拼进上下文之前先检查是否藏了异常内容比如网页抓取结果里经常有“忽略之前所有指令”这类的注入文本必须在进入模型上下文前隔离或标注否则模型容易被带偏。预算熔断一定要做在 Harness 层。我在一个项目里给每个任务设置了 token 上限和费用上限达到阈值后 Harness 直接终止 Loop并生成一条“预算熔断”记录。不做这个一个 Loop 失控跑半小时费用能让你怀疑人生。熔断之后还要有恢复机制最简单的就是保存现场的上下文快照人工决策后重新发起。2.3 插件化与技能体系skill 怎么设计才不翻车现在的 Agent 框架基本都支持插件和 skill热词里常提到的deepseek harness、claude agent skills就是这类生态。skill 本质就是一组指令、工具定义、示例和触发条件的打包。一个好的 skill 应该做到一个 skill 解决一类完整任务而不是解决一个原子操作。比如“金融研报解读”是一个 skill它内部包含读取数据、调用计算工具、生成结构化结论这三个步骤如果把它拆成三个 skill模型反而不知道怎么串联。插件加载有一个我踩过的坑。热词里有一条很典型的报错harness failed to load plugins web boot: 1 entry did not activate huayu-yuan。这类“入口未激活”问题十有八九是下面几个原因插件 manifest 里配置的入口文件路径不对、入口函数名没有按约定导出、或者插件依赖缺失导致初始化抛异常。排查顺序我建议是先看 manifest 的 entry 字段指向的文件是否存在再确认入口函数是不是用了框架约定的命名和导出方式最后看插件的依赖包有没有装全。插件目录里不要放多余文件我就见过一个项目把测试用例打进插件包入口扫描逻辑找不到主入口直接报错。skill 太多也要注意触发冲突。我的经验是给每个 skill 写一个“触发条件”声明Harness 在组装上下文时先按触发条件做一轮粗筛能选出 3 到 5 个 skill 再进入模型决策。2.4 离线环境部署与版本回退Harness 工程化的最后两公里很多人部署 Agent 是在隔离网络环境没有公网访问能力。这时候配deepseek harness之类组件要注意提前把依赖包全部下载好用本地源或者离线包安装。我习惯的做法是在一台有外网的机器上把所有依赖导出成 wheel 文件连同模型权重一起拷进内网再用本地路径安装。安装完成后先跑一个最小冒烟测试确认 harness 能成功加载模型和核心插件再上业务量。有一个细节容易被忽略离线包的依赖版本锁定文件装完以后内网机器上如果之前有旧版本会出现各种怪异的“冲突但不报错”的问题所以内网环境最好用干净的基础镜像。版本回退也值得讲。Harness 的配置、skill 定义、prompt 模板全都要进 git发布时打 tag。因为配置类的错误不像代码有编译期检查往往运行到一半才爆出来。我的习惯是每次发布都记录当前所有组件的版本组合一旦线上出现问题直接切到上一个 tag。代码回退这件事看起来不起眼关键时刻能救命。我有一次只改了一个 prompt 词上线后模型开始大量重复调用搜索工具在没有一键回退机制的情况下只能手动改配置重启多花了二十分钟。现在每个 harness 项目上线前我都会先确认回退路径可用。3. Loop 层主循环是 Agent 的发动机怎么调才不熄火3.1 一次完整的 Agent Loop 里发生了什么Agent Loop 的每一步本质上就是“组装上下文 → 调模型 → 执行动作 → 观察结果”这四个动作的循环。我用一个极简的 Python 伪代码说明实际框架会复杂很多但核心骨架长这样MAX_ITERATIONS 15 def run_agent(initial_state, harness): state initial_state for step in range(MAX_ITERATIONS): messages harness.assemble_context( state.history, state.tool_results, state.memory ) response model.call(messages) if response.has_tool_call(): result harness.execute_tool( response.tool_name, response.tool_args ) state.tool_results.append(result) state.history.append(response) continue if response.is_final_answer(): return response.text raise MaxIterationsError(loop exceeded limit)生产版本还会有重试、超时、重复检测、记忆压缩但思想上完全一样。每次循环里模型拿到的输入都要包含“上一步发生了什么”否则它就无法感知自己的行动结果。这一步缺失是很多 Agent 表现差的根源——模型做了动作但下一个请求里压根没带上动作结果它只能在原地空转。3.2 记忆是怎么“塞”进 Loop 的记忆是 Loop 能持续工作的燃料也是容易失控的地方。我一般把 Agent 的记忆分成三层短期记忆是最近几轮对话和工具调用记录直接在上下文中完整保留长期记忆是向量库检索出的相关片段按相关性动态注入结构化记忆是用户属性、任务状态这类必须准确的数据存成 JSON 或数据库记录不靠模型猜。这三层里最容易出问题的是长期记忆检索出来的片段如果不做相关性阈值过滤塞进去一堆无关信息反而把模型带偏。记忆在 Loop 里还有一个经典坑序列化的时候出现循环引用。热词里那条self referencing loop detected for property mem_memberinfo with type system就是典型问题——工具返回的对象里有互相引用的字段比如 memory 对象里挂了一个指向整个系统对象的引用框架在序列化时陷入死循环。解决思路很粗暴实用把敏感对象先转成 DTO只保留需要的标量字段或者开启引用循环忽略选项在序列化层直接跳过重复引用。我在项目里遇到过两次根源都是工具函数直接返回了 ORM 实体对象后来统一改成返回字典问题再没出现过。记忆还要考虑 token 预算。上下文窗口是有限的Loop 跑得越久历史越长。我的做法是设一个阈值超出后做摘要压缩把最老的几轮对话交给模型生成一段摘要保留摘要而丢弃原文。摘要会损失细节但总比上下文爆炸直接报错强。3.3 Loop 的终止条件、重试与自引用检测Loop 最怕的是“停不下来”。终止条件分两类正常终止是模型自己输出最终答案异常终止是达到了迭代上限、预算上限或超时时间。迭代上限我一般设在 10 到 20 之间任务简单设 10复杂任务设 20但不建议超过 30——超过 30 步的任务大概率是流程设计有问题应该用 Graph 拆成多步而不是指望一个 Loop 硬跑。比迭代上限更难处理的是“重复循环”。模型在同一个问题上反复调用同一个工具每次都拿到差不多的结果它还是不结束。这个现象有点像 ffmpeg 里的 loop 滤镜同一段画面不停循环输出如果不给终止条件就会无限播放。我的解决办法是给 Loop 加一个“指纹检测”把最近五步的工具名和参数摘要算成一个哈希如果连续出现相同指纹就触发降级策略——要么终止要么换一种提示策略重新尝试。有一次排查线上 Agent 卡死查日志发现模型在连续 11 次调用一个翻译工具每次都传一模一样的参数指纹检测直接把问题暴露了。工具调用失败后的重试也要限次数。工具报错时把错误信息回填给模型让模型修正参数重试通常有效但同一工具连续失败三次以上就不要再让模型硬试直接跳到降级分支。降级分支可以是告诉用户“工具暂时不可用”也可以是换备用工具。3.4 Loop 的观测看不到循环在干嘛就没法调调 Loop 没有日志等于盲飞。每个迭代周期我会输出一行 JSON Lines 日志包含迭代序号、模型输出截断、调用的工具名、工具返回状态、耗时、token 消耗。调试时用 request_id 把所有日志串起来就能完整回放一次 Agent 的思考链条。重点关注的指标有三个平均迭代次数、工具失败率、首次工具调用时间。平均迭代次数突然上升通常是上下文里缺信息模型在反复摸索工具失败率升高一般是 schema 描述变差了或者上游接口不稳。这些指标配上告警比肉眼盯日志强得多。有一次告警告诉我工具失败率从 5% 飙到 60%查下来发现是下游 API 悄悄改了返回字段要不是指标盯着用户反馈出来之前根本不知道。3.5 给模型“退一步”的实话Loop 调试的经验多了以后我反而觉得最关键的是承认“模型不是万能的”。上下文越长模型越容易在后期犯糊涂。我有个习惯Loop 跑到一定步数后如果发现模型连续两轮都没有新动作就把上下文里最老的内容压缩掉一部分给它“减负”。这就像人工作太久需要停下来换个角度模型也一样。还有生产环境里模型参数尽量固定低温度0 到 0.3固定 seed方便回归对比。看到有人在生产环境把温度拉到 0.8Agent 的输出一天一个样测试用例都跑不稳这基本是把随机性当卖点了。4. Graph 层把 Loop 从“直线冲刺”升级成“路线图”4.1 为什么需要 Graph线性循环的第一个短板Loop 只解决“单条路径反复走”的问题。真实业务没这么简单一个任务里可能要先判断数据质量质量好走 A 路径质量差走 B 路径可能同时要查三个来源再汇总可能中间需要人工审核。这些结构用 Loop 硬写会很痛苦因为分支和并行逻辑只能写在提示词里让模型自己“决定”结果不可控。Graph 把流程结构从提示词里抽出来变成显式的代码路径。举个例子一个舆情分析 Agent如果只用 Loop模型要自己决定“先清洗数据还是先写报告”很容易乱。用 Graph 就清晰了清洗节点 → 分析节点 → 报告节点 → 人工审核节点。每个节点是一个独立函数有明确的输入输出节点之间的流转条件写死在边里。模型不再负责决定流程顺序只负责当前节点内的事情。人工介入放在 Graph 里特别顺手。把“人工审核”定义成一个特殊节点Agent 跑到这里就挂起等人工确认后再继续后续节点。这个体验是纯 Loop 很难给的Loop 里你想暂停要么靠外部强杀要么靠模型自觉都不靠谱。4.2 可视化编排与 Snap Graph Builder把图纸变成可运行系统我做过几个项目是用可视化工具搭 Graph 的像是热词里提到的snap graph builder本质上就是把节点和边画出来自动生成可执行的定义文件。可视化编排最大的价值不是“不用写代码”而是让产品和业务能直接看到 Agent 的执行路径而不是打开一堆代码才能理解。评审流程时对着图讲比对着代码讲效率高一个量级。这类工具还有一个隐藏功能快照。把某个节点运行上下文保存成快照失败时可以拿到快照重放逐步排查。这就像游戏存档Loop 时代你想复现一个问题得碰运气Graph 时代直接读档。我建议哪怕是手写代码做 Graph也要给每个节点的输入输出做版本化记录至少保留最近几天的状态快照。调试复杂 Agent 的时候这个习惯能省你半天时间。4.3 商图与状态压缩图论给 Agent 的启发Graph 层做多了我反而回头发现图论模型挺有用。热词里的“商图quotient graph”原本是图论概念把等价的顶点合并成一个超顶点得到一个简化后的图。你可以把它理解成地铁线路图——把相邻的小站合并成一个大站只看主干走向。拿这个思路看 Agent 运行日志效果很好把相同或高度相似的中间状态折叠起来就能看到 Agent 是不是卡在某个状态绕圈。有一次调试一个超长任务我按“工具名参数摘要”把状态归一化折叠出商图之后一眼看出来 Agent 在“搜索→阅读→搜索→阅读”之间反复横跳没有真正的信息增量。这个循环在原始日志里看不出名堂因为每一步的文本都不一样但折叠成商图后本质状态就几个问题立刻清楚了。顺便提一句图结构在跨领域也很流行比如脑疾病分类里用局部到全局的图神经网络建模。这提醒我们一件挺本质的事很多复杂问题都适合先建立局部结构再聚合出全局结论。Agent 编排也一样先让各个子任务独立跑局部逻辑再用一个聚合节点把结果汇总这种“局部到全局”的模式比让一个 Loop 从头管到尾稳定得多。4.4 从 Loop 改造成 Graph 的最小步骤如果现在你的 Agent 是一个线性 Loop想升级成 Graph我建议按这个顺序改不要想着一步到位第一步在纸上画出主流程的 4 到 6 个步骤节点。第二步标出哪些地方需要判断分支、哪些需要并行、哪些需要人工。第三步把每一个节点函数化明确输入、输出、抛错行为最好做到每个节点可以独立在测试脚本里调用。第四步引入一个统一状态对象所有节点只能从状态对象取值并把执行结果写回状态对象。节点之间不直接传参。第五步先按串行方式把整个流程跑通再加入并行的分支和重试机制。状态对象是 Graph 的核心。节点可以理解为纯函数输入是状态的一部分输出是状态的更新。这样设计的好处是任务中途挂了只要有状态快照就能重新拉起节点重试不会产生重复副作用因为节点只改状态不改外部资源。如果做不到节点幂等至少要保证节点内部的“有副作用操作”和“状态更新”分开这样更稳妥。5. 生产实践三层架构落地与排坑实录5.1 最小可行架构长什么样一个能跑生产的 Agent 服务代码上至少分成四个模块。Harness 模块负责工具注册、权限、上下文组装和熔断Loop 模块负责迭代执行、终止检测、重试和重复循环识别Graph 模块负责流程定义、状态对象和人工审核挂起运行时模块负责请求队列、并发控制、日志采集和指标暴露。第一版不需要很复杂但这些边界要清晰。我在项目里吃过亏前几版把工具调用和流程控制写在了一起结果加一个工具要动 Loop 的代码加一个节点也要动 Loop 的代码改了两周就变成了谁都不敢碰的代码块。配置化是这里的关键。工具列表、Graph 定义、prompt 模板全部做成配置代码主体保持稳定。项目上线后你会发现业务需求大多在“改配置”层面就能满足不需要每次都发版。只有做 Harness 层的新能力才需要动代码频率会低很多。5.2 常见问题排查速查表现象可能原因解决方案Loop 无限循环不退出终止条件没写或 max_iterations 没设加迭代上限触摸配重复指纹检测工具调用一直报参数错误schema 描述不清、缺少示例补参数描述和示例限制枚举值插件加载失败entry did not activate入口路径错误、入口函数名不符、依赖缺失按 manifest 路径逐项检查确认入口导出上下文越滚越大模型开始糊涂没有做压缩和裁剪滑动窗口 摘要机制工具返回结果带注入指令结果未隔离直接进了上下文在 Harness 层做结果清洗或隔离标注多步骤任务状态丢失节点用了局部变量没有统一状态对象引入全局 state每个节点只读写状态对象预算超支没有计费和熔断Harness 层记录 token 和费用超阈值自动终止序列化时自引用循环报错工具返回了互相引用的对象转成 DTO只保留标量字段或开启循环忽略这张表是我从好几个项目的线上问题里整理出来的每一条都见过不止一次。排查的时候记住一个原则先看配置再看日志最后才怀疑框架和模型。大部分问题都是配置写错或者环境不一致。5.3 版本管理与发布回退给 Agent 上个保险Agent 项目比普通后端项目更需要版本管理。普通后端代码出问题报错能定位到行Agent 里一个提示词调不好表现是“效果变差”不是崩溃。所以我把所有可能影响行为的元素都纳入版本管理prompt 模板、工具 schema、skill 列表、Graph 定义、模型参数。这些东西改了任何一处都要走 MR 评审和回归测试。回归测试这件事不能偷懒。我会准备一组合格用例每个用例有输入和期望的结构化输出。每次发布前跑一遍对比和上一版的输出差异。模型输出有随机性所以我会把温度调低在固定参数下跑回归至少能挡住大部分明显的回归问题。回退机制做得简单点配置存储带版本号状态对象也记录 graph_version。线上出问题后一键把配置切回上一版本再重新发起任务。注意这里要保证状态对象能兼容旧版本配置所以状态对象里不要存和流程强相关的字段只存任务业务数据否则新旧配置之间切换会产生字段对不上。5.4 团队协作与后续扩展三层架构的长期价值三层架构还有一个隐性价值团队分工变清晰了。有人专门打磨 Harness 层的工具质量和安全策略有人专门优化 Loop 的效率和记忆策略有人专门设计 Graph 的业务流程和评审机制。各层的关注点完全不同硬塞在一个文件里只会互相干扰。工具工程师不会天天去改流程定义流程设计师也不用深入了解每个工具的入参细节。后续想做多 Agent 协作的时候这个架构也更容易扩展。每个 Agent 保留自己的 Harness 和 Loop通过 Graph 层或者消息队列来协作。这个模式有点像知识库工具里的插件生态Agent 之间通过定义良好的接口传递任务和结果而不是互相直接改对方的内部状态。比如在 Obsidian 这种笔记工具里集成助手插件本质上也是在 Harness 层控制它能查哪些笔记、能改哪些文件规则清楚才不会被模型带乱。踩过几次坑之后我现在不管做什么 Agent 项目都会先问三个问题这个动作需要什么样的 Harness 边界主循环怎样才能明确判断“完成”哪些业务路径需要显式地用 Graph 固定下来把这三个问题答清楚项目基本不会跑偏。最后分享一个小技巧每次循环结束把关键状态 dump 一份到本地日志带时间戳。这个东西平时不起眼等你想复盘一次诡异 Bug 的时候你会无比感激当时的自己。