多Agent协作控制面
症状:开了多 agent,总要人工告诉别的 agent 当前进度、规则变了没;写了文档但别处没更新;做过的东西反复造轮子。
问题定义收敛成四层
| 层 | 解决什么 | 关键词 |
|---|---|---|
| 0 统一入口 | Claude / Codex / Cursor 读到的规则不一样 | 所有 agent 读同一套规则 |
| 1 共享当前进度 | 谁在干什么、做到哪、下一个怎么接 | 当前、临时、跨 worktree |
| 2 独立协作 | 多 agent 不互相踩代码、不混改、不覆盖 | 一人一个 worktree + branch |
| 3 复用知识 | 做过的找不到、相似 bug 反复踩 | 长期知识、可搜索、跟 Git 走 |
这四层不要混:把「当前状态」写进「长期知识」,或反过来,都会让下一层失真。
每层对应一种文件,职责单一
入口层:agent.md / AGENTS.md / CLAUDE.md → 统一入口
当前层:~/.agent-state/<proj>/STATUS.md → 现在谁在做什么
隔离层:git branch + git worktree → 并行施工隔离
历史层:journal.md → 为什么当时这么做
知识层:.agent/registry/ → 组件 / bug / 惯例索引
复盘层:lessons/ → 心智模型怎么改
规格层:specs/ + .specify/ → 要做什么、怎么做
一句话:status 管现在,journal 管过去,registry 管可复用,lesson 管教训,branch/worktree 管隔离。
status.md不是日志,是「当前战场地图」:当前分支 / 目标 spec / 正在动的文件 / 不要碰的文件 / 最近一次验证结果 / 阻塞点 / 下一步 1-3 件 / 最后更新人。要短、可覆盖。registry不要写成百科。组件条目只回答「干什么、在哪、什么时候复用、有什么坑」;bug 条目必须写「症状 → 根因 → 修法 → 防复发信号」,只写「修好了」等于没写。lesson不要被 registry 顶替:registry 负责「下次能搜到」,lesson 负责「为什么会发生、之后心智模型怎么改」。复杂 bug 可以两边都写。
跨 worktree 的依赖与共享配置
隔离层只解决「不互相踩」,不解决「怎么共享」。依赖的分发规则:
- 共享依赖、vendor 库、共享配置默认落 main,feature 分支通过
merge/rebasemain 拿到; - worktree 之间只共享提交,不共享工作区:staged 但未 commit 的依赖不算进仓库,别的 worktree 看不到 —— 所以「我装好了」不等于「别人有了」;
node_modules不进 git;国内项目不建议外部 CDN,依赖应本地化 vendor。
配套的防撞车机制(隔离层的执行细则):开工前写 session 卡、声明 touching 与 do-not-touch、看到别人声明在改的文件就不碰、一 agent 一 worktree 一 branch。
版本线与分支模型
main = 稳定发布版;大版本用一条集成分支(如 1.0version)承载;小功能分支一律从集成分支开出,做完合回集成分支;集成分支整体测试稳定后,再一次性 merge 进 main。
main
└─ 1.0version ← 大版本集成区
├─ 1.0-toc
├─ 1.0-ai-panel
└─ 1.0-settings
- 不要让所有人直接改
main,也尽量不要所有人直接改集成分支 —— 集成分支是汇总区,不是公共工作区。 - 多个分支各有未提交改动时,不要把未收口的状态直接塞进
main:先把各自的改动提交或明确丢弃,再评估大功能分支。 - 落后的分支不要在落后状态上继续堆,基于最新基线重开一个小分支单独合。
- 「在分支中开分支」= 从当前点开新分支:在 worktree 里
git switch -c <new>,或从主目录git worktree add <dir> -b <new> <base>(后者更适合一 agent 一 worktree,不占用原 worktree)。
推倒重来之前:先开保险分支,不要拿 stash 当主备份
要 reset --hard upstream/main + 重装依赖这种「推倒重来」时,第一步不是 pull,而是先把本地改动完整落成提交:
git switch -c codex/backup-before-rebuild
git add -A
git commit -m "wip: save local work before rebuild"
# 之后才切回 main、fetch upstream、reset --hard、重装依赖
git stash不适合做主备份:它不覆盖未跟踪的新文件,也不形成一个可随时回来挑改动的基线。- 备份分支的价值是「可挑拣」:重建后从 backup 分支里按需重新合入需要的改动(登录页修正、schema 文档、skill、测试……),而不是全量回放。
- 推倒重来前先清点本地有哪些未提交改动(含未跟踪文件),确认都进了备份分支再 reset。
领先/落后只是提交计数,不是先进程度
A---B---C main
\
D---E old-branch
old-branch 对 main「领先 2」,同时「落后 2」。领先只说明有 main 没有的提交,不等于更新、更先进,也不代表应该直接合。
所以判断一条老分支要不要处理,不能看领先数,要看 diff 内容和产品是否还需要:可能是(a)有价值但还没合、(b)已被别处用另一种方式实现、(c)旧实验/废弃,可直接清理。
worktree 的两个坑
- 分支不是目录,不能
cd到分支。worktree 已经把每条分支 checkout 到一个固定文件夹,要进的是文件夹。在主目录里git checkout <branch>会被拒绝,因为该分支已被某个 worktree 占用 —— 这正是「两个目录不会同时改同一条分支」的保护。看别的 agent 的改动用cd <worktree>或git -C <worktree> diff。 .git/worktrees/<旧名>是 Git 内部元数据,别手删。分支改名后元数据目录名可能还是旧的(旧名对应新分支),Git 照样能对应上;worktree 目录里的.claude/settings.local.json是该 worktree 自己的配置,也不是旧分支残留。
worktree 的第三个坑:detached HEAD 让别人看不到你
用 git worktree add --detach 建的 session worktree 不挂任何分支,停在 origin/main 上。好处是接活之前不会多出空分支;代价是别人在分支列表里看不到你的工作,会被问「为什么没看到你的分支」。
对应约定:
- 分支对应 issue,不是对应 session。接到 issue 才从
origin/main切fix/zoo-XX-...;没接活时处于 detached 状态是正常的。 - session 专属 worktree 要定期同步 main:每次开工前
git fetch origin main并 rebase/merge,因为同时有多个 agent 在推 main。 - worktree 不共享工作区配置:
.env.local是 gitignored,不会随 worktree 复制(见 worktree是独立目录产物与配置都隔离)。要跑数据库命令或本地 dev 时,得单独补一份。 - 收尾后及时删掉 worktree + 分支,否则列表里会积累一堆历史遗留,反而看不出「谁在做哪个 issue」。
入口分裂的典型症状
根因往往很蠢:AGENTS.md 里写「事实进 .Codex/memory/」,但目录里实际存在的是 .claude/。于是:
- 入口索引指向不存在的路径 → 死链;
- Codex 读
AGENTS.md、Claude 读CLAUDE.md,两个 agent 进来读到两套世界; - 规则「写了」但没生效,因为没人指向它。
修法是让 AGENTS.md 和 CLAUDE.md 退化成薄壳,都指向同一份真实源(.agents/START_HERE.md 或 agent.md)。工具专属配置才留在 .claude/ / .codex/。
.agents 和 .agent 为什么不是重复
.agents = 工具箱(能力目录) → 有哪些 skill / 命令可以调用,如 speckit-*
.agent = 项目白板(协作目录) → 现在什么状态、谁在哪个分支、修过什么 bug、有什么可复用
有 .agents 仍然需要 .agent:能力目录不存当前进度、不存 worktree 状态、不存 bug/组件索引。名字太像时,协作目录可改名 .agent-collab/。
Git 能替代多少
Git 擅长回答:改了什么(diff)、谁在哪个分支(branch/log)、文件历史(log -- path)、bug 哪次引入/修复(bisect/blame)、哪些没合并。
Git 不擅长回答:「现在下一个 agent 该接什么」「这个 bug 当时为什么这么修」「这个组件能不能复用」「这条规则刚变了要不要重读」「这个分支卡在用户拍板还是测试失败」。
所以分支表、最近变更、bug 索引都能部分由 Git 生成(写好 commit message,git log --grep 就是 bug 历史库),但**「当前意图」和「未完成状态」Git 不知道**,STATUS.md 不能省。
同一 worktree 内的并发写冲突
隔离层(一 agent 一 worktree)只挡住「跨 worktree 踩代码」,挡不住同一个工作区里并行跑多个 session —— 同一批文件被两个 session 同时写时,症状是「我的改动被回退了 / 文件被 linter 改了」。
分诊:先确认被怀疑的工具是否有写能力(lint 脚本只有 eslint .、没有 --fix,就不可能是它),再去看哪些文件同时带着多个来源的 diff —— 那些就是冲突点。
修复:暂停所有在改同一批文件的 session → 保存对方的 diff → 按 hunks 合并,不 reset 整个文件(否则删掉的是别人的改动)→ 只跑 lint/build 验证,不跑任何带 --fix、格式化、checkout、restore 的命令。
结论:并发写冲突要用「声明 + 按 hunk 合并」处理,不要用「整文件回滚」。 更根本的解法是让每个 session 有明确的文件所有权(touching / do-not-touch),同一时刻只有一个 session 写同一文件。
中途收紧范围时,已改的禁区文件不要自行回退
给某个 agent 下发「不许再动这几个文件」时,要同时说明已经改了的怎么办:正确指令是「不要回退,报告具体改动位置后继续允许范围」。
理由:此刻的回退动作本身就是一次写入,可能删掉别人(或你自己别处)的改动 —— 收紧范围是为了避免冲突,自行回滚反而制造冲突。所以收到新范围时先自查:
- 禁区文件我改过没有?改过 → 报告位置,不 revert;
- 没改过 → 直接确认「无禁区改动」,继续按新范围做。
把「收缩范围」和「清理现场」拆成两件事:前者立刻执行,后者留给能看清全局的人。
与 project-setup skill 的关系
原理相同:都是先建项目协作骨架,让 agent 不靠对话记忆工作。区别是规模——从「单 session 项目初始化」升级成「多 agent / 多 worktree 协作系统」。
Issue 追踪器作为「当前进度层」的落地
「当前谁在做什么」不必自己发明 STATUS.md —— 用 Linear/Jira 这类 issue 追踪器 + MCP 也能充当控制面的第 1 层(共享当前进度)。关键是把状态机写死:Issue 实现完保持 In Progress 等用户验收,不自行设 Done。见 Issue完成由用户验收后再关闭。
角色分三层:主管 / Issue Lead / 子 Agent
单层「总控 + 一堆 Worker」不够——一个班长管不了所有 issue。正确形态是三层(2026-07-29 @ 22:14):
你 ── 目标与最终验收
主管 Agent ── 项目级协调(跨 issue)
Issue Lead ── 对一个 issue 端到端负责(Player-Coach:自己写也派子 agent)
子 Agent ── 只对明确产物负责(research / implementation / test / review / docs)
- 主管:接收目标并 grill、建 Parent Issue 与验收标准、决定拆成哪些可独立交付的 issue、为每个 issue 指派 Lead、处理 issue 间依赖与冲突、做项目级架构验收、最终 assign 给你。
- Issue Lead:拿到的是完整 Outcome(结果)而不是机械步骤,自主决定要不要继续拆、要不要并行、分配哪些文件边界、自己做什么、怎么集成、何时达到完成标准。
- 子 Agent 不直接与你交流,也不直接改 Linear 最终状态,结果一律回到 Issue Lead。
并行的默认原则
存在两个以上互不依赖、责任边界清楚的工作流时,默认并行派子 agent;当拆分成本、合并冲突或协调成本高于节省时间时,由 Lead 自己顺序完成。
不适合并行:多个 agent 同时改同一文件、接口契约还没定、第二项依赖第一项结果、五分钟能干完。
Linear 是「交付柜台」,不是 Agent 群聊
- Linear 里的 comment = 单个 agent 与你的交接点(需求、状态、责任人、验收证据、你的最终决策)。
- Agent 之间的交流不放在 Linear,交给 Trellis/运行时承载(执行计划、Spec 注入、研究资料、跨会话恢复)。
- 否则 Linear 会被 raw transcript 淹没,变成噪声与隐私风险;要同步只同步 curated handoff(精选交接信息)。
- 各层真源分工:Linear 需求与优先级 · Trellis 执行上下文 · Git 代码与可审计产物 · CI/Production 质量与可用性。
- 你只面对主管 agent,子 agent 做得好不好与你无关——这是最省注意力的形态。前提是主管 agent 必须真正对 scope、integration、evidence 负责,否则只是把黑盒套了一层黑盒。
- 最终验收用独立的
User Acceptance状态或明确 assignee handoff,不要只靠截图或直接 Done(见 Issue完成由用户验收后再关闭)。
环境隔离 ≠ 目录划分
环境隔离指的是账号、数据库、Secret、部署与数据边界的隔离,不等同于 apps/ / src/ 目录划分。一开始就把测试/正式环境的账号与数据边界定好;偷懒省下的一天,后面会以「大灾难」的形式还回来。
经验要过 Lesson Gate 再晋升,不能由每个 worker 直写 spec
每个 agent 执行完都复盘是好的,但不能让每个 worker 直接修改长期 spec —— 会很快产生重复、冲突甚至错误经验,形成 knowledge pollution(知识污染)。
Worker 完成
→ 提交 Lesson Candidate(候选经验)
→ Check Agent 验证事实
→ 主管 Agent 去重、分类
→ 晋升到正确的知识层
→ 后续 agent 自动注入
候选经验的最小字段:result: candidate|none、surprise(最反直觉的发现)、root_cause、failed_attempts(哪些办法无效)、reusable_rule(下次应该怎么做)、evidence(文件/测试/日志/提交)、confidence。
按用途分层,而不是堆进一个目录:
| 内容 | 位置 |
|---|---|
| 只对当前 issue 有用 | .trellis/tasks/<issue>/retrospective.md |
| 具体踩坑与完整案例 | lessons/<topic>.md |
| 以后写代码必须遵守的契约 | .trellis/spec/ |
| Agent 本身的工作方式需改进 | .trellis/agents/ 或对应 skill |
| 只是执行过程记录 | workspace journal |
| 没有可复用经验 | 明确写 lesson: none,不制造文档 |
⇒ 缺的往往不是「能不能写 lesson」,而是每个 worker 完成时强制产出候选经验,并由主管负责晋升与下次检索。
相关
- Issue完成由用户验收后再关闭 —— issue 追踪器充当当前进度层时的状态机约束
- 职责要先分家再谈实现 —— 三层角色的分家
- 真源要按维度显式指定 —— Linear / Trellis / Git / CI 各管一个维度
- 远端主线可能与本地主线无共同祖先 —— 重建/重置工作区前要先分清两套历史
- 通用脚本要靠工具适配器接入 —— 入口统一之后,自动化 brief 还得有工具适配层
- 入库前先分层清洗 —— 同样是「按职责分层,不混」的思路
- 文件被回退先分清是工具还是并发会话 —— 本节的分诊与修复细则
- 自动提交脚本要先确认当前分支 —— 协作工具的自动 commit 要有分支护栏
- 什么该进git什么该ignore —— 协作数据入库 / 运行时状态 ignore 的切分
- worktree是独立目录产物与配置都隔离 —— 隔离的代价:产物与配置都不共享