MAESTRO · 重构全景 2026-06-22 · 现状 → 目标 → 计划
现状架构
Maestro 当前系统全景——进程模型、前后端、数据流、调度算法、执行管道与模型档位。
系统总览
MAESTRO 系统架构(现状) ⚠ 现状问题点 / 重构待改 用户端 Browser — Vanilla JS SPA web/index.html + app.js 多项目看板 任务列表实时刷新 任务详情 plan/spec/events/runs 审批 approve / reject ⚠ 无流式输出 只能看 transcript Daemon 进程(唯一 DB 写者) Fastify API Server :4517 REST 40+ · WS /ws src/api/server.ts Orchestrator Loop · 每 15s ingest → reap → claim src/daemon/orchestrator.ts ⚠ rankByScore:简单优先级加总,无 CPM / 无防饥饿 / 忽略文件耦合 ⚠ claimOne 无 CAS → 并发可双重领取同一任务 SQLite DB ~/.maestro/maestro.db WAL + 外键 tables(5 张) projects · tasks · runs · events · approvals task 用单一扁平 status 列建模 ⚠ 缺列:claimed_at · task_type · scope · owned_files · version ⚠ 状态用扁平 FSM(16 个混合 status),无 schedule × work_type 正交建模 Worker 进程(每任务独立 · 不接触 DB) executor pipeline(复审内联、串行) syncMain createWorktree runTask (CC) verify 可选 worktreeDiff ⚠ reviewCode 串行 ⚠ reviewSec 串行 exec_review planner pipeline(只读) buildPlannerPrompt runPlanner (CC 只读) ⚠ decompose JSON 缺 ownedFiles conflict pipeline(无专用 agent) git merge ⚠ CC 自己猜着解 双复审 Claude Code Agent(Headless) cc.ts · @anthropic-ai/claude-code SDK for-await 流式(仅落 transcript) executor 白名单 Read/Edit/Write/Glob/Grep · Bash(git,test) · WebSearch planner 白名单 Read/Glob/Grep · Bash(git log/diff/show) src/mcp/ MCP src/sync/ todo 同步
前端(现状)
维度现状
技术栈Vanilla JS + CSS,无构建工具
入口web/index.html 静态文件,Fastify 直接 serve
状态管理全局 JS 变量 + 手动 DOM 操作
实时通信WebSocket 收事件后手动更新 DOM
流式输出无(只看完成后 transcript)
代码分割单文件 app.js (~1500 行)
类型安全无(纯 JS)
进程模型(现状)
角色职责
Daemon唯一 DB 写者;API+WS 服务;Orchestrator 调度循环;ingest worker 产出
Worker每任务独立 OS 进程;不碰 DB;纯文件通信
CC Agent在 worker 内 SDK headless 运行;工具白名单隔离
通信file + SIGTERM(可存活 daemon crash)
REST API 主要路由(重构后 · 完整契约见 ③设计方案 ⑧ 前端 API 契约)

Projects

GET /api/projects
POST /api/projects
GET /api/projects/:id
PATCH /api/projects/:id
DELETE /api/projects/:id
POST /api/projects/:id/sync
POST /api/projects/reorder

Tasks

GET /api/projects/:id/tasks
POST /api/projects/:id/tasks
GET /api/tasks/:id
POST /api/tasks/:id/plan
POST /api/tasks/:id/spec
POST /api/tasks/:id/decide
POST /api/tasks/:id/requeue
POST /api/tasks/:id/cancel
DELETE /api/tasks/:id
POST /api/tasks/:id/attachments
POST /api/tasks/:id/takeover

Runs / Events

GET /api/tasks/:id/runs
GET /api/runs/:id/transcript
GET /api/tasks/:id/events
GET /api/projects/:id/events
GET /api/agents
GET /api/usage
GET /api/metrics
GET /api/tasks/:id/stream
GET /api/health
WS /ws
数据库 Schema(现状)
projects id · name · repo_path · autonomy · concurrency · max_retries · timeout_ms verify_cmd · model · checks · auto_approve_plan · auto_approve_exec tasks id · project_id · parent_id · depth · title · body · complexity · status priority · deps(JSON) · plan · spec · operations · result(JSON) retry_baseline · next_eligible_at · last_run_error · created_at · updated_at ⚠ 缺少:schedule_status · work_type · work_plan · work_history · claimed_at · scope · expected_output · version runs id · task_id · kind(planner/executor/conflict) · status · worktree · branch transcript_ref · session_id · worker_pid · last_seq · started_at · ended_at ⚠ 缺少:trace_id events id · project_id · task_id · type · payload(JSON) · at approvals id · task_id · gate(plan/spec/exec) · action · actor · reason · at
任务状态机(生命周期)
Maestro 任务状态机(现状 · 扁平 FSM) Hard Medium Easy cancel 拆解完成 accept reject 重拆 子任务全 done 方案完成 accept reject claim 退回 worker 启动 执行完成 accept(合并) 依赖未满足 依赖满足 执行失败 失败 reject(返工) 重试 超阈值 失败超限 失败超限 重排队 重新分析 重写方案 暂停 恢复 init analyzing plan_review decomposed speccing spec_review ready queued executing exec_review done blocked failed stuck paused cancelled Hard 拆解 Medium 方案 执行调度 Agent 运行 异常/失败 暂停(虚线) 注:paused 与所有活跃态双向互通;cancelled 任意态可达(略去以保主干清晰)。本图为重构前扁平 FSM,对照「重构目标」的 schedule × work_type 正交模型。
调度算法(现状)

评分公式

score(t) = baseScore(priority) P0=3,P1=2,P2=1
         + Σ baseScore(d) d=已完成依赖
         + Σ baseScore(b) b=被本任务阻塞
⚠ 无防饥饿 · 忽略文件耦合 · 无 CAS

调度流程

ingest 读 outbox → 写 DB
reap 心跳超时 → failAttempt
claim rankByScore → startRun
⚠ 并发 tick 可双重领取
执行管道(现状)
syncMain
createWorktree
runTask (CC)
verify 可选
worktreeDiff
reviewCode 串行
reviewSecurity 串行
exec_review
⚠ 串行复审(耗时翻倍) ·  verdict=reject 只是建议不挡人工审核 ·  无分项检查闸 ·  无 diff 体量限制
模型档位(现状)
角色EasyMediumHard
executorclaude-sonnet-4-6claude-opus-4-8claude-opus-4-8
plannerclaude-sonnet-4-6claude-opus-4-8claude-opus-4-8 ⚠
reviewerclaude-sonnet-4-6claude-opus-4-8claude-opus-4-8 ⚠
conflictclaude-opus-4-8 ⚠(不够强)
回退链['claude-opus-4-8', 'claude-sonnet-4-6'](缺 fable-5)
重构后架构
Phase 1(本次实施)完成后的目标态。绿=新增 琥珀=变更 红=移除
系统总览(重构后)
MAESTRO 系统架构(重构后目标态) ★ Phase 1 新增/变更 ★★ Phase 2 规划 用户端 Browser — Vanilla JS SPA web/index.html + app.js (Phase 1 不变) ★★ React 18 + Vite Zustand · IBM Plex Mono [Phase 2] ★★ SSE 流式 token 输出 GET /api/tasks/:id/stream [Phase 2] Daemon 进程(唯一 DB 写者) Fastify API Server :4517 REST 40+ 路由 · WS /ws src/api/server.ts(沿用) Orchestrator Loop · 每 15s ingest → reap → claim ★ 调度重构(scoring.ts) rankU = CPM 最长链 + agingBonus(防饥饿) − couplingPenalty(文件耦合) ★ casClaimTask:CAS 防双 claim ★ 按 work_type dispatch agent SQLite DB ~/.maestro/maestro.db WAL + 外键 ★ tasks:schedule_status × work_type(取代扁平 status) + work_plan / work_history 队列 · owned_files · scope · expected_output · claimed_at · version ★ runs.kind 扩 plan|code|review|pr|conflict|research · + trace_id ★ projects + checks / diff_max_files / agent_rules ⛔ 已弃用:task_type / sub_status / state_snapshot / parent_version_id(被 schedule_status + work_plan 队列取代) work_plan 队列(线性、运行时决定执行路径) [ {type, spec, owned_files, expected_output}, … ] ← Orchestrator pop 队头 → (ready, 下一 work_type) waiting(plan|review) 为人工闸,不占并发名额 Worker 进程(每任务独立 · 不接触 DB) — 按 work_type 拆独立 run plan run(Planner Agent) runPlanner maxTurns 90/60/40 ★ decompose JSON + ownedFiles / expectedOutput ingest → 子任务 work_plan ① code run(Executor Agent) syncMain createWorktree runTask (CC) verify ✓ ★ checks 分项闸 worktreeDiff ★ diff 体量闸 commit ② review run(Reviewer Agent · 独立、可选) ★ Promise.all 双复审(code + security) ★ verdict=reject → 硬拦截(回 code) waiting(review) 人审 ③ pr run(PR Agent · review 通过后串联) 创建 PR --no-ff 合并 冲突 → conflict run done ✓ conflict run(Conflict Agent · 抢占调度 · opus-4-8) git merge 制造冲突 CC 解冲突 ★ 双复审 commit Claude Code Agent(Headless) cc.ts @anthropic-ai/ claude-code SDK for-await 流式 ★ 模型档位(统一最强档) executor: sonnet / opus / opus planner-hard · reviewer · conflict → opus-4-8 回退链 [opus-4-8, sonnet-4-6] (fable-5 暂不可用,opus 充当最强档) ★ Agent 记忆注入 L1–L4 L4 全局规范 · L2 项目 agent_rules L3 拆解背景(父/兄弟)· L1 上次失败 daemon claim 时组装进 job.json ★★ onToken callback → EventEmitter → SSE [Phase 2]
Phase 1 变更清单
文件变更内容类型
schema.sql + db.tstasks 状态列重构:status→schedule_status + work_type/work_plan/work_history/scope/owned_files/expected_output/claimed_at/version;runs.kind 扩 review|pr|conflict + trace_id;projects 加 diff_max_files/checks/agent_rules;ensureColumn 向后兼容旧库变更
types.ts新增 ScheduleStatus / WorkType / WorkStep 类型;Task 接口以 schedule_status × work_type + work_plan 队列取代旧 status 单态新增
mappers.tsTaskRow/ProjectRow 追加字段;rowToTask/rowToProject 映射变更
store.tscasClaimTask(CAS);transition→ready 清 claimed_at;requeueTask 清零;assertNoActiveRun 执行期锁变更
orchestrator.tsclaimOne 接 casClaimTask;rankByScore 传 inFlight+nowMs;按 work_type dispatch agent(code/plan/review/pr/conflict)变更
scoring.tsrankU(CPM) + agingBonus(防饥饿) + couplingPenalty(文件耦合) + 新签名 rankByScore变更
models.tsplanner-hard/reviewer/conflict → opus-4-8(最强档);回退链 [opus-4-8, sonnet-4-6](fable-5 暂不可用,恢复后改档位表即可)变更
runner.ts + protocol.tsdecompose JSON 扩展 ownedFiles/expectedOutput;prompt 更新变更
ingest.tsdecompose-result 映射新字段到子任务变更
pipeline.tscode run:checks 分项闸(verify 后逐项执行)+ diff 体量闸(超 diffMaxFiles 硬失败);复审从 code pipeline 拆出为独立 reviewer run;新增 pr / conflict run 分支新增

