Files
sanguo_moziplus_v2/skills/trial-and-error-patterns.md
2026-05-27 00:06:34 +08:00

7.6 KiB
Raw Permalink Blame History

name, description
name description
trial-and-error-patterns 从任务失败和重试中提炼的可复用模式:Counter/锁生命周期、进程管理、续杯机制、广播路径、JSON解析验证、中断恢复。 在涉及并发控制、任务重启、系统资源管理的复杂任务时触发。

Trial and Error Patterns

来源:从三国团队对话历史中的 226 次试错和 200 次成功中蒸馏(批次1-2)

适用场景

  • 并发控制任务(counter/锁)
  • 任务重启/续杯(retry)机制
  • 进程管理(启动/退出/资源释放)
  • 多路径调度(广播 vs 单播)
  • 外部命令输出解析
  • Agent 中断恢复与自检

模式清单

模式 1:Counter/锁的生命周期必须贯穿完整调用链(severity: high

场景Agent 并发控制需要 counter/锁机制。如果 acquire-release 不成对,会导致永久占满或双重释放。

错误做法 retry 路径提前调 on_complete("retry_release") 释放 counter,但续杯 spawn 时 counter 已释放,导致同一 Agent 被并发 spawn 多次。另一条路径中 _do_retry 的 on_complete 设为 Nonecounter 仍被占用但无法释放,续杯永远无法 acquire。 [批次2卡片6]

正确做法

  1. Counter 贯穿 retry 链:acquire 在 spawn 前,release 在最终退出(done/failed)后
  2. Retry 不释放 counter,而是复用同一个 counter slot
  3. 所有 release 调用都要带 session_id
  4. release 后短期 cooldown,防止立即 re-acquire 导致抖动
  5. 广播 spawn 路径也要 catch AgentBusyError

关键细节

  • 司马懿建议的四层控制:cooldown → global limit → per-agent → per-session
  • counter 提前释放曾导致 zhipu API 429rate limit 被打爆),Gateway 假死

模式 2:进程退出 ≠ 资源释放(severity: high

场景:异步系统中,子进程退出和回调执行不是原子操作。假设进程退出时资源已释放,会出现竞态条件。

错误做法 spawner 在 retry 时:进程退出 → on_complete 未调用 → counter 仍占用 → can_acquire 返回 False → AgentBusyError → 续杯永远不会成功。 [批次2卡片7]

正确做法

  1. 资源释放应该有明确的触发点,不能依赖"进程退出自然释放"
  2. 在 retry 入口手动释放 counter,并将 on_complete 设为 None 防止 double release
  3. 审查每条退出路径(正常退出、异常退出、超时退出、retry 退出)是否都有对应的资源释放
  4. on_complete=None 后需要替代兜底机制(如 ticker 超时),并记录为已知 trade-off

关键细节

  • 这类竞态条件在单元测试中难以复现,只有 E2E 真实环境才能暴露

模式 3:续杯(retry)机制是最大假死来源(severity: high

场景:任务执行超时后需要 retry"续杯")。retry 机制本身有 Bug 时,会导致无限重试或永久卡住。

错误做法 多个 Bug 叠加导致续杯死循环:

  1. retry_count 在续杯中永远不变(retry_count=1/3),续杯永不停
  2. Mail 续杯用 RETRY_PROMPT(含 review 标记指令),Agent 标了 review,但 auto_complete 只认 done
  3. 续杯创建新 session 而非复用 main session,每次重试丢失之前工作 [批次2卡片8]

正确做法

  1. 续杯模板必须专用(如 MAIL_RETRY_PROMPT),明确禁止状态转换命令
  2. retry_count 必须在每次续杯时递增,max_retries 硬性兜底
  3. 续杯应复用 main session 保持上下文连续性,不创建新 session
  4. use_main_session 在所有路径(主 dispatch、legacy broadcast、retry)必须统一

关键细节

  • 续杯模板的"不要执行状态转换"指令是关键——Agent 容易自作主张标 done
  • 续杯死循环会阻塞 tick,导致整个调度停滞

模式 4:广播路径与单播路径行为必须一致(severity: medium

场景:任务调度有两条路径:指定 Agent 的单播和广播认领。如果两条路径的行为不一致,某些任务类型会系统性地出问题。

错误做法 广播路径 ticker._broadcast_claimspawn_full_agent 时没传 task_iddb_path,导致续杯时无法查询任务状态,retry_count 永远不变。 [批次2卡片9]

正确做法

  1. 所有 spawn 路径(单播、广播、retry)必须传递完整的上下文参数
  2. 新增功能时逐条检查是否覆盖了所有 spawn 路径,不能只改单播忘了广播
  3. 统一 TickResult 返回值 + 方法注册,避免路径分歧

关键细节

  • 状态机枚举值不一致是同类问题——SQL 查询用 'executing' 但实际枚举是 'working',重启恢复时 working 节点被遗漏 [批次2卡片2]
  • 安装目录同步(auto-sync)可能静默修改配置,需要与设计文档对照

模式 5stdout JSON 解析路径必须实测验证(severity: medium

场景:外部命令的输出结构可能随版本变化。如果解析路径是猜的而非实测的,会导致静默失败。

错误做法 spawner 代码取 data.response.meta.transport,但实际 openclaw agent --json 的输出是 { response: { meta: { transport, ... } } },路径不对。解析不到数据时走了 fallback,掩盖了问题。这个 Bug 从第一天就存在。 [批次2卡片14]

正确做法

  1. 解析外部命令输出前,先用实际命令验证输出格式
  2. 评审时要求看到实测输出样例,不接受"应该是这样"
  3. 解析失败时显式报错或日志警告,不要静默 fallback

关键细节

  • 静默失败比报错更危险——fallback 掩盖了问题,导致所有依赖 stdout 判定的功能(如幻觉门控)静默失效
  • 外部 CLI 的 --json 输出格式属于外部接口,需要像对待 API 一样验证

模式 6:中断恢复后必须自检(severity: medium

场景:Agent 在执行过程中出现中断(进程挂起、网络断开、超时等),恢复后继续工作,但未检查中断期间发生了什么。

错误做法: Agent 在设计评审完成后开始编码时"死掉"了 40 分钟,恢复后直接继续编码,没有自查中断原因、没有检查中断期间是否有新的指令或状态变化。 [批次1卡片10]

正确做法

  1. 发现自己有中断时,主动检查中断原因和时长
  2. 检查中断期间是否错过了重要事件(新邮件、状态变更、用户指令)
  3. 向用户汇报中断情况和恢复状态
  4. 如果中断导致上下文丢失,回退到最近已确认的状态重新开始

关键细节

  • Agent 不一定能感知自己的中断,但可以通过时间戳、日志断点来推断
  • 中断可能影响依赖链上的其他 agent——任务遗漏或重复执行

检查清单(快速参考)

并发控制:

  • Counter 的 acquire 和 release 是否成对?
  • 所有 release 调用是否带 session_id
  • 续杯路径是否复用 counter slot 而非释放再获取?

Retry/续杯:

  • retry_count 是否每次递增?max_retries 是否兜底?
  • 续杯模板是否明确禁止状态转换?
  • 是否复用 main session 而非创建新 session
  • 进程退出后资源是否有明确的释放触发点?

路径一致性:

  • 广播路径和单播路径是否传递相同的完整参数?
  • 新增功能是否覆盖了所有 spawn 路径?

外部接口:

  • stdout JSON 解析路径是否用实际命令验证过?
  • 解析失败时是否显式报错而非静默 fallback?

中断恢复:

  • 中断恢复后是否检查了中断期间的事件?
  • 是否向用户汇报了中断情况?