将 maestro/tsk_74Acz1Nh4K1J 分支的 DESIGN.md 改动手动合并: 1. §19 分期状态:新增「🟡 worker 池化(已评估,暂缓)」说明 2. §20 风险与注意事项:新增「SDK 抖动 → resume 续跑」条目 保留了 HEAD 中已有的更详细版本的「自动执行安全」和「中断恢复(多进程 re-adopt)」条目。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
22 KiB
Maestro — 多项目 TODO 管理与 Agent 执行系统 · 设计方案 v0.2
本地优先的 daemon:管理多个本地 git 项目的任务,按复杂度分级驱动审批闸,并用后台 agent(Claude Code)自动执行可执行任务。
当前实现状态(2026-06):Phase 1(核心 + 看板 + MCP)与 Phase 2(编排器 + 执行器 + 双复审 + 自动合并)已全面落地;Phase 3 通知、额度透传、归档详情已一并完成,并把执行从 in-process 重构为 多进程解耦(daemon 监工 + 独立 worker 进程 + 文件通讯 + 重启 re-adopt,见 §7)。本文档对应已实现代码,不再是纯设计预稿。
Context(为什么做)
前身是 ~/.claude/skills/todo(JSON + HTML 单机原型):单项目、无真正后端、确认靠临时 http hack、无自动执行。它验证了「复杂度分级 + 确认闸 + 看板」的形态,但不可扩展。Maestro 把它升级为正式系统:多项目、任务层级化、复杂度驱动的审批闸、完整状态机、本地常驻 daemon、每项目一个后台执行 agent、与 Claude Code 深度集成(MCP + Agent SDK)。
0. 关键决策
| 维度 | 决策 |
|---|---|
| 运行时 | Node / TypeScript |
| 部署 | 本地优先 daemon(跑在用户机器,管理本地 git 项目) |
| Claude Code 集成 | MCP 服务(任务读写)+ Claude Agent SDK(headless 自动执行) |
| 自动合并边界 | PR accept 默认自动 --no-ff 合并(merge:false 逃生口:主检出脏时跳合并只通过) |
1. 系统总览
maestrod(核心 daemon,常驻 · 唯一 DB 写者 · 监工)
├─ 任务存储 SQLite ├─ 项目注册表
├─ REST + WebSocket API ├─ MCP server(给任意 Claude Code 会话)
├─ 编排器 Orchestrator └─ 监工:spawn worker / ingest outbox / 回收死 worker
│ (领取 + 文件通讯 + 判活,自身不跑 agent)
│
└── worker 进程(每任务一个,独立 OS 进程,哑执行器·完全不碰 DB)
执行 agent(executor run):Agent SDK 在 worktree 跑 headless CC
code review agent(reviewer run):只读,审 diff 产出报告 + verdict
安全审计 agent(security run):只读,仅审安全问题
↕ 文件通讯:job.json(daemon→worker)/ outbox.ndjson + heartbeat(worker→daemon)/ SIGTERM(取消)
Web 看板(多项目·实时·页面内 accept/reject)
闭环:建任务 →(Hard 分析拆解 / Medium 写方案 / Easy 写操作)→ 前置审批闸 → ready → 编排器 score 调度领取 → worktree 起 headless CC 执行 → verify → 双复审(code review + 安全审计)→ exec_review(带复审报告)→ accept 自动合并 → done。
2. 复杂度分级
Hard / Medium / Easy ↔ 旧 tier 1/2/3。每个任务(含子任务)都带 complexity,决定它走哪条「产出 + 审批」路径。(见 src/model/complexity.ts)
3. 任务层级
parent_id + depth;默认 ≤3 层,极复杂 ≤4 层(创建第 5 层时拒绝)。子任务各自带 complexity。叶子任务才可执行;被拆解的 Hard 是容器,进度由子任务汇总。
4. 三条产出路径与审批闸
| 复杂度 | 产出(执行前) | 前置闸 | reject 规则 |
|---|---|---|---|
| Hard | 强制进 plan 分析 → 产出「分析 + 任务拆解(子任务,各带复杂度)」 | plan_review,accept 才进下一步 |
必须带改进意见 → 退回重做并附 feedback |
| Medium | 写「具体方案 = 改动内容 + 为什么这么做」 | spec_review,accept/reject |
同上(必带意见) |
| Easy | 写清「将执行的操作」记录在任务上 | 无前置闸,直接可执行 | — |
结果闸 exec_review(所有产生改动的执行后、合并前):
- 展示 code review 报告(含 verdict:approve/reject)+ 安全审计报告(含 securityVerdict)
accept默认触发--no-ff自动合并(见 §11),成功后任务状态 →done,异步清理 worktree + 任务分支accept+merge:false(逃生口):仅通过审批,不触发合并(手动处理冲突或工作区脏时用)reject(必带改进意见)→ 返工,ready→ 等下一轮编排器重新领取
5. 状态机(见 src/model/status.ts)
init · analyzing · plan_review · decomposed · speccing · spec_review · ready · blocked · queued · executing · exec_review · failed · needs_attention · done · paused · cancelled
主要流转:
init→(Hard)analyzing→plan_review—accept→decomposed—子全 done→done;—reject(+意见)→analyzinginit→(Medium)speccing→spec_review—accept→ready;—reject(+意见)→speccinginit→(Easy,写完 operations)→readyready—deps 满足→queued→executing→exec_review—accept(auto-merge)→done;—reject(+意见)→ready(返工)executing→failed—自动重试 ≤2 次→queued/needs_attention(3 次失败后)
合法流转表与守卫在 TRANSITIONS / canTransition()。
6. 数据模型(见 src/model/types.ts 与 src/store/schema.sql)
- Project:repo_path、default_branch、verify_cmd、autonomy、model、concurrency、status、logo、sort_order、last_sync_at
- Task:project_id、parent_id、depth、complexity、status、priority(P0/P1/P2)、deps[]、plan/spec/operations、result(含 summary/verdict/securitySummary/securityVerdict/branch/worktree/commits/prUrl)、assignee、source_ref(旧 todo 映射用)、next_eligible_at(持久化退避)、retry_baseline(手动重投基线)
- ApprovalRecord:gate(plan/spec/exec)、action、actor、reason(reject 必填)、at
- Run:kind(planner/executor/reviewer/security)、worktree、branch、status、transcript_ref、claude_session_id、error、worker_pid(多进程执行的 worker pid,判活用)、last_seq(outbox ingest 幂等游标)
- Event(append-only):审计 + 实时看板推送源
TaskResult.prUrl 在合并后写 merged:<mergeCommit>,复用字段记录合并产物。
7. 多进程执行架构:监工 + worker(见 src/daemon/orchestrator.ts、src/daemon/ingest.ts、src/executor/{protocol,worker,pipeline}.ts)
执行已从早期的 in-process(编排器自己 await 跑 agent)重构为 多进程解耦。核心铁律两条:
- daemon = 唯一 DB 写者 + 监工:只负责领任务、spawn worker、把 worker 的产出摄取入库、回收死 worker。所有 DB 写(含执行进度/结果落库)都在 daemon 进程内,故 WS 推送照常经
store.subscribe触发。 - worker = 哑执行器:每个执行任务 spawn 一个独立 OS 进程(
node dist/executor/worker.js <runId>),它跑「worktree → 执行 → verify → 双复审 → 产出」全流程,但完全不碰 DB(连读都不碰),也不做任何重试 /needs_attention决策。重试、退避、转终态全归 daemon。
二者只经 文件 + 进程信号 通讯,故天然跨 daemon 重启持久、无内存在途态:
| 方向 | 载体 | 说明 |
|---|---|---|
| daemon → worker | runs/<runId>/job.json |
worker 的唯一输入:完整 Task + Project + 确定性 worktree 路径/分支。worker 据它自行 pickModel/buildPrompt,无需查 DB。 |
| daemon → worker | SIGTERM |
取消 / 超时。worker 兜底报 failed + done 后干净退出。 |
| worker → daemon | runs/<runId>/outbox.ndjson |
追加式、带单调递增 seq 的进度/结果流(started/phase/failed/result/done)。daemon 幂等 ingest(按 run.lastSeq 游标,只消费 seq>lastSeq)。 |
| worker → daemon | runs/<runId>/heartbeat |
心跳文件,worker 每 10s touch(刷 mtime)。daemon 据其 mtime「年龄」判活。 |
领取规则(编排循环由 MAESTRO_ORCH_INTERVAL 秒驱动,默认 15,0 关闭;每轮 ingest → reap → claim):
- 候选 = 叶子(非容器)、status=
ready或queued(重试/重启遗留)、deps 全done auto-easy模式只领easy任务;auto-approved领全部- 并发闸读
store.countExecuting(每个executing对应一个活 worker),退避读持久化的task.nextEligibleAt(早于它不领) - 按 §8 Score 降序排列,同分创建早的优先
领取一个任务(claimOne):算分支/worktree 路径 → executing → startRun(executor) → 写 job.json → spawnWorker(记 worker_pid)。spawn 用 detached:false(保持与 daemon 同会话,worker 内起的 claude 才能沿用 macOS keychain 鉴权)+ unref(daemon 退出后 worker 作为孤儿被 launchd 收养、继续跑)。env 用黑名单收敛:剥离 *_SECRET/*_TOKEN/*_KEY/AWS_/CF_/GH_ 等真密钥,保留会话变量(keychain 需要)与 ANTHROPIC_/CLAUDE_ 鉴权变量。
worker 执行管线(runPipeline,纯执行、全程经 emit 写 outbox):
createWorktree→emit phase:executing→runTask;失败 →emit failed+done,返回(不抛)runVerify(项目verify_cmd);失败 →emit failed+doneworktreeDiff→emit phase:reviewing→ 双复审顺序执行(code review → 安全审计,任一失败折叠为verdict=null、不挡任务)- 成功 →
emit result(branch/worktree/diffSummary/commits/executor/code/security)+done
daemon ingest(ingest.ts,把 outbox 映射为 DB 写):
result→ 建 reviewer/security 两条复审 run +setResult(四字段)+ 转exec_review+ 收尾 executor runfailed→finishRun(failed)+failTaskAttempt(失败/重试策略,见下)- 幂等:每条处理后
setLastSeq(seq);崩溃重启续读时已落库的seq不重做,终态记录只消费一次
失败/重试/退避(store.failTaskAttempt,是 ingest 的 failed、reaper 判死、reconcile 判死的唯一落点):净失败次数(相对 retry_baseline)未到 project.maxRetries(默认 2)→ queued + 持久化退避 next_eligible_at = now + min(30s·2^(n-1), 10min);到上限 → needs_attention(清退避)。
回收死 worker(reaper,每轮 tick):对每个 executing 任务按 worker_pid + 心跳 判活,死 → failTaskAttempt 回收重试。
重启 re-adopt / 回收(reconcileInterrupted,daemon 启动时):逐个 executing 任务判活——
- 活(心跳新鲜,或 boot 窗口内 pid 仍在)→ re-adopt:保留
executing,daemon 续 ingest 它的 outbox(worker 全程没停); - 死 →
failTaskAttempt回收重试。
判活逻辑(isWorkerAlive,见 protocol.ts):主信号=心跳新鲜(年龄 < HEARTBEAT_GRACE_MS=60s);started 后 BOOT_GRACE_MS=30s 启动空窗内额外接受 pid 存活兜底;窗口后仅认心跳——规避 pid 复用误判(被复用的 pid 不会更新本 run 的心跳)。
8. Score 调度(见 src/model/scoring.ts)
在领取那一刻现算(不落库):
score = baseScore(priority) // 自身分:P0=3 / P1=2 / P2=1
+ Σ baseScore(dep.priority) for dep where dep.status=done // 链条惯性:前置投入越重越优先出活
+ Σ baseScore(w.priority) for w where w.status=blocked // 解锁效应:等我的人越多越优先
- 依赖与解锁均只算一层(不递归):deps 在创建时即固定且只能引用已存在任务,依赖图天然无环;递归会让深链分数膨胀失控,故不递归。
- 同分时按
createdAt升序(先建先出)。 GET /api/agents暴露scheduling: "score"字段,前端可展示当前调度模式。
9. 双复审 PR 流程(见 src/executor/reviewer.ts)
执行成功 + verify 通过后,编排器顺序起两个独立的只读 headless Claude Code:
| Run | kind | 产出 | 工具白名单 |
|---|---|---|---|
| Code Review | reviewer |
做了什么/怎么做/测试情况/逐条问题/结论 + VERDICT: approve|reject |
Read/Grep/Glob + git diff/log/show/status |
| 安全审计 | security |
凭证泄露/注入/权限/UI红线词/部署风险 + VERDICT: approve|reject |
同上(只读) |
- 提示词强制限制:不得修改任何文件,不得执行除 git 只读命令之外的命令,核对执行者自述与实际 diff 一致性。
- 机器解析
VERDICT行(最后一个匹配项),去掉 VERDICT 行后的全文作为summary。 - 任一复审失败(抛错或无法起动)→
summary=失败原因,verdict=null,不挡主任务进exec_review。 - 看板在
exec_review卡片展示两份 markdown 报告 + verdict 徽章(建议通过 / 建议拒绝 / 未复审)。
10. 模型分级与回退(见 src/executor/models.ts)
| 角色 | Easy | Medium | Hard |
|---|---|---|---|
| executor(执行改动) | claude-sonnet-4-6 | claude-opus-4-8 | claude-fable-5 |
| reviewer(code review + 安全审计) | claude-sonnet-4-6 | claude-opus-4-8 | claude-opus-4-8 |
优先级(高→低):
project.model(若设置,executor/reviewer 均用此模型)- 环境变量覆盖(
MAESTRO_MODEL_EASY/MEDIUM/HARD,MAESTRO_MODEL_REVIEW_EASY/MEDIUM/HARD) - 上表默认值
回退链:claude-opus-4-8 → claude-sonnet-4-6。模型不可用(not_found/invalid/permission 等错误含 model 关键词)时,同一 run 内自动切换回退模型重试一次;链上全失败才抛错。
11. 自动合并边界(见 src/executor/merge.ts + src/api/server.ts)
POST /api/tasks/:id/decide { action:"accept" } 在 exec_review 时默认触发 mergeBranch:
两条路径(避免 git 重复检出问题):
defaultBranch正被主检出(git rev-parse HEAD一致)→ 直接在主检出里git merge --no-ff。要求工作区干净,否则拒绝并提示「先提交/暂存,或使用 merge:false 逃生口」。- 否则 → 在
<dataDir>/worktrees/_merge/<taskId>/建临时 worktree 检出 defaultBranch 后合并,合并后无论成败都删临时 worktree。
冲突处理:git merge --abort 后复原,error 返回冲突文件列表;两侧分支均无损,任务保留在 exec_review。
合并成功后(异步):删任务分支 git branch -d <branch> + 回收任务 worktree(失败只记日志,不影响响应)。
merge:false 逃生口:POST /decide { action:"accept", merge:false },仅通过审批,不合并分支(用于主检出有未提交改动时手动处理)。
12. 归档与运行历史(见 src/api/server.ts + src/store/store.ts)
GET /api/tasks/:id/events:单任务全量事件升序(状态流转/审批/运行历史),供归档详情时间线。GET /api/tasks/:id/runs:该任务全部 run 记录(kind/status/started_at/ended_at/transcript_ref/error)。GET /api/tasks/:id:含result(branch/commits/diffSummary/summary/verdict/securitySummary/securityVerdict/prUrl)。- 看板归档详情面板:状态流转时间线 + run 列表 + code review / 安全审计报告展开。
13. macOS 通知(见 src/daemon/notify.ts)
daemon 启动时订阅 Store 事件流,符合条件时用 osascript display notification 发原生通知:
| 触发事件 | 通知文案 |
|---|---|
status → plan_review / spec_review |
⏳ <任务名> 等待审核(拆解/方案) |
status → exec_review(复审均通过) |
⏳ <任务名> 等待审核(PR) |
status → exec_review(任一 verdict=reject) |
⚠ <任务名> 复审建议拒绝(PR 待审核) |
status → needs_attention |
❌ <任务名> 连续失败需人工 |
approval.granted gate=exec |
✅ <任务名> 已合并完成 |
- 同一任务同类型通知 60s 内只发一次(抑制重复触发)。
MAESTRO_NOTIFY=0关闭;非 macOS 平台自动不启用。- osascript 字符串注入防护:去换行、转义反斜杠/双引号、截断 80 字符。
14. 额度透传(见 src/daemon/usage.ts)
GET /api/agents 响应中附带 usage 字段,展示 Claude 订阅额度消耗:
{
"usage": {
"session": { "percent": 42, "resetsAt": "2025-06-13T18:00:00Z" }, // 5 小时滚动窗口
"weekly": { "percent": 18, "resetsAt": "2025-06-16T00:00:00Z" } // 7 天窗口
}
}
- 数据源:
GET https://api.anthropic.com/api/oauth/usage,与 Claude Code statuslinerate_limits同一端点。 - 凭证:优先
~/.claude/.credentials.json(OAuth accessToken),其次 macOS keychain(service "Claude Code-credentials")。token 只进内存,绝不写日志/响应。 - 60s 内存缓存 + 并发去重(in-flight 共享);任何失败(无凭证/网络/非200)→
null,前端降级显示。
15. 后端架构
- 存储 SQLite(better-sqlite3,单文件零运维);run 转录日志存 JSONL 文件,Run 表引用路径。daemon 是唯一 DB 读写者(worker 进程完全不碰 DB,见 §7)。
- API:REST(CRUD)+ WebSocket(
/ws,Store 事件广播,驱动看板实时刷新)。 - 项目绑定:
maestro project add <repo>记录 repo 路径/默认分支/verify/autonomy/model/并发;daemon 中央 DB 行。 - Worktree:执行在 repo 的 git worktree(隔离分支),合并由 §11 逻辑控制,不自动 push。
- 进程间通讯目录:
<dataDir>/runs/<runId>/(job.json/outbox.ndjson/heartbeat),是 daemon 与 worker 的唯一共享面,跨重启持久(见 §7)。worker_pid、last_seq、next_eligible_at三列落 DB,使在途态/退避无需内存即可恢复。
16. Web 看板(web/)
多项目切换(侧栏拖动排序)/ 全局总览;任务树(层级折叠)、复杂度徽章、状态、内联审批闸(accept/reject,reject 必填意见)、实时执行状态、exec_review 的 code review + 安全审计报告 + diff/分支信息。实时 WS 驱动。
项目支持自定义 logo(仓库内文件路径或外链),无 logo 时取首字母徽章兜底。
17. Claude Code 集成
- MCP server(交互面):项目里的 Claude Code 配
.mcp.json接maestro-mcp。工具:list_projects/create_project/list_tasks/get_task/create_task/decompose_task/write_plan/write_spec/write_operations/update_status/get_next_executable/get_pending_approvals(accept/reject 在 Web 看板操作)。 - Agent SDK(自动面):编排器 spawn 的独立 worker 进程用 Claude Agent SDK 起 headless CC 跑 executor / reviewer / security run(见 §7 多进程架构)。
- approve/reject 是用户动作,走看板按钮(或直接
POST /api/tasks/:id/decide)。
18. 从现有 skill 迁移
旧 todo.mjs/JSON 作为 v0 原型被取代。同步引擎(src/sync/todo-sync.ts):读旧 todo/todo.json,tier1/2/3 → hard/medium/easy,单向幂等同步(POST /api/projects/:id/sync)。旧侧 done/accepted 任务自动沿合法路径推到 maestro done。
19. 分期实施状态
- ✅ Phase 1(核心,已完成):daemon + SQLite + 项目注册 + 任务模型 + 状态机 + Web 看板 + MCP(读写 + 审批)+ CLI + todo 同步引擎。
- ✅ Phase 2(自动化,已完成):编排器 + score 调度 + Agent SDK executor(Easy/Medium/Hard worktree 自动执行)+ verify + 双复审(code review + 安全审计)+ 自动合并(--no-ff + 逃生口)。
- ✅ Phase 3(打磨,核心已完成):多进程执行解耦(daemon 监工 + 独立 worker 进程 + job.json/outbox/heartbeat 文件通讯 + 重启 re-adopt,见 §7)+ macOS 原生通知 + Claude 订阅额度透传 + 归档详情时间线 + 模型分级(复杂度→模型映射 + env 覆盖 + 回退链)+ 项目 logo + 侧栏拖动排序。
- 🔲 后续:并发资源精细监控 / 指标面板 / 可选远程看板 / PR 平台集成(GitHub PR 链接)。
- 🟡 worker 池化(已评估,暂缓):长驻 worker 从队列拉任务、省 spawn 冷启动开销,仅在 spawn 量很大时才有收益。当前模型是干净的「一 run ↔ 一 pid」:
setWorkerPid/isWorkerAlive/reaper/reconcileInterrupted全靠「每 run 一个 pid + 一份心跳」判活与恢复,且 worker 与 DB 完全隔离 + 重启 re-adopt 天然得到崩溃恢复。池化会打破该不变量(一个池 worker 在跑哪个 run?单进程崩溃波及 N 个任务、共享 worktree 冲突),收益只在高并发量下兑现。现规模下保持「每任务一进程 + 失败从头重跑」的简单模型,不引入池化。
20. 风险与注意事项
- Agent SDK headless 执行可靠性:verify_cmd 把关 + 双复审 + needs_attention 人工兜底。executor run 最多 100 轮、30min 超时兜底;reviewer run 最多 40 轮、15min 超时兜底。
- SDK 抖动 → resume 续跑(
cc.ts):单次会话若因流式异常中断(流断 / 没收到 result,非超时取消、非 max_turns 等终态),且已拿到 sessionId,则在 worker 进程内用resume接着原会话续跑一次(保留已干的活,不从头重来),剩余预算不足时至少留 60s。这是 worker 内部健壮性小优化,与「重启靠 re-adopt、整体失败靠 daemon 从头重跑」正交、不替代它们;MAESTRO_SDK_RESUME=0可关闭。与模型不可用回退(换模型重开会话)互斥。 - 自动执行安全:worktree 隔离(改动不进主检出)+ 不自动 push + merge 需 exec_review accept + 安全审计 run 专门检查凭证/注入/权限问题;worker 进程不碰 DB(无法越权改任务状态),spawn 时黑名单剥离真密钥类 env。
- 中断恢复(多进程 re-adopt):daemon 重启后
reconcileInterrupted()按worker_pid + 心跳真判活——worker 仍活则 re-adopt(保留 executing,续 ingest 其 outbox,不打断正在跑的 agent);已死则failTaskAttempt回收重试。daemon 与 worker 解耦后,daemon 崩溃/重启不再丢失在途执行。 - 长任务上下文超限:100 turns / 30min 兜底,超时后转 failed → 重试。
- worktree 泄漏:合并后异步清理,daemon 启动时
git worktree prune清残留。