调度算法(重构前)

score(t) = baseScore(priority)
         + Σ baseScore(d)  // 已完成依赖
         + Σ baseScore(b)  // 被阻塞任务

无防饥饿(低优先永远靠后)
忽略文件耦合
无 CAS 防双重 claim

调度算法(重构后)

score(t) = rankU(t)        // CPM 关键路径(最长链)
         + agingBonus(t)  // 防饥饿,48h 线性到 base
         - couplingPenalty(t) // 未合并 worktree 文件重叠 -0.5/个

rankU(t) = baseScore(t.priority)
         + max(rankU(d) | d 直接依赖我)  // 取最长,非求和
// 叶子无 dependent → 仅 baseScore;最长链封顶,不膨胀
agingBonus = base × min(1, waitHours/48)  // 48h 达 base
casClaimTask: WHERE claimed_at IS NULL
执行管道(重构后)— 按 work_type 拆独立 run
每个 work_type 是一次独立 Worker run,run 间由 work_plan 队列串联——复审不再内联进 executor,而是独立 reviewer run;合并独立成 pr run。
① code run
syncMain
createWorktree
runTask (CC)
verify ✓
checks分项闸 ★
worktreeDiff
diff体量闸 ★
commit
② review run(独立 reviewer run,可选)
Promise.all 双复审(code + security)★
verdict=reject → 硬拦截(回 code)★
waiting(review) 人审
③ pr run(review 通过后串联)
创建 PR
--no-ff 合并
冲突 → conflict run
done ✓
★=新增硬闸 · 复审为独立 reviewer run(非 code 内联),run 内 code+security 仍 Promise.all 并行 · work_plan 不含 review 时整段可跳过、code 完成直接进 pr
模型档位(重构后)
角色EasyMediumHard备注
executorclaude-sonnet-4-6claude-opus-4-8claude-opus-4-8不变
plannerclaude-sonnet-4-6claude-opus-4-8claude-opus-4-8Hard 升最强档
reviewerclaude-opus-4-8claude-opus-4-8claude-opus-4-8三档统一最强,不被 project.model 降档
conflictclaude-opus-4-8(固定,不降档)
回退链['claude-opus-4-8', 'claude-sonnet-4-6']
⚠ 设计目标是「最强档」;claude-fable-5 暂不可用,当前一律以 claude-opus-4-8 充当最强档(commit a5a3c96 已全量替换)。fable-5 恢复后只需改 models.ts 档位表,结构不变。
Phase 2 预告(本次不含)

SSE 流式

cc.ts onToken callback
→ daemon EventEmitter
→ GET /api/tasks/:id/stream
前端实时看 token

Hook 合约

.maestro/hooks.ts
MaestroHooks 接口
5s 超时取消

Trace ID

JobSpec.traceId
outbox 每条携带
跨进程可观测

React 前端

React 18 + Vite
Zustand 状态管理
IBM Plex Mono 设计
SSE token 显示
完整设计方案
Maestro 大重构的完整技术蓝图:schedule × work_type 状态机架构 · 数据库 Schema 变更 · Agent 记忆注入 · Phase 1 实施路线。
架构决策总览
🗂 分期标注:各章头部 Phase 1 = 本期核心重构(对应 ④具体实现 T1–T6);Phase 2 = 能力扩展(成本预算 / 安全沙箱 / 交互接管 / SSE 等);混合 = 章内分属两期,以卡片内标注为准。标注为建议范围,最终以实施计划为准。
六大核心决策
#决策选择关键理由
D1状态机架构schedule_status × work_type 双轴,替代平铺 FSMschedule_status 对齐 OS 进程模型(OS 五态),描述资源调度;work_type 描述当前执行何种工作(plan/code/review/pr…);执行路径由混合模式动态决定,无需静态枚举所有路径
D2任务类型扩展work_type 注册表(新类型 = 加 agent + dispatcher 分支)核心代码零改动;schedule_status 通用状态(ready/running/waiting…)完全复用,无需改状态机
D3DB 状态存储schedule_status + work_type + work_plan 队列(JSON)schedule_status / work_type 双列供 SQL 索引快速过滤与调度;work_plan 队列自带恢复位置,不引入 XState snapshot
D4Agent 记忆四层注入(L1 失败原因 / L2 项目规范 / L3 跨任务上下文 / L4 全局规范)worker 不碰 DB,所有记忆由 daemon claimOne 组装进 job.json;ROI 由高到低
D5流程硬闸复审 reject 变硬闸 + 分项 checks + diff 体量闸消除"复审仅建议"漏洞;静态检查前置防止无效执行进审批
D6并发调度CAS claimed_at + couplingPenalty(文件重叠降分)防双重 claim 竞态;相同文件的任务并发执行概率降低,冲突减少
重构前后对比快览

现有设计(平铺 FSM)

• 16 状态平铺,全局 TRANSITIONS 表
• CANCEL/FAIL 每个子状态重复声明
• code + security 复审串行
• 暂停恢复需 paused_return_to 字段
• 新任务类型 = 修改核心转移表
• claimed_at 无 CAS,存在双重领取竞态
• reviewer reject 不挡流,仅建议
• agent prompt 无失败记忆 / 无规范注入

重构后(schedule × work_type + 四层记忆)

• schedule_status × work_type 双轴正交状态
• work_plan 队列动态决定执行路径(混合模式)
• waiting 人工闸不占并发名额,running 才占
• (paused, work_type) 自带恢复位置,无需额外字段
• 新 work_type = 加 agent + dispatcher 分支,零状态机改动
• CAS claimed_at + couplingPenalty 调度
• reviewer reject = 硬闸,回退重执行
• L1-L4 记忆注入 + 项目/全局规范
进程模型铁律(不改)

Daemon — 唯一 DB 写者

REST + WebSocket API(:4517)
Orchestrator 调度循环(ingest → reap → claim)
写 job.json 时组装全部记忆(L1-L4)
读 outbox.ndjson 入 DB,状态机 send(event)

Worker — 不碰 DB

读 runs/<runId>/job.json
执行 agent pipeline,追加 outbox.ndjson
任何 DB 数据 → daemon 组装进 job.json 下发
状态机 actor 在 daemon 侧管理
状态设计(schedule × work_type)Phase 1
任务提交与入口(Ingestion)

提交方式不变——主入口仍是网页表单。提交后任务落 (init, 无 work_type),再由系统赋 work_type / 产 work_plan 进入调度。

三条入口

