diff --git a/docs/design/topic11-multi-project-proposal.md b/docs/design/topic11-multi-project-proposal.md index 35554be..194e3b0 100644 --- a/docs/design/topic11-multi-project-proposal.md +++ b/docs/design/topic11-multi-project-proposal.md @@ -179,6 +179,41 @@ class DaemonHealth: - `_check_working_tasks()` 中,working 任务超过 `task_timeout`(默认 10 分钟)视为完成 - 视为完成时主动 `decrement()`,防止计数器泄漏 +### 5.5 Daemon 崩溃恢复(v2 新增) + +**设计原则**:状态全在 SQLite,Daemon 无状态。重启 = 重新加载所有项目 + 所有任务状态,继续执行。 + +**恢复流程**: + +``` +PM2 检测 Daemon 挂了 → 重启 Daemon + │ + ├── 读取 _registry.yaml → 恢复项目列表 + ├── 遍历每个 active 项目 → 打开 SQLite 连接 + │ └── 读取所有任务状态(pending/working/completed/failed/...) + ├── 为每个 active 项目启动 ProjectSlot 线程 + │ + └── ProjectSlot._tick() 扫描 working 任务 + └── working 任务 → 重新 spawn Agent(可能冗余执行一次) + └── Agent 自己判断:上次做了没有?产出在不在? + ├── 产出已存在 → 跳过,直接报告完成 + ├── 做到一半 → 继续完成 + └── 没做过 → 正常执行 +``` + +**关键假设**: +1. **SQLite 是真相来源**——所有任务状态、产出记录都在 `.db` 文件里,Daemon 内存无状态 +2. **Agent 能判断重复**——AI native,Agent 看到 task context + 已有 output 文件,能自主判断是否需要重新执行 +3. **冗余执行无害**——即使一个节点被多执行一次,结果是幂等的(Agent 会检查产出是否已存在) +4. **无限续杯**——重启后 working 节点重新执行,属于已有的"无限续杯"机制(项目内运转机制,不在此设计) + +**不需要额外存储**: +- 不需要 checkpoint 文件——SQLite 就是 checkpoint +- 不需要 recovery log——`task_attempts` 表已记录每次尝试 +- 不需要状态快照——每次 tick 从 SQLite 实时读取 + +**唯一注意**:`ActiveAgentCounter` 重启后从零开始——但 `_tick()` 会重新扫描 working 任务并重新计数,所以没问题。 + ### 5.4 并发调度模型(v2 新增) #### 5.4.1 问题