
写在前面为什么“能跑”和“扛得住”是两码事聊到 Code Agent 的 Harness 设计前面几篇我们分别拆过上下文管理、工具调用编排和状态机流转。今天这篇是系列的第四篇主题是容错与恢复。为什么要把这个单独拎出来写因为我见过太多 demo 阶段惊艳全场、一上生产就原形毕露的 Agent 项目。问题几乎都出在同一个地方只考虑了“正常路径”没考虑“世界会以各种姿势出bug”。在聊容错之前先明确一个概念这里的 Harness指的是包在 Agent 核心推理循环外面的那一层“脚手架”。它不是模型本身而是负责把模型接进真实环境的那套基础设施。比如你用什么方式管理上下文、怎么调用工具、怎么解析输出、怎么把一个任务的中间状态保存下来。容错与恢复就是这套基础设施里负责“事情搞砸了怎么办”的机制。真实的业务场景里大模型 API 会超时、第三方工具会有随机限流、用户会在一句话里塞五个互相矛盾的需求、上下文中途会被截断。任何一个问题处理不好整个 Agent 就白干了。这篇文章我会结合自己做过的几个项目把容错与恢复这件事掰开揉碎讲清楚每一层该做什么、不该做什么以及踩过哪些坑。1. 容错设计的基本盘先搞清楚“错”从哪里来1.1 故障源全景不止“模型抽风”这一种很多人一想到 Agent 出问题第一反应就是“模型答错了”。其实模型答错只是故障冰山的一角。我在实际项目里总结过故障来源大致分四类第一类是基础设施故障。网络闪断、API 限流、服务端 5xx、本地磁盘满了导致缓存写不进去。这类故障的特点是你完全控制不了只能通过超时、重试、退避策略去缓解。第二类是工具链故障。比如 Agent 调了个内部 API结果参数格式不对、鉴权过期、下游服务返回了非 JSON 的结构。更常见的是某些外部服务只管限流不管体验一分钟内调用超过 10 次就直接把你封了。第三类是解析故障。这一步特别容易被新手忽略——模型输出内容本身可能完全正确但格式不对。你要求它输出 JSON它偏偏前后都包了 markdown 代码块你要求它调用某个工具它把参数名写成了近似值。所有依赖模型输出严格格式的 Harness都可能在这一步挂掉。第四类是逻辑故障。模型“自信地”选错了工具、算错了参数、对一个明显超出自身能力的问题强行给出了结果。这种最棘手因为 API 没报错、格式也合规但结果就是不对。四类故障的处理策略完全不同。基础设施故障靠退避重试工具链故障靠参数校验和降级方案解析故障靠容错解析器和规则约束逻辑故障靠的是后面要讲的“置信度校验 人工介入兜底”。如果不对故障进行分类就容易用一种方案去处理所有问题结果什么都没处理好。1.2 容错的三个层次与其对应的设计目标根据故障发生在哪个环节容错设计可以分三个层次每一层目标不一样交互层容错Agent 和外部世界打交道时发生的错误。目标是把“临时性故障”和“永久性故障”尽可能区分开临时故障就重试永久故障就放弃并向上反馈。推理层容错模型在生成过程/输出格式中出问题。目标是让 Harness 能“矫正”模型输出而不是直接让整个任务失败。任务层容错整个多步任务执行到一半失败了。目标是能不能从断点恢复而不是从头再来。这个分层视角特别重要。如果你把“模型 API 超时”和“模型生成了非法 JSON”都当成“异常”用同一套 try-catch 处理那你的 Harness 天花板就很低。实际设计时这三层应该分别维护各自的错误类型、处理策略和恢复机制。2. Harness 容错的核心机制设计2.1 超时控制所有容错的“第一道闸门”绝大多数线上故障的起点都是“某个环节卡住了”。模型 API 没有返回、外部工具 socket 挂起、本地代码执行死循环。如果没有超时控制整个 Agent 会无限期阻塞不仅拖垮当前任务还会积压后续请求最后把整个系统搞崩。不要只给 API 调用设超时要给“每一个可能阻塞的环节”都设超时。我在一个体验过线上事故的项目里吃过这个亏只给模型 API 设了 60 秒超时没给工具调用设结果工具请求第三方接口时因为网络问题一直挂着一个任务 20 分钟没结束连取消都做不到。我后来定的这套超时体系基本没再出大问题环节超时值超时后的处理模型调用首 token 延迟30s重试1次换备用模型模型完整响应5-10分钟视任务复杂度记录超时点任务转入断点恢复工具调用20s重试2次指数退避后标记失败本地代码执行依据用户提示判断默认60s强制终止进程并返回 stderr循环总时长30分钟强制终止保存上下文供复盘超时值最忌讳拍脑袋。建议你把这四个数据都记下来API 响应时间 P95、工具调用耗时分布、历史最大 token 生成耗时、排队等待时间。然后按“P95 的两倍再加一点缓冲”来设置。宁可超时后重试几次也比卡住不恢复好——用户能接受慢但接受不了没反应。重试也不是无脑重试。指数退避是默认规则first_retry 是 1s后面翻倍1s、2s、4s、8s……上限控制在 10 次以内。同时要加 jitter抖动把实际等待时间在理论值上随机偏移 ±20%。因为如果同一时刻大批请求都在等同一个故障服务大家退避节奏一模一样恢复后又会同时飞出去把服务再打挂一次。这就叫重试风暴。所有做分布式系统的人都知道这个坑做 Agent 的 Harness 也要懂。2.2 错误分类与重试策略别把“救不活的”反复救我见过有的 Harness 把所有异常一视同仁一律重试三次。这带来的问题是一个因为“参数类型错误”导致必定失败的调用被反复重试了三次白白等了好几分钟才进入失败分支。而一个因为“API 限流”的调用可能在第三次重试时恰好限流解除能成功。所以错误分类要非常细致。我的做法是给每个错误打上标签retryabletrue链路超时、5xx、限流429、网络抖动。这些重试能救活。retryablefalse参数校验失败、鉴权 401/403、资源不存在 404、JSON 解析失败。这些重试多少次都一样。degradabletrue说明有替代方案可以绕过。比如主模型挂了可以用备用模型顶上主工具挂了可以走 fallback 接口。这里有一个特别容易忽略的教训429 限流也分“可恢复”和“不可恢复”。很多 API 返回 429 时会带一个Retry-After头告诉你要等多久。如果你不管这个字段直接套用默认的指数退避去重试很可能在服务器要求的时间之前就冲过去了结果第二次 429白等。正确做法是优先读取Retry-After如果大于你的最大容忍时间就不要重试了直接降级或放弃。有人可能在纠结 429 到底是 HTTP 层的错误还是工具层的错误。这不是重点重点是你的设计里必须有这么一层“错误分类器”并且能被调用链里任何一个环节复用。2.3 状态快照与原子化恢复让任务“从断点继续”多步 Agent 任务最怕的是做到第 5 步时挂掉结果第 1 步到第 4 步的结果全丢了必须从头来。这不仅浪费钱每次调用都烧 token还拖垮了用户体验。我的做法是把“用户会话状态”和“任务执行中间态”分开保存。用户会话状态包括用户原始输入、历史上下文、偏好设置任务执行中间态包括已经完成的工具调用及其结果、当前所处步骤、已生成的中间产物。每一轮 Agent 执行完毕后就把这些内容序列化成一个 JSON 快照存入 Redis或任意 KV 存储key 是agent_session:{session_id}TTL 设成 24 小时。快照里还要记录一个heartbeat字段用于后续展示进度。恢复流程是这样的Agent 进程收到新的用户输入。先查 Redis 中是否有未完成的任务快照。如果有把快照反序列化重建上下文管理器。判断快照中记录的最后状态例如“正在执行工具A”还是“等待模型决定下一步”。根据状态决定是恢复执行工具 A 还是跳过直接进入模型推理。这里要特别留心一个“副作用一致性”问题。如果第 5 步是“向用户邮箱发送了一封邮件”结果发送成功了但还没来得及保存快照进程就崩了恢复时如果重发一遍用户就会收到两封邮件。解决办法是给每个工具调用生成一个全局唯一的tool_call_id在工具调用前先写入“调用记录”工具执行成功后再更新“调用结果”。恢复时看到调用记录里没有结果就先查一下这是不是个幂等操作可以安全重放或者直接标记为“结果未知”让人工介入确认。这就是为什么说要给工具定义“副作用等级”只读操作可以放心重放写操作最好做成幂等的不可逆操作比如“删除服务器”绝对不能自动重放。2.4 上下文窗口溢出的“软恢复”比直接截断优雅得多上下文溢出是 Agent 特有的“内存不够”问题也可以看作一种特殊故障。很多人的第一反应是“直接丢掉最早的对话”但代价常常是模型失去了前置信息逻辑开始错乱。更糟的是早期对话里可能还藏着用户明确表达过的工作偏好丢了就再也回不来了。我建议用“分层摘要”机制去缓解。当上下文占用接近阈值比如达到上限的 75%时Harness 触发后台任务把最早的一部分消息用模型压缩成几百字的摘要保留关键信息用户的原始目标、已经确认的约束、涉及的关键实体丢弃细节对话。摘要生成后替换掉原始消息把上下文腾出来。要注意的是摘要过程本身也会消耗 token而且如果设计不好摘要生成的耗时会让用户等待很久。我的经验是把摘要任务设计成异步的不阻塞主流程同时处理完之后要人工做一次“摘要质量抽检”否则模型可能会在摘要里丢掉关键信息后面任务就越跑越偏。顺带一提摘要对比较早的环节尤其重要越早越容易丢细节。如果上下文已经满了怎么办这时 Harness 要发出一个状态通知给用户而不是静默截断。比如“检测到上下文已满系统自动精简了之前的对话某些细节可能丢失。你可以补充这些关键信息……”。这样既避免了硬截断也让用户有了重新明确需求的入口。3. 实操把容错逻辑落地到 Harness 代码3.1 一个轻量级的错误分类器和重试器这一节我直接给一套可用的设计思路基于 TypeScript 风格的伪代码重点看结构不要纠结具体实现。错误分类器的核心是维护一个“错误 → 策略”的映射表。我用的是装饰器模式给每个工具函数标上元数据这个工具可不可以重试、有没有降级方案、副作用等级是什么。然后在一个统一的execute_with_resilience函数里包一层。type ErrorPolicy { retryable: boolean; maxRetries: number; baseDelayMs: number; degradable: boolean; fallback?: () Promiseany; }; const policyRegistry new Mapstring, ErrorPolicy(); function registerToolPolicy(toolName: string, policy: ErrorPolicy) { policyRegistry.set(toolName, policy); } // 统一执行入口所有的工具调用都走这里 async function executeWithResilience(toolName: string, args: any) { const policy policyRegistry.get(toolName) ?? { retryable: true, maxRetries: 2, baseDelayMs: 1000, degradable: false }; let attempts 0; let lastError: any; while (attempts policy.maxRetries) { try { // 真正调用工具 return await toolExecutor.execute(toolName, args); } catch (e) { lastError e; const retryable isRetryableError(e) policy.retryable; if (!retryable || attempts policy.maxRetries) { // 不可重试或已重试耗尽判断能否降级 if (policy.degradable policy.fallback) { return await policy.fallback(); } throw lastError; } // 指数退避 jitter const delayMs policy.baseDelayMs * Math.pow(2, attempts) randomJitter(); await sleep(delayMs); attempts; } } } function isRetryableError(e: any): boolean { // 5xx / 429 / 网络层 ECONNRESET / ETIMEDOUT 都返回 true // 4xx非429、解析类错误返回 false const retryableStatus [408, 425, 429, 500, 502, 503, 504]; if (e?.status retryableStatus.includes(e.status)) return true; if (e?.code ECONNRESET || e?.code ETIMEDOUT) return true; return false; }写这段代码的核心意图是把“能不能重试”这个决策从业务逻辑里剥离出来。业务代码只需要关心“这一步该调什么工具”容错策略统一由 Harness 层接管。这样新增工具时只需要注册策略不需要改调用链。3.2 断点续跑机制的实现要点用事件溯源保存“执行到哪一步”快照恢复听上去简单但真正落地时最麻烦的是“记录哪些数据”。只存一个“进度百分比”没有任何价值只存对话历史也没法恢复工具执行状态。我采用的是“事件溯源”的思路把整个 Agent 执行过程看成一系列不可变的事件流Harness 只需要 append不需要修改历史。事件流里的事件类型包括user_message_received: 记录用户 ID、输入内容、时间戳。context_snapshot_taken: 记录当前 token 数、消息摘要、关键状态。tool_call_started: 记录tool_call_id、工具名、参数、开始时间。tool_call_succeeded: 记录工具名、输出摘要或全文、耗时。tool_call_failed: 记录失败原因、错误类型。model_inference: 记录模型请求参数、输出内容、token 使用情况。task_finished: 记录最终输出结果。所有事件追加写入一个事件表Postgres 或 MongoDB 都能胜任。快照只是“某个时间点所有事件的累计视图”。恢复时只需要扫描事件表找到最后一个成功的事件从那个事件的下一个动作开始尝试续跑。这个设计最爽的地方是天然具备审计能力。即使不需要容错这套事件流日志也可以用来调试 prompt、分析模型行为、统计工具成功率。有一点要提醒事件流日志在数据量大了以后会非常占空间。我的策略是保留最近 2 天全量事件超过 2 天的聚合为“任务级摘要”只保存最终结果和头部几个关键事件。这样不至于事后复盘时发现数据被清得干干净净。3.3 从零搭建 Harness 恢复流程的四个步骤第一步定义状态结构。确定存储模型——用 JSON blob 还是事件表状态里放哪些字段。建议至少包含session_id、user_id、task_context、tool_logs、last_event_type、created_at、updated_at。第二步实现快照的原子写入。每次状态变更必须在一个事务里完成“写事件流 更新状态视图”。要么都成功要么都失败。避免出现事件记录和状态不一致的情况。第三步实现恢复入口。在 Agent 启动和用户回复触发时检查有没有进行中的任务。有就提示用户“检测到一个未完成的任务是否继续”由用户确认。不要自作主张地恢复——有些场景用户就是不想继续了。第四步定期做“故障演练”。具体做法是用一个随机数生成器在任务执行过程中随机中断进程然后检查恢复流程能不能接上。这个测试要作为 CI 的一部分每个新版本发布前必须跑一遍。不演练的容错机制等于没有容错机制。4. 上下文损坏、并发冲突与恢复后的“认知断层”4.1 上下文不一致比崩溃更隐蔽的故障快照恢复有一个很隐蔽的坑上下文不一致。假设用户在执行第 3 步时临时改了主意说“算了不查数据库了换个 API 吧”但第 3 步的工具结果已经写进上下文了。恢复时如果你直接拿旧快照重建上下文模型会发现上下文里有个工具结果但用户的最新指令已经否定了它。这时候模型的行为不可预测——它可能忽略用户最新指令继续顺着旧上下文走。我的解决办法是恢复时额外注入一条系统消息明确指出“以下是历史上下文快照其中有部分内容可能已被最新指令覆盖请以用户最新指令为准。”这条消息虽小但能显著降低模型跑偏的概率。与之类似的问题还有“时间混乱”。如果快照是昨天的今天恢复时模型不知道“现在”是几点可能导致它对时效性任务的判断出错。2024 年后的大模型普遍支持在系统提示里注入当前时间恢复时务必把“当前时间 快照生成时间”同时注入让模型自己判断哪些信息过期了。4.2 并发控制同一会话的“单飞模式”用户不会因为你实现了断点续跑就只用一个设备。他可能手机开一个会话电脑又开一个会话。如果两个会话同时触发同一个 session_id 的恢复事件流就乱了一个进程恢复了状态开始跑另一个进程也恢复了同一个状态开始跑后面的写入互相覆盖。这是分布式系统里经典的“共享状态竞争”问题。我的方案很朴素对同一个 session_id 加分布式锁Redis SETNX 过期时间。恢复任务开始前先抢锁抢不到就返回“该任务正在另一个会话中执行”。锁的过期时间要设成比任务最大耗时长一点点以防进程崩溃后锁不释放其他会话永远进不来。这个“一点点”通常设成任务超时值的 1.5 倍。记得在 finally 块里释放锁。还可以考虑版本号机制每次快照更新时自增version恢复时带上 version 参数写入时校验当前 version 是否变化。如果变了说明有并发写入冲突直接中止并通知用户。但实际工作中加锁已经够用了版本号机制适合更复杂的协作场景。4.3 静默降级 vs 显式降级如何向用户交代容错机制经常导致一个“看不见的副作用”Harness 为了能完成任务悄悄用了一个替代方案但用户并不知道。比如主向量数据库挂了Harness 自动切到了备用库结果搜索结果质量下降模型给出的答案开始变得离谱。用户不知道原因只感觉“怎么突然智商变低了”。这是特别常见的“静默降级”陷阱。我后来定了一条规则凡是发生了“非用户指令触发的方案切换”都必须生成一条显式提示。可选的范围很小但至少要让用户感知到异常。比如在回复里加一句“由于主数据源暂时不可用当前回答基于备用数据源部分内容可能不完整。”显式降级还有一层好处用户可能会因为反馈“那就算了不查了”你就能提前终止任务省下时间。如果一直闷头降级到完成用户看到的可能是一个很不可靠的结果信任感会大打折扣。5. 纵深防御把“最后一次重试”也不能出的问题也兜住5.1 全局熔断器防止“重试”把系统打成雪崩前面讲了单次工具调用的重试策略但如果整个下游服务都在挂重试并不能解决问题。你需要一个“熔断器”在连续失败达到阈值时快速放行到降级路径而不是继续重试。我用的是标准熔断器三态模型关闭态正常所有请求正常放行统计失败率。打开态故障短路连续失败次数 阈值比如 5 次或失败率 50%近 10 次窗口后续请求直接走降级逻辑不真正发起调用。半开态试探恢复熔断打开后等待冷却时间比如 30s允许 1-3 个请求通过成功率达到阈值熔断关闭否则继续保持打开。这个模式在微服务架构里已经非常成熟但在 Agent 场景里容易被忽略。原因很简单Agent 的一次调用会触发很多工具开发者只盯着单个工具的重试没有全局视角。结果是每个工具都重试了 3 次下游服务被打了 10 倍的流量最终把自己打挂。熔断器还要结合 HTTP 的Retry-After做“节流”处理。如果熔断器打开时降级方案也不可用那 Harness 应该停止自动重试直接进入人工上报流程。这个阈值点要小不要贪。5.2 “逃生舱”设计人工接管应成为第一优先无论 Harness 做得再好总会有机器判断不了的情况。这时候人工接管不是“兜底”而应该是一种被积极设计的“逃生舱”。具体来说有三种做法阈值触发人工接管当同一任务连续失败超过 3 次或执行时间超过预算的 150%Harness 自动暂停并发送通知给用户附带当前执行摘要、已尝试的 方案列表、可能的原因判断。用户可以选择“调整后继续”“停在这里”“换一种方式重新开始”。置信度过低触发人工确认当 Harness 对模型输出结果信心不足时比如解析置信度 0.6在交付最终答案前请求用户确认。这对“删除操作”“发送邮件”“下单”这类不可逆动作尤其重要。完全失败时的人工工单如果任务彻底失败Harness 要生成一个“失败报告”包含事件时间线、关键错误栈、重试历史、上下文快照位置。用户或开发者可以直接拿着这个报告去调试而不是对着一个泛泛的“出错了”干瞪眼。人工接管机制听起来简单但它改变的是 Agent 的整体设计理念不是“尽最大可能做一个全自动的 Agent”而是“让 Agent 知道自己的能力边界在边界处优雅地放手”。这个思维转变是我做容错设计收获最大的一点。5.3 可观测性没有日志的容错就是盲人摸象在所有容错机制之上还必须有一层可观测性。没有完整的日志和监控就算实现了重试、快照、熔断出了问题你也不知道具体哪一环出错了。我建议的监控指标分三层第一层是“接口层指标”包括重试次数、重试成功率、熔断器状态变化次数、平均恢复时间。这些指标能在长时间维度上告诉你容错机制本身是否健康。第二层是“任务层指标”包括任务总成功率、平均任务时长、上下文溢出触发率、人工接管触发率。任务成功率下降时第一反应去看上下文溢出触发率是否升高——两者常常强相关。第三层是“执行层追踪”给每个任务分配一个trace_id贯穿所有事件流。无论出现什么故障都能按trace_id查询到完整的事件链。我见过太多项目日志里每条消息孤零零的没法串联排查问题要开十来个窗口手动对时间戳。这事最好从第一天就做起。后补日志和追踪的成本是前期的十倍不止而且往往效果还是打折的。6. 我踩过的坑和值得借鉴的经验6.1 经验一不要把“重试成本”和“恢复成本”混为一谈有一个项目我给所有工具都配了 maxRetries5。看起来是增强了容错性但实际效果是一个参数写错的调用白白重试了 5 次每次间隔指数增长加起来拖了整整 3 分多钟用户早就关了页面。后来我做了两个改动第一非重试类错误4xx、参数问题、解析失败直接零重试第二为重试增加“预算”——单个任务的所有重试时间加起来不能超过 30 秒。超过预算直接进入降级或人工接管流程。这个成本预算的思路比单纯限制重试次数更实用。因为不同任务需要的重试次数不一样限死次数会导致一部分任务过度重试、另一部分任务不够重试。6.2 经验二使用“任务编排层”而不是“单一循环”最早我做 Agent 时就是一个 while 循环里反复调用模型。一旦某个环节出错整个循环就断掉很难做恢复。后来改用“任务编排”结构把一次 Agent 执行拆成节点组成的图每个节点执行完把状态交给编排器编排器决定下一个要执行的节点。这样“断点续跑”就变成了“从图的某个节点重新开始跑”。结构上它很像工作流引擎只不过这个工作流的每一步都可能由模型决策。这个调整顺带解决了一个问题工具调用之间可以有并行和分支而不再是一条线性链。容错时可以只重放失败的子树不用重放整个图。6.3 经验三用“checkpoint 频率”平衡成本与安全快照不是越频繁越好。如果每执行一步就存一次Redis 的写入压力会很大而且大部分快照根本用不上任务就正常跑完了。如果存得太少恢复时长会变长丢失最近几步的状态。我试验下来比较合适的节奏是在三个节点强制存快照——每个“模型决策完成、即将执行工具”前、每个“工具执行成功”后、以及“一轮完整循环结束”时。这种频率下最多丢失一次工具调用的状态恢复成本可以接受。另外快照本身也要设体积上限。如果一个会话的上下文膨胀到几万 token快照 JSON 会很大。不要全量存可以把上下文做摘要后再存。恢复时能力会有所损失但比什么都存不下来好。6.4 经验四故障演练要“随机化”一到发布节点才想着验证容错机制是来不及的。我在团队里做了一个“混沌小工具”每隔一段时间随机杀掉一个 Agent 工作进程或随机让一个下游 API 返回 500。刚开始跑的时候每周都能抓出几个恢复流程的 bug。比如 Redis 锁没释放、事件流写入时序错了、快照里的某个字段没反序列化出来。故障演练的一个关键是随机性必须足够“真”。不能每次都在同一个步数杀掉进程否则你会无意中只对某一段逻辑健壮另一段还是脆的。让它随机分布到任务执行的不同阶段才能暴露更多问题。6.5 经验五用户沟通文案也是容错的一部分最后说一个经常被忽略的点容错机制的用户体验最后都落在“文案”上。错误提示写得烂会让用户觉得系统很不可靠写得好反而能提升信任。我比较推崇的文案风格是不要只说“出错了”要说“哪一步出了什么错”。给出下一步建议让用户知道接下来会发生什么。不要让用户干等提供“停止重试”“跳过此步骤”“重新生成”这类按钮。如果原因是模型服务限流或超时可以直说“当前模型服务负载较高”这远比“系统错误”有说服力。把容错当成产品体验的一环而不是纯粹的后台技术这是我做这个系列最大的体验升级。很多用户其实不介意 Agent 出现异常他们介意的是“出异常后毫无反馈地卡在原地”。7. 一个可参考的 Harness 容错配置模板为方便直接套用我把前面提到的设计汇总成一份基础配置模板。不同项目需要微调但这个模板可以作为起点。配置项推荐值说明模型调用超时首 token 30s / 完整响应 600s依据模型和任务复杂度调整工具调用超时20s高耗时工具单独配置重试最大次数3可重试错误非重试错误设为 0重试退避策略指数退避 1s/2s/4s jitter 20%避免重试风暴熔断器阈值5 次失败 或 50% 失败率按服务重要程度调整熔断器冷却时间30s半开态试探恢复快照频率工具调用前后 每轮循环结束平衡成本与安全快照 TTL24h任务不能隔天恢复就清理上下文溢出阈值75% 触发摘要90% 强制截断并通知人工接管条件连续失败≥3次 或 超时150%触发后立刻通知用户事件流保留时间全量 2 天之后聚合摘要兼顾审计和存储成本这个模板背后的核心思想就一句话容错设计不是一个独立的“错误处理模块”而是贯穿整个 Harness 所有模块的横切关注点。从上下文管理到工具调用从状态存储到用户交互每一层都需要考虑“如果这里出错了该怎么恢复”。8. 离线断网、幂等控制等高级场景实践如果上面的内容已经能满足大多场景下面两个更极端的情况也可以提前考虑。8.1 离线断网环境下的任务降级有些 Agent 会跑在用户的本地机器上网络随时可能断开。比如笔记本电脑合盖、地铁隧道里没信号。断网会导致什么模型 API 不可用、外部工具不可用但本地缓存和本地代码执行仍然可用。我的经验是做一个“离线模式”开关。检测到连续几次 API 调用失败后自动切换为离线模式不再发起新的模型请求而是直接读取最近一次快照提示用户“当前处于离线状态我可以继续处理本地任务但无法调用外部模型和在线工具。”如果本地有模型可以离线推理那最好没有的话就把任务队列挂起等网络恢复后由用户一键续跑。这个设计的关键在于不要在断网时反复重试 API网络恢复前没意义而是快速报出“当前不可行”并给出可用的替代范围。处理得好用户会觉得这个 Agent 很聪明处理得不好用户只会觉得它卡死了。8.2 幂等控制的更多细节让重放安全前面提到过幂等这里再往深说一点。很多工具调用天然不是幂等的比如“发邮件”“下单”“转账”。要让重放安全我更推荐“预检 提交”两步模式。比如一个“发送验证邮件”工具。调用前先创建一个delivery_id写入事件流状态为 pending。真正发送时带上这个delivery_id。如果 Harness 在发送过程中崩溃恢复后发现有一个 pending 的 delivery不要直接重发而是先调一个查询接口确认这个delivery_id是否已经真实发出很多邮件服务商支持按 custom_id 查询。如果已发出就标记为 succeeded不再重发如果没发出才真正发送。这个模式本质上是在工具层和 Harness 层之间建立一种“两阶段提交”的语义。不是所有工具都能支持但对那些“只执行一次”的高风险操作非常值得。9. 补充关于 DeepSeek Harness 的一些使用观察顺手回应一下评论区常问的 “DeepSeek Harness” 相关搜索。很多人可能是从 DeepSeek 相关的 Agent 项目接触到 Harness 这个词的。需要澄清的是这里讨论的“Harness”是一个通用的 Agent 外围框架概念并不是 DeepSeek 专属的产品。你的项目用 DeepSeek、Claude、GPT 或者本地开源模型这一套容错设计同样成立。从我实测的使用体验看当前很多模型服务的超时重试策略都还比较“糙”。模型厂商不会替你保证链路的可靠性用户侧的 Harness 必须自己扛。所以不管模型用的是哪家围绕容错与恢复的这层设计建议在项目初期就着手等出了问题再补惨痛的线上事故会让你后悔。从安装、配置的角度来说不同 Harness 开源实现的侧重点差异很大。有的主打本地优先有的主打云端任务编排。评估一个 Harness 方案的容错能力时重点看三件事是否支持断点快照与恢复、是否内置了可配置的重试与熔断策略、是否提供了完整的事件追踪机制。三者缺一就要慎重。10. 最后说几点心得体会做 Code Agent 的 Harness 设计做到“容错与恢复”这一层时我最大的感受是这个领域不像写业务代码有一套明确的正解。大部分设计决策都是在“成本”“复杂度”和“体验”之间做权衡。没有完美的容错方案只有在你当前的约束条件下最适合的方案。如果你只能从这篇文章里带走三个点我的建议是第一先把超时控制做对。超时是容错的基石围绕超时的重试、退避、熔断能根治大部分线上问题。第二从第一天就做事件溯源。把 Agent 的每一步执行都记录成事件它是断点续跑的前提也是事后复盘的一手资料。后补永远比从零做更难。第三不要追求“永远不挂”要追求“挂了之后恢复得快 用户感知良好”。容错设计的最终目标不是消灭异常而是管理异常。最后再分享一个我个人的工作习惯每次给 Harness 加一种新的容错机制时我都会顺手写一个“模拟故障”的测试用例放进 CI 里。哪怕当前用不上以后有新改动时它也能自动验证恢复路径是通的。这些东西在平时看起来很“浪费”真正出事故时你就会发现它是你最大的底气。