入口路径用途
网页表单「+ 新建任务」→ POST /api/projects/:id/tasks人手动提交(主入口
MCP 工具create_task / decompose_taskClaude Code 会话 / agent 编程建任务
todo.json 同步src/sync/ 单向同步legacy /todo 数据自动导入
GitHub issue(未来)webhook 入站 → 建任务TODO #1 双向同步,本期不含

提交字段(极简 + 可选增强)

字段必填?说明
title✅ 必填唯一硬要求
complexity默认 autoauto = LLM 异步分类回填(hard/medium/easy);也可手填
priority / deps可选缺省 P2 / 无依赖
owned_files / scope / expected_output可选新模型增强:喂调度 couplingPenalty + 给 planner 更准起点;缺省由 planner 补全
设计取向:提交极简(只逼 title),质量靠 planner 兜;可选字段让愿意填的人给更强的调度/拆解信号,但从不强制。

提交后流转

提交
(init, 无 work_type)
plan run:planner 产 work_plan
(ready, 首 work_type)
调度
Hard/Medium 走 plan(planner 拆解 / 写 spec 产 work_plan 队列);Easy 跳过 plan,直接 work_plan=[code, review, pr] → ready。autonomy(manual / auto-easy / auto-approved)决定提交后是自动起跑还是等人放行

附件支持(图片 / 文件)Phase 2

设计
表单「+ 新建任务」支持拖拽 / 粘贴截图 + 上传文件(bug 截图、设计稿、规格文档、数据样本)
API / 存储POST /api/tasks/:id/attachments(multipart)→ 存 tasks/<taskId>/attachments/tasks.attachments JSON 存 [{name, type, path}]
喂给 agent附件路径随 job.json 下发 → worker 把图片直接喂进 agent 多模态输入、文件作为 Read 目标;附件是任务上下文的一等部分(并入 ⑤ AgentContext)

AI 提任务(agent-initiated)Phase 2

已部分存在(MCP create_task + planner decompose 产子任务)。扩展为一等能力:

来源设计
执行中衍生agent 执行时发现额外工作 → 经 MCP 提关联任务(parent / sibling),不打断当前 run
巡检 agent可注册的 groomer agent(work_type,呼应 ⑤ 动态扩展)周期扫 repo / backlog / issue → 提议任务
来源标记tasks.source(human|ai|sync|github)+ created_by(提交它的 agent run id),区分人提 vs AI 提
🛡 护栏:AI 提的任务默认进「待确认新任务」人工闸(不自动起跑),人审后才入调度——防 AI 自我增殖任务失控烧钱(呼应 ⑥ 预算护栏)。是否需人审由项目 autonomy + AI-task 开关决定;纯子任务拆解(planner decompose)已有 plan_review 闸覆盖。
schedule_status(调度状态,对齐 OS 进程模型)
状态OS 类比含义可用 work_type
initNew任务刚创建,尚未确定执行路径,无 work_type
readyReady有 work_type,等待 Orchestrator 调度任意
runningRunningWorker 正在执行当前 work_type 对应的 agent任意
waitingBlocked (I/O)等待人工输入(审批/拒绝),无 agent 在运行保持完成步骤的 work_type(如 plan / review)
blockedBlocked (deps)等待任务依赖完成,deps 满足后自动回 ready保持上次 work_type
pausedStopped人工暂停,恢复时回到 ready(保持 work_type)保持上次 work_type
failedZombie当前步骤失败,退避后自动重试(回 ready);超重试上限 → stuck同失败时 work_type
stuckStopped (wait human)任务卡住需人工介入:①重试耗尽 ②agent 主动上报歧义/不可解 ③审批超时兜底。自动化停止,等人决策保持卡住时 work_type
doneTerminatedwork_plan 队列全部完成,终态
cancelledKilled人工取消,终态
stuck 是一等 schedule_status 值,直接存 DB 列——可用 WHERE schedule_status = 'stuck' 直接查,无需 retry_count 联合判断。人工处理完成后可转 ready / done / cancelled。
work_type(工作类型,决定用哪个 Agent)

Agent 类型(schedule = ready → running)

work_type执行者主要工作
planPlanner Agent分析任务,产出初始 work_plan(步骤队列 + 每步 spec)
codeExecutor Agent在 worktree 里实现代码变更,commit
reviewReviewer Agent代码审查 + 安全审计(run 内 Promise.all 并行),输出 verdict。独立 run、不并入 code;可选(work_plan 可省略)
prPR Agentreview 通过后创建 PR + 完成合并(冲突 → conflict)
conflictConflict Agentgit merge 制造冲突 → CC 解 → 复审
researchResearch Agent技术调研,产出报告(无代码修改)

人工闸(schedule = waiting,无 Agent 运行)

waiting 的 work_type 直接沿用上一步完成时的值,不引入新枚举——waiting(plan) 等人审 plan 输出,waiting(review) 等人审代码复审结果。

当前 work_type等待内容通过 → 下一步拒绝 → 下一步
plan人审 Planner 产出的 work_planpop 队头,进 ready(下一 work_type)重回 ready(plan),带拒绝原因重规划
review人审执行结果 + 复审 verdictpop → pr 步(提 PR + 合并)→ doneready(code),带拒绝原因重做

为什么 waiting ≠ running

人工闸没有 worker 进程在运行,Orchestrator 不应把并发名额算在它上面;running 才占名额,waiting 不占。

混合模式:下一步如何决定(Hybrid Next-Step)

Plan 阶段:产出初始 work_plan 队列

{
  "type": "plan-complete",
  "work_plan": [
    { "type": "code",
      "spec": "实现 auth 模块",
      "owned_files": ["src/auth/*.ts"],
      "expected_output": "typecheck 通过" },
    { "type": "review" },
    { "type": "pr",
      "spec": "创建 PR 到 main" }
  ]
}
// → waiting(plan)  等人审计划
// 人 approve → pop 队头 → ready(code)

执行阶段:每步可修改剩余队列

{
  "type": "step-complete",
  "result": "发现 auth 复杂,拆成2步",
  "update_plan": [          // ← 可选,修改剩余队列
    { "type": "code",
      "spec": "先实现 session 层" },
    { "type": "code",       // ← 动态插入新步骤
      "spec": "再实现 token 层" },
    { "type": "review" }
  ]
}
// 无 update_plan → pop 队头继续
// update_plan 非空 → 替换剩余队列

队列为空时

step-complete 后 work_plan 队列为空 → schedule_status = done,任务完成。

Easy 任务无需 plan 步骤:init 时 Orchestrator 直接写入默认队列 [code, review],跳过 plan。

完整转移规则
init ready running waiting done paused blocked failed stuck cancelled 写入队列 claim plan/review done review approve→pr approve / reject pr merge code→review (auto) error retry retry 耗尽 agent 上报 人工: 重试 人工: 完成 人工: 取消 deps⬇ deps✓ 暂停 恢复 自动 人工 异常 自动串联
执行路径示例

Hard 任务(需规划)

(init, -) ↓ Orchestrator 写队列 [plan, code, review, pr] (ready, plan) ↓ worker 启动 (running, plan) ↓ plan 产出 work_plan(可拆多 code 步 / 决定免 review) (waiting, plan) ↓ 人 approve (ready, code) → (running, code) ↓ code agent 完成(commit;run 结束) (running, review) ← 自动串联(独立 reviewer run,非 code 内联) ↓ review 完成(run 内并行双复审 verdict) (waiting, review) ↓ 人 approve (running, pr) ← approve 后串联 ↓ pr agent 提 PR + 合并(冲突→conflict run) done ✓

Easy 任务(跳过规划)

(init, -) ↓ Orchestrator 写队列 [code, review, pr] (ready, code) ↓ worker 启动 (running, code) ↓ code agent 完成 (running, review) ← 自动串联(独立 reviewer run) ↓ review agent 完成 (waiting, review) ↓ 人 approve (running, pr) → 提 PR + 合并 → done ✓

review 可选:work_plan 不含 review 时,code 完成 → 直接 (running, pr);纯只读任务可仅 [research],无 code/review/pr。

动态拆步(运行中修改计划)

(running, code) ↓ agent 发现比预期复杂 ↓ step-complete + update_plan: [code(token层), review] (ready, code) ← 新步骤(session 层) (running, code) ← 再次 code(token 层) (running, review) → (waiting, review) → done ✓
DB 字段(tasks 表关键变更)
字段类型说明
schedule_statusTEXT取代旧 status:init|ready|running|waiting|blocked|paused|failed|stuck|done|cancelled
work_typeTEXT当前工作类型:plan|code|review|pr|conflict|research|null
work_planTEXT JSON剩余步骤队列 [{type, spec, owned_files, expected_output}]
work_historyTEXT JSON已完成步骤记录(审计+重试上下文)
claimed_atTEXTCAS 锁,NULL=可领取,非NULL=已锁

Orchestrator 关键查询

-- 可调度任务(schedule 维度)
SELECT * FROM tasks
WHERE schedule_status = 'ready'
  AND claimed_at IS NULL
  AND (next_eligible_at IS NULL
       OR next_eligible_at <= datetime('now'))
ORDER BY score DESC;

-- 派发 agent(work_type 维度)
switch (task.work_type) {
  case 'plan':    return runPlannerAgent(task);
  case 'code':    return runExecutorAgent(task);
  case 'review':  return runReviewerAgent(task);
  case 'pr':      return runPRAgent(task);
  case 'conflict':return runConflictAgent(task);
}
waiting(人工闸)不占并发名额:Orchestrator 的 inflight 计数只统计 schedule_status=running 的任务;waiting 的任务不计入,也不占 project.concurrency
与旧设计对比
维度旧设计(平铺 FSM)新设计(schedule × work_type)
状态数量16 个混合状态(analyzing/speccing/ready/executing/exec_review…)10 个 schedule_status × N 个 work_type,正交组合
执行路径静态,TRANSITIONS 表写死动态,work_plan 队列运行时决定
新增工作类型改 TRANSITIONS 表 + 加 status 枚举加 work_type 枚举 + 加 agent dispatcher 分支
人工审批闸plan_review/spec_review/exec_review 是独立 status(额外枚举)waiting(plan) / waiting(review),直接沿用卡住时 work_type,无新枚举
stuck逻辑态:failed + retry_count ≥ max_retries 联合推导,无独立 DB 值一等状态:独立 DB 值,直接查询;人工处理后出 ready / done / cancelled
并发占用所有非终态都计入 inflight只有 running 计入 inflight;waiting / stuck 不占名额
暂停恢复需 paused_return_to 字段记录位置(paused, work_type) 自带位置,恢复即 (ready, same work_type)
可观测性一个 status 字段,需要枚举含义两个字段,(running, code) 直接表达"正在写代码"
实现复杂度中(平铺 transition 守卫)低(两字段 + 队列 pop/push)
并发调度(调度单位 · CAS 防双领 · couplingPenalty 错峰)
维度决策
最小调度单位task(一次一个 work_step);并发发生在不同 task 之间,不在 task 内部。work_plan 队列严格线性
阶段内并行判据 = 是否需要独立 worktree / 写权限:只读·汇总型 → agent 内 Promise.all(占 1 槽);需独立写 → 拆子任务交调度器(占 N 槽)
并行建模并行 = 拆子任务parent decomposed → 子全 done → done 做 join;不引入队列内并行分支节点(保队列模型简洁)
防双重 claimCAS claimed_at(见下);即「执行期锁」的 DB 落地
文件错峰couplingPenalty(见下):与所有持未合并 worktree 的任务(running + waiting(review) + 带改动 failed)的 owned_files 重叠扣分,软错峰非硬禁;agingBonus 兜底防饥饿
并发预算槽只认 running;agent 内并行不额外占槽,调度器并行占 N 槽,project.concurrency 封顶

CAS 防双重 claim

-- 抢占:仅当未被锁定才成功
UPDATE tasks SET claimed_at = :now
WHERE id = :id AND claimed_at IS NULL;
-- changes==1 → 抢到;==0 → 已被占,放弃
单条 SQL 行级原子,无论多少路径同时领同一任务,最多一个成功。run 结束(finishRun / failAttempt / reap)置 NULL 释放。比"用 run.status 反推"更直接、可索引。

couplingPenalty 文件错峰

score(t) = rankU(t) + agingBonus(t)
         − 0.5 × | t.owned_files ∩
            (未合并 worktree 任务的 owned_files 并集) |
重叠越多分越低 → 越靠后领 → 大概率等冲突任务先跑完。按任务级 owned_files 并集算,覆盖所有分支上有未合并提交的任务(running + waiting(review) 待合并 + 带改动 failed),不只 running——否则 code 完进 waiting 等人审时分支已有提交,只看 running 会漏算、照样撞冲突。软错峰:实在没别的可干仍会跑,agingBonus 防永久饥饿。

阶段内并行:放调度器还是放 agent?

并行的性质放哪占槽例子
共享同一 worktree、只读 / 汇总(不互相写)agent 自己并行(worker 内 Promise.all)1 槽双复审 code + security(都只读同一 diff)
各自要写、可隔离(不同文件 / 模块)拆子任务交调度器(各自 worktree)N 槽feature 拆「auth 层」+「UI 层」
🔗 闭环:planner 拆并行子任务时须给互斥的 owned_files,它们才真并行;给重叠了 couplingPenalty 自动把它们串起来——拆解质量与调度错峰相互校正。
反模式:同一 worktree 内多个写手并发(git index / 文件竞态)。要并行写就必须隔离 worktree,而隔离 worktree 即「子任务」——于是天然回到调度器并行。
交互平面 · stuck 任务人工接管(interactive run)Phase 2

自治流水线之外的第二执行平面:任务卡在 stuck 时,人可「接管」,像终端一样与带完整上下文的 agent 长对话改完,再交回自治管道。不改造自治 Worker,作为并列能力接入。

两个执行平面

维度自治平面(现有)交互平面(新增)
触发orchestrator 按 score 调度人显式接管(on-demand,POST /api/tasks/:id/takeover
传输纯文件(job.json / outbox / heartbeat)实时双向runs/<runId>/console.sock(unix socket)↔ daemon ↔ 客户端 WS
节奏跑完即退、可 reap人驱动、按对话推进
重启可恢复✅ 文件协议保证❌ 牺牲,改由 session resume 找回
run kindplan / code / review / pr / conflictinteractive
两平面共享 worktree / transcript+session / DB 状态(经 daemon)/ task 模型,只在传输 + 生命周期触发上不同——自治内核保持纯净,交互作为边界清晰的独立能力。

接管会话流程

stuck
人点接管 /takeover
spawn interactive run(复用 worktree + resume session)
人 ↔ agent 长对话
人选出口

接管期间

schedule_status=running + run.kind=interactive
work_type 保持卡住时的值(不污染枚举)
reaper 对 interactive 关执行超时,heartbeat 仍发
idle-TTL 30min 无活动 → checkpoint session id → 退出 → 回 stuck;重连即 resume(挂起/恢复无缝)

结束出口(人选)

修好 → 交回:commit WIP,回 ready 进下一 work_type(review/pr)
修好 → 直接完成:走 pr / 合并 → done
放弃 → 回 stuck / cancelled
session id 落 work_history → 后续自治 run 可 resume,接管对话喂 L1–L4 记忆

关键决策(已定)

决策点取定
schedule_status复用 running + run.kind=interactive,不加状态枚举;reaper 单点特判
工具权限有界可配:项目级自定义白名单,但只能在允许超集内勾选——effective = 用户选 ∩ 允许超集git push / 部署等红线永不入超集
idle-TTL30 分钟挂起 + resume 重连
并发名额占 1 个 project.concurrency(在用 worktree + agent),可单独配小池避免抢占自治吞吐
📦 Schema 影响极小runs.kind 扩枚举加 interactive(TEXT 兼容、非 work_type);session id 复用 work_historyconsole.sockruns/<runId>/ 下的文件——无需新增列。新增的是 daemon 的 takeover API + WS↔socket 桥接 + reaper 对 interactive 的特判 + 项目级 interactive_tools 配置(受允许超集约束)。
数据库 Schema 变更Phase 1
★ 本方案新增 / 变更字段汇总(schedule × work_type 模型)

核心是 tasks 表的状态建模:用 schedule_status + work_type + work_plan 队列 取代旧 status 单字段;状态机以「队列驱动」实现(非 XState 快照),故 state_snapshot / sub_status 不引入。

字段类型用途性质
tasksschedule_statusTEXT调度状态机:init|ready|running|waiting|blocked|paused|failed|stuck|done|cancelled,取代旧 status取代 status
taskswork_typeTEXT当前工作类型,决定 dispatch 哪个 agent:plan|code|review|pr|conflict|research|null新增
taskswork_planTEXT JSON剩余步骤队列 [{type, spec, owned_files, expected_output}],运行时动态可改新增
taskswork_historyTEXT JSON已完成步骤记录(审计 + 重试上下文)新增
tasksclaimed_atTEXTCAS 锁,NULL = 可领取、非 NULL = 已锁定(防双重领取)新增
tasksversionINTEGER乐观锁版本号,执行期任务修改检测新增
tasksscopeTEXTfile / module / service / cross-service 改动范围,喂调度 couplingPenalty新增
tasksowned_filesTEXT JSON任务声明的主要修改文件路径,喂 couplingPenalty 文件重叠 + diff 越界闸;planner decompose 给子任务赋值新增
tasksexpected_outputTEXT任务级一句话可验证完成标准(per-step 的在 work_plan 里)新增
tasksattachmentsTEXT JSON提交附件 [{name, type, path}](图片/文件);存 tasks/<id>/attachments/,喂 agent 多模态/Read新增
taskssourceTEXT任务来源 human|ai|sync|github;AI 提的默认进待确认闸新增
taskscreated_byTEXT创建者(人 / 提交它的 agent run id),配合 source 审计新增
projectsagent_rulesTEXT项目级 agent 专用规范(markdown),L2 记忆注入新增
projectsdiff_max_filesINTEGERdiff 体量闸阈值(默认 100 文件)新增
projectschecksTEXT JSON分项检查命令 {lint, typecheck, build}新增
projectsbudget_usdREAL项目级预算上限(当期);当期 spend 超额 → 自动 paused 停领(见 ⑥ 护栏)新增
projectsbudget_periodTEXT预算周期 day|month(配合 budget_usd)新增
runskindTEXT对齐 work_type:plan|code|review|pr|conflict|research,外加 interactive(人工接管 run,非 work_type、不进调度队列)扩枚举
runstrace_idTEXT跨 run 追踪 ID,关联同任务多次运行新增
runsusageTEXT JSON每次模型调用的用量 [{model, input_tokens, output_tokens, cache_read, cache_write}],喂成本计算新增
runscost_usdREAL本 run 折算成本(按 模型×token 价目表);汇总到 task / project新增
已弃用(早期 XState 草案)state_snapshot / sub_status(被 schedule_status + work_plan 队列取代)、task_type(任务性质由 work_plan 步骤组成表达,并入 work_type)、parent_version_id(无对应场景)。
注:owned_files 同时存在「任务级」(声明级,喂调度/diff 闸)与「work_plan 每步级」(执行级,当前步声明),两者并存不冲突。
Entity Relationship 图(数据库表结构与关系)
projects 🔑 id TEXT PK name TEXT repo_path TEXT UNIQUE default_branch TEXT verify_cmd TEXT autonomy TEXT model TEXT concurrency INTEGER max_retries INTEGER timeout_ms INTEGER ★ checks TEXT (JSON) ★ diff_max_files INTEGER ★ budget_usd REAL ★ budget_period TEXT auto_approve_plan INTEGER auto_approve_exec INTEGER ★ agent_rules TEXT status TEXT sort_order INTEGER logo TEXT last_sync_at TEXT created_at TEXT tasks 🔑 id TEXT PK 🔗 project_id → projects.id 🔗 parent_id → tasks.id (可空) depth INTEGER title TEXT complexity TEXT ★ schedule_status TEXT ★ work_type TEXT ★ work_plan TEXT (JSON) ★ work_history TEXT (JSON) priority INTEGER deps TEXT (JSON) ★ scope TEXT ★ owned_files TEXT (JSON) ★ expected_output TEXT ★ claimed_at TEXT ★ version INTEGER ★ attachments TEXT (JSON) ★ source TEXT ★ created_by TEXT result TEXT (JSON) assignee TEXT retry_baseline INTEGER next_eligible_at TEXT source_ref TEXT created_at / updated_at TEXT approvals 🔑 id TEXT PK 🔗 task_id → tasks.id gate TEXT (plan|review) action TEXT (accept|reject) actor TEXT reason TEXT at TEXT runs 🔑 id TEXT PK 🔗 task_id → tasks.id kind TEXT (plan|code|review|pr|conflict|research) ★ trace_id TEXT ★ usage TEXT (JSON) ★ cost_usd REAL worktree TEXT branch TEXT status TEXT started_at / ended_at TEXT transcript_ref TEXT claude_session_id TEXT error TEXT worker_pid INTEGER last_seq INTEGER events 🔑 id TEXT PK 🔗 project_id (无 FK 约束) 🔗 task_id (可空, 无 FK 约束) type TEXT payload TEXT (JSON) at TEXT 1:N 1:N 1:N 1:N 0..1:N (可空) parent 图 例 ★ = 本方案新增列 🔗 = 外键引用 🔑 = 主键 有 FK 约束 弱引用(无 FK) 关键字段说明 字段 所属表 含义 schedule_status tasks 调度状态机,取代旧 status:init|ready|running|waiting|blocked|paused|failed|stuck|done|cancelled work_type tasks 当前工作类型,决定 dispatch 哪个 agent:plan|code|review|pr|conflict|research|null work_plan tasks 剩余步骤队列 [{type, spec, owned_files, expected_output}],运行时动态可改 work_history tasks 已完成步骤记录,审计 + 重试上下文 claimed_at tasks CAS 锁,NULL=可领取、非 NULL=已锁定,防 Orchestrator 双重领取 version tasks 乐观锁版本号,执行期任务修改检测(D6) scope tasks 改动范围 file/module/service/cross-service,喂调度 couplingPenalty owned_files tasks 任务声明的主要修改文件 JSON,喂 couplingPenalty 文件重叠 + diff 越界闸 expected_output tasks 任务级一句话可验证完成标准(per-step 的在 work_plan 内) agent_rules ★ projects 项目级 agent 专用规范文本,注入全部 agent prompt 头部(L2 记忆) diff_max_files projects diff 体量闸阈值(默认 100),执行后改动文件数超限即拦截 checks projects 分项静态检查命令 JSON {lint, typecheck, build},任一非 0 硬失败 budget_usd / budget_period projects 项目级预算上限(当期 day|month),超额自动 paused 停领(⑥ 护栏) kind runs run 类型,对齐 work_type:plan|code|review|pr|conflict|research + interactive(人工接管,非 work_type) trace_id runs 跨 run 追踪 ID,关联同一任务的多次运行 usage / cost_usd runs 每次模型调用 token 用量 + 折算成本(模型×token 价目),汇总到 task / project last_seq runs outbox.ndjson 已消费行号,daemon ingest 幂等重试游标 attachments tasks 提交附件 [{name,type,path}](图片/文件),喂 agent 多模态 / Read source / created_by tasks 任务来源 human|ai|sync|github + 创建者,AI 提的默认进待确认闸 payload events 事件附加数据 JSON,审计日志 + WebSocket 实时推送源
核心表说明
行数量级作用关键约束
projects少量(<100)项目配置、自动化策略、并发/超时参数repo_path UNIQUE
tasks中量(<10k)任务主表,三类任务(hard/medium/easy)均在此表,以 complexity+status+run.kind 区分FK project_id, parent_id(自引用)
approvals中量审批闸记录(plan/spec/exec),reject 必须带 reasonFK task_id
runs中量(<50k)每次 agent 运行(planner/executor/reviewer/security/conflict),含 transcript 路径和 ingest 游标FK task_id
events大量(仅追加)审计日志 + WebSocket 实时推送源,无 FK 约束(高性能追加)INDEX (project_id, at)
★ 本方案新增列

projects.agent_rules (L2)

TEXT,项目级 agent 专用规范正文(markdown)
daemon 在 claimOne 写 job.json 时注入全部 4 类 agent 的 prompt 头部
文件:schema.sql + types.ts + mappers.ts + store.ts

非表字段(JobSpec.context)

protocol.ts 新增可选字段,由 daemon 组装、随 job.json 下发
内容:parentPlan(父任务拆解意图)+ siblings(兄弟任务状态摘要)
worker 读 job.json 即可,无需访问 DB
Agent 记忆注入P1 核心(L1) · P2(L2–L4)
当前缺口
buildPrompt executor:title/id + operations/spec/plan + 执行约束。无 lastRunError、无项目规范。
buildPlannerPrompt planner:title/id + 草稿 + depth。无 lastRunError、无项目规范。
buildReviewPrompt reviewer:任务要求 + 执行者自述 + diff 命令。无项目规范。
cc.ts:80 settingSources:['project'] → SDK 自动读项目 CLAUDE.md(现已生效 ✓)
task.lastRunError 字段已存在于 Task 类型,但从未注入任何 prompt
orchestrator claim 时拿到的 task 对象不带 last_run_error(仅审核 UI 子查询)
数据流铁律 → 决定方案形态
worker 进程完全不碰 DB,只读 runs/<runId>/job.json(protocol.ts 铁律)
所有来自 DB 的记忆,必须由 daemon 在 claimOne 写 job.json 时组装进去
JobSpec 已携带完整 task + project 对象 → lastRunError 和 agentRules 天然可随 job.json 下发
L3 跨任务上下文 → 扩展 JobSpec.context? 可选字段

L1 — 注入 lastRunError(ROI 最高)

现状:last_run_error 是 pendingApprovals 子查询,claim 时的 task 不带它
改 store.ts:抽 lastRunErrorOf(taskId) 方法(复用现有子查询 SQL)
改 orchestrator.ts:claimOne 写 job 前赋值 task.lastRunError
改 runner.ts:3 处 build*Prompt 插「## 上次失败原因(重试任务)」段
5 行代码,零协议改动(字段已在 Task 上)

L2 — 项目级 agent 规范(存 DB projects 表)

schema.sql:projects 表加 agent_rules TEXT 列 + 幂等迁移
types.ts/mappers.ts:Project 加 agentRules?、row ↔ 字段映射
project 已随 job.json 下发 → worker 端 4 处 prompt 插「## 项目规范」头部
API/Web 编辑入口后续补;本期先打通存储+注入

L3 — 跨任务上下文

protocol.ts JobSpec 加 context?: { parentPlan?: string; siblings?: {...}[] }
store.ts:取 parent.plan + 同 parentId 兄弟摘要的方法
orchestrator.ts claimOne:若 task.parentId,组装 job.context 再下发
runner.ts buildPrompt:注入「## 拆解背景(父任务意图 + 兄弟状态)」
仅做 DB 读;不做 git 演化检索/RAG(留后续)

L4 — 全局 agent 规范(maestro 自维护)

不读 user 全局 ~/.claude/CLAUDE.md(避免个人习惯干扰受控执行)
settingSources:['project'] 保持不变
maestro 维护 ~/.maestro/agent-global.md
daemon 启动时读一次缓存,claimOne 随 job.json 下发,4 处 prompt 头部注入
文件不存在则跳过(可选,缺省空)
注入顺序(prompt 内记忆段排布)
[L4] ## 全局执行规范[L2] ## 项目规范(必须遵守)[L3] ## 拆解背景(父任务意图 + 兄弟状态)## 任务内容[L1] ## 上次失败原因(重试任务)## 执行约束
项目 CLAUDE.md 由 SDK 经 settingSources:['project'] 自动进 system prompt,不在 user prompt 里重复。
Agent 规格与注册表Phase 1
Agent 注册表模型(work_type → AgentSpec,可动态扩展)

每个 work_type 对应一条 AgentSpec。Orchestrator 派发 = 注册表查表,而非写死 switch。新增一种 agent = 注册一条 AgentSpec,调度内核 / 状态机零改动。六个维度——流程 / 每步模型 / skills+subagents / prompt / 上下文 / 输出——全部声明在 AgentSpec 里。

interface AgentSpec {
  workType:    WorkType;        // 'plan'|'code'|'review'|'pr'|'conflict'|'research'|'writing'|…
  runKind:     string;          // 落 runs.kind
  pickModel:   (t, p) => ModelId; // 每步档位(可按 complexity 分档)
  tools:       ToolPattern[];     // 工具白名单(∩ 全局允许超集;红线永不入集)
  skills?:     SkillId[];         // 注入的 skill(如 writing-plans / elements-of-style)
  subagents?:  AgentType[];       // 可调度的 subagent(如 Explore / general-purpose)
  needsWorktree: boolean;        // 写类 true(隔离改动)
  buildPrompt: (ctx: AgentContext) => string;  // 含 L1–L4 记忆
  parseOutput: (raw: string) => StepResult;    // 输出解析
  gate?:       'plan' | 'review' | null;        // 完成后的人工闸
  maxTurns:    number;
  preempt?:    boolean;          // 抢占调度(如 conflict)
}

// 派发 = 查表,不是写死 switch
const spec = AGENT_REGISTRY[task.work_type];
await spec.run(task);  // run 内:buildPrompt → cc(model,tools,skills) → parseOutput → gate

AgentContext(上下文,daemon 在 claim 时组装进 job.json)

{
  task, project,        // job.json 携带
  step,                 // work_plan 当前步 {type, spec, owned_files, expected_output}
  memory: { global,     // L4 全局规范
            projectRules,// L2 项目 agent_rules
            decompCtx,  // L3 父任务意图 + 兄弟状态
            lastError },// L1 上次失败
  diff?, reviewerNotes? // review / pr 用
}
plan Planner Agent
维度内容
触发(ready, plan);Hard→decompose / Medium→spec;只读、不建 worktree
流程buildPlannerPrompt → runPlanner(CC 只读) → 解析输出 → (waiting, plan) 人审
模型/档easy=sonnet-4-6 · medium=opus-4-8 · hard=opus-4-8(最强档)
skills/subagentskill superpowers:writing-plans(拆解质量);subagent Explore(大范围只读检索代码演化)
toolsRead / Glob / Grep + Bash(git log/diff/show);maxTurns 90/60/40
prompttitle/id + spec/plan 草稿 + depth + 剩余可拆层(≤3) + 「产子任务表格(人审) + 末尾 fenced JSON」
上下文L4 全局 → L2 项目规范 → L3 拆解背景 → 任务内容 → L1 上次失败;project CLAUDE.md 经 settingSources 入 system
输出decompose: 表格 + JSON{title,complexity,priority,deps,owned_files,expected_output};spec: 三段 md(改动/为什么/验收)
code Executor Agent
维度内容
触发(ready, code)建 worktree(隔离写)
流程syncMain → createWorktree → buildPrompt → runTask(CC, acceptEdits) → 必须 commit → verify → checks 闸 → diff 体量闸
模型/档easy=sonnet-4-6 · medium/hard=opus-4-8
skills/subagentsubagent general-purpose / Explore(并行只读调研、省主上下文);skill:项目可配领域 skill
toolsRead/Edit/Write/Glob/Grep + Bash(git 无 push, test) + WebSearch;maxTurns 100
prompttitle/id + operations??spec??plan + owned_files 范围 + expected_output + 执行约束(禁越界 / 必 commit 含任务 ID)
上下文L1–L4 + work_plan 当前 step.spec / step.owned_files
输出执行自述 + 含任务 ID 的 commit
review Reviewer Agent
维度内容
触发(ready, review);code 完成自动串联;独立 run、只读、可选
流程Promise.all(reviewCode, reviewSecurity) 并行 → 合并 verdict → reject 硬拦截(回 code) / pass → (waiting, review)
模型/档三档统一 opus-4-8(最强,不被 project.model 降档,保自审严格度)
skills/subagentskill:code-review 清单 / 安全审计清单;无 subagent(聚焦审单一 diff)
toolsRead/Glob/Grep + Bash(git diff/show/log);maxTurns 40;不写、复用 code 的 worktree 只读
prompt任务要求 + 执行者自述 + worktreeDiff + 「输出 VERDICT 模板(pass/reject + 理由)」
上下文任务 spec + code 自述 + diff + L2 项目规范
输出双 verdict + 意见;任一 reject → 硬退回 code(带意见)
pr PR Agent
维度内容
触发review 通过后 (ready, pr)
流程创建 PR(gh) → --no-ff 合并 → 冲突? → 建 conflict run : done
模型/档opus-4-8(PR 文案 / 合并决策;多为确定性操作)
skills/subagentskill:PR 描述模板;无 subagent
toolsBash(git, gh —受控:建 PR / 合并,禁强推);不建新 worktree
prompt任务摘要 + commit 列表 + 关联 issue + PR 模板
上下文任务 + 分支 + diff 摘要 + review 结论
输出PR url + merge 结果(成功 → done / 冲突 → conflict run)
conflict Conflict Agent
维度内容
触发pr 合并冲突自动建(抢占调度,不排队等 score)
流程补救 worktree git merge 制造冲突 → CC 解(保两边意图) → 跑测试 → commit → 双复审
模型/档opus-4-8 固定(最强,不随原任务复杂度降档)
skills/subagentskill:解冲突纪律(禁丢任一方改动 / 解完验证);subagent Explore(查两边意图)
toolsRead/Edit/Write + Bash(git merge仅此分支白名单, test)
prompt专用解冲突模板(保留两边意图 + 禁丢改动 + 解完跑测试 + commit 沿用合并信息)
上下文两分支 diff + 冲突文件 + 原任务意图 + L1 上次失败
输出解冲突自述(解了哪些文件 / 取舍 / 测试结果)+ commit
另有 research(纯只读调研,不写、无 gate)与 interactive(人工接管,见「交互平面」专题)两个 work_type,规格同样登记在 AGENT_REGISTRY。
动态新增 Agent(写作 / 视频发布 示例)

三步加一个 agent:① work_type / runs.kind 加枚举值(TEXT 兼容、无需改表结构)→ ② 写一条 AgentSpec 注册进 AGENT_REGISTRY → ③(可选)模型档位表 + 工具允许超集补该角色。调度器 / 状态机零改动——一旦某任务的 work_plan 含该 work_type,Orchestrator 自动按注册表派发。

// 示例 1:写作 agent(后期加内容生产能力)
AGENT_REGISTRY['writing'] = {
  workType:'writing', runKind:'writing',
  pickModel: () => 'claude-opus-4-8',
  tools:['Read','Write','WebSearch'], needsWorktree:true,
  skills:['elements-of-style:writing-clearly-and-concisely'],
  subagents:['Explore'],          // 查资料
  buildPrompt: ctx => 写作 brief + L2 项目文风规范 + 素材,
  gate:'review',                  // 产出走人审
  maxTurns:60,
};

// 示例 2:自动发视频 agent(对外副作用,须显式授权)
AGENT_REGISTRY['video-publish'] = {
  workType:'video-publish', runKind:'video-publish',
  pickModel: () => 'claude-opus-4-8',
  tools:['Read','Bash(ffmpeg:*)','Bash(youtube-upload:*)'], // 受控发布工具
  needsWorktree:false,
  buildPrompt: ctx => 视频元数据 + 平台 + 发布策略,
  gate:'review',                  // 对外发布强制人审闸
  maxTurns:30,
};
🛡 红线护栏:对外产生副作用的 agent(发布 / 部署 / push)默认强制 gate:'review' 人工闸,且工具受允许超集约束——「未审不发」是硬约束,新 agent 不能绕过。纯生产/只读 agent(writing 草稿、research)可配 gate:null 自动放行。
运维与护栏(Ops & Guardrails)Phase 2
成本与预算治理(精确到项目 · 按 模型 × token 计费)

自治系统自动 spawn 付费 agent,必须有成本闸防失控烧钱。每个 run 从 Agent SDK 抓 token 用量 → 按每模型价目算成本 → 落库 → 汇总到 task / project → 项目级预算闸超额自动停领。

// 1. 每次模型调用的用量落 runs.usage(cc.ts 从 SDK message.usage 抓)
runs.usage = [{ model, input_tokens, output_tokens, cache_read, cache_write }, …]

// 2. 价目表($/Mtok,各模型 in/out/cache 单价不同)
const MODEL_PRICES = {
  'claude-opus-4-8':   { in:15, out:75, cacheRead:1.5, cacheWrite:18.75 },
  'claude-sonnet-4-6': { in:3,  out:15, cacheRead:0.3, cacheWrite:3.75  },
  'claude-haiku-4-5':  { in:1,  out:5,  cacheRead:0.1, cacheWrite:1.25  },
};
// 3. run 成本 = Σ 每次调用(按该次的 model 单价)
cost_usd = Σ ( in_tok·P.in + out_tok·P.out + cache_read·P.cacheRead + cache_write·P.cacheWrite ) / 1e6
// 4. 汇总:task.cost = Σ runs.cost_usd ; project 当期 spend = Σ task.cost(按 budget_period)

// 5. 项目级预算闸(Orchestrator claim 前)
if (project.budget_usd && periodSpend(project, project.budget_period) >= project.budget_usd) {
  pauseProject(project);  // 新任务不再领取;在途 run 跑完不打断;通知人工
  continue;
}
维度设计
计费粒度per 模型调用 → per run(runs.cost_usd)→ per task → per project(当期)
预算闸projects.budget_usd + budget_period(day|month);超额 → project 自动 paused 停领,可单独配全局日上限做二级保险
消费端/api/usage(成本明细:项目/模型/时段)与 /api/metrics 由此填充,不再是空端点
价目维护价目表为代码常量,模型变更/调价改一处;缓存命中(cacheRead)显著降本,纳入计算
限流与全局并发背压
机制设计
两层并发闸project.concurrency(单项目)+ 新增 MAESTRO_MAX_INFLIGHT(全局在途上限);claim 受 min(项目闸, 全局闸) —— 防多项目×并发把 API 打爆
API 429 / overloadedcc.ts 捕获 Anthropic 限流 → run 标 throttled不计任务失败、不消耗 max_retries)→ 退避后重排;与 failAttempt 严格区分
spawn 速率令牌桶限制每秒新起 worker 数,避免 tick 一次性雷鸣群(thundering herd)冲击 API + 磁盘
与成本闸协同budget 超 → 停领(不再 claim);429 → 慢领(退避);两者正交叠加
资源生命周期与 GC
资源回收策略
worktreetask done/cancelled/reap 后删 worktree;已合并的临时分支删除,未合并分支保留待人查
runs/<runId>/job.json / outbox / heartbeat / transcript 保留 retention_days(可配)→ 过期归档或删;transcript 大文件单独策略
eventsappend-only → 定期 rollup(旧事件按 task 聚合)/ 归档到冷表,防无限增长拖慢看板查询
启动 GCdaemon 启动扫孤儿 worktree(无对应活跃 run)→ 清理;扫残留 runs 目录对账
SQLite周期 WAL checkpoint + 低峰 VACUUM,控制 DB 文件膨胀
可观测性(metrics / health / trace)
内容
/api/metrics队列深度(各 schedule_status 计数)· 吞吐(done/h)· agent 时延 + turn 数分布 · review 通过/reject 率 · 成本(per project / model)
/api/healthdaemon 存活 + DB 可写 + 在途 worker 数 + 最近一次 tick 时间(探活/告警用)
trace_idJobSpec.traceId 贯穿 outbox 每条 → 串起一次任务跨多 run(plan→code→review→pr)的全链;从 Phase 2 提前为一等公民(成本/排障都依赖它聚合)
审计源events 表已是审计 + WS 推送源,metrics 在其上做聚合,不另起存储
依赖图完整性(环检测 / 死锁)
时机设计
decompose 落库时planner 产出 deps → 建边前跑环检测(拓扑排序 / DFS);发现环 → 拒绝该 decompose 结果 + planner 重试(带「deps 成环,请重排」意见)
运行时死锁若存在非终态 task 但可运行集为空(全部 blocked 互等)→ 标记死锁 + 通知人工,不空转
防御(prompt)planner prompt 明确「deps 用子任务序号引用、禁自环 / 互环」,从源头降低成环概率
安全与沙箱(Security & Sandbox)Phase 2
安全模型(沙箱 · 凭证 · 脱敏 · 对外授权)
设计
沙箱MAESTRO_SANDBOX:worker 进程 OS 级写围栏(只允许写自己的 worktree + runs/<runId>/)+ ulimits(CPU/内存/fd);配置落 sandbox.sb。默认 off,生产建议 on
工具红线(全局)任何 agent 不可逾越:禁 git push / 禁部署 / 禁对外发布(除非显式授权 agent + 人审闸);实际工具 = AgentSpec.tools ∩ 全局允许超集,红线永不入超集
凭证git / API 凭证不写进 worktree、不入 job.json;由 daemon 以最小权限注入 worker env,按 run 生命周期回收;解冲突/PR 的 git 操作受控、不外推
secret 脱敏outbox / transcript / events 落盘前过滤已知 secret 模式(token / key / 凭证),防泄漏到审计层与看板
对外副作用授权产生外部副作用的 agent(video-publish / deploy)强制 gate:'review' 人审闸 + 项目级显式开关,未授权不执行——「未审不对外」是硬约束
与 ⑤ Agent 规格的工具白名单、交互平面的「有界可配工具」一脉相承:能力按需授予、红线全局兜底、对外副作用强制人审
前端 API 契约(REST + WS)P1 现有 · P2 ★新路由
统一约定
约定
Base URLhttp://127.0.0.1:4517MAESTRO_PORT);仅本地监听
Content-Typeapplication/json;附件上传用 multipart/form-data;SSE 为 text/event-stream
鉴权本地无鉴权(仅 127.0.0.1);如需远程暴露另加(本期不含)
错误格式统一 { "error": "<message>" }StoreError400、不存在 → 404、状态机非法转移 → 400、内部 → 500
时间字段ISO8601 字符串(TEXT)
Projects API
方法路径请求体(关键字段)响应
GET/api/projectsProject[]
POST/api/projects{name*, repoPath*, defaultBranch?, autonomy?, concurrency?, model?}Project
GET/api/projects/:idProject
PATCH/api/projects/:id部分字段:autonomy / concurrency / model / budget_usd / budget_period / checks / agent_rules / diff_max_files / verify_cmdProject
DELETE/api/projects/:id{ok}
POST/api/projects/:id/sync{created, done, skipped}
POST/api/projects/reorder{order: id[]}{ok}
GET/api/projects/:id/tasksTask[](任务树)
GET/api/projects/:id/eventsEvent[]
Tasks API
方法路径请求体(关键字段)响应
POST/api/projects/:id/tasks{title*, complexity?(auto|hard|medium|easy), priority?, deps?, parentId?, owned_files?, scope?, expected_output?}Task
GET/api/tasks/:idTask
POST/api/tasks/:id/plan{plan*}Task
POST/api/tasks/:id/spec{spec*}Task
POST/api/tasks/:id/decide{action: accept|reject, actor?, reason?, merge?}(人工闸)Task
POST/api/tasks/:id/transition{to*, meta?}(守卫校验合法转移)Task
POST/api/tasks/:id/requeueTask
POST/api/tasks/:id/cancelTask
DELETE/api/tasks/:id{ok}
POST ★/api/tasks/:id/attachmentsmultipart: files[](图片/文件){attachments:[{name,type,path}]}
POST ★/api/tasks/:id/takeover—(起交互接管 run){runId, console}
Runs / 观测 API
方法路径请求体 / 查询响应
GET/api/tasks/:id/runsRun[]
GET/api/runs/:id/transcripttext/ndjson(transcript)
GET/api/tasks/:id/eventsEvent[]
GET ★/api/tasks/:id/streamtext/event-stream(agent token SSE,Phase 2)
GET/api/agentsAgentSpec[](注册表)
GET ★/api/usage?project&period{byProject, byModel, total} 成本明细
GET ★/api/metrics{queueDepth, throughput, latency, reviewPassRate, cost}
GET ★/api/health{ok, db, inflight, lastTickAt}
WebSocket 事件契约(/ws

连接 ws://127.0.0.1:4517/ws,连上后服务端单向推送(客户端无需发消息)。每条消息 = 一行 events 表记录,前端据 type 增量更新看板,无需轮询

// 消息封装(JSON)
{ type, projectId, taskId, payload, at }
type触发payload 关键
task.created新任务(任意入口)task
task.updated任务字段变更task / 变更字段
status.changedschedule_status 变更from / to / work_type
complexity.changedAUTO 复杂度回填complexity / reason
run.startedworker 起跑runId / kind
run.finishedrun 结束runId / result
approval.granted / approval.rejected人工闸决策gate / reason
merge.failed / merge.remediated合并冲突 / 收口branch
project.syncedtodo 同步完成created / done / skipped
run.progressSSE 配套(Phase 2)runId / token 增量
budget.exceeded项目当期成本超预算projectId / spend / budget
★ = 本次重构新增路由 / 事件。完整 Task / Project / Run / Event 结构见 ③ 数据库 Schema 的字段表(响应体即各表行的 camelCase 映射)。
Phase 1 实施计划
六大任务(T1–T6)及 checkbox 进度追踪,实施前须完成 ③ 设计方案审阅。
Phase 1 实施计划
全局约束
npm test 全套测试,使用 new Store(':memory:'),禁止 mock
npm run typecheck 类型检查
npm run build 编译到 dist/
最强档模型 ID:'claude-opus-4-8'(fable-5 暂不可用)
禁止 git push / 禁止部署
禁止重复实现已有功能(见"已完成项")
已完成项(禁止重复实现)
verdict→硬闸 ingest.ts 149–165 行:任一 reject 退回重执行
Promise.all 并行双复审 pipeline.ts 154 行
syncMain 执行前同步主分支 pipeline.ts 121–127 行
conflict 管道 runConflictPipeline pipeline.ts 211–273 行
planner maxTurns 按复杂度 90/60/40 runner.ts 168 行
depth 注入 防无限拆 buildPlannerPrompt 133 行
git 只读工具 log/diff/show runPlanner 171 行
deps 序号→taskId 映射 ingest.ts 103–116 行
迁移说明:以上均实现在旧扁平 FSM(analyzing/speccing/executing/exec_review)上。重构到 schedule×work_type 时业务逻辑可复用(双复审 / 解冲突 / syncMain / planner 档位与 prompt / deps 映射),但状态机接线须重指向新 schedule_status×work_type:① review 现为独立 work_type,「Promise.all 双复审」须从 executor(code) run 拆出到独立 reviewer run;② 「verdict→硬闸」「exec_review 转移」重定位到 review 步的 waiting(review) 闸 + pr 步合并。属 T1–T6 的迁移内容,非推倒重写。
T1
Schema 迁移 + Task 类型扩展
schema.sql · db.ts · types.ts · mappers.ts · store.ts
0/10
  • 测试步骤 1:写失败测试
    test('Task 新字段默认值', () => {
      const store = new Store(':memory:');
      const p = store.createProject({ name:'p', repoPath:'/tmp/t-'+Math.random(), autonomy:'manual' });
      const t = store.createTask({ projectId: p.id, title:'feat', complexity:'easy' });
      assert.strictEqual(t.scheduleStatus, 'init');
      assert.deepStrictEqual(t.workPlan, []);
      assert.strictEqual(t.version, 1);
    });
  • 运行步骤 2:确认失败
    tsx --test test/store.test.ts 2>&1 | grep -E "fail|Error" | head -5
  • 实现步骤 3:schema.sql — tasks 加状态/队列等 9 列,runs 加 trace_id
      schedule_status   TEXT NOT NULL DEFAULT 'init',
      work_type         TEXT,
      work_plan         TEXT NOT NULL DEFAULT '[]',
      work_history      TEXT NOT NULL DEFAULT '[]',
      claimed_at        TEXT,
      version           INTEGER NOT NULL DEFAULT 1,
      scope             TEXT,
      owned_files       TEXT NOT NULL DEFAULT '[]',
      expected_output   TEXT
      -- runs 表:
      trace_id          TEXT
  • 实现步骤 4:db.ts — openDb 末尾追加 10 个 ensureColumn
      ensureColumn(db,'tasks','schedule_status',"schedule_status TEXT NOT NULL DEFAULT 'init'");
      ensureColumn(db,'tasks','work_type','work_type TEXT');
      ensureColumn(db,'tasks','work_plan',"work_plan TEXT NOT NULL DEFAULT '[]'");
      ensureColumn(db,'tasks','work_history',"work_history TEXT NOT NULL DEFAULT '[]'");
      ensureColumn(db,'tasks','claimed_at','claimed_at TEXT');
      ensureColumn(db,'tasks','version','version INTEGER NOT NULL DEFAULT 1');
      ensureColumn(db,'tasks','scope','scope TEXT');
      ensureColumn(db,'tasks','owned_files',"owned_files TEXT NOT NULL DEFAULT '[]'");
      ensureColumn(db,'tasks','expected_output','expected_output TEXT');
      ensureColumn(db,'runs','trace_id','trace_id TEXT');
  • 实现步骤 5:types.ts — 新增 ScheduleStatus/WorkType/WorkStep + Task 接口字段
    export type ScheduleStatus = 'init'|'ready'|'running'|'waiting'|'blocked'|'paused'|'failed'|'stuck'|'done'|'cancelled';
    export type WorkType = 'plan'|'code'|'review'|'pr'|'conflict'|'research';
    export type TaskScope = 'file'|'module'|'service'|'cross-service';
    export interface WorkStep { type: WorkType; spec?: string; ownedFiles?: string[]; expectedOutput?: string; }
    // Task 接口追加:
      scheduleStatus: ScheduleStatus; workType: WorkType|null;
      workPlan: WorkStep[]; workHistory: WorkStep[];
      scope: TaskScope|null; ownedFiles: string[]; expectedOutput: string|null;
      claimedAt: string|null; version: number;
  • 实现步骤 6:mappers.ts — TaskRow 加字段 + rowToTask 映射
    // TaskRow 追加:
      schedule_status:string; work_type:string|null; work_plan:string; work_history:string; owned_files:string; claimed_at:string|null; version:number;
    // rowToTask 追加:
      scheduleStatus: r.schedule_status ?? 'init',
      workType: r.work_type ?? null,
      workPlan: r.work_plan ? JSON.parse(r.work_plan) : [],
      workHistory: r.work_history ? JSON.parse(r.work_history) : [],
      ownedFiles: r.owned_files ? JSON.parse(r.owned_files) : [],
      version: r.version ?? 1,
  • 实现步骤 7:store.ts — PatchTaskInput 支持新字段(workPlan/workHistory 用 JSON.stringify 序列化)
    按已有参数化查询模式处理;禁止字符串拼接传值。
  • 运行步骤 8:npm test
    npm test 2>&1 | tail -15
    预期:新测试通过,旧测试不受影响。
  • 运行步骤 9:typecheck
    npm run typecheck
  • 提交步骤 10
    git add src/store/schema.sql src/store/db.ts src/model/types.ts src/store/mappers.ts src/store/store.ts test/store.test.ts
    git commit -m "feat(model): 新增 scheduleStatus/workType/workPlan/workHistory/scope/version 字段 + schema 迁移"
T2
CAS Claim + 执行期锁
store.ts · orchestrator.ts
0/10
  • 测试步骤 1:写失败测试(双重 claim + ready 清零)
    test('CAS — 同任务不能双重领取', () => {
      assert.ok(store.casClaimTask(task.id,'run-1'));
      assert.ok(!store.casClaimTask(task.id,'run-2'));
    });
    test('transition→ready 清空 claimed_at', () => {
      store.decide(task.id,'reject','human','改');
      assert.ok(store.casClaimTask(task.id,'run-3'));
    });
  • 运行步骤 2:确认失败
    tsx --test test/orchestrator.test.ts 2>&1 | grep casClaimTask | head -3
  • 实现步骤 3:store.ts — casClaimTask
    casClaimTask(taskId:string,_runId:string):boolean {
      const r=this.db.prepare(
        `UPDATE tasks SET claimed_at=?,updated_at=? WHERE id=? AND claimed_at IS NULL`
      ).run(now(),now(),taskId);
      return r.changes===1;
    }
  • 实现步骤 4:transition() to==='ready' 清 claimed_at;requeueTask 同步清零
    if(to==='ready'){
      this.db.prepare(`UPDATE tasks SET status=?,claimed_at=NULL,updated_at=? WHERE id=?`).run(to,now(),taskId);
    }
  • 实现步骤 5:orchestrator.claimOne — startRun 后接 casClaimTask
    if(!store.casClaimTask(task.id,run.id)){log.error(`CAS失败`);return;}
  • 实现步骤 6:store.ts — assertNoActiveRun 私有方法
    private assertNoActiveRun(taskId:string){
      const {n}=this.db.prepare(`SELECT COUNT(*) n FROM runs WHERE task_id=? AND status='started'`).get(taskId) as {n:number};
      if(n>0) throw new StoreError('任务执行中,禁止修改');
    }
    在 setSpec / setPlan / patchTask 入口调用 assertNoActiveRun。
  • 测试步骤 7:补执行期锁测试
    test('执行中禁止修改 spec', () => {
      store.startRun(t.id,'planner');
      assert.throws(()=>store.setSpec(t.id,'x'),/执行中/);
    });
  • 运行步骤 8:npm test
    npm test 2>&1 | tail -15
  • 运行步骤 9:typecheck
    npm run typecheck
  • 提交步骤 10
    git add src/store/store.ts src/daemon/orchestrator.ts test/orchestrator.test.ts
    git commit -m "feat(store): CAS claimed_at 防双重 claim + 执行期锁"
T3
调度算法升级(rankU + agingBonus + couplingPenalty)
scoring.ts · orchestrator.ts
0/7
  • 测试步骤 1:写失败测试
    test('agingBonus — 新建任务为 0', () =>
      assert.strictEqual(agingBonus({createdAt:new Date().toISOString(),priority:1} as Task,Date.now()),0));
    test('couplingPenalty — 1个重叠=-0.5', () =>
      assert.strictEqual(couplingPenalty({ownedFiles:['a.ts','b.ts']} as Task,[{ownedFiles:['a.ts']} as Task]),0.5));
  • 运行步骤 2:确认失败
    tsx --test test/orchestrator.test.ts 2>&1 | grep -E "agingBonus|coupling" | head -3
  • 实现步骤 3:重写 scoring.ts — 导出 rankU / agingBonus / couplingPenalty / 新签名 rankByScore
    export function rankU(id:string,byId:Map<string,Task>,deps:Map<string,Task[]>,cache:Map<string,number>):number{
      if(cache.has(id))return cache.get(id)!;
      const t=byId.get(id); if(!t){cache.set(id,0);return 0;}
      const v=baseScore(t.priority)+(deps.get(id)??[]).reduce((s,d)=>s+rankU(d.id,byId,deps,cache),0);
      cache.set(id,v);return v;
    }
    export const agingBonus=(t:Task,nowMs:number)=>
      Math.min(baseScore(t.priority),(nowMs-Date.parse(t.createdAt))/3_600_000/48);
    export function couplingPenalty(t:Task,inFlight:Task[]):number{
      const s=new Set(t.ownedFiles??[]); if(!s.size)return 0;
      let n=0; for(const x of inFlight)for(const f of(x.ownedFiles??[])) if(s.has(f))n++;
      return n*0.5;
    }
  • 实现步骤 4:orchestrator.ts — claimable() 传入 inFlight 列表
    const inflightList=tasks.filter(t=>inflight.has(t.id));
    return rankByScore(candidates,tasks,inflightList,d.nowMs());
  • 运行步骤 5:npm test
    npm test 2>&1 | tail -15
  • 运行步骤 6:typecheck
    npm run typecheck
  • 提交步骤 7
    git add src/model/scoring.ts src/daemon/orchestrator.ts test/orchestrator.test.ts
    git commit -m "feat(scoring): CPM rankU + agingBonus + couplingPenalty 调度算法"
T4
模型档位升级(统一最强档 opus-4-8)
models.ts
0/6
  • 测试步骤 1:写失败测试
    test('planner hard = opus-4-8', () =>
      assert.strictEqual(pickModel({complexity:'hard'}as Task,{model:null}as Project,'planner'),'claude-opus-4-8'));
    test('reviewer 不被 project.model 降档', () =>
      assert.strictEqual(pickModel({complexity:'easy'}as Task,{model:'claude-sonnet-4-6'}as Project,'reviewer'),'claude-opus-4-8'));
    test('MODEL_FALLBACK_CHAIN[0] = opus-4-8', () =>
      assert.strictEqual(MODEL_FALLBACK_CHAIN[0],'claude-opus-4-8'));
  • 运行步骤 2:确认失败
    tsx --test test/models.test.ts 2>&1 | grep -E 'opus|reviewer' | head -3
  • 实现步骤 3:models.ts — 4 处修改
    // 1. 回退链(fable-5 暂不可用,opus-4-8 为最强档)
    export const MODEL_FALLBACK_CHAIN=['claude-opus-4-8','claude-sonnet-4-6'] as const;
    // 2. planner 分档:easy=sonnet,medium/hard=opus(hard 不再与 easy 同档)
      hard:['MAESTRO_MODEL_PLAN_HARD','claude-opus-4-8'],
    // 3. reviewer 三档统一最强 opus-4-8(不被 project.model 降档)
      reviewer:{easy:[...,'claude-opus-4-8'],medium:[...,'claude-opus-4-8'],hard:[...,'claude-opus-4-8']},
    // 4. conflict 固定最强(pickModel 首行)
    if(role==='conflict')return 'claude-opus-4-8';
    ⚠ fable-5 是设计目标最强档,暂不可用;当前一律用 opus-4-8 充当,fable-5 恢复后把以上 4 处的 claude-opus-4-8 换回 claude-fable-5 即可,结构不变。
  • 运行步骤 4:npm test
    npm test 2>&1 | tail -15
  • 运行步骤 5:typecheck
    npm run typecheck
  • 提交步骤 6
    git add src/executor/models.ts test/models.test.ts
    git commit -m "feat(models): 复审/解冲突/planner-hard 统一最强档 opus-4-8"
T5
Planner Decompose JSON 扩展(ownedFiles + expectedOutput)
runner.ts · protocol.ts · ingest.ts
0/8
  • 测试步骤 1:ingest decompose-result 映射测试
    test('decompose-result — ownedFiles 落入子任务', () => {
      appendOutbox(run.id,{type:'decompose-result',plan:'x',
        subtasks:[{title:'类型',complexity:'easy',priority:0,deps:[],
          ownedFiles:['src/model/types.ts'],expectedOutput:'typecheck通过'}]});
      ingestRun(store,noopLog,run.id);
      const [child]=store.childrenOf(parent.id);
      assert.deepStrictEqual(child.ownedFiles,['src/model/types.ts']);
      assert.strictEqual(child.expectedOutput,'typecheck通过');
    });
  • 运行步骤 2:确认失败
    tsx --test test/ingest.test.ts 2>&1 | grep -E "ownedFiles|expectedOutput" | head -3
  • 实现步骤 3:protocol.ts — decompose-result 子任务加 ownedFiles? / expectedOutput?
    subtasks: Array<{title:string;complexity:Complexity;priority?:number;deps?:number[];
      ownedFiles?:string[];expectedOutput?:string}>;
  • 实现步骤 4:runner.ts — buildPlannerPrompt decompose 分支加两条 bullet + 更新 JSON 示例
    '- ownedFiles:主要修改文件路径 JSON string[](无则填 [])',
    '- expectedOutput:一句话可验证完成标准(如:"typecheck通过")'
  • 实现步骤 5:ingest.ts — decompose-result deps 映射之后追加 ownedFiles/expectedOutput 映射循环
    for(let i=0;i<rec.subtasks.length;i++){
      const sub=rec.subtasks[i] as {ownedFiles?:string[];expectedOutput?:string};
      if(!createdIds[i])continue;
      const patch:Record<string,unknown>={};
      if(Array.isArray(sub.ownedFiles)&&sub.ownedFiles.length) patch.ownedFiles=sub.ownedFiles;
      if(typeof sub.expectedOutput==='string'&&sub.expectedOutput.trim()) patch.expectedOutput=sub.expectedOutput.trim();
      if(Object.keys(patch).length) store.patchTask(createdIds[i],patch);
    }
  • 运行步骤 6:npm test
    npm test 2>&1 | tail -15
  • 运行步骤 7:typecheck
    npm run typecheck
  • 提交步骤 8
    git add src/executor/runner.ts src/executor/protocol.ts src/daemon/ingest.ts test/ingest.test.ts
    git commit -m "feat(planner): decompose JSON 扩展 ownedFiles/expectedOutput"
T6
管道硬闸(checks 分项 + diff 体量)
pipeline.ts · schema.sql · db.ts · types.ts · mappers.ts
0/10
  • 测试步骤 1:写失败测试(checks + diff 体量)
    test('checks — lint 失败阻止 exec_review', async () => {
      job.project.checks=JSON.stringify({lint:'exit 1'});
      const emitted=[];
      await runPipeline(job,mockDeps,p=>emitted.push(p));
      assert.ok(emitted.find(e=>e.type==='failed'&&e.error.includes('lint')));
    });
    test('diff 体量闸 — 超 diffMaxFiles 失败', async () => {
      job.project.diffMaxFiles=2;
      mockDeps.worktreeDiff=async()=>({diffSummary:' a.ts | 1\n b.ts | 1\n c.ts | 1',commits:[]});
      const emitted=[];
      await runPipeline(job,mockDeps,p=>emitted.push(p));
      assert.ok(emitted.find(e=>e.type==='failed'));
    });
  • 运行步骤 2:确认失败
    tsx --test test/pipeline.test.ts 2>&1 | grep -E "diff|checks" | head -3
  • 实现步骤 3:schema.sql + db.ts — projects 加 diff_max_files
    -- schema.sql:
      diff_max_files INTEGER NOT NULL DEFAULT 100
    -- db.ts:
      ensureColumn(db,'projects','diff_max_files','diff_max_files INTEGER NOT NULL DEFAULT 100');
  • 实现步骤 4:types.ts + mappers.ts — Project 加 diffMaxFiles
    // types.ts: diffMaxFiles: number;
    // rowToProject: diffMaxFiles: r.diff_max_files ?? 100,
  • 实现步骤 5:PipelineDeps 加可选 runCheck 注入点(测试用)
    runCheck?: (cmd:string, cwd:string) => void;
  • 实现步骤 6:pipeline.ts — verify 之后插入 checks 分项闸
    if(job.project.checks){
      let obj={}; try{obj=JSON.parse(job.project.checks)}catch{}
      for(const [name,cmd] of Object.entries(obj as Record<string,string>)){
        try{ (deps.runCheck??defaultRunCheck)(cmd,wt.dir); }
        catch(e){ emit({type:'failed',error:`[${name}]失败`,...}); return; }
      }
    }
  • 实现步骤 7:pipeline.ts — worktreeDiff 之后插入 diff 体量闸
    const maxF=job.project.diffMaxFiles??100;
    if(maxF>0){
      const fc=diff.diffSummary.split('\n').filter(l=>l.includes('|')).length;
      if(fc>maxF){ emit({type:'failed',error:`diff超限:${fc}>${maxF}文件`,...}); return; }
    }
  • 运行步骤 8:npm test
    npm test 2>&1 | tail -15
  • 运行步骤 9:typecheck + build
    npm run typecheck && npm run build
  • 提交步骤 10
    git add src/executor/pipeline.ts src/store/schema.sql src/store/db.ts src/model/types.ts src/store/mappers.ts test/pipeline.test.ts
    git commit -m "feat(pipeline): checks 分项闸 + diff 体量闸 (diffMaxFiles)"
Agent 记忆注入(L1–L4)— 涉及文件
src/store/schema.sql projects 加 agent_rules
src/store/store.ts lastRunErrorOf + agentRules
src/model/types.ts Project.agentRules
src/store/mappers.ts agent_rules 映射
src/executor/protocol.ts JobSpec.context
src/daemon/orchestrator.ts claimOne 组装记忆
src/executor/runner.ts 3 处 build*Prompt
src/executor/reviewer.ts buildReviewPrompt
src/executor/cc.ts 不改 settingSources 保持 project
~/.maestro/agent-global.md 新建 全局规范模板
测试:build*Prompt 纯函数单测(有/无各记忆字段时段落正确)
验证:failed→retry 任务 transcript 含「上次失败原因」