
还记得我第一次写 Agent 原型时的代码吗一台笔记本一个 while 循环套上模型调用工具函数全部写死在代码里。跑通 Demo 的那一刻觉得自己已经掌握 Agent 开发了直到被生产环境狠狠教育模型偶尔会反复调用同一个失败工具上下文越滚越大最终爆掉想回滚一个版本无从下手想让两个任务并行跑更是天方夜谭。回头去复盘问题根本不是模型不够聪明而是底层缺了一套工程结构。后来我逐渐把 Agent 工程拆成 Harness、Loop、Graph 三层来设计整个系统的稳定性、可观测性、可维护性都上了一个台阶。这篇文章把这三层架构讲透重点落在真实项目里会踩的坑Harness 这层我会聊运行时底座、Skill 离线部署、插件加载失败和版本回退Loop 这层聊循环失控、上下文膨胀、多窗口状态隔离Graph 层聊多 Agent 编排、商图监控、流程建模。无论你是刚入门的开发者还是在维护线上 Agent 服务的老手这套分层都能直接拿来用。1. 三层架构到底是什么为什么 Harness、Loop、Graph 缺一不可1.1 从能跑的 Agent 代码到工程化系统很多人的第一个 Agent 都是这么写出来的一个 while True把用户问题塞给大模型模型说要调工具就调工具把结果拼回去再问模型直到模型说完成了。这套东西跑通一个简单对话没问题但它有三个致命缺陷没有边界。模型能接触到什么工具完全靠提示词约束一个提示词写漏了模型就可能去调用不该调的系统命令。没有恢复机制。循环中途崩了没有 checkpoint下一次启动什么上下文都没有整个任务从零再来。没有拓扑。任务只能线性推进遇到先并行采集三个数据源、再汇总分析这种需求代码就爆炸了。Harness、Loop、Graph 三层拆分本质就是把这三个问题分别交给不同的层去解决。1.2 三层各自的职责与边界拿我自己的定义来说这三层的边界非常清晰层次一句话定义解决的核心问题生产中的典型产物HarnessAgent 的运行时与边界设施模型怎么连、工具怎么给、技能怎么挂、记忆怎么存、权限怎么控配置文件、插件系统、Skill 包、审计日志LoopAgent 的决策与行动循环模型与工具如何反复交互直到任务完成或明确失败主循环状态机、重试策略、终止条件Graph多 Agent 的编排拓扑多个子 Agent 和工具节点如何组织成 DAG、分支、并行、人机回环编排定义、节点数据契约、商图监控视图它们之间的关系是递进的Graph 层决定这件事分几步做、哪些步骤并行Loop 层决定每一步内部怎么反复试Harness 层决定所有这些运行在什么样的沙箱里、能被谁调用、出问题时到哪里查。1.3 直观类比驾驶舱、飞行检查和塔台航线我的经验是纯讲概念容易晕用飞机来类比会清楚得多。Harness 是驾驶舱与仪表盘。飞行员通过仪表盘看高度、油量、航向通过操纵杆控制飞机。Harness 给模型提供了仪表盘工具调用接口、上下文窗口、日志、安全权限。模型不直接碰发动机而是通过 harness 暴露的控件来操作。Loop 是飞行员每圈的标准检查程序。每次循环都是观察仪表数据、做判断、操纵飞机、再看仪表反馈。单架飞机飞得好不好就取决于这个循环是否稳定。Graph 是塔台和航线图。多架飞机同时在空中谁先起飞、谁在哪个高度层飞行、谁需要绕飞是塔台基于航线图来协调的。Graph 层就是塔台它管理的是多个 Loop 实例之间的编排关系。顺带说一句别被 Harness 这个词吓到。做硬件的人听到 harness 会想到线束把一堆散乱的线整理成一个可插拔的连接器做 Agent 的人说 harness是把模型调用、工具调用、记忆存取这些散乱的逻辑整理成一个可运维的运行时外壳。两个领域的直觉是相通的。2. Harness 层Agent 的运行时底座与可控边界2.1 Harness 到底在管哪些事我见过不少团队把 Harness 理解成一个启动 Agent 的入口脚本这个理解太窄了。真正的 Agent Harness 至少要管好下面这几件事缺一件都会在生产环境里还债组件职责生产环境容易踩的坑模型网关统一封装底层模型 API支持多模型切换、流式输出、超时重试换模型时只改代码不改系统提示词行为大变工具注册表声明 Agent 可以调用的工具包含参数 schema、权限级别工具返回数据过大直接把上下文撑爆插件总线动态加载插件扩展 harness 能力插件入口未激活加载失败但主程序不报错Skill 管理把提示词、工具组合、示例打包成可复用的技能单元Skill 包放在错误目录harness 启动时根本没有扫描到记忆接口为 Loop 层提供短期上下文、摘要、向量检索的统一读写入口记忆写操作丢在主循环里性能瓶颈审计与安全记录工具调用链、敏感操作确认、权限拦截审计日志不全事故后无法追溯对这些组件我个人的排序是安全边界和审计日志优先级最高然后是记忆接口。模型网关和工具注册表反而是相对容易替换的部分。2.2 Harness 和 Agent 的区别运行时与业务逻辑很多人问过我同一个问题Harness 和 Agent 到底是不是一回事我的理解是这样的。Agent 是一个由模型权重、系统提示词、工具 schema 共同定义出来的行为体。你把提示词改一句话Agent 的行为就变了。但 Harness 是承载这个行为体的环境——挂载哪些模型、开放哪些工具、按什么权限策略审核、用什么样的日志格式记录。Harness 不会因为改一句提示词而变化它是相对稳定的运行骨架。用一个具体例子说明。用同一个开源 LLM 底座分别接两套提示词和工具集你可以跑出一个客服 Agent和一个代码审查 Agent。但如果换了一个 Harness给 Agent 提供不了代码仓库读取能力代码审查 Agent 就算提示词写得再好也跑不起来。这就能看出来Agent 解决的是聪明不聪明Harness 解决的是能不能稳定地跑在受控环境里。2.3 实操私有化部署 Harness 与 Skill 加载2.3.1 离线环境的安装思路在生产里很多 Agent 服务要部署到隔离网络环境。比如企业内网要跑一套基于开源模型的 Agent 助手外网的公共模型服务和插件市场都不可达。这个场景我部署过好几次踩过的坑可以汇总成一句话先把所有依赖在外面解决掉再整体搬进去最后逐个验证入口。第一准备依赖包。在能联网的机器上把所有 Python 或 Node 依赖下载成离线安装包尤其要注意一些需要编译的依赖必须指定与目标服务器相同的 CPU 架构和操作系统版本否则装进去就是段错误或者缺库报错。第二确认版本一致性。Harness 主程序、模型网关插件、内置工具插件之间通常有版本匹配关系跨版本组合时会出现运行期才爆的兼容性问题。第三用白名单方式启动。先只启用核心插件跑通最小链路再逐步放开其他插件。这个习惯能帮你快速定位是环境问题还是插件本身的问题。2.3.2 Skill 的本质与离线部署步骤网上搜Agent Skill 教程的非常多但很多教程只讲了前端怎么调没讲清 Skill 到底是什么。我在实践中把它理解为三件套一个结构化的提示词片段、一组工具调用的绑定声明、一份可执行的示例或脚本。Harness 启动时会扫描指定的 Skill 目录把每个 Skill 注册成可加载的能力单元。放在内网服务器上的部署步骤其实不复杂在开发机上把 Skill 目录整体打个包里面通常包含 skill 描述文件、提示词模板、配套脚本。把 Skill 包拷贝到目标服务器的数据目录下注意不是放在主程序目录而是要放到 Harness 配置里指定的技能根目录。重启 Harness或者在运行时触发技能目录重载。用一条包含该 Skill 关键动作的测试指令验证服务端日志里能看到 skill 命中工具调用记录能查到才能算部署成功。我踩过最冤的一次坑是 Skill 包放对了目录但描述文件里的格式写错了一个字段Harness 启动时直接静默跳过从外部看就是Agent 什么技能都没有。排查半天最后是在日志的 Debug 级别里才看到解析告警。2.3.3 插件加载失败的排查链路启动时如果出现类似 entry did not activate 的插件加载失败别慌按下面这条链查大概率 10 分钟内定位第一步打开完整日志找到该插件入口的加载记录。Harness 一般会打印是哪一步没激活是入口文件不存在还是初始化函数抛了异常。第二步检查插件的入口路径配置是否与实际文件名一致。很多加载失败纯粹是大小写或者路径前缀对不上。第三步检查插件版本与 Harness 主版本是否兼容。跨大版本插件经常出现 API 签名不匹配表现为加载了但没有任何功能暴露。第四步把该插件临时从启动列表里去掉确认主程序能正常启动。这一步能帮你判断是不是某个插件拖垮了整个启动链。2.3.4 代码回退的正确姿势热词里有一条是deepseek harness 代码回退其实就是升级翻车后要回退到上一版本。我的建议是不要直接覆盖旧版本而是先做三件事备份当前配置目录和 Skill 目录。因为新版本启动时可能会迁移配置结构回退后会用到原配置。确认旧版本与当前 Skill 包的兼容性。Skill 是一种相对独立的东西但如果是随版本一起更新的内置 Skill回退之后要一并还原旧版本。回退后跑一遍冒烟用例。至少覆盖一次带工具调用的多轮对话确认模型网关、工具执行、日志输出三条链路都通。我曾经因为只回退了二进制包、没有回退配套 Skill导致新老版本加载同一份 Skill 时解析规则不一致整条 Agent 链路瘫痪半小时。从那以后我学乖了版本回退永远是一个整体快照的回退不是单个文件的替换。2.4 Harness 层的安全边界安全这个话题单独拎出来说。Agent 的能力越强安全边界越重要。我在 Harness 层至少有四个必做项工具白名单。模型只能调用显式注册过的工具注册时就要填权限级别比如只读、可写、高危。高危操作二次确认。对删除文件、执行系统命令这类操作Harness 可以拦截下来要求人工在交互界面点确认再放行。调用链审计。每次工具调用都要记录谁触发的、带了什么参数、返回了什么、用了多长时间。上下文隔离。不同会话、不同用户的 Agent 实例在记忆和文件系统访问层面要隔离防止 A 用户的会话读到 B 用户的数据。很多团队在 Demo 阶段觉得这些是多余的等 Agent 真的开始连数据库、发邮件时才知道安全边界不是某一行代码而是 Harness 层的整体设计。3. Loop 层Agent 的心跳循环也是最容易失控的地方3.1 三种主流循环形态对比Loop 层是 Agent 真正思考 行动的地方。生产里我常用的循环形态有三种不是所有任务都该用同一种循环形态工作方式适合场景典型风险ReAct推理、行动交替进行每次观察工具结果后生成下一步推理需要动态查信息、反复实验的任务容易在同一个工具上打转Plan-and-Execute先规划出步骤清单再逐个执行执行结果反馈给规划器多步骤明确的任务环境变化后计划过期Self-Refine生成结果后自我批评再基于批评意见重写写作、代码改进、需要质量迭代的任务token 消耗成倍增加ReAct 的核心循环用伪代码表示非常直观while not finished: thought, action, action_input model(context) if action finish: finished True else: observation tool_execute(action, action_input) context.append(observation)这个循环简洁但它暴露了 Agent 生产化最尖刻的问题**循环什么时候才真正finished**模型说 finish 不代表结果正确模型也可能永远不说 finish就在那儿一遍遍调同一个工具。3.2 循环失控的典型症状与刹车机制很多人看到 self-referencing loop detected 这个报错是在写后端序列化的时候对象相互引用导致无限递归。我在 Agent 系统里见过另一层意义上的自我引用循环症状是高度相似的模型在上下文里反复引用自己前面某一步的推理导致同一段内容不断膨胀最终要么把上下文撑爆要么把同一个工具调用重放几十遍。应对循环失控我的经验是必须上刹车机制而且要在设计初期就上不是等出了事故再补最大迭代步数。无论什么循环都要有一个硬上限。常见做法是在任务开始前根据任务复杂度分配一个步数预算比如 20 步或 50 步超了就强制收敛。Token 预算。每次循环前检查已消耗 token接近阈值时强制要求模型进入收尾总结。幂等工具设计。工具本身要做到重复调用不会产生副作用差异。比如创建订单这种操作必须带幂等键循环重放时不会真的一次次创建订单。人工中断与人工确认。给人类操作员一个随时可以暂停循环、修改上下文、重新指定方向的入口。行为检测。识别到模型连续三次调用同一个工具且入参完全一致时主动打断并提示当前动作已重复建议换策略。有一次测试环境跑一个多小时的批量任务某个子 Agent 在循环里反复调用一个读取接口因为返回数据一直不满足条件它又只会这一个动作白白烧了上万次调用。加了一个相同工具连续调用次数上限之后这类问题直接被提前终止系统会自动标记为需要人工介入。3.3 多窗口与会话隔离下的 Loop 状态管理热词里有个loop 多窗口实际指的是多会话并行场景。一个生产服务里往往同时跑着十几个任务每个任务一个独立的 Loop 实例这些实例之间不能共享上下文不然 A 任务的中间状态就会污染 B 任务的推理。我的设计遵循三条原则Loop 实例独立。每个会话/窗口维护自己的历史消息列表和状态对象Harness 层通过会话 ID 做路由。状态快照定期落盘。每隔 N 步把当前上下文摘要、待执行步骤、已执行结果序列化保存。进程崩溃后可以恢复到最近的检查点继续跑而不是整条链重来。状态隔离不等于数据完全隔离。如果两个任务需要共享某个知识库查询结果图Graph层可以通过只读共享数据源的方式传递而不是直接共享 Loop 内的原始上下文。3.4 上下文窗口溢出与记忆策略长任务跑到后面最头痛的就是上下文爆掉。我的方案是分三级记忆短期上下文当前循环窗口内的消息。控制数量超过阈值就做一轮压缩。中期摘要每完成一个子目标把这段对话压缩成摘要摘要取代原始对话加入上下文。压缩时我会要求模型保留已完成的关键动作、未解决的关键问题、可复用的事实。长期记忆把需要跨会话保留的事实写入向量数据库通过检索按需拉取而不是全量塞进上下文。到这里你会发现记忆已经不只是 Loop 层的事它需要 Harness 层提供存储接口。这就是三层架构的有趣之处每一层有个自包含的职责但真正跑得稳靠的是层与层之间的契约。记忆接口就是 Loop 与 Harness 之间的契约之一。4. Graph 层从单 Agent 单循环到多 Agent 编排4.1 为什么循环本身不够还需要图单个 Loop 哪怕再稳定能处理的也只是线性任务。生产里真正的复杂任务长这样先判断用户意图再决定调用 NLP 服务还是订单服务还要同时查库存和物流最后汇总生成报告如果某个环节失败还要分支重试。这种拓扑没法用一个大循环写完。于是在 Loop 之上我们引入 Graph 层用节点和边描述整个任务流节点是 Agent、工具调用、人工确认点或简单的转换逻辑。边是数据流和依赖关系。图上还可以有条件分支、并行分支、循环回路。我常用的编排思路是Graph 管拓扑Loop 管单点深度。Graph 上一个查库存节点内部是 Loop 还是单次调用这是节点自己的事Graph 不关心。反过来Loop 里冒出的需要并行处理两个数据源这种请求应该上抛给 Graph 层由 Graph 来做真正的并行。4.2 有向无环图、条件边、并行与人机回环拿一个实际的运维故障诊断 Agent 举例Graph 可以设计成节点 1收集信息。输入服务器 IP汇聚日志、监控指标、工单上下文。节点 2分析原因。基于节点 1 的数据生成候选故障原因列表。这个节点内部是一个 Self-Refine 循环可能要跑好几轮。节点 3/4/5并行验证。三个子 Agent 分别验证 CPU 异常、磁盘异常、网络异常它们互不依赖可以同时跑。节点 6人机回环。把候选原因和验证证据发给运维人员确认等人工确认后再往下走。节点 7生成修复建议并执行。这里有几个 Graph 层常见的工程细节值得展开说。条件边。节点 2 如果判断无候选原因直接走节点 7 输出无法诊断建议人工介入而不是硬走验证分支。条件边的判定逻辑不能写在节点内部要作为图的一部分显式建模这样才能在监控图上看到走了哪条分支。并行节点之间的数据契约。节点 3/4/5 并行跑完后输出的结果格式必须统一否则节点 6 没法聚合。我一般在建模时给每条边定义 schema节点输入输出都按 schema 校验。人机回环。这是 Graph 层比单体循环强太多的地方。单循环里要插一个等人回应的操作非常别扭Graph 层可以直接挂一个人工确认节点这个节点执行到一半暂停等 Harness 层的交互接口收到人工反馈后再继续。4.3 商图视角把细粒度执行压成可监控的层面商图这个词最早是图论里的概念英文叫 quotient graph。它的核心思想很朴素把图中一些节点按某种等价关系合并成一个超级节点从而得到一个更粗粒度的图。在大图上商图可以保留整体结构、压缩细节。Agent 系统的监控就是一个非常适合用商图的场景。底层的 Graph 可能有几十个节点有 Agent 节点、工具节点、分支判断节点还有重试节点直接看监控面板会非常杂乱。我的做法是定义阶段标签把同属一个阶段比如信息收集、原因分析、方案执行的节点聚合为商图上的一个超级节点。监控面板默认展示商图看到的是三五个阶段框和它们之间的流转状态。某一阶段异常时点击进入原始图查看具体是哪个底层节点卡住。商图不是花架子。它解决的是真实的生产痛点一个几小时的长流程任务运维要能一眼看出它现在流到哪一步、卡在哪里如果只展示原始节点图信息密度太大人眼根本处理不过来。4.4 用 Graph Builder 建模的实际流程现在不少编排框架都带可视化 Graph Builder你可以用拖拽方式搭出节点和边再导出为结构化定义。我自己的建模步骤一般是五步列出所有步骤先不管并行把流程从头到尾写出来。标注依赖找出哪些步骤必须等前面某一步完成哪些可以同时进行。标出分支条件哪一步的结果会改变后续路径。标出人工介入点哪里必须由人确认或输入。定义节点间数据契约明确每个节点输出什么、下一个节点需要什么。把这五步做完Graph 的定义基本就成型了代码层面的实现只是机械的翻译工作。很多团队的 Agent 项目乱不是因为模型不给力而是步骤 1 和步骤 2 根本没人认真做直接跳到代码层写编排。5. 三层联动一次请求在生产环境中的完整旅程5.1 从入口到 Harness再到 Loop再到 Graph把三层拆开讲完之后最后合起来看一次真实请求是怎么跑的。用户发来一条消息假设是帮我看看服务器为什么这么慢。完整链路是这样的入口接入。HTTP/WebSocket 请求到达 Harness 暴露的接口Harness 先做身份认证和会话识别。Harness 加载上下文。Harness 从记忆接口取这个会话的长期记忆从 Skill 仓库加载相关技能组装出系统提示词和工具列表。进入 Loop。Harness 把组装好的上下文交给 Loop 层Loop 开始标准的思考-行动-观察循环。Loop 发现任务复杂。多轮对话后Loop 意识到需要并行查日志和查监控此时它把这些动作抽象成一个子任务列表上抛给 Graph 编排引擎。Graph 编排。Graph 层按既有拓扑创建多个子任务节点有些节点内部又各自创建了自己的 Loop 实例去执行深度探索。结果汇聚。子任务全部结束后Graph 层把结果按数据契约汇总交回最初的 Loop 实例。Loop 总结回答。最初的 Loop 基于汇聚结果生成最终回答返回用户。Harness 收尾。Harness 把这次的完整轨迹写入审计日志把值得沉淀的事实写入长期记忆。这个链路看下来你会发现每一层都只做好自己的事Harness 提供能力与边界Loop 提供思考与行动Graph 提供组织与调度。任何一个环节的生产故障都能快速定位到具体层级去排查。5.2 Graph 编排介入的时机判断有读者可能会问是不是所有请求都要走完完整链路我的答案是不要过度设计。简单任务比如把这段英文翻译成中文直接在 Loop 层两三步内完成就够了没必要建一张图。Graph 的价值在复杂任务里才显现出来。我一般用两条标准来判断是否要启用 Graph任务是否需要并行。只要出现同时查多个数据源再合并单体循环就很难优雅处理上 Graph。任务是否有分支和人工介入。只要路径会随中间结果改变或者中途需要人确认上 Graph 能让你用结构化的方式管理这些变化。实际上我在生产里会把两者结合起来默认请求先在 Loop 跑Loop 遇到复杂度超出阈值的情况时再触发 Graph 编排。相当于让 Graph 作为升级通道而不是让所有流量都先过一遍图。5.3 回滚、重试与观测三层分别负责什么最后用一张表总结三层在生产运维中分别扛哪类问题运维动作Harness 层Loop 层Graph 层版本回滚回退 Harness 二进制、配置、Skill 包回退循环策略参数回退编排定义任务重试保证工具调用可重放循环从最近 checkpoint 恢复单个节点重跑不整图重跑观测工具调用审计、资源消耗循环次数、token 消耗、终止原因商图状态、节点耗时、分支走向有个细节值得特别提醒Graph 层支持单节点重跑这看起来只是编排能力实际对生产成本影响极大。如果整条图重跑意味着之前所有子 Agent 调用的模型费用全部白费。能在错误节点精准重跑省下来的直接就是真金白银。5.4 不同实现生态下的统一分层模型聊实现的时候很多人会提到基于 Rust 写的轻量 Agent、挂载在 Obsidian 里的个人知识管理 Agent、或者主打端侧运行的 Agent 运行时。这些项目听上去形态各异但底层几乎都逃不出三层模型Rust 实现往往把 Harness 做得极小适合资源受限的边缘环境以 Obsidian 知识库为记忆源的 Agent本质是把长期记忆放到本地文件系统Harness 提供检索工具主打随时可用的轻量 Agent 运行时则是在端侧做了一个自适应的 Harness 层把模型接入、工具权限、会话管理全部收敛在里面。我个人的观点是技术栈可以百家争鸣但架构分层值得统一。统一分层带来的好处是团队沟通时可以拿层来对齐比如这个 bug 在 Loop 层、那个需求要改 Graph 的条件边一句话说清楚问题域比扒着源码争论半天高效得多。从最早那个 while 循环原型到今天维护几十个线上 Agent 服务我最大的体会是Agent 工程化并不神秘它就是把模型调用这个单一能力装进可控的运行时配上会收敛的循环再接上可编排的拓扑。三者各司其职系统的复杂度就压得住。最后分享一个我在新项目里执行的小习惯先画 Graph再定 Loop最后配 Harness。流程没理顺之前不写代码循环策略没定之前不接模型权限边界没画清楚之前不接工具。顺序反了后面的返工一定让代价翻倍。