Files
sanguo_moziplus_v2/docs/research/distill-skills-draft/system-design-lessons.md
T
2026-05-26 23:44:29 +08:00

9.1 KiB
Raw Blame History

name, description
name description
system-design-lessons 系统设计实战教训:Counter/锁生命周期、进程管理、续杯机制、 广播路径一致性、JSON 解析验证。涉及并发控制、进程调度、 外部命令集成时触发。

系统设计教训

来源:从三国团队(庞统/司马懿/诸葛亮)~2GB 对话历史中蒸馏 卡片数:7(批次2卡片4/5/6/7/8/9/14,部分与批次1卡片7有交叉)

适用场景

  • 设计并发控制、锁、counter 机制时
  • 实现进程管理(spawn/retry/exit)时
  • 集成外部命令(stdout JSON 解析)时
  • 实现广播/单播路径时
  • 需要三层一致性审查(PRD→设计→代码)时

经验清单

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

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

正确做法

  1. Counter 贯穿 retry 链acquire 在 spawn 前,release 在最终退出(done/failed)后
  2. Retry 不释放 counter,而是复用同一个 counter slot
  3. Cooldown 机制:release 后短期冷却,防止立即 re-acquire 导致抖动
  4. 三层控制:cooldown → global limit → per-agent → per-session

⚠️ 常见错误

  • _handle_exit retry 路径提前调 on_complete("retry_release") 释放 counter,导致同一 Agent 被并发 spawn 多次 [simayi/mail]
  • _do_retry 中 counter 仍被占用但 on_complete=None,续杯永远无法 acquire

关键细节

  • 所有 release 调用都要带 session_idcounter v2.1 的教训)
  • on_complete 回调的时序:进程退出 ≠ 回调完成
  • 广播 spawn 路径也要 catch AgentBusyError

反面教训counter 提前释放导致 zhipu API 429rate limit 被打爆),Gateway 假死。修复后 counter 泄漏导致续杯死循环。

来源:批次2卡片6counter生命周期,freq=3


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

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

正确做法

  1. _do_retry 中手动释放 counter,并将 on_complete 设为 None 防止 double release
  2. 资源释放应有明确触发点,不能依赖"进程退出自然释放"
  3. 每条退出路径都要有对应的资源释放

⚠️ 常见错误

  • 进程退出 → on_complete 未调用 → counter 仍占用 → spawn_full_agent 的 can_acquire 返回 False → AgentBusyError → 续杯永远不成功

关键细节

  • on_complete=None 后续杯 spawn 完成无幻觉门控,靠 ticker 超时兜底——影响可接受但需记录
  • 安装目录同步是必须的检查项(司马懿每次评审都验证)

反面教训:未处理回调时序导致续杯死循环,任务永远无法完成。

来源:批次2卡片7(进程管理,freq=2)


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

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

正确做法

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

⚠️ 常见错误

  • retry_count 在续杯中永远不变(retry_count=1/3),续杯永不停
  • Mail 续杯用 RETRY_PROMPT(含 review 标记指令),Agent 标了 review 但 _mail_auto_complete 只认 done
  • 续杯创建新 session 而非复用 main session,每次重试是全新上下文,丢失之前工作

关键细节

  • 续杯模板的"不要执行状态转换"指令是关键——Agent 容易自作主张标 done
  • 司马懿在 Bug-8 评审中确认 use_main_session 必须统一

反面教训:续杯死循环阻塞 tick,导致整个调度停滞。PM2 restart 后旧 session 仍在续杯,新 session 又被创建,资源爆炸。

来源:批次2卡片8(续杯机制,freq=5+)


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

场景:任务调度有指定 Agent 的单播和广播认领两条路径。路径行为不一致会导致系统性问题。

正确做法

  1. 所有 spawn 路径(单播、广播、retry)必须传递完整上下文参数
  2. 评审时重点检查:新功能是否覆盖了所有 spawn 路径
  3. 广播 spawn 路径也要 catch AgentBusyError

⚠️ 常见错误

  • 广播路径 ticker._broadcast_claimspawn_full_agent 时没传 task_iddb_path
  • 导致续杯时无法查询任务状态,retry_count 永远不变 [simayi/mail]
  • Mail 变广播导致所有 Agent 假死——路径不一致的典型案例

关键细节

  • 司马懿在 v2.7.2 Pipeline 重构评审中建议统一 TickResult 返回值 + 方法注册
  • 每次新增参数时,必须检查所有 spawn 调用点

