
1. 为什么“能跑”的 Agent 和“稳定”的 Agent 是两回事做过 AI Agent 的人大概都有过这种体验Demo 阶段惊艳得不行一旦放到真实业务里跑上几天各种诡异问题就开始冒出来——同一个输入今天返回正常结果明天就卡在某个环节死循环工具调用偶尔成功偶尔超时重试之后状态全乱上下文越堆越长模型开始“忘记”最初的任务目标。这些问题不是模型能力不够而是Harness 工程没做到位。所谓 Harness直译是“挽具、约束装置”在 AI Agent 语境里它指的是包裹在模型外面那一整套调度、约束、容错、状态管理的工程层。模型是发动机Harness 是底盘、传动和刹车。发动机再强底盘散了车也跑不起来。很多人把注意力全放在提示词工程和模型选型上却忽略了 Harness 才是决定 Agent 能不能上生产的关键。这篇内容适合三类人看一是正在从零搭建 AI Agent、卡在“Demo 能跑但线上不稳”阶段的开发者二是已经有一套 Agent 框架、想系统性补强工程健壮性的工程师三是对 Agent 架构感兴趣、想理解“harness 和 agent 到底什么区别”的技术管理者。我会把 Harness 的核心机制拆开讲包括任务编排、状态管理、工具调用容错、上下文治理、可观测性这几个模块每个模块都会给出可落地的设计思路和实操要点尽量做到看完就能对照自己的项目改。需要先明确一个概念边界Agent 是“做什么”Harness 是“怎么保证做对、做完、做不坏”。Agent 定义了目标、可用工具和决策逻辑Harness 负责把这一整套东西稳定地驱动起来。两者是内容和载体的关系缺一不可。理解了这一点后面所有的机制设计就都有了落脚点。2. Harness 工程的整体架构与设计取舍2.1 核心分层把“决策”和“执行”彻底分开我在实际项目里踩过最大的一个坑就是早期把决策逻辑和执行逻辑揉在一起写。模型输出一段文本代码直接解析这段文本然后去调工具调完把结果拼回上下文再喂给模型。这种写法在单轮任务里没问题但一旦涉及多步任务、并行工具调用、失败重试代码就会迅速变成一团乱麻改一处崩三处。后来我把架构改成了明确的分层核心就一句话决策层只负责“想”执行层只负责“做”中间用结构化协议通信。具体分四层接入层接收外部请求做参数校验、鉴权、限流把请求转成内部统一的任务描述格式。编排层Harness 核心维护任务状态机决定下一步调用哪个工具、是否需要重试、是否要压缩上下文、何时终止任务。执行层真正去调用工具、访问外部服务、读写数据把结果标准化后回传编排层。观测层贯穿全流程的日志、指标、链路追踪用于事后排查和实时监控。这么分的好处是每一层都可以独立测试和替换。比如你想换一个模型只动编排层里的决策模块想加一个新工具只动执行层的注册表。层与层之间通过明确定义的数据结构通信而不是靠字符串拼接这就从根上避免了“模型输出格式一变整条链路就崩”的问题。2.2 状态机驱动为什么不用 while 循环很多简易 Agent 的实现就是一个while not done循环模型说不继续了就退出。这种写法的问题在于没有明确的状态定义就无法做精确的容错和恢复。任务跑到一半进程挂了重启之后完全不知道之前做到哪了某个工具连续失败三次循环里没有计数器只能靠模型自己“意识到”要放弃而模型往往意识不到。我的做法是用显式状态机来驱动整个任务流程。一个典型任务会经历这些状态INIT初始化→PLANNING规划→EXECUTING执行工具→OBSERVING处理结果→REFLECTING反思是否继续→DONE或FAILED。每个状态之间的转移条件都写死在代码里而不是交给模型自由发挥。这样做最直接的好处是可恢复。每个状态转移时都把当前状态和关键上下文持久化到存储里Redis 或数据库都行进程重启后从最后一个稳定状态继续而不是从头再来。对于长任务来说这个能力是刚需。另一个好处是可观测你能清楚地知道任务卡在哪个状态、停留了多久排查问题时不用靠猜。2.3 幂等设计让重试变得安全工具调用失败要重试这是常识。但重试有个前提这个操作必须是幂等的否则重试一次就多扣一次钱、多发一条消息、多写一条脏数据。Harness 工程里必须把幂等当成一等公民来对待。具体做法是给每个工具调用生成一个唯一的调用 ID可以用任务 ID 步骤序号 参数哈希生成执行层在真正执行前先查一下这个 ID 是否已经执行过。如果执行过直接返回上次的结果不再重复执行。这个“执行记录”可以放在 Redis 里设置一个合理的过期时间。对于本身就不幂等的外部操作比如发消息、下单要在工具定义层面就标注出来编排层遇到这类工具时重试策略要更保守——比如先查询状态确认是否真的失败了再决定要不要重试而不是无脑重试。这个细节看起来小但在真实业务里能避免大量事故。3. 核心机制拆解让 Agent 稳下来的五个关键点3.1 工具调用容错超时、重试与熔断的组合拳工具调用是 Agent 最容易出问题的环节。网络抖动、下游服务限流、参数格式错误任何一种都能让任务中断。我一般会给每个工具配置三样东西超时时间、重试策略、熔断阈值。超时时间要分层设置。比如一个查询类工具我通常设 5 秒超时一个生成类工具调用大模型设 30 到 60 秒。超时时间不能拍脑袋定要看这个工具 P99 的响应时间在这个基础上留 50% 的余量。设太短会误杀正常请求设太长会把整个任务拖死。重试策略我用的是指数退避 抖动。第一次失败等 1 秒第二次等 2 秒第三次等 4 秒同时每次等待时间加上一个随机抖动比如 ±30%避免多个任务同时重试造成雪崩。重试次数一般不超过 3 次超过就判定为失败交给上层处理。熔断是防止“一个坏工具拖垮整个系统”的关键。当某个工具在统计窗口内比如 1 分钟失败率超过阈值比如 50%就暂时把它熔断后续调用直接快速失败不再真正发起请求。等过一段时间比如 30 秒再放一个请求过去试探成功了就恢复。这套机制在微服务里很成熟搬到 Agent 的工具调用上同样适用。# 工具调用容错的简化示意 class ToolInvoker: def invoke(self, tool_name, params, call_id): # 幂等检查 if self.cache.exists(call_id): return self.cache.get(call_id) # 熔断检查 if self.circuit_breaker.is_open(tool_name): raise ToolCircuitOpenError(tool_name) for attempt in range(MAX_RETRIES): try: result self._do_invoke(tool_name, params, timeoutself._get_timeout(tool_name)) self.cache.set(call_id, result, ttl3600) self.circuit_breaker.record_success(tool_name) return result except TimeoutError: self.circuit_breaker.record_failure(tool_name) if attempt MAX_RETRIES - 1: raise time.sleep(self._backoff(attempt))3.2 上下文治理别让模型“撑死”在历史里多步任务跑下来上下文会越来越长这是必然的。工具返回的结果、模型的思考过程、中间状态全堆在上下文里很快就会触到模型的窗口上限。更麻烦的是上下文越长模型对关键信息的注意力越容易被稀释开始“跑偏”。我的上下文治理策略分三层。第一层是裁剪工具返回的结果如果很长只保留和当前任务相关的部分其余截断。比如一个搜索工具返回了 20 条结果我只把前 5 条塞进上下文剩下的存到外部存储需要时再按 ID 取。第二层是摘要当对话轮次超过一定数量比如 10 轮把之前的交互压缩成一段摘要保留关键决策和结论丢掉过程细节。第三层是结构化记忆把任务的关键信息目标、已完成步骤、待办事项、重要中间结果单独存成一个结构化的“任务状态对象”每轮都把它放在上下文的固定位置而不是散落在历史对话里。这三层配合下来上下文长度能控制在合理范围内同时关键信息不会丢。实测下来一个原本跑十几轮就开始胡言乱语的任务治理之后能稳定跑三四十轮。3.3 循环检测与终止条件防止 Agent “鬼打墙”Agent 陷入死循环是特别常见的问题。表现是模型反复调用同一个工具、反复输出相似的内容、或者在一个步骤上无限重试。如果不加干预任务会一直跑下去烧钱又烧时间。我的做法是设置多重终止条件任何一个触发就强制结束或转人工。第一重是步数上限比如一个任务最多 50 步超过就终止。第二重是重复检测记录最近 N 步的工具调用签名工具名 参数哈希如果发现连续几步签名高度相似判定为循环强制中断。第三重是无进展检测如果连续几步任务状态对象没有实质变化没有新增已完成步骤、没有新的关键信息也判定为卡住。触发终止后不是简单报错而是把当前状态、已完成的部分、卡住的位置整理成一份报告要么返回给用户要么转人工处理。这样至少用户知道发生了什么而不是干等一个永远不返回的请求。3.4 可观测性出问题时你能看到什么Agent 出问题是常态关键是出问题之后你能不能快速定位。我见过太多项目Agent 跑挂了之后日志里只有一句“任务失败”完全不知道失败在哪一步、当时上下文是什么、工具返回了什么。这种项目排查问题全靠猜效率极低。可观测性我一般做三件事。第一是结构化日志每一步的状态转移、工具调用、模型输入输出都打成结构化日志JSON 格式带上任务 ID、步骤 ID、时间戳。这样可以用日志系统直接检索和聚合。第二是链路追踪给每个任务生成一个 trace ID贯穿所有环节任何一个环节出问题都能顺着 trace 把整条链路串起来看。第三是关键指标监控任务成功率、平均步数、工具调用失败率、平均耗时、Token 消耗这些指标做成看板异常时告警。有了这三样排查问题的效率能提升一个数量级。以前可能要复现半天现在看一眼 trace 就知道卡在哪。3.5 权限与安全边界让 Agent 知道“什么不能做”Agent 能调工具就意味着它能对真实世界产生副作用。发消息、改数据、调接口这些操作一旦失控后果可能很严重。所以 Harness 必须有一层权限控制。我的做法是给每个工具定义明确的权限等级和影响范围。只读类工具查询、搜索权限最低可以直接调用写入类工具创建、更新需要额外的确认机制比如在沙箱环境先试跑或者要求人工确认高危类工具删除、支付、对外发送必须走审批流程不能由 Agent 自主决定。另外所有工具的参数都要做校验和清洗防止模型生成的参数里带有注入类的内容。工具返回的结果也要做脱敏避免敏感信息进入上下文后被模型无意中泄露。这些安全措施看起来是额外成本但比起出事之后的代价这点成本完全值得。4. 从零搭建一个稳定 Harness 的实操路径4.1 第一步定义任务协议和状态模型动手写代码之前先把任务协议定下来。任务协议描述了一个任务从创建到结束的完整生命周期包括任务的数据结构、状态枚举、状态转移规则。这一步看起来是设计工作但它直接决定了后面代码的组织方式值得花时间。我的任务数据结构一般包含这些字段任务 ID、任务类型、原始输入、当前状态、已完成步骤列表、待办步骤列表、关键上下文结构化、创建时间、更新时间、重试计数、错误信息。状态枚举就是前面说的那几个INIT、PLANNING、EXECUTING、OBSERVING、REFLECTING、DONE、FAILED。状态转移规则要写清楚从 INIT 只能到 PLANNING从 PLANNING 可以到 EXECUTING 或 DONE如果不需要执行从 EXECUTING 到 OBSERVING 或 FAILED以此类推。把这些规则用代码固化下来任何非法转移直接抛异常这样能从根上避免状态错乱。4.2 第二步实现编排引擎的主循环编排引擎是 Harness 的心脏它的主循环大致是这样读取当前任务状态 → 根据状态决定下一步动作 → 执行动作 → 更新状态 → 持久化 → 循环直到进入终态。这里有个关键设计点每一步动作都要是“可重入”的。也就是说如果进程在两步之间挂了重启后能从持久化的状态继续而不是从头开始。这就要求每一步动作执行前先把“即将执行什么”记录下来执行完再更新状态。这样即使执行到一半挂了重启后也能知道当时在做什么决定是重做还是跳过。主循环里还要处理并发。如果同时有多个任务在跑不能让它们互相干扰。我的做法是每个任务独立一个执行上下文共享的工具调用层做好并发控制比如连接池、限流器任务之间通过任务 ID 隔离。4.3 第三步接入工具并配置容错参数工具接入我建议用注册表模式。每个工具定义成一个对象包含工具名、描述、参数 schema、执行函数、超时时间、重试策略、权限等级。启动时把所有工具注册到一个注册表里编排层通过工具名查找和调用。参数 schema 用 JSON Schema 来定义这样既能给模型看用于生成正确的参数也能在执行前做校验。模型生成的参数先过一遍 schema 校验不合法直接打回让它重新生成而不是带着错误参数去调工具。容错参数要针对每个工具单独配置不能一刀切。查询类工具超时短、重试多写入类工具超时长、重试少甚至不重试调用大模型的工具超时要给足。这些参数最好做成配置项方便调整而不用改代码。4.4 第四步加上上下文管理和循环检测上下文管理模块负责在每轮交互前把上下文整理成合适的形态喂给模型。具体做三件事裁剪过长的工具返回结果、对历史对话做摘要、把结构化任务状态放到固定位置。循环检测模块在每步执行后运行检查是否触发了终止条件。我一般维护一个滑动窗口记录最近 5 步的工具调用签名和任务状态快照检测到重复或无进展就触发终止。这两个模块都是“横切关注点”不改变主流程的逻辑但显著提升稳定性。实现上可以做成独立的组件在主循环的合适位置调用。4.5 第五步搭建观测和告警最后一步是把观测体系搭起来。结构化日志用现成的日志库就能做关键是字段要统一、要带 trace ID。链路追踪可以用 OpenTelemetry 这类标准方案也可以自己用 trace ID 串。指标监控用 Prometheus Grafana 是常见组合把任务成功率、失败率、平均步数、Token 消耗这些指标暴露出去。告警规则我一般设这几条任务失败率超过 10% 告警、平均步数突增告警、Token 消耗突增告警、工具熔断触发告警。告警不是越多越好太多会麻木这几条覆盖了主要的异常场景。5. 实战中踩过的坑与排查速查表5.1 那些文档里不会写的教训坑一模型输出格式不稳定解析代码天天崩。早期我直接用正则解析模型输出模型稍微换个说法就解析失败。后来改成让模型输出 JSON并且用 JSON Schema 约束同时在解析失败时做一次“修复重试”把错误信息喂回模型让它重新输出。这个改动让解析成功率从 80% 多提升到 99% 以上。坑二重试把下游打挂了。有一次某个工具的下游服务不稳定Agent 疯狂重试结果把下游彻底打挂连带影响了其他业务。后来加了熔断和重试限流单个工具的重试并发数做了限制才解决这个问题。教训是重试必须有限流保护否则重试本身就是故障源。坑三上下文里的“脏数据”导致模型跑偏。有一次工具返回的结果里带了一段无关的广告文本模型居然把这段文本当成了任务目标开始执行完全不相关的操作。后来在工具返回结果进入上下文之前加了一层清洗去掉无关内容问题才消失。永远不要假设工具返回的内容是干净的。坑四状态持久化没做重启全丢。早期版本状态只存在内存里服务一重启所有进行中的任务全丢用户那边看到的就是请求永远不返回。加上持久化之后重启能恢复用户体验好了很多。长任务的状态必须持久化这是底线。5.2 常见问题速查表问题现象可能原因排查方向解决思路任务卡住不返回工具调用超时未处理查工具调用日志看是否有超时加超时和重试设置任务级超时模型反复调用同一工具循环检测缺失看最近几步的工具调用签名加重复检测触发后强制终止上下文超长报错未做上下文治理看每轮上下文长度加裁剪、摘要、结构化状态重试导致数据重复操作不幂等查是否有重复写入加调用 ID 做幂等控制任务成功率忽高忽低下游服务不稳定看工具失败率指标加熔断和限流隔离故障模型输出解析失败输出格式不稳定看解析失败的原始输出用 JSON Schema 约束加修复重试排查问题无从下手可观测性缺失看日志和 trace 是否完整补结构化日志和链路追踪5.3 几个提升稳定性的小技巧技巧一给模型“思考”和“行动”分开。让模型先输出一段思考不执行任何操作再输出具体的工具调用。这样模型有更多机会“想清楚再动手”减少冲动调用。实现上就是在提示词里明确要求分两段输出解析时分别处理。技巧二关键步骤加“确认点”。对于影响较大的操作在执行前让模型再确认一次比如“你确定要执行删除操作吗”模型确认后再执行。这能过滤掉一部分误操作。技巧三用“影子模式”验证新工具。新接入的工具先跑影子模式也就是真正调用但不产生实际副作用或者写到测试环境观察一段时间没问题再正式启用。这个做法能避免新工具上线带来的事故。技巧四定期回放失败案例。把失败的任务日志收集起来定期回放分析找出共性问题。很多稳定性问题都是反复出现的回放能帮你发现规律从根上解决。6. 关于 Harness 工程的一点个人体会做 AI Agent 这几年我越来越觉得 Harness 工程的价值被低估了。大家热衷于讨论模型能力、提示词技巧、Agent 架构但真正决定一个 Agent 能不能上生产的往往是这些“不性感”的工程细节——超时设多少、重试怎么做、状态怎么存、上下文怎么管。我个人的经验是一个 Agent 项目 80% 的稳定性问题都能在 Harness 层解决而不是靠换更强的模型。模型再强遇到网络抖动、下游故障、上下文超长这些工程问题照样会挂。反过来Harness 做扎实了哪怕用一个中等能力的模型也能跑出稳定的效果。如果让我给正在做 Agent 的朋友一个建议那就是先把 Harness 的骨架搭好再往里填模型和工具。骨架稳了后面加什么都不会散。骨架不稳加什么都是往沙子上盖楼。这个顺序不能反。另外Harness 工程没有一劳永逸的方案它是个持续迭代的过程。每遇到一个新问题就补一个机制进去慢慢就长成了一个健壮的系统。别指望一开始就设计完美先跑起来再根据实际问题逐步加固这是更务实的路径。