多Agent协作控制面

**多 agent 协作的核心矛盾不是「文档不够多」,而是「协作控制面分裂」**——不同 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 / rebase main 拿到;
  • 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 下发「不许再动这几个文件」时,要同时说明已经改了的怎么办:正确指令是「不要回退,报告具体改动位置后继续允许范围」。

理由:此刻的回退动作本身就是一次写入,可能删掉别人(或你自己别处)的改动 —— 收紧范围是为了避免冲突,自行回滚反而制造冲突。所以收到新范围时先自查:

  1. 禁区文件我改过没有?改过 → 报告位置,不 revert;
  2. 没改过 → 直接确认「无禁区改动」,继续按新范围做。

把「收缩范围」和「清理现场」拆成两件事:前者立刻执行,后者留给能看清全局的人。

与 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 完成时强制产出候选经验,并由主管负责晋升与下次检索。

相关