# 经验卡片集 — 批次2 司马懿纠正 + 庞统经验混合

> 数据源：151 个片段（庞统经验 21 条 + 司马懿纠正 122 条 + Sanguo Mail 纠正 8 条）
> 归纳时间：2026-05-26
> 覆盖时间段：2026-05-06 ~ 2026-05-26

---

## 汇总表

| # | 分类 | 严重度 | 频率 | 来源 | 核心教训 |
|---|------|--------|------|------|----------|
| 1 | 代码评审 | high | 5+ | simayi-correction | 评审必须验证最终代码，不能只审方案 |
| 2 | 代码评审 | high | 3+ | simayi-correction | 状态机/枚举值不一致是高频 Bug 根源 |
| 3 | 系统设计 | high | 3 | simayi-correction, experience | 评审者自我纠正：承认判断错误比坚持错误更有价值 |
| 4 | 系统设计 | high | 4+ | simayi-correction | 设计文档-代码一致性审查不能省 |
| 5 | 系统设计 | medium | 2+ | experience | "一键三连"闭环标准：文档→代码→最终确认 |
| 6 | 系统设计 | high | 3 | simayi-correction | Counter/锁的生命周期必须贯穿完整调用链 |
| 7 | 系统设计 | high | 2 | simayi-correction | 进程退出 ≠ 资源释放：on_complete 回调时序是关键 |
| 8 | 编排调度 | high | 5+ | simayi-correction | 续杯（retry）机制是最大的假死来源 |
| 9 | 编排调度 | medium | 3 | simayi-correction | 广播路径与单播路径必须行为一致 |
| 10 | 编排调度 | medium | 3+ | simayi-correction | E2E 测试要用真实环境，不能只测单元 |
| 11 | Agent 行为 | medium | 4+ | simayi-correction | Agent 认领任务前必须检查角色匹配 |
| 12 | Agent 行为 | high | 2 | simayi-correction | inform 类型邮件不应触发完整 Agent 执行 |
| 13 | 沟通协作 | medium | 3 | pangtong-correction, simayi-correction | 回答用户问题要直接，不要绕弯子 |
| 14 | 系统设计 | medium | 2 | simayi-correction | stdout JSON 解析路径必须实测验证 |
| 15 | 经验总结 | low | 2 | experience | 跨项目调研的实践对照表要做落地映射 |

---

## 卡片 1：评审闭环必须包含最终代码确认

```yaml
---
category: "代码评审"
tags: [review, 闭环, 最终代码]
severity: "high"
frequency: 5
sources: ["simayi-correction", "experience"]
---
```

### 场景
多步骤开发中，方案评审通过后，实现阶段可能有微调。如果评审者只确认了方案而没看最终代码，Bug 可能被遗漏。

### 错误做法
庞统在 M2 阶段让司马懿 review 了 Checkpoint 和 Hooks 的**方案**，但实现后只说"已采纳建议"，没发最终代码让司马懿确认。用户发现后指出：
> "一键三连最终肯定是以司马懿 review 完你的最终成果物收尾的，不是他 review 完你的方案就算完事" [pangtong/experience]

### 正确做法
**一键三连 = 改文档 → 改代码 → 司马懿确认最终代码片段**。每个方案落地后，必须把最终代码（不是方案描述）发给评审者确认。司马懿在评审中多次明确引用代码行号做交叉验证，这种精度只有看最终代码才能做到。

### 关键细节
- "已采纳"不等于"已验证"——采纳后的代码可能引入新问题
- 司马懿多次在代码评审中发现方案评审没暴露的 Bug（如 `_do_retry` 续杯失效）
- 续杯评审中，司马懿看到最终代码才发现 on_complete=None 导致幻觉门控丢失

### 反面教训
未确认最终代码导致 Checkpoint 实现与设计不一致，返工修复。

---

## 卡片 2：状态机枚举值不一致是高频 Bug 根源

```yaml
---
category: "代码评审"
tags: [状态机, 枚举, 一致性]
severity: "high"
frequency: 3
sources: ["simayi-correction"]
---
```

