Agent-almanac circuit-breaker-pattern
git clone https://github.com/pjt222/agent-almanac
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/zh-CN/skills/circuit-breaker-pattern" ~/.claude/skills/pjt222-agent-almanac-circuit-breaker-pattern-644fe6 && rm -rf "$T"
i18n/zh-CN/skills/circuit-breaker-pattern/SKILL.md断路器模式
工具故障时的优雅降级。一个调用五个工具且其中一个损坏的智能体不应该完全失败——它应该识别损坏的工具、停止调用它、将范围缩减到剩余可实现的内容,并如实报告跳过了什么。此技能使用来自分布式系统的断路器模式规范化该逻辑,并将其适配到智能体工具编排中。
核心洞见,来自 kirapixelads 的「厨房火灾问题」:指挥官(编排层)不能下厨。将「尝试什么」和「怎么尝试」的关注点分离,可以防止编排器陷入失败工具的重试循环中。
适用场景
- 构建依赖多个可靠性不一的工具的智能体
- 设计部分结果优于完全失败的容错智能体工作流
- 智能体被困在损坏工具的重试循环中,而不是继续使用正常工具
- 从任务中途的工具中断中优雅恢复
- 强化现有智能体防止级联工具故障
- 过时或缓存的工具输出被当作新鲜数据使用
输入
- 必需:智能体依赖的工具列表(名称和用途)
- 必需:智能体正在尝试完成的任务
- 可选:已知的工具可靠性问题或过去的故障模式
- 可选:故障阈值(默认:3 次连续失败后打开断路器)
- 可选:每个周期的故障预算(默认:5 次总故障后暂停并报告)
- 可选:半开探测间隔(默认:打开后每第 3 次尝试)
步骤
第 1 步:构建能力映射
声明每个工具提供什么以及存在什么备用方案。此映射是范围缩减的基础——没有它,工具故障会让智能体不知下一步该怎么做。
capability_map: - tool: Grep provides: content search across files alternatives: - tool: Bash method: "rg or grep command" degradation: "loses Grep's built-in output formatting" - tool: Read method: "read suspected files directly" degradation: "requires knowing which files to check; no broad search" fallback: "ask the user which files to examine" - tool: Bash provides: command execution, build tools, git operations alternatives: [] fallback: "report commands that need to be run manually" - tool: Read provides: file content inspection alternatives: - tool: Bash method: "cat or head command" degradation: "loses line numbering and truncation safety" fallback: "ask the user to paste file contents" - tool: Write provides: file creation alternatives: - tool: Edit method: "create via full-file edit" degradation: "requires file to already exist for Edit" - tool: Bash method: "echo/cat heredoc" degradation: "loses Write's atomic file creation" fallback: "output file contents for the user to save manually" - tool: WebSearch provides: external information retrieval alternatives: [] fallback: "state what information is needed; ask user to provide it"
对每个工具,记录:
- 它提供的能力(一行)
- 哪些备用工具可以部分覆盖它(附降级说明)
- 当没有工具备用方案时的手动后备方案
预期结果: 覆盖智能体使用的每个工具的完整能力映射。每个条目至少有一个后备方案,即使没有工具备用方案。映射使通常隐式的内容变为显式:哪些工具是关键的(无备用方案)以及哪些是可替换的。
失败处理: 若工具列表不清晰,从技能的 frontmatter 中的
allowed-tools 开始。若备用方案不确定,将其标记为 degradation: "unknown — test before relying on this route" 而非省略。
第 2 步:初始化断路器状态
为每个工具设置状态追踪器。每个工具以 CLOSED 状态(健康,正常运行)开始。
断路器状态表: +------------+--------+-------------------+------------------+-----------------+ | 工具 | 状态 | 连续失败次数 | 最后失败时间 | 最后成功时间 | +------------+--------+-------------------+------------------+-----------------+ | Grep | CLOSED | 0 | — | — | | Bash | CLOSED | 0 | — | — | | Read | CLOSED | 0 | — | — | | Write | CLOSED | 0 | — | — | | Edit | CLOSED | 0 | — | — | | WebSearch | CLOSED | 0 | — | — | +------------+--------+-------------------+------------------+-----------------+ 故障预算:0 / 5 已消耗
状态定义:
- CLOSED — 工具健康。正常使用。追踪连续失败次数。
- OPEN — 工具已知损坏。不要调用它。路由至备用方案或降级范围。
- HALF-OPEN — 工具曾经损坏但可能已恢复。发送一次探测调用。若成功,转换到 CLOSED。若失败,返回 OPEN。
状态转换:
- CLOSED -> OPEN:当连续失败达到阈值时(默认:3 次)
- OPEN -> HALF-OPEN:经过可配置的间隔后(例如每 3 个任务步骤)
- HALF-OPEN -> CLOSED:探测调用成功时
- HALF-OPEN -> OPEN:探测调用失败时
预期结果: 状态表已为所有工具初始化为 CLOSED 状态和零失败计数。故障阈值和预算被明确声明。
失败处理: 若无法提前枚举工具列表(动态工具发现),在首次使用每个工具时初始化状态。该模式仍然适用——只是逐步构建表格。
第 3 步:实现调用追踪循环
当智能体需要调用工具时,遵循此决策序列。这是指挥官逻辑——它决定「是否」尝试调用,而非「如何」执行。
每次工具调用前: 1. 在断路器表中检查工具状态 2. 若 OPEN: a. 检查是否到了半开探测的时间 - 是 → 转换到 HALF-OPEN,继续进行探测调用 - 否 → 跳过此工具,路由至备用方案(第 4 步) 3. 若 HALF-OPEN: a. 进行一次探测调用 b. 成功 → 转换到 CLOSED,将连续失败重置为 0 c. 失败 → 转换到 OPEN,增加故障预算消耗 4. 若 CLOSED: a. 正常进行调用 每次工具调用后: 1. 成功: - 将连续失败重置为 0 - 记录最后成功时间戳 2. 失败: - 增加连续失败计数 - 记录最后失败时间戳和错误消息 - 增加故障预算消耗 - 若连续失败 >= 阈值: 转换到 OPEN 日志:「[工具] 断路器开启:[失败次数] 次连续失败」 - 若故障预算耗尽: 暂停——不继续任务 向用户报告(第 6 步)
指挥官从不立即重试失败的调用。它记录失败、检查阈值,然后继续前进。重试仅通过 HALF-OPEN 探测机制在后续步骤中发生。
预期结果: 智能体在每次工具调用前后遵循的清晰决策循环。工具健康状态持续追踪。指挥官层从不在失败工具上阻塞。
失败处理: 若跨调用追踪状态不可行(例如,无状态执行),降级为更简单的模型:统计总故障次数,并在预算时暂停。三状态断路器是理想的;故障计数器是最小可行模式。
第 4 步:在断路器开启时路由至备用方案
当工具的断路器是 OPEN 时,查阅能力映射(第 1 步)并路由至最佳可用备用方案。
路由优先级:
- 降级较低的工具备用方案 — 使用另一个提供类似能力的工具。在任务输出中注明降级情况。
- 降级较高的工具备用方案 — 使用能力损失较大的另一个工具。明确标注结果中缺失的内容。
- 手动后备方案 — 报告智能体无法做什么,以及用户需要提供什么信息或操作。
- 范围缩减 — 若没有备用方案且没有可行的后备方案,将依赖的子任务从范围中完全移除(第 5 步)。
路由决策示例: 需要的工具:Grep(断路器 OPEN) 任务:查找所有包含 "API_KEY" 的文件 路由 1:带 rg 命令的 Bash → 降级:失去 Grep 内置的格式化 → 决策:可接受——使用此路由 若 Bash 也 OPEN: 路由 2:直接读取可疑的配置文件 → 降级:需要猜测哪些文件;无法广泛搜索 → 决策:部分——仅尝试已知的配置路径 若 Read 也 OPEN: 路由 3:询问用户 → 「我需要查找包含 'API_KEY' 的文件,但我的搜索 工具不可用。你能运行:grep -r 'API_KEY' . 吗?」 → 决策:后备——用户提供信息 若用户不可用: 路由 4:范围缩减 → 从任务范围中移除「查找 API 密钥引用」 → 记录:「SKIPPED: API key search — no tools available」
预期结果: 当工具断路器开启时,智能体透明地路由至备用方案或降级范围。路由决策和任何降级都记录在任务输出中,使用户知道受到了什么影响。
失败处理: 若能力映射不完整(未列出备用方案),默认为范围缩减并报告。永不静默跳过工作——始终记录跳过了什么以及原因。
第 5 步:将范围缩减至可实现的工作
当工具断路器开启且备用方案耗尽时,将任务缩减至使用正常工具仍可完成的内容。这不是失败——这是诚实的范围管理。
范围缩减协议:
- 列出剩余子任务
- 对每个子任务,检查它需要哪些工具
- 若所有必需工具都是 CLOSED 或有可行的备用方案:保留子任务
- 若任何必需工具是 OPEN 且无备用方案:将子任务标记为 DEFERRED
- 继续缩减后的范围
- 在结束时报告延迟的子任务
范围缩减报告: 原始范围:5 个子任务 [x] 1. 读取配置文件 (Read: CLOSED) [x] 2. 搜索已废弃的模式 (Grep: CLOSED) [ ] 3. 运行测试套件 (Bash: OPEN — 无备用方案) [x] 4. 更新文档 (Edit: CLOSED) [ ] 5. 部署到预发布环境 (Bash: OPEN — 无备用方案) 缩减后的范围:3 个子任务可实现 延迟:2 个子任务需要 Bash(断路器 OPEN) 建议:立即完成子任务 1、2、4。 子任务 3 和 5 需要 Bash——将在下一个周期中探测 或用户可以手动运行命令。
不要尝试延迟的子任务。不要重试断路器开启的工具,寄希望于它们能运行。断路器的存在正是为了防止这种情况——信任其状态。
预期结果: 任务被清晰地划分为可实现的和延迟的工作。智能体完成所有可实现的工作,并报告延迟项目及其原因和解除阻塞的条件。
失败处理: 若范围缩减移除了所有子任务(每个工具都损坏),直接跳至第 6 步——暂停并报告。没有可用工具的智能体不应假装取得进展。
第 6 步:处理陈旧性并标记数据质量
当工具返回可能陈旧的数据(缓存结果、过时快照、先前获取的内容)时,明确标记而非将其当作新鲜数据处理。
陈旧性指标:
- 工具输出与先前调用完全匹配(可能是缓存命中)
- 数据引用的时间戳早于当前任务
- 工具文档提到了缓存行为
- 结果与其他近期观察相矛盾
标记协议:
呈现可能陈旧的数据时: 「[STALE DATA — retrieved at {timestamp}, may not reflect current state] File contents as of last successful Read: ...」 「[CACHED RESULT — Grep returned identical results to previous call; filesystem may have changed since]」 「[UNVERIFIED — WebSearch result from {date}; current status unknown]」
永不静默地将陈旧数据呈现为当前数据。用户或下游智能体必须知道数据质量以做出合理决策。
预期结果: 所有可能陈旧的工具输出都带有明确标签。新鲜数据不标注(标注保留用于不确定性,而非确认)。
失败处理: 若无法确定陈旧性(无时间戳,无比较基准),注明不确定性:「[FRESHNESS UNKNOWN — no baseline for comparison]」。对新鲜度的不确定性本身就是信息。
第 7 步:强制执行故障预算
追踪所有工具的总故障次数。当预算耗尽时,智能体暂停并报告,而非继续积累错误。
故障预算执行: 预算:每个周期 5 次故障 当前:4 / 5 已消耗 故障 1:Bash — "permission denied"(第 3 步) 故障 2:Bash — "command not found"(第 3 步) 故障 3:Bash — "timeout after 120s"(第 4 步) 故障 4:WebSearch — "connection refused"(第 5 步) 状态:强制暂停前还剩 1 次故障 → 下一次工具调用以高度谨慎进行 → 若失败:暂停并生成状态报告
预算耗尽时:
故障预算耗尽 — 暂停中 已完成工作: - 子任务 1:读取配置文件(成功) - 子任务 2:搜索已废弃的模式(成功) 未完成工作: - 子任务 3:运行测试套件(失败 — Bash 断路器 OPEN) - 子任务 4:更新文档(未尝试 — 已暂停) - 子任务 5:部署到预发布环境(未尝试 — 已暂停) 工具健康状态: Grep: CLOSED(健康) Read: CLOSED(健康) Edit: CLOSED(健康) Bash: OPEN(3 次连续失败 — 权限/命令/超时) WebSearch: OPEN(1 次失败 — 连接被拒绝) 故障:5 / 5 预算已消耗 建议: 1. 调查 Bash 故障 — 可能是环境问题 2. 检查 WebSearch 的网络连接 3. 解决后从子任务 4 恢复
暂停并报告的功能与电气系统中的断路器相同:它防止损害积累。一个持续调用损坏工具的智能体会浪费上下文窗口,用重复错误混淆用户,并可能产生不一致的部分结果。
预期结果: 当故障预算耗尽时,智能体干净地停止。报告包括已完成的工作、未完成的工作、工具健康状态和可操作的后续步骤。
失败处理: 若智能体无法生成干净的报告(例如,状态追踪丢失),输出任何可用的信息。部分报告比静默继续要好。
第 8 步:关注点分离——指挥官 vs. 执行者
验证编排逻辑(第 2-7 步)与工具执行干净地分离。
指挥官(编排)做:
- 追踪工具健康状态
- 决定是否调用工具、跳过还是探测
- 当工具断路器开启时路由至备用方案
- 强制执行故障预算
- 生成状态报告
指挥官不做:
- 立即重试失败的工具调用
- 修改工具调用参数以解决错误
- 捕获并压制工具错误
- 对工具失败原因做出假设
- 执行本身需要工具的后备逻辑
若指挥官在「下厨」(通过工具调用来解决其他工具的故障),则分离已被破坏。指挥官应该路由至备用工具或缩减范围——而非尝试修复损坏的工具。
预期结果: 编排决策与工具执行之间有清晰的边界。指挥官层可以在不引用特定工具 API 或错误类型的情况下被描述。
失败处理: 若编排和执行纠缠在一起,通过将决策逻辑提取到每次工具调用前运行的单独步骤中来重构。决策步骤产生四个输出之一:CALL、SKIP、PROBE 或 PAUSE。执行步骤根据该输出采取行动。
第 9 步:检测级联故障
当多个工具共享基础设施(网络、文件系统、权限)时,单一根本原因可能同时触发多个断路器。检测并处理这种相关模式,而非独立处理每个断路器。
级联故障指标:
- 3 个以上工具在同一任务步骤或短时间窗口内转换为 OPEN
- 故障共享相同的错误特征(例如,「连接被拒绝」、「权限被拒绝」)
- 先前具有独立故障历史的工具突然一起失败
响应协议:
- 当第二个断路器开启时,检查故障类别是否与第一个匹配
- 若相关:标记为系统性故障 — 暂停所有工具调用,而非仅停止损坏的工具
- 报告可疑的根本原因:「多个工具因[共同模式]失败 — 可能是[网络/文件系统/权限]问题」
- 在系统性故障期间不要探测半开工具 — 探测也会失败并浪费预算
- 仅在用户确认基础设施问题已解决后才恢复探测
退避倍增: 当级联故障触发时,对半开探测使用指数退避:在第 3 步探测,然后第 6 步,然后第 12 步。将最大间隔限制在 20 步以防止断路器永久锁定。这防止了快速探测使正在恢复的系统超载。
预期结果: 相关故障被检测并作为单个系统性事件处理,而非 N 个独立的断路器触发。故障预算将系统性事件计为一次,而非 N 次。
失败处理: 若相关检测不可行(故障有不同的错误特征,尽管原因相同),回退到独立的每工具断路器。系统仍然优雅降级——只是预算消耗得更快。
第 10 步:调用前工具选择层
在执行断路器循环(第 3 步)之前,可选地验证工具是否可用且可能成功。这减少了来自可预测故障的不必要断路器触发。
调用前检查:
| 检查 | 方法 | 失败时的操作 |
|---|---|---|
| 工具存在 | 验证工具在 allowed-tools 列表中 | 跳过——甚至不尝试 |
| MCP 服务器健康状态 | 检查服务器进程/连接状态 | 立即路由至备用方案 |
| 资源可用性 | 验证目标文件/URL/端点存在 | 路由或降级范围 |
决策表:
调用前评分: AVAILABLE → 继续进行断路器循环(第 3 步) DEGRADED → 谨慎继续,将故障阈值降低 1 UNAVAILABLE → 跳过工具,路由至备用方案(第 4 步),不消耗预算
调用前检查是建议性的,而非权威性的。通过调用前检查的工具在执行期间仍可能失败。断路器仍是主要可靠性机制。
预期结果: 可预测的故障(缺失的工具、无法访问的服务器)在消耗故障预算之前被捕获。断路器只处理真正的运行时故障。
失败处理: 若调用前检查不可用或增加了过多开销,完全跳过此步骤。第 3 步中的断路器循环处理所有故障——调用前选择是优化,不是要求。
验证清单
- 能力映射涵盖所有工具,备用方案和后备方案已记录
- 断路器状态表已为所有工具初始化
- 状态转换遵循 CLOSED -> OPEN -> HALF-OPEN -> CLOSED 循环
- 故障阈值被明确声明(非隐式)
- 在范围缩减之前尝试了备用路由
- 范围缩减已记录延迟子任务及其原因
- 陈旧数据被明确标记——永不呈现为新鲜数据
- 故障预算在耗尽时强制执行暂停并报告
- 指挥官逻辑不执行工具调用或重试失败的调用
- 状态报告包含已完成的工作、未完成的工作和工具健康状态
- 无静默故障——每次跳过、延迟和降级都有记录
- 当 3 个以上工具同时开启时检测到级联故障
- 系统性故障模式在基础设施确认恢复之前暂停所有探测
- 调用前检查(若使用)不会因可预测故障消耗故障预算
常见问题
- 重试而非断路:反复调用损坏的工具会浪费故障预算和上下文窗口。3 次连续失败是一种模式,不是坏运气。打开断路器。
- 指挥官下厨:编排层应该决定「尝试什么」,而非「如何修复损坏的工具」。若指挥官在为 Bash 故障编写解决方案命令,它已经越过了分离边界。
- 静默范围缩减:在不记录的情况下丢弃子任务会产生看起来完整但实际上不完整的结果。始终报告跳过了什么。
- 将陈旧数据当作新鲜数据:缓存或先前获取的结果可能不反映当前状态。标记不确定性而非忽略它。
- 过早开启断路器:单次瞬态故障不应该开启断路器。使用阈值(默认:3 次)来过滤噪声和信号。
- 开启后永不探测:永久开启的断路器意味着智能体永远不会发现工具已恢复。半开探测对于恢复至关重要。
- 忽略故障预算:没有预算,智能体可以跨不同工具积累数十次故障,同时仍然「取得进展」。预算强制进行诚实的检查点。
- 级联退避倍增:当依赖链中的多个工具各自应用指数退避时,复合延迟会成倍增长。限制链上的总聚合退避,而非仅每个工具。
- 陈旧的发现分数:调用前选择(第 10 步)缓存工具可用性评估。若在条件变化时缓存未失效,智能体可能跳过已恢复的工具或尝试不可用的工具。在任何系统性故障事件后重新检查分数。
相关技能
— 互补模式:尽早失败在工作开始前验证输入;断路器在工作期间管理故障fail-early-pattern
— 当故障预算耗尽或范围缩减显著时,升级至专家或人类escalate-issues
— 将反复出现的工具故障模式记录为运行手册以加速诊断write-incident-runbook
— 当多个工具降级时评估当前方法是否能适应;与范围缩减决策配合使用assess-context
— 双时钟架构将观察与决策分离;补充模式,用于降低智能体循环中的观察成本du-dum