多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 自己的配置,也不是旧分支残留。

入口分裂的典型症状

根因往往很蠢: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 写同一文件。

与 project-setup skill 的关系

原理相同:都是先建项目协作骨架,让 agent 不靠对话记忆工作。区别是规模——从「单 session 项目初始化」升级成「多 agent / 多 worktree 协作系统」。

相关