### 场景
系统有多处引用任务/节点状态值（数据库查询、状态转换、恢复逻辑），如果枚举定义与实际使用不一致，会导致静默失败。

### 错误做法
M1 遗留 Bug P0-2：`get_all_executing_tasks` SQL 查询用 `'executing'`，但实际枚举值是 `'working'`，导致重启恢复时 working 节点被遗漏。
> "启动恢复查询用节点状态包含 'executing'，但实际节点状态枚举中无此值（应为 'working'）" [pangtong/simayi-review]

E2E 测试发现 `failed→pending` 转换不清 `assignee`，与另一路径 `ticker._transition_status` 的行为不一致。

### 正确做法
1. 状态枚举集中定义，所有引用点从枚举取值，硬编码字符串零容忍
2. 状态转换函数只保留一个入口（如 `_transition_status`），禁止分散在多个模块
3. 评审时重点检查：新代码中的状态值是否与枚举定义一致

### 关键细节
- 司马懿在 counter v2.1 评审中发现 `max_per_agent=2` vs 设计文档的 `1`，也是同类问题
- auto-sync 可能静默修改配置文件，需要与设计文档对照

### 反面教训
状态不一致导致任务永久卡住（P0-1）和重启恢复失败（P0-2）。

---

## 卡片 3：评审者自我纠正是高质量评审的标志

```yaml
---
category: "代码评审"
tags: [自我纠正, 评审质量, 诚实]
severity: "high"
frequency: 3
sources: ["simayi-correction", "mail"]
---
```

### 场景
评审者在审查代码时可能做出错误判断。高质量的处理不是掩盖，而是主动承认并修正。

### 错误做法
无。此处记录的是正确做法的范例。

### 正确做法
庞统在评审 spawner-monitor 时发现两处自己之前的判断有误，主动发邮件给司马懿修正：
> "修正 1: 问题4 recent_compact 不是死代码，是设计实现遗漏" — 原判断"建议删除"，修正为"设计文档 §B2/B3 明确要求" [pangtong/mail]

司马懿回复：
> "你说得对，我判断错了。设计文档 §B2/B3 明确要求区分 compact 进行中和进程卡死" [simayi/mail]

### 关键细节
- 评审者翻回设计文档交叉验证，是发现误判的有效手段
- 庞统和司马懿都在同一次评审中做了自我纠正——这说明交叉验证机制在工作
- 修正邮件明确标注"修正"而非静默修改，保留了审计轨迹

### 反面教训
如果不主动修正，错误判断会被当作已确认结论传播，后续实现可能基于错误假设。

---

## 卡片 4：设计文档-代码一致性审查不可省

```yaml
---
category: "系统设计"
tags: [设计文档, 代码一致性, 三层审查]
severity: "high"
frequency: 4
sources: ["simayi-correction"]
---
```

### 场景
迭代开发中设计文档可能更新不及时，或者代码实现偏离设计。需要 PRD→设计→代码的三层一致性审查。

### 错误做法
spawner-monitor 实现中，设计文档 §B2（compact 进行中继续等）在代码中完全遗漏。司马懿初评时也未发现，直到庞统重新读设计文档才暴露：
> "代码只实现了 B1/B3/B4，B2 遗漏了。recent_compact 是 B2 实现的数据基础" [pangtong/mail]

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

### 关键细节
- 庞统专门发了 #311 邮件请求"PRD→设计→代码三层一致性审查"
- 司马懿在 counter 生命周期评审中发现设计说 crash 3 次标 failed，但代码只 release counter 无计数

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

---

## 卡片 5：方案确认的完整闭环标准

```yaml
---
category: "系统设计"
tags: [闭环, 一键三连, 确认标准]
severity: "medium"
frequency: 2
sources: ["experience"]
---
```

### 场景
多方案并行开发时，哪些方案算"完成"容易模糊。

### 错误做法
庞统在 M2 阶段同时推进多个方案，只跟踪了"方案评审"状态，没跟踪"最终代码确认"状态。用户追问才发现 Checkpoint 和方案 C 的代码未闭环。

### 正确做法
**一键三连收尾标准**（用户明确定义）：
1. 改文档（设计/方案）
2. 改代码（实现）
3. **司马懿 review 并确认最终代码片段**