反面教训:广播路径缺失参数导致续杯死循环,影响所有通过广播认领的任务。

来源:批次2卡片9(广播路径,freq=3)


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

场景openclaw agent --json 的输出结构可能随版本变化。解析路径是猜的而非实测的会导致静默失败。

正确做法

  1. 解析外部命令输出前,先用实际命令验证输出格式
  2. 评审时要求看到实测输出样例
  3. JSON 解析失败时应该报错,不应静默走 fallback

⚠️ 常见错误

  • spawner 代码取 data.response.meta.transport,但实际输出格式不同

    "P0_parse_stdout_json 解析路径错误(根因)" [pangtong/mail]

  • 解析不到数据时走了 fallback,掩盖了问题——静默失败比报错更危险

关键细节

  • 这个 Bug 从第一天就存在("从第一天就存在的根因 bug")
  • 司马懿在 Spawner v3.0 评审中指出新路径 data.result.meta.executionTrace 需确认
  • 所有依赖 stdout 判定的功能(如幻觉门控)都会受影响

反面教训:解析路径错误导致 stdout 信息丢失,幻觉门控等关键功能失效。

来源:批次2卡片14JSON解析,freq=2


6. 设计文档-代码三层一致性审查(severity: high

场景:迭代开发中设计文档更新不及时,或代码实现偏离设计。需要 PRD→设计→代码的三层审查。

正确做法

  1. 每次代码评审前,先对设计文档逐条检查实现覆盖度
  2. 设计文档用明确编号(§B1/B2/B3)便于逐条追踪
  3. 安排"背靠背一致性审查":PRD→设计→代码全覆盖
  4. 司马懿的核心职责就是做这种三层对照

⚠️ 常见错误

  • 设计文档 §B2 在代码中完全遗漏,评审者初评也未发现 [pangtong/mail]

    "代码只实现了 B1/B3/B4B2 遗漏了"

关键细节

  • 庞统专门发 #311 邮件请求三层一致性审查
  • 司马懿在 counter 评审中发现设计与代码不一致
  • 三层审查顺序:先 PRD→设计(需求覆盖),再设计→代码(实现忠实)

反面教训:设计-代码不一致导致假死检测失效,compact 后无法区分"还在跑"和"卡死了"。

来源:批次2卡片4(设计-代码一致性,freq=4+)


7. E2E 测试必须用真实环境(severity: medium

场景:单元测试只能验证单个模块逻辑。编排系统的 Bug 往往出在模块交互,只有真实环境 E2E 能暴露。

正确做法

  1. E2E 测试走真实 Ticker + 真实 Agent spawn,不手动推动状态
  2. 测试用例覆盖完整生命周期:创建→调度→执行→续杯→完成/失败
  3. 司马懿独立跑 E2E 并做根因分析,与庞统修复做交叉验证
  4. 每次修复后全量重跑 E2E(不是只跑失败的用例)

⚠️ 常见错误

  • 只跑单元测试就部署,dashboard 上大量任务停在中间状态 [user/simayi-correction]
  • 手动改 DB 推进任务(污染数据、掩盖真正 bug)

关键细节

  • 司马懿 E2E 报告格式:通过/失败计数 + 每个失败用例的根因分析
  • 从 6/10 → 8/10 → 9/10 → 10/10 的渐进修复证明 E2E 驱动开发有效
  • MEMORY.md 中 L1-L7 经验教训也是 E2E 驱动复盘的产出

反面教训:未做 E2E 导致假死问题在生产环境中才暴露,排查成本远高于测试阶段。

来源:批次2卡片10E2E测试,freq=3+ MEMORY.md L1-L7


检查清单(快速参考)

  • Counter 的 acquire-release 是否成对?retry 链中是否贯穿?
  • 进程退出后的 on_complete 回调是否处理?有无资源泄漏?
  • 续杯模板是否专用?是否禁止状态转换指令?
  • retry_count 是否在每次续杯时递增?
  • 续杯是否复用 session(不创建新 session)?
  • 新增参数时,所有 spawn 路径(单播/广播/retry)是否都传了?
  • 外部命令输出格式是否实测验证过?
  • JSON 解析失败是报错还是静默 fallback?
  • 设计文档是否逐条检查了实现覆盖度?
  • E2E 测试是否走真实环境(非手动推进状态)?