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

207 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: system-design-lessons
description: >-
系统设计实战教训: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_claim``spawn_full_agent` 时没传 `task_id``db_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 测试是否走真实环境(非手动推进状态)?