每步完成都要有明确证据（邮件编号、代码片段引用）。

### 关键细节
- 状态跟踪表要包含三列：方案评审 / 最终代码确认 / 状态
- 司马懿的确认必须是具体的（"代码片段与实际改动一致"），不是笼统的"通过"

### 反面教训
未闭环的方案被标记为"已完成"，实际遗漏了代码审查环节。

---

## 卡片 6：Counter/锁的生命周期必须贯穿完整调用链

```yaml
---
category: "系统设计"
tags: [counter, 锁, 生命周期, 资源管理]
severity: "high"
frequency: 3
sources: ["simayi-correction"]
---
```

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

### 错误做法
spawner `_handle_exit` 中 retry 路径提前调 `on_complete("retry_release")` 释放 counter，但续杯 spawn 时 counter 已释放，导致同一 Agent 被并发 spawn 多次：
> "retry 路径提前调 on_complete("retry_release") 释放 counter，导致续杯 spawn 时 counter 已空" [simayi/mail]

司马懿在评审中发现 `_do_retry` 中 counter 仍被占用但 on_complete=None，导致续杯永远无法 acquire。

### 正确做法
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

### 关键细节
- 所有 `release` 调用都要带 session_id（counter v2.1 的教训）
- on_complete 回调的时序：进程退出 ≠ 回调完成
- 广播 spawn 路径也要 catch AgentBusyError

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

---

## 卡片 7：进程退出 ≠ 资源释放

```yaml
---
category: "系统设计"
tags: [进程管理, 回调时序, on_complete]
severity: "high"
frequency: 2
sources: ["simayi-correction"]
---
```

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

### 错误做法
spawner 在 retry 时：进程退出 → on_complete 未调用 → counter 仍占用 → spawn_full_agent 的 can_acquire 返回 False → AgentBusyError → 续杯永远不会成功。

### 正确做法
1. 在 `_do_retry` 中手动释放 counter，并将 on_complete 设为 None 防止 double release
2. 资源释放应该有明确的触发点，不能依赖"进程退出自然释放"
3. 司马懿在评审中强调：需要确认每条退出路径都有对应的资源释放

### 关键细节
- on_complete=None 后续杯 spawn 完成无幻觉门控，靠 ticker 超时兜底——影响可接受但需要记录
- 安装目录同步是必须的检查项（司马懿每次评审都验证）

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

---

## 卡片 8：续杯（retry）机制是最大假死来源

```yaml
---
category: "编排调度"
tags: [续杯, retry, 假死, 死循环]
severity: "high"
frequency: 5
sources: ["simayi-correction"]
---
```

### 场景
任务执行超时后需要 retry（"续杯"）。如果 retry 机制本身有 Bug，会导致无限重试或永久卡住。

### 错误做法
多个 Bug 叠加导致续杯死循环：
1. retry_count 在续杯中永远不变（retry_count=1/3），续杯永不停
2. Mail 续杯用 RETRY_PROMPT（含 review 标记指令），Agent 标了 review，但 _mail_auto_complete 只认 done
3. 续杯创建新 session 而非复用 main session，每次重试都是全新上下文，丢失之前工作

### 正确做法
1. 续杯模板必须专用（MAIL_RETRY_PROMPT），明确禁止状态转换命令
2. retry_count 必须在每次续杯时递增，max_retries 兜底
3. 续杯应复用 session 保持上下文连续性
4. 司马懿在 Bug-7 评审中确认：retry 应复用 main session，每次重试不创建新 session

### 关键细节
- 续杯模板的"不要执行状态转换"指令是关键——Agent 容易自作主张标 done
- 司马懿在 Bug-8 评审中发现 use_main_session 在三处（主 dispatch、legacy broadcast、retry）必须统一

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

---

## 卡片 9：广播路径与单播路径行为必须一致

```yaml
---
category: "编排调度"
tags: [广播, 单播, 路径一致性]
severity: "medium"
frequency: 3
sources: ["simayi-correction"]
---
```

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

