页边批注 · A Note in the Margin
用 Claude Code 搭 PM/Worker/Auditor 三 agent 自动开发流水线:Issue → PR → merge 全闭环
几周前我写过一篇偏「方法论」的长文,讲怎么把 AI 从单兵 Copilot 升级成 PM/Worker/Auditor 三层金字塔(那篇是 6 周路线图 + canary + 度量体系的宏图)。这篇不一样:宏图我没全做,但最小闭环已经在真实的 bug 上跑通并合并了。所以这篇只讲一件事——那条最小闭环到底是怎么用 Claude Code 搭出来的、每一行设计为什么这么写、踩了哪些坑,以及它现在还差什么(我会诚实地把没做完的部分单列一节,而不是吹成全自动生产系统)。
代码很短,短到可以逐段贴出来读。这恰恰是我想传达的核心判断:多 agent 自动开发流水线的难点不在编排框架,在职责分离的拓扑、状态外置的纪律、和确定性的安全闸——这三样都可以用文件系统 + GitHub + 几个 shell 脚本表达,不需要重型基础设施。
0. 它现在能做什么(先放真实证据)
不绕弯子,先放跑通的那一单。我在自己的一个前端项目 cue(一个影视配乐教学互动站)里开了一个 P3 的布局 bug,打上 auto-eligible 标,然后没再碰键盘,直到最后那一下「批准合并」:
Issue#1 ──worker(Opus,125s)──▶ PR#2 ──auditor(Sonnet,90s)──▶ ✅LGTM ──pyf 批准──▶ merged
这是 runs.jsonl 执行账本里这一单的真实记录(原文照搬,没修饰):
{"ts": "2026-05-23T00:48:30.477948+00:00", "role": "worker", "task": "fix pyf-labrary/cue#1 (cue)", "issue": 1, "duration_s": 125.0, "result": "ok"}
{"ts": "2026-05-23T00:56:27.466030+00:00", "role": "auditor", "task": "review pyf-labrary/cue PR#2 (cue)", "issue": 1, "duration_s": 90.0, "result": "ok"}
{"ts": "2026-05-23T12:10:24.551876+00:00", "role": "pm", "task": "triage pyf-labrary/cue#1: already resolved — PR#2 merged, issue CLOSED/COMPLETED; no re-dispatch (would duplicate)", "issue": 1, "result": "ok"}
这几行后面会逐字段拆。这里先注意最后那行 PM 的记录:它是我故意在 Issue 已经合并关闭之后又对它跑了一次 PM 分诊——PM 没有重复派工,而是核对了 GitHub 和账本两个事实源,判断「已解决、再派会重复」,直接 no-op 退出。这是整套设计里我最在意的一个性质:幂等——下一节会讲为什么它是无人值守的前提。
Worker 在那次跑里写出来的 PR,根因分析是这样的(节选自真实 PR body,不是我事后补的):
根容器
relative isolate建立了一个层叠上下文。hero 背景图 div 是absolute … z-0(带显式 z-index 的定位元素),而下方带border-t的「情绪详情」section 是无定位、无 z-index 的普通流块。按 CSS 绘制顺序,z-index:0的定位元素绘制在普通流块之上……修复:给该 section 加relative z-10,与 HERO 同层。
一个真实的 CSS 层叠上下文 bug,被一个 headless agent 定位到根因、给出最小修复、跑了 npm run build、开了 PR、写清验收标准——全程我没看一眼代码,直到 Auditor 评论 LGTM 后我点了合并。这就是这条流水线当前的能力边界。下面讲它怎么搭的。
1. 为什么是「三个 agent」而不是「一个更强的 agent」
这点上一篇讲过心法,这里只补一句落到实现的判断:职责分离(Separation of Duties)不是 prompt 能模拟的,必须是结构保证。
让同一个 agent loop 既写代码、又 review 自己写的代码、又决定要不要合并,本质上是让它自己制衡自己——再强的模型也做不到,因为它没有「另一个视角」,它只有自己刚才那条推理链的延续。会计和信息安全里把这叫 SoD:经手钱的人不能同时是审账的人。放到 agent 流水线里就是:
- PM:拆解、分诊、派工、验收编排。不碰代码。
- Worker:在隔离的分支里写码、跑测试、开 PR。只能动自己那条 feature 分支。
- Auditor:审 diff + 安全扫,结论评论回 PR。结构上不持写权——它根本没有 Edit/Write 工具,想改也改不了。
- 人(我):只做最后那一下合并审批,以及红线 ack。
关键在最后两条的「结构上」。我不是在 Auditor 的 prompt 里写「请你不要改代码」(那是软约束,模型可能不遵守、可能幻觉、可能被任务诱导绕过),而是在启动它的命令行里就没给它 Edit/Write 工具。这是确定性的:
# Auditor 的工具白名单——注意没有 Edit/Write,从机制上保证它改不了代码
claude -p "$prompt" \
--append-system-prompt "$(cat ~/army/roles/auditor.md)" \
--permission-mode default \
--model sonnet \
--allowedTools Bash Read Grep Glob
对比 Worker 的启动命令,差别一目了然:
# Worker——给了 Edit/Write,且用 acceptEdits 模式让它能连续改文件不卡 ack
claude -p "$prompt" \
--append-system-prompt "$(cat ~/army/roles/worker.md)" \
--permission-mode acceptEdits \
--model "$model" \
--allowedTools Bash Edit Write Read Grep Glob
PM 同理,白名单也只有 Bash Read Grep Glob——它能调 gh 命令操作 Issue、能读项目代码做分诊判断,但不能直接改业务代码。三个角色的写权限差异,全部落在 --allowedTools 这一行上,而不是 prompt 里的客套话。
工程判断:能用白名单表达的约束,就不要用 prompt 表达。 白名单是 Claude Code 给你的确定性闸门,prompt 是概率性请求。安全相关的东西,永远优先选确定性的那个。
模型分层也在这三条命令里:PM = Opus(判断要准),Worker 默认 Opus(写码质量优先,琐碎活降 Sonnet),Auditor = Sonnet(review 够用,巡检/日报可降 Haiku)。底座是 Claude Code 登录 Claude Max 订阅跑,所有 headless 进程挂同一份订阅 auth,边际美元成本约等于零——这是当初选 Claude Code 而不是只吃 BYOK 的运行时当底座的决定性理由(详见项目里的 ADR-001)。
2. 架构:三段触发 + 一份注册表,没有常驻框架
整套东西的物理结构异常简单。所有「运行时之家」在一个目录 ~/army/:
~/army/
├── CLAUDE.md 组织宪法(PM 运行于此时自动加载)
├── roles/
│ ├── pm.md PM 角色手册
│ ├── worker.md Worker 角色手册
│ └── auditor.md Auditor 角色手册
├── projects.yaml 项目注册表:project → 路径/repo/部门/worker 画像/测试
├── bin/
│ ├── army-dispatch poller:扫 auto-eligible Issue → 唤 PM(默认 DRY)
│ ├── army-run-pm 唤 PM 分诊 + 派工某 Issue
│ ├── army-run-worker 建 worktree + 起 Worker 修 + 开 PR + 记账
│ ├── army-run-auditor 起 Auditor 只读审 PR + 评论
│ ├── army-ledger 写一行 JSON 到 runs.jsonl
│ ├── army-guard 总开关:PAUSE 时让任务自退
│ ├── army-watchdog 出口探活(curl api.anthropic.com)
│ └── pm 本地直连:交互对话 PM(同一角色,另一道门)
├── runs.jsonl 执行账本(事实源之一)
└── worktrees/<proj>/ Worker 的隔离工作区
bin/ 下所有脚本加起来 305 行(wc -l 实测)。没有 Airflow、没有 Celery/RabbitMQ、没有自建状态机引擎、没有数据库。
它的运行模型是事件式 + 状态外置:
┌─────────────────────────────────────────────────────────────┐
│ 事实源(状态全在这里,不在任何进程的内存里) │
│ │
│ GitHub Issues + Projects ←── 任务/状态(label 路由) │
│ GitHub PR ←── 代码评审载体 │
│ ~/army/projects.yaml ←── 项目注册表(路径/repo/测试)│
│ ~/army/runs.jsonl ←── 执行账本 │
│ ~/army/pyf-docs/ ←── PRD/决策/报告知识库 │
└───────────────────────────┬─────────────────────────────────┘
│ 每次唤起都从这里"水合"
┌───────────────────┼───────────────────┐
▼ ▼ ▼
army-run-pm army-run-worker army-run-auditor
(cwd=~/army, (cwd=worktree, (cwd=项目目录,
Opus, 只读) Opus, 可写自分支) Sonnet, 只读)
│ │ │
└─ 干完即弃 ────────┴───────────────────┘
(没有常驻 agent;进程跑完就没了,状态早写回事实源)
注意这里没有常驻的 agent 进程。PM、Worker、Auditor 都是「唤起 → 从文件/GitHub 把这次需要的状态读进来(水合)→ 干活 → 把结果写回事实源 → 进程退出」。它们之间不靠共享内存、不靠消息传递、不靠长连接对话来协作——它们靠的是同一份外置事实源。Worker 开了 PR,这个事实写在 GitHub 上;Auditor 下次被唤起时去 GitHub 读到这个 PR,就知道该审什么。中间没有任何「PM 把消息发给 Worker」的环节。
这是从内容自动化项目(tv / bodhi 短剧流水线)里沿用过来的纪律:记忆在文件里,不在聊天历史里。 它带来三个直接好处,下一节展开。
projects.yaml 是把任务路由到正确工作环境的注册表。每个 project 登记它的路径、repo、worker 画像(code 走 PR-to-merge / media 走产物落盘 / research 走文件级改)和测试命令:
projects:
cue:
department: software
worker_type: code
path: ~/claw/apps/cue
repo: pyf-labrary/cue # push 即 GH Actions 部署
test: "npm run test"
worktree_base: ~/army/worktrees/cue
bywork:
department: quant
worker_type: research # 非 git repo → 无 PR 流,走文件级 + ledger
path: ~/bywork
repo: null
派活的时候,「切项目」就是「换 Worker 的 --cwd」——而不是某个全局锁。Worker 一旦 cd 进项目目录,Claude Code 会自动加载那个项目自己的 CLAUDE.md,于是它立刻拿到该项目的工具链、约定、红线。一份注册表 + cwd 切换,就把「军团能干多少种活」做成了可插拔的——加一个部门 = 在 yaml 里加一行 + 给那个项目写好 CLAUDE.md。
3. 状态机 + 账本:无状态、可恢复、幂等
我的宿主是一台 WSL,它会关机、会被 Windows 重启、网络会断。任何依赖「进程一直活着、内存里记着进度」的设计在这种环境下都会碎。所以整套流水线的第一性原理是:任何一个进程随时可以死,重来一遍不出错。
实现它只需要两条规矩:
规矩一:状态在 GitHub / 文件,不在内存。 任务状态机的单一事实源是 GitHub Projects 的 status 字段,配合 label 路由(auto-eligible 表示军团可接、army/triaged 表示 PM 已分诊、project:<name> 指路由)。状态机长这样:
pyf 开 Issue(打 auto-eligible 标)
│
▼
triage ──────────┐ PM 读 + 判断接不接
接 + 拆解 │ │ 不接/缺信息
▼ ▼
ready blocked(评论说明原因,等 pyf 输入)
PM 派工 │
▼
in-progress ───┐ Worker:改码/测/开 PR + 记账
worker 失败 3× │
▼ │
review ◀─────┘ Auditor:审 diff + 安全扫
不过 → 评论 + 回 in-progress(worker 修)
过 │
▼
awaiting-approval 推审批给 pyf
pyf 批准 │
▼
done 合并 PR + 关 Issue(Closes #N 自动关)
进程重启后,dispatcher 不靠记忆重建队列,而是重新 gh issue list / gh pr list + 读 Project status,把世界重新拼出来。session-id 映射丢了也无害——下次唤起重新水合即可。
规矩二:每个动作执行前先查现态,做成幂等。 这就是第 0 节那个「PM 对已合并的 Issue#1 no-op」的来历。PM 被唤起处理一个 Issue,第一件事是 gh issue view 看它现在什么状态;如果已经有开放/已合并的 PR,就不重复派工。Worker 同理:如果该 Issue 已经有开放 PR 了,就不重复开。briefings/reports 的幂等键是「日期 + 类型」,存在即更新而非新建。
账本是这套机制的可观测面。每个角色干完都调 army-ledger 追加一行 JSON 到 runs.jsonl。脚本本身只有几十行,核心是这段 Python:
rec = {"ts": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"role": role, "task": task}
if issue: rec["issue"] = int(issue) if issue.isdigit() else issue
if duration: rec["duration_s"] = float(duration)
if files: rec["files_touched"] = [f for f in files.split(",") if f]
if result: rec["result"] = result
with open(ledger, "a") as fh:
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
为什么是 append-only 的 JSONL 而不是数据库?因为它跨运行时、可 grep/diff、永久留存、零依赖。换底座、换模型,账本格式不变,历史可续。我要看的「军团整体健康、token 花在哪、各角色耗时」是跨工具聚合的,单厂 dashboard 给不了。回头看第 0 节那三行账本,字段就很清楚了:
{... "role": "worker", "issue": 1, "duration_s": 125.0, "result": "ok"} ← 写码 125 秒
{... "role": "auditor", "issue": 1, "duration_s": 90.0, "result": "ok"} ← 审码 90 秒
{... "role": "pm", "task": "...already resolved...no re-dispatch...", "issue": 1, "result": "ok"} ← 幂等 no-op
role + issue 让你能把一单的全生命周期串起来,duration_s 是真实耗时(不是估的),result 是 ok/fail。这就是这条流水线的「飞行记录仪」。
4. 一次派工的完整链路:把脚本读一遍
现在把第 0 节那条闭环用代码走一遍。三个脚本,每个都短。
4.1 PM 分诊 + 派工(army-run-pm)
PM 被唤起后干的事,全写在注入给它的 prompt 里:
prompt="你是 PM,处理 $repo 的 Issue #$issue(项目 $proj)。
1) gh issue view $issue --repo $repo 读它。
2) 分诊:军团能否接、是否清晰可执行。不接/信息不足 → gh issue comment 说明原因,不派工,结束。
3) 接 → gh issue edit $issue --add-label army/triaged,再派 worker:
~/army/bin/army-run-worker $proj $issue $wmodel(建 worktree + 起 worker + 开 PR)。
4) 报告 worker 开的 PR 号 + 一句结论;记 ledger。
铁律:不亲自改代码、不合并 PR(合并是 pyf 审批)。Auditor 由 dispatcher 另行触发,你不用管。"
start=$(date +%s); cd ~/army
claude -p "$prompt" \
--append-system-prompt "$(cat ~/army/roles/pm.md)" \
--permission-mode default \
--model opus \
--allowedTools Bash Read Grep Glob
几个设计点:
cd ~/army是有意的——PM 运行在控制面之家,Claude Code 自动加载这里的CLAUDE.md(组织宪法:服务谁、事实源、红线、工作路径铁律)。Worker/Auditor 运行在别处,拿不到这份,所以靠--append-system-prompt注入角色手册。这是「靠 cwd 拿身份」和「靠注入拿身份」两种手段的分工。- PM 派工是直接调下一个脚本
army-run-worker,不是「发消息给 Worker」。链式调用,进程级编排,没有队列中间件。 - 「分诊」是真判断:PM 会判定 Issue 信息够不够、军团接不接得了。接不了就
gh issue comment说明原因、打住,不硬派。这是把「PM 决策准确率」做成可度量的地方(验收目标是一周 3-5 单、准确率 >80%)。
4.2 Worker:worktree 物理隔离 + 改 + 测 + 开 PR(army-run-worker)
Worker 脚本最有意思的是 git worktree 那一段——它是「多 Worker 并行不打架」的物理基础:
branch="army/issue-$issue"
wt="$HOME/army/worktrees/$proj/issue-$issue"
git -C "$path" fetch origin --quiet || true
[[ -d "$wt" ]] || git -C "$path" worktree add "$wt" -b "$branch" origin/main 2>/dev/null \
|| git -C "$path" worktree add "$wt" -b "$branch"
每个 Worker 在 ~/army/worktrees/<proj>/issue-N 下有一份独立的工作目录 + 独立的分支,都基于最新的 origin/main 拉出来。两个 Worker 同时改同一个 repo 的不同 Issue,物理上在两个目录、两条分支,互不污染对方的工作区。这比「同一个 checkout 来回切分支」干净得多——切分支会让正在跑的两个 agent 互相踩脚。
然后起 Worker,跑完记账:
cd "$wt"
claude -p "$prompt" \
--append-system-prompt "$(cat ~/army/roles/worker.md)" \
--permission-mode acceptEdits \
--model "$model" \
--allowedTools Bash Edit Write Read Grep Glob
rc=$?
dur=$(( $(date +%s) - start ))
~/army/bin/army-ledger --role worker --issue "$issue" --duration "$dur" \
--result "$([[ $rc -eq 0 ]] && echo ok || echo fail)" --task "fix $repo#$issue ($proj)"
Worker 的 prompt 里钉死了一条红线:绝不 push 到 main/master,只在本分支开 PR,PR body 写 Closes #N(合并即自动关 Issue,这是状态机里「done」那一步的自动化)。acceptEdits 模式让它能连续改文件而不在每次编辑卡一次人工 ack——因为它在隔离 worktree 里,且最终产物要过 PR 门 + Auditor + 人审三道关,让它在沙盒里自由改是安全的。
4.3 Auditor:只读 review,且改不了代码(army-run-auditor)
Auditor 的核心已经在第 1 节讲过——白名单没有 Edit/Write。它的 prompt 里还藏了一个真实踩过的坑:
prompt="审查 PR #$pr(仓 $repo)。
1) gh pr view + gh pr diff 看改动,对照它要修的问题。
2) 审:修复是否正确、是否最小化、有无副作用/回归、安全(无注入/泄密/越权/误删)。
3) 结论用 gh pr comment 写回(用 comment,不要 review --approve,
因作者同账号会被 GitHub 拒):通过写「LGTM」+ 理由;有问题写「⚠️」+ 逐条列出。
SoD 铁律:你只读——不改任何代码、不 merge PR(合并是 pyf 的审批)。"
那个坑是:当前 Worker 和 Auditor 用的是同一个 GitHub bot 账号,GitHub 不允许 PR 作者 approve 自己的 PR。所以 Auditor 走的是 gh pr comment 写结论,而不是 gh pr review --approve。这是「身份分离没做到底」留下的一个真实约束,我把它记在了技术债清单里(见第 6 节)——要彻底分权,得给 Auditor 单开一个 bot 账号。
注意 Auditor 不合并 PR。即使它评了 LGTM,合并这一下永远是我(人)来点。这是流水线里唯一一个永远保留的人工闸——也是「awaiting-approval → done」那一步。整条流水线我把人工干预收敛到了这一个点上。
5. 安全:硬约束 > 软约束
这条流水线敢「放手让 agent 自己改代码、自己开 PR」,前提是有几道闸拦住它干蠢事。我把安全分成三个层次,确定性的优先级永远高于概率性的。
第一层:工具白名单(确定性,已落地)。 前面讲过——Auditor/PM 没有 Edit/Write,结构上改不了代码。这是数学上拦得住的。
第二层:总开关(确定性,已落地)。 每个脚本第一行都是 ~/army/bin/army-guard || exit 0。guard 读 ~/.army-state,是 PAUSE 就让调用方自己 skip。一条命令停全军:
echo PAUSE > ~/.army-state # 紧急停
echo RUN > ~/.army-state # 恢复(或删掉文件)
配套有一个 army-watchdog,curl 探 api.anthropic.com,探不到(出口代理挂了)就记日志退 1。
第三层:红线规则(规划中,目前靠软约束 + 人审兜底)。 我写了一份 runtime-无关的红线 spec,列了「永远要人 ack」的硬规则:push main/master、改 .env/secret/删 prod、≥10MB 二进制进站点、跳过内容合规备案等等。它的设计意图是编译成 Claude Code 的 PreToolUse hook,用硬编码 regex 在工具执行前拦——因为 LLM 是概率性的,「在 prompt 里写了别 push main」是软约束(可能不遵守、可能被任务诱导绕过、可能幻觉),而 hook 是确定性的。
但我要诚实:这层红线 hook 现在还没挂上。 当前 Worker 守红线靠的是三道软兜底——roles/worker.md 里的角色手册约束 + PR 门(它只能开 PR 不能直接合)+ 我最后人审。在挂上 hard hook 之前,我不会把它切到无人值守。而且红线 hook 上线策略也写死了「第一周只 log 不拦」——一个误判会卡死整条流水线,得先观察确认无误报再切真拦。
这三层的取舍逻辑:能确定性拦的(白名单、总开关)现在就硬拦;要等 hook 才能确定性拦的(红线),在它就位前宁可用「软约束 + 收窄能力 + 人审」三重兜底压着,也不放开无人值守。 安全这件事上,「还没做到的部分」必须当成「还没安全」对待,而不是「prompt 写了应该没事」。
6. 当前真实状态:哪些跑通了,哪些还没
技术博客最容易翻车的地方是把 demo 吹成生产。这一节专门划清楚边界。
已经是能跑的实物(不是图纸):
~/army/控制面骨架全部就位:组织宪法CLAUDE.md、三份角色手册、项目注册表、bin/下 8 个脚本(共 305 行)、账本、知识库脚手架。- 三角色(PM/Worker/Auditor)都在真实 bug(cue Issue#1)上端到端验证过,PR#2 已合并。
- 工具白名单 SoD、git worktree 隔离、
runs.jsonl账本、army-guard总开关——这几样是实打实在用的。 - PM 的幂等:对已合并的 Issue 正确 no-op,核了 GitHub + 账本两个事实源。
还没做完(按我自己的路线图,诚实列出):
- dispatcher 仍是 DRY 模式。
army-dispatch这个 poller 现在默认只「gh查 + 记日志,不真起 PM」(防止意外烧 token),要ARMY_DISPATCH_LIVE=1才真唤起。也就是说目前每一单仍是我手动army-run-pm <proj> <N>触发的,自动接单的最后一根线还没接通。 - Auditor 还靠手动触发。 poller 里「发现 review 态新 PR → 自动唤 Auditor」这条路径还没写,当前是我手动
army-run-auditor。 - 红线 hard hook 没挂。 见第 5 节——无人值守前必须先挂上(log-only 先行)。
- Telegram 桥 + systemd 常驻没接。 设计里 PM 应该能收 Telegram 的
@PMping、审批走 IM 卡片,dispatcher 应该是 systemd 用户服务定时跑。这两样都还没落地(systemd unit 这台机器上目前也确实没装)。换句话说,它现在是一套「手动触发也能跑通全闭环」的流水线,但还不是 24h 无人值守的常驻系统。
技术债(小但真实):
- Worker 的 ledger 记了两遍(角色手册自记 + 包装脚本记),该二选一。
- 凭据管理:项目 remote 一律走 SSH / 凭据助手,别让 token 明文落进
.git/config。 - Worker 的 worktree 在
~/army/worktrees/下、不在项目树内,拿不到工作区根的CLAUDE.md(只能拿项目自身那份)——需要时用--add-dir补。 - Auditor 与 Worker 同 bot 账号,导致前面那个「不能 self-approve、只能 comment」的妥协——要彻底分权得单开账号。
把这些列出来不是给自己挑刺,而是因为一套自动开发流水线真正的难点恰恰在这些边角:身份分离、凭据管理、幂等、续跑、误判防护。能跑通一个 happy-path demo 不难,难的是把这些边角逐个收口到敢无人值守。这份清单就是从 demo 到生产之间还差的路。
7. 为什么没上 Airflow / 没自建编排框架
最后回到最初那个判断,把它讲透。一开始我也考虑过更「正经」的方案——上一篇方法论文里甚至画过 13 个 agent 槽位 + Sentry + Grafana + canary 的团队版大图。落到单人个人项目时我全砍了,理由是:
Claude Code 原生就给了搭多 agent 编排所需的全部机制:
claude -pheadless 模式 = 可脚本化唤起的 agent 进程;--append-system-prompt= 给同一个底座注入不同角色身份;--allowedTools= 确定性的权限隔离(SoD 的结构保证);--modelper-task = 模型分层旋钮(判断用 Opus、审查用 Sonnet);- PreToolUse hook = 硬红线所需的确定性闸门(待挂);
- 加载 cwd 的
CLAUDE.md= 每个项目自带工具链/约定/红线。
剩下要我「自己搭」的,根本不是 agent loop、不是工具执行器、不是记忆引擎——那些借底座的就好。要搭的只是一层薄胶水:项目注册表(一份 yaml)、三个唤起脚本(链式调用,进程级编排)、一个账本(append JSONL)、一个总开关(读一个文件)。状态机的事实源直接用 GitHub Issues/PR/Projects——它本来就是工程团队的工单池 + 评审台 + 看板,免费、有 API、零运维。
一句话总结这个取舍:编排框架(Airflow/队列/状态机引擎)解决的是「进程怎么调度、状态存哪、消息怎么传」——但当你把状态全外置到 GitHub + 文件、让每个 agent 干完即弃、用进程链式调用替代消息传递时,这些问题根本不出现,于是框架也就不需要了。 300 行脚本不是「简陋版的 Airflow」,它是一个根本不需要 Airflow 的架构。
这也是我最想留给读者的工程判断:搭多 agent 自动开发流水线,先别急着选编排框架。先把三件事想清楚——职责怎么分权(用白名单,不用 prompt)、状态放哪(外置到 GitHub/文件,别留内存)、安全闸怎么确定性化(hook/白名单,不靠模型自觉)。这三样想透了,剩下的胶水可能比你以为的薄得多。
附:复现这套的最小清单
如果你想照着搭一套自己的:
- 底座:Claude Code,登录订阅跑(headless
claude -p),边际成本压到接近零。 - 事实源:GitHub repo + Issues + Projects + 一份
projects.yaml+ 一个 append-onlyruns.jsonl。状态全往这里放。 - 三个唤起脚本:PM(
--allowedTools Bash Read Grep Glob,cwd=控制面之家)、Worker(加Edit Write+acceptEdits+ git worktree 隔离)、Auditor(去掉Edit Write,结构上只读)。链式调用,不上队列。 - 总开关:每个脚本第一行读一个 state 文件,
PAUSE就自退。 - 红线:写一份 runtime-无关的硬规则 spec,编译成 PreToolUse hook,先 log-only 一周再切真拦。无人值守前必挂。
- 验收:拿真实的低风险 bug(P3/cosmetic)开练,目标一周跑通 3-5 单、PM 决策准确率 >80%,再逐步把 dispatcher 从 DRY 切 LIVE、把 Auditor 自动化、接 IM 审批。
别一上来就追无人值守。先让「手动触发也能跑通全闭环」稳定下来——那已经是把 AI 从「单兵 Copilot」毕业成「会自己分诊、写码、自审、走流程」的流水线了。后面那几根线(自动接单、IM 审批、常驻)是锦上添花,不是地基。