多 Agent 并发开发指南(同一台电脑)
通用开发规范。适用:同一台电脑上多个 opencode 实例 / 多个会话同时开发同一个仓库。
1. 结论先行
- 同一个目录 + 不同分支不可行。git 的 HEAD / index / working tree 按目录共享,任一实例
git switch/checkout会把文件在另一个实例脚下整体换掉。 - 要并发,必须让每个 agent 使用独立的工作目录。推荐
git worktree:共享同一个.git对象库,各自独立 HEAD/index/工作区,最省空间。 - worktree 只从已提交的 commit 检出,所以当前未提交改动必须先提交(或 stash)作为基线。
2. 文件修改机制(为什么"改不同行"大多没事)
- opencode 的
edit工具是精确字符串替换(oldString → newString),不是整文件覆盖。执行时匹配磁盘当前内容,找不到就报错,不会盲目写成旧版本。 - 两个 agent 改同一文件的不同行,绝大多数情况互不影响。
- 真正的风险:竞态(TOCTOU)。两方几乎同时读入同一版本 → 各自写回,若写回按整文件写出,后写者会覆盖前者。opencode 无文件锁,窗口小但不为零。
write工具是整文件覆盖,并发时最危险,能不用就不用,坚持用edit。
3. 隔离方案对比
| 方案 | 隔离度 | 成本 | 说明 |
|---|---|---|---|
| git worktree(推荐) | 文件级完全隔离 | 低 | 同一 .git,多工作目录,各自分支 |
| 完全 clone | 最彻底 | 高 | 独立 .git,同步/推送更麻烦 |
| 同目录多分支 | ❌ 不可行 | — | HEAD/工作区共享,互相覆盖 |
4. 通用执行步骤
4.1 提交基线
cd <repo-root>
git status
git add -A && git commit -m "chore: snapshot before worktree concurrency"
# 或:git stash(stash 列表在同仓库各 worktree 间共享,可在新 worktree 里 apply)
4.2 创建 worktree
cd <repo-root>
git worktree add -b feat/agentA ../<repo>-agentA
git worktree add -b feat/agentB ../<repo>-agentB
git worktree list
4.3 初始化每个 worktree
- 拷贝被 gitignore 的配置(如
backend/.env)。 - 安装依赖(如
uv sync/pnpm install/npm ci)——依赖目录随 worktree 独立,需各自装。 - 分配不同端口(见下)。
4.4 并发工作约定
- 每个 agent 只在自己 worktree 目录内操作,不跨目录改文件。
- 小步 commit;定期
git fetch并从mainmerge/rebase。 - 不并发跑会争抢资源的命令(测试、启动服务、写同一 SQLite)。
4.5 收尾
cd <repo-root>
# 分支合并回 main 后
git worktree remove ../<repo>-agentA
git worktree prune
5. 通用隔离点清单(并发前逐项检查)
| 类别 | 需要隔离的东西 | 做法 |
|---|---|---|
| 进程停止 | 按路径子串 pkill -f "<项目/子目录>" 会跨 worktree 误杀 |
改 PID 文件 kill,兜底用 worktree 绝对路径匹配 |
| 端口 | 后端 / 前端 / 调试端口硬编码 | 端口改读环境变量,各 worktree 分配不同端口 |
| 服务发现 | 前端 proxy target、服务间调用地址硬编码 | 跟随端口用环境变量透传 |
| 配置 | .env、密钥等被 gitignore,新 worktree 不存在 |
手动拷贝 |
| 依赖 | node_modules / .venv 随目录独立 |
每个 worktree 各自安装 |
| 数据 | SQLite / 本地缓存文件 | 随 worktree 目录天然隔离 |
| 日志 | 相对路径日志目录 | worktree 内独立,通常无碍 |
| 全局缓存 | pnpm store / uv cache 等 | 共享只读,一般安全 |
6. 端口分配参考
| 实例 | 后端 | 前端 |
|---|---|---|
| 主目录 | 8000 | 5175 |
| Agent A | 8100 | 5275 |
| Agent B | 8200 | 5375 |
启动前各自 export 对应端口环境变量。
7. 风险
- worktree 基于已提交状态,未提交改动不会出现在 worktree 中;基线提交/暂存是硬前置。
- 改造启动脚本(PID 文件、端口环境变量)属侵入式改动,需回归验证原命令仍正常。
- 若仓库是超级仓库(含多个项目),worktree 会检出全部项目;基线提交会一并固化其他项目改动,需确认范围。
8. 案例:超级量化(capital markets)
8.1 仓库前提
- git 仓库根:
~/microarch/产品(超级仓库,含 capital markets、software industry、official_website 等多个项目),tracked 约 305 文件。 - 工作区常有大量未提交改动,横跨多项目(含整目录删除/移动),需先提交基线。
8.2 已核实的隔离点
| 冲突点 | 位置 | 现状 | 后果 |
|---|---|---|---|
| 进程停止 | KA04-构建/dev.sh:20-30 |
pkill -f "capital markets/KA04-构建/..." 路径子串匹配 |
worktree 路径含同样子串,任一 worktree 执行 ./dev.sh stop 会杀掉所有 worktree 的进程 |
| 后端端口 | KA04-构建/dev.sh:15;实际在 backend/engine/config.py:55 api_port=8000 |
硬编码 | 两个 worktree 抢 8000 |
| 前端端口 | KA04-构建/frontend/vite.config.ts port: 5175 |
硬编码 + strictPort: true |
两个 worktree 抢 5175,启动失败 |
| 前端代理 | 同上 proxy target | 硬编码 127.0.0.1:8000 |
后端换端口后代理失效 |
.env |
KA04-构建/backend/.env |
被 gitignore | 新 worktree 缺配置 |
现成能力:backend/engine/config.py:7 env_prefix="",故 api_port 可用环境变量 API_PORT 覆盖,无需改后端代码。
8.3 拟改动(待执行)
KA04-构建/dev.sh:端口环境变量化并透传;进程停止改 PID 文件 + worktree 绝对路径兜底。
BACKEND_PORT="${BACKEND_PORT:-8000}"
FRONTEND_PORT="${FRONTEND_PORT:-5175}"
# 后端:透传 API_PORT(config.py 读 API_PORT)
( cd "$ROOT/backend" && API_PORT="$BACKEND_PORT" nohup uv run python -m engine.main \
>"$LOGS_DIR/backend.log" 2>&1 </dev/null & echo $! >"$LOGS_DIR/backend.pid" )
# 前端:端口 + 代理目标一起透传
( cd "$ROOT/frontend" && VITE_PORT="$FRONTEND_PORT" VITE_API_PORT="$BACKEND_PORT" \
env -u http_proxy -u https_proxy -u all_proxy -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY \
nohup pnpm dev >"$LOGS_DIR/frontend.log" 2>&1 </dev/null & echo $! >"$LOGS_DIR/frontend.pid" )
# stop_all:优先按 PID 文件 kill 进程组;兜底用本 worktree 绝对路径($ROOT),不再用 "capital markets/" 子串
KA04-构建/frontend/vite.config.ts:
const apiPort = process.env.VITE_API_PORT ?? '8000'
server: {
host: '127.0.0.1',
port: Number(process.env.VITE_PORT ?? 5175),
strictPort: true,
proxy: {
'/api': { target: `http://127.0.0.1:${apiPort}`, changeOrigin: true },
'/ws': { target: `ws://127.0.0.1:${apiPort}`, ws: true },
},
}
8.4 执行清单
- [ ] 提交当前全部改动到 main,形成基线
- [ ] 改
KA04-构建/dev.sh(PID 文件 + 端口透传 + 去子串 pkill) - [ ] 改
KA04-构建/frontend/vite.config.ts(端口 + 代理读环境变量) - [ ] 创建 worktree(agentA / agentB)
- [ ] 各 worktree 拷
.env+uv sync+pnpm install - [ ] 分配端口并验证各自可独立启动
- [ ] 合并分支 → 移除 worktree → prune