### 错误做法
广播路径 `ticker._broadcast_claim` 调 `spawn_full_agent` 时没传 `task_id` 和 `db_path`，导致续杯时无法查询任务状态，retry_count 永远不变：
> "广播路径不传 task_id/db_path → 续杯时 retry_count=1/3 永远不变" [simayi/mail]

### 正确做法
1. 所有 spawn 路径（单播、广播、retry）必须传递完整的上下文参数
2. 评审时重点检查：新功能是否覆盖了所有 spawn 路径
3. 司马懿多次提醒：广播 spawn 路径也要 catch AgentBusyError

### 关键细节
- Mail 变广播导致所有 Agent 假死的事件就是路径不一致的典型案例
- 司马懿在 v2.7.2 Pipeline 重构评审中建议统一 TickResult 返回值 + 方法注册

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

---

## 卡片 10：E2E 测试必须用真实环境

```yaml
---
category: "编排调度"
tags: [E2E测试, 真实环境, 集成测试]
severity: "medium"
frequency: 3
sources: ["simayi-correction"]
---
```

### 场景
单元测试只能验证单个模块逻辑。编排系统的 Bug 往往出在模块交互（spawn → 执行 → 续杯 → 状态转换），只有真实环境 E2E 能暴露。

### 错误做法
早期只跑了单元测试，部署后发现大量中间状态卡住的任务。用户发现：
> "dashboard的任务中，大量的停在了中间阶段" [user/simayi-correction]

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

### 关键细节
- 司马懿的 E2E 报告格式：通过/失败计数 + 每个失败用例的根因分析
- 测试从 6/10 → 8/10 → 9/10 → 10/10 的渐进修复过程证明 E2E 驱动开发的有效性

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

---

## 卡片 11：Agent 认领任务前必须检查角色匹配

```yaml
---
category: "Agent 行为"
tags: [认领, 角色匹配, 任务分配]
severity: "medium"
frequency: 4
sources: ["simayi-correction"]
---
```

### 场景
黑板广播任务时，所有 Agent 都能看到。如果审查者（司马懿）认领了编码任务，会导致角色错配。

