多个会话共用一个仓库:worktree + 现场可见¶
几个 agentica 会话(终端里的 CLI、手机 IM 遥控的 CLI、Gateway 自己的 agent)同时改同一个 仓库时,真正花钱的不是合并冲突,而是两件事:
- 互相覆盖:两个会话在同一个工作目录里编辑同一批文件,还会抢 git 的
index.lock。 - 互相打听:为了搞清"你是不是也在改这个文件 / 你落后 main 几个 commit",得发消息、 等对方回、而答案在第三个会话提交的那一刻就过期了。
agentica 对这两件事的回答是分开的:worktree 解决第 1 个,presence 带 git 状态解决第 2 个。
一、不用问就知道对方在干什么¶
每个 CLI 会话在自己的心跳里发布 git 位置(agentica/git_state.py → agentica/peers.py),
所以 list_agents(以及 /list-agents)直接就能看到:
agentica-52 [peer=52d79d86]
status: running a turn
cwd: /Users/xuming/Documents/Codes/agentica
git: main @ 8ca321e · 3 dirty
dirty: CHANGELOG.md, agentica/gateway/main.py, docs/advanced/gateway.md
working on: 把 wechat 的 -14 重登逻辑补上
git:一行 = 分支 · head · 相对基准分支的+ahead/-behind· 脏文件数dirty:一行 = 具体路径(最多 12 个,超出显示+N more)- 采集有 10s 缓存,心跳只在内容变化或 30s 到点时才落盘——不会因为这个功能变成每秒三次
git调用
基准分支是本地 main(没有则 master),不是 upstream:另一个会话提交到本地 main 还没
push 时,origin/main 看不见它——而那个会话恰恰是你马上要撞上的人。
没发布过这些字段的老会话记录只会显示
git: <branch>,不会显示 "clean"。"clean" 是别人 据以决定能不能 rebase 的信息,缺字段不许冒充它。
二、对方改了哪些文件:看 presence,不写进工具结果¶
真正要动手之前,用 list_agents(或 /list-agents)看对方心跳里的 dirty:。那是现场可见的 git 状态,写文件工具不会再往结果里追加「另一个会话也改了这个文件」——提醒曾经挂在 write_file / apply_patch 上,模型会把它当成写入合同的一部分。
需要自己在写入时查一次的调用方,仍可用 agentica.peer_conflicts.PeerConflictChecker;产品默认路径是看 presence,不是改工具结果。
三个刻意的取舍(对 presence / checker 都成立):
- 只提醒、不拦截。两个会话同时改一个文件有时正是对的(一个写实现、一个写测试)。
- 只比同一个仓库。peer 会发布
repo_root,所以是精确比较;同一个仓库的两个 worktree 仍然会命中。 - 同一个文件同一个 peer,checker 只说一次。每次编辑都提醒等于训练 agent 忽略它。
三、worktree:一个任务一个目录、一个分支¶
会话身份留在主 checkout。worktree 是一棵给工具用的树,不是把这场对话搬进去。
已经在跑的会话不必重启。模型读内置 worktree skill(人也可用 /worktree):
agent 调用:worktree(action="new", name="gateway-peers")
→ /repo/.agentica/worktrees/gateway-peers
然后:read_file / write_file / apply_patch / execute(..., work_dir=<该路径>)
glob / grep(..., path=<该路径>)
漏传 work_dir / path,调用落在当前会话目录(通常是 main)。这是可观察的错,比把会话 cwd 绑到树上、树一删全挂要好修。
worktree 工具的动作:
| 动作 | 作用 |
|---|---|
worktree(action="status") |
列出本仓库所有 worktree,附带当前会话所在目录 |
worktree(action="new", name="<任务>") |
创建或复用健康 checkout,返回路径;会话不搬家 |
worktree(action="merge", name="<任务>") |
把该分支并回本地基准分支,然后删除 checkout |
worktree(action="remove", name="<任务>") |
丢掉一个 agentica 的 wt/* checkout。脏树由 git 拒绝;未合入的提交留在分支上 |
new(name="main") 会被拒绝——main 不是任务名。没有 main / use 搬家动作。登记项不再是 checkout(目录没了、或占坑的裸目录)时,new 拒绝而不是销毁占坑内容:先 remove 那个名字,或换一个名字。
退出时:只动以 --worktree 启动的进程脚下那棵 wt/* 树。没有独有工作 → 自动删;有未提交或未合并的提交 → 留在盘上。手动 git worktree add、detached、Claude Code 的树一律不碰。
目录位置与命名¶
默认:仓库内 <repo>/.agentica/worktrees/<任务>,分支 wt/<任务>(Claude Code 的 .claude/worktrees/ 形态)。
~/Documents/Codes/agentica 主 checkout(main)
~/Documents/Codes/agentica/.agentica/worktrees/gateway wt/gateway
~/Documents/Codes/agentica/.agentica/worktrees/paper wt/paper
首次创建时会在 .agentica/worktrees/.gitignore 里写一个 *,自我忽略:git status 干净,且不动仓库里那个被跟踪的 .gitignore。从主 checkout glob / grep 会跳过仓库内的任何 worktree(问 git worktree list,不是按名字猜)。把 path 指到某棵树上,才能搜到那棵树自己的文件。
两种情况需要换地方,都用 ~/.agentica/config.yaml 里的设置(settings: 块;嵌套 worktree.root 和扁平行 worktree.root 都行):
settings:
worktree:
root: sibling # 旧默认:../<repo>-<任务>
# root: ~/worktrees # 绝对路径 → <root>/<repo>/<任务>
link: [".env", ".envrc"] # 新 worktree 里 symlink 过来的 gitignored 文件
- 不想 worktree 落在仓库里(比如经常
git clean -xdff)→root: sibling。清死登记项从会话所在仓库问 git,不从树路径的父目录反查,所以 sibling / 绝对根和默认布局一样能remove后再new。 - 父目录塞了二十个仓库,想集中放 → 绝对路径
- 共享挂载的父目录不可写 → 默认的仓库内布局正好不需要写父目录
git clean -xdf(单 -f)对嵌套 checkout 是 Skipping repository,安全;git clean -xdff(双 -f)会 Removing .agentica/。不想被主仓库的 clean 扫到 → root: sibling。
.env 是 symlink 而不是拷贝:轮换一次密钥所有 worktree 同时生效,而且机器上只存在一份。
合并回去,然后删除 worktree¶
worktree(action="merge", name="<任务>") 的顺序是刻意的:
- 先在那棵 worktree 里把基准分支合进当前分支。冲突留在写代码的目录里,而不是扔在人人共用的主 checkout。
- 再在主 checkout 里把分支并进基准分支,此时必然是 fast-forward。两个会话同时 merge 时,git 自己的
index.lock就是互斥锁,这里只是等它(重试 5 次)。 - 删除 worktree。 会话一直在它原来的目录,不搬家。合完后与本地 base 齐平,删除不会丢掉独有提交。
会被明确拒绝(而不是替你猜)的情况:worktree 里有未提交改动、主 checkout 不干净、主 checkout 不在基准分支上。分支已经全部落在 base 上不是错误——那是任务做完的样子,随后照常清理 checkout。
remove 只拦所有权(agentica 的 wt/*,不是主 checkout,不是 detached / Claude Code)。脏树交给 git 拒绝,git 的话原样转达。未合入 base 的提交不是拒绝条件:checkout 删掉,git branch -d 会留下分支。以 --worktree 启动的进程退出时的自动清理才看「有没有独有工作」——那是猜测,不是一条显式 remove。
四、几个实测出来的坑¶
agentica命令永远跑主目录的代码。console script 是 editable 安装,sys.path[0]是 bin 目录,所以在 worktree 里敲agentica加载的仍是主 checkout 的 agentica。而python -m pytest/python x.py在 worktree 里 import 的是该 worktree 的代码。 自举(用 agentica 改 agentica)时:测试用python -m pytest,要跑 worktree 版 CLI 用python -m agentica.cli.main。- gitignored 文件不会进 worktree:
.env缺失的症状是"会话起来了但连不上任何模型", 和真正的原因八竿子打不着。默认 symlink.env,其它用worktree.link加。 index.lock冲突基本上是"多进程同一个工作树"的症状,切了 worktree 就没了; 但仓库级操作(worktree add、fetch、gc)仍会短暂锁.git。- 按任务命名,不要按会话命名。 同名只在任务未完成时复用,合完即拆;两个会话仍能在同一个未完成的任务上接力,目录不会因此堆下去。
~/.agentica是共享的:session 列表和 profile 覆盖按 work_dir 分桶(正是我们要的), 但 cron、skills、MEMORY.md 是全局共享——别指望它们被隔离。