### 错误做法
simayi-challenger（审查者）认领了 coding 类型任务：
> "问题1：Agent 角色错配（P1）— simayi-challenger（审查者）认领了 coding 任务" [pangtong/mail #337]

数据中有大量司马懿被广播认领 E2E coding 任务的记录（frag_0076, frag_0305, frag_0320 等），说明这是系统性问题。

### 正确做法
1. 任务模板中明确角色匹配规则：只认领符合专长的任务
2. 评审/审查类角色不应认领编码任务
3. 任务描述中包含"只认领符合你专长的任务"的提示，但 Agent 仍然会忽略

### 关键细节
- 这个问题出现了 20+ 次（大量 E2E 广播任务被司马懿认领）
- 根因是黑板没有角色过滤机制，完全靠 Agent 自律

### 反面教训
角色错配导致编码任务被审查者执行，产出质量不符合预期。

---

## 卡片 12：inform 类型邮件不应触发完整 Agent 执行

```yaml
---
category: "Agent 行为"
tags: [邮件, inform, 资源浪费]
severity: "high"
frequency: 2
sources: ["simayi-correction", "mail"]
---
```

### 场景
Agent 之间的邮件有两种：需要回复的（request）和纯通知的（inform）。如果 inform 也触发完整 Agent 执行，会浪费大量 token 和 counter 资源。

### 错误做法
庞统的 daemon 用 `openclaw agent --agent simayi-challenger --timeout 300` 处理 inform 邮件，Agent 被完整 spawn 执行，310s 超时后重新投递：
> "daemon 用 openclaw agent --agent simayi-challenger --timeout 300 处理 inform 类型邮件，310s 超时后重新投递" [simayi-correction]

### 正确做法
1. Mail 类型简化为两种：inform（通知）和 request（需要回复）
2. inform 类型由 ticker 直接标 done，或用轻量 prompt（"读完标 done"）让 Agent 感知但不做完整执行
3. 默认类型是 request，inform 是显式指定的特殊场景

### 关键细节
- 庞统和用户讨论后确认：inform 仍让 Agent 看到内容（可能包含重要信息），但 prompt 告诉它不需要回复
- 这种设计比 ticker 直接标 done 更合理

### 反面教训
inform 触发完整执行 + 超时重投递，形成死循环，消耗大量 API 调用和 token。

---

## 卡片 13：回答用户问题要直接，不要绕弯子

```yaml
---
category: "沟通协作"
tags: [沟通, 直接回答, 效率]
severity: "medium"
frequency: 3
sources: ["pangtong-correction", "experience"]
---
```

### 场景
用户提出具体问题时，Agent 有时回避核心问题，用相关信息绕弯子。

### 错误做法
庞统在讨论 compact 卡死问题时，反复讨论"compact 发生的概率"和"v1 vs v2 的区别"，而用户已经明确说了"集中讨论 compact 发生后如何处理"：
> "你说得对，我一直在说废话。子进程就是 `openclaw agent`，等子进程就是等 `openclaw agent` 执行完" [pangtong/experience]

> "别绕了，我都说过了，请你集中在 compact 发生之后如何处理" [user]

### 正确做法
1. 先直接回答用户的问题（哪怕答案是"不能/不知道"）
2. 不确定时说"不能"，然后说明可以做什么
3. 庞统最终的模式值得学习："处理方式就三个问题：超时后怎么处理、重试用什么 session、重试几次后放弃。这就是全部。没有更复杂的了。"

### 关键细节
- "不能区分超时原因"本身就是一个有用的答案
- 不需要区分原因也能给出处理方案——区分原因只对监控有意义

### 反面教训
绕弯子浪费多轮对话，用户反复追问同一问题。

---

## 卡片 14：stdout JSON 解析路径必须实测验证

```yaml
---
category: "系统设计"
tags: [JSON解析, stdout, 实测]
severity: "medium"
frequency: 2
sources: ["simayi-correction"]
---
```

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

### 错误做法
spawner 代码取 `data.response.meta.transport`，但实际 `openclaw agent --json` 的顶层结构是 `{ response: { meta: { transport, ... } } }`：
> "P0：_parse_stdout_json 解析路径错误（根因）— openclaw agent --json 输出格式是 { response: { meta: { transport, ... } } } 但代码取的路径不对" [pangtong/mail]

### 正确做法
1. 解析外部命令输出前，先用实际命令验证输出格式
2. 评审时要求看到实测输出样例
3. 司马懿在 Spawner v3.0 评审中指出 JSON 解析路径需验证：`data.result.meta.executionTrace` 是新路径，需确认

### 关键细节
- 这个 Bug 从第一天就存在（"从第一天就存在的根因 bug"）
- 静默失败比报错更危险——解析不到数据时走了 fallback，掩盖了问题

### 反面教训
解析路径错误导致 stdout 信息丢失，所有依赖 stdout 判定的功能（如幻觉门控）失效。

---

## 卡片 15：跨项目调研的实践对照要做落地映射

```yaml
---
category: "经验总结"
tags: [调研, 对照表, 落地映射]
severity: "low"
frequency: 2
sources: ["experience"]
---
```

### 场景
Agent 调研多个开源项目的最佳实践后，需要将发现映射到自己的项目架构中。

### 错误做法
庞统调研了 OpenAI Agent SDK、LangGraph、Claude Code 三个项目，产出了详细的对照表，但部分条目只停留在"可借鉴"层面，没有明确"怎么做"。

### 正确做法
1. 调研产出必须包含"moziplus 可借鉴点"列，明确优先级（M2 直接借鉴 / 够用 / 暂不需要）
2. 标注哪些是**可直接复用的模式**（如 Lifecycle Hooks → on_node_start/end），哪些需要适配
3. 司马懿在评审 Pipeline 重构时引用了业界调研，确认"Temporal 模式最接近我们"

### 关键细节
- 调研本身就是 Agent 经验声明的一种形式
- 庞统的对照表格式（优秀实践 / 来源 / 核心模式 / 可借鉴点）是好的模板

### 反面教训
没有落地映射的调研容易变成"资料搬运"，无法转化为架构改进。
