摘要:一队编码 Agent 同时改一个仓库时,卡点不在写代码,而在隔离、路由与回流。本文用三个可运行脚本把三件事落地,并附 git 2.50.1 下的实测输出。
@toc
一、问题:为什么"每个 Agent 一个终端"跑不动
并行跑多个编码 Agent,一开始都很顺:开三个终端、分配三个任务、各干各的。真正的问题在快收工时暴露。
1.1 三个反复出现的症状
症状一:互相踩。 两个 Agent 改了同一个文件,先合的那个把后合的直接覆盖掉。合并时才发现,代码已经对不上。
症状二:重复劳动。 A 已经建好了接口,B 不知道,又照着另一套命名重写了一个。多出来的不是产出,是返工。
症状三:失败找不到人。 CI 挂了,报错指向三个文件,但没人说得清是哪个 Agent 改坏的。想让它自己修,也不知道该把日志发给谁------只能人工逐个比对 diff。
这三个症状有个共同点:都不是模型能力问题,是工程协调问题。 模型写代码没问题,是"一队 Agent"这种工作方式缺少配套的工程约束。
1.2 这和多智能体架构不是同一件事
需要先把边界划清。本文讨论的不是"多智能体怎么设计"------比如几个 Agent 该用什么拓扑、要不要中心调度器、状态怎么在它们之间传,那是架构选型问题。
本文讨论的是另一侧:架构已经定了,一队 Agent 要同时动同一个仓库,工程上怎么保证它们不打架、出了错怎么找到责任人。
打个比方:前者是"工厂怎么布局",本篇是"三条产线同时开动时,物料怎么分、异常怎么回流到对应工位"。布局定了之后,后面这套东西一样要有人做。
1.3 三件套:隔离、路由、回流
把这一侧要做的事收拢,是三件:

| 件 | 解决的问题 | 落地形式 |
|---|---|---|
| 隔离 | 并行不等于互相踩 | 每个 Agent 一个 git worktree,独立分支 |
| 路由 | 任务分给谁、边界在哪 | 任务清单声明"谁能改哪块路径" |
| 回流 | 失败与评审怎么回到责任 Agent | CI 失败按 diff 归属,路由回对应会话 |
三件缺一,都会退化成"每个 Agent 开一个终端"------并行是假的,冲突是真的。
二、隔离:给每个 Agent 一个 git worktree
2.1 为什么是 worktree,不是 clone 也不是切分支
三种做法在真实团队里都有人用,差别在于"共享"和"隔离"的比例:
- 切分支:共用一个工作目录。切分支时另一个 Agent 的未提交改动会被带着走,等于没有隔离。
- clone 多份 :每个 Agent 一份完整仓库。隔离最彻底,但各自一份
.git,本地构建缓存、依赖目录都要重来,磁盘和首次构建成本翻倍。 git worktree:同一个.git对象库,多个独立工作目录。隔离到工作区一级,但共享提交历史与对象库------拉取、构建缓存可以复用。
git worktree 恰好落在中间:工作区独立,仓库共享。 对"一队 Agent 并行改同一个仓库"这个场景,这是最省的做法。
2.2 建工作区:一个循环就够
把任务清单读进来,逐个建工作区。脚本不用长:
bash
#!/usr/bin/env bash
# spawn_agents.sh ------ 给一队 Agent 各建一个隔离工作区
# 环境:git 2.50.1 / macOS(实测 2026-09-18)
# 用法: ./spawn_agents.sh <仓库路径> <任务清单文件>
set -euo pipefail
REPO="${1:?用法: spawn_agents.sh <仓库路径> <任务清单>}"
TASKS="${2:-tasks.txt}"
ROOT="$(cd "$REPO" && git rev-parse --show-toplevel)"
WT_DIR="$(dirname "$ROOT")/wt"
mkdir -p "$WT_DIR"
while IFS='|' read -r task branch owner exclusive; do
[ -z "${task:-}" ] && continue
wt="$WT_DIR/$task"
if [ -d "$wt" ]; then echo "SKIP $task(工作区已存在)"; continue; fi
git -C "$ROOT" worktree add -q -b "$branch" "$wt" main
echo "SPAWN $task -> $wt branch=$branch owner=$owner exclusive=${exclusive:-none}"
done < "$TASKS"
echo "---- worktree list ----"
git -C "$ROOT" worktree list
配套的任务清单是四段式,用 | 分隔(后面第三节会用到最后一列):
text
task-a|feat/api|agent-a|src/api/,config/ports.yaml
task-b|feat/ui|agent-b|src/ui/
task-c|feat/report|agent-c|src/report/,config/ports.yaml
实测输出(三个任务、一个已存在的工作区会自动跳过):
text
SPAWN task-a -> /private/tmp/ma-demo/wt/task-a branch=feat/api owner=agent-a exclusive=src/api/,config/ports.yaml
SPAWN task-b -> /private/tmp/ma-demo/wt/task-b branch=feat/ui owner=agent-b exclusive=src/ui/
SPAWN task-c -> /private/tmp/ma-demo/wt/task-c branch=feat/report owner=agent-c exclusive=src/report/,config/ports.yaml
---- worktree list ----
/private/tmp/ma-demo/repo 01f4462 [main]
/private/tmp/ma-demo/wt/task-a 01f4462 [feat/api]
/private/tmp/ma-demo/wt/task-b 01f4462 [feat/ui]
/private/tmp/ma-demo/wt/task-c 01f4462 [feat/report]
脚本是幂等的:工作区已存在就跳过,不会重复建分支。重跑一遍只会打印三行 SKIP。
2.3 一个容易误判的点:worktree 不是沙箱
git worktree 解决的是工作区隔离 ,不解决执行隔离 。两个 Agent 的工作区互相看不到对方的未提交改动,但它们跑在同一台机器上、同一个用户权限下------能读到 ~/.ssh,能连同一个数据库,能占同一个端口。
所以隔离要分两层看:
| 层 | 手段 | 解决什么 |
|---|---|---|
| 工作区隔离 | git worktree |
文件互不覆盖、分支互不干扰 |
| 执行隔离 | 容器 / 沙箱 / 独立运行账号 | 权限、网络、文件系统的边界 |
只做第一层,等于把"代码不打架"解决了,但"进程不越界"没解决。这两件事要分开立项,别指望一个 git worktree 全都管。
三、路由:把"谁能改什么"写成声明
3.1 声明为什么要写进任务清单
Agent 的边界如果只写在提示词里------"你只负责接口部分"------等于没写。工具调用一旦发生,提示词拦不住它去改别的文件。
有效的位置是执行之前:把"谁能改哪块路径"写成一份机器可读的声明,在建工作区时就绑上去,之后每次检查都拿它当尺子。
这就是任务清单里第四列的作用。它的价值不在于记录,而在于可以被脚本比对。
3.2 开工前发现冲突:独占资源检查
先看这份清单里,config/ports.yaml 被 task-a 和 task-c 同时声明了------这是真实场景里最常见的一类冲突:两个任务都要改同一个配置文件。
agent_status.py 做两件事:采集各工作区状态,然后检查两类问题。
python
#!/usr/bin/env python3
"""agent_status.py ------ 采集一队 Agent 的工作区状态 + 声明边界检查
环境:Python 3.13 / git 2.50.1(实测 2026-09-18)
用法:python agent_status.py <仓库路径> <任务清单>
退出码:0 = 全部合规;1 = 存在资源冲突或越界
"""
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
def git(repo: Path, *args: str) -> str:
out = subprocess.run(["git", "-C", str(repo), *args],
capture_output=True, text=True, check=True)
return out.stdout.strip()
def load_tasks(path: Path) -> list[dict]:
rows = []
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
task, branch, owner, *rest = line.split("|")
rows.append({
"task": task.strip(), "branch": branch.strip(), "owner": owner.strip(),
"exclusive": [p.strip() for p in (rest[0].split(",") if rest else []) if p.strip()],
})
return rows
def changed_files(repo: Path, branch: str) -> list[str]:
diff = git(repo, "diff", "--name-only", f"main...{branch}")
return [l for l in diff.splitlines() if l.strip()]
def collect(repo: Path, tasks: list[dict]) -> list[dict]:
wt_dir = repo.parent / "wt"
for t in tasks:
wt = wt_dir / t["task"]
if not wt.exists():
t.update(state="MISSING", dirty="-", ahead="-", files=[])
continue
dirty = git(wt, "status", "--porcelain")
t["dirty"] = str(len([l for l in dirty.splitlines() if l.strip()]))
t["ahead"] = git(repo, "rev-list", "--count", f"main..{t['branch']}")
t["files"] = changed_files(repo, t["branch"])
t["state"] = "WORKING" if t["dirty"] != "0" else "IDLE"
return tasks
def exclusive_conflicts(tasks: list[dict]) -> dict[str, list[str]]:
owners: dict[str, list[str]] = defaultdict(list)
for t in tasks:
for res in t["exclusive"]:
owners[res].append(t["task"])
return {res: o for res, o in owners.items() if len(o) > 1}
def boundary_violations(tasks: list[dict]) -> list[tuple[str, str, str]]:
"""改动文件落在自己声明的独占路径之外 → 越界"""
bad = []
for t in tasks:
if not t["exclusive"]:
continue
for f in t["files"]:
if not any(f.startswith(p) or f == p for p in t["exclusive"]):
bad.append((t["task"], t["owner"], f))
return bad
def main() -> int:
repo = Path(sys.argv[1]).resolve()
tasks = collect(repo, load_tasks(Path(sys.argv[2])))
print(f"{'任务':<10}{'分支':<13}{'负责人':<10}{'状态':<9}{'未提交':<6}{'超前main':<7}改动文件")
print("-" * 82)
for t in tasks:
files = ",".join(t.get("files", [])) or "-"
print(f"{t['task']:<10}{t['branch']:<13}{t['owner']:<10}{t.get('state','-'):<9}"
f"{t.get('dirty','-'):<6}{t.get('ahead','-'):<7}{files}")
problems = 0
conflicts = exclusive_conflicts(tasks)
print("\n[1] 独占资源冲突:")
if conflicts:
problems += 1
for res, owners in conflicts.items():
print(f" CONFLICT {res} 被 {len(owners)} 个任务同时声明 -> {', '.join(owners)}")
else:
print(" OK")
print("\n[2] 声明边界检查(改动是否越界):")
bad = boundary_violations(tasks)
if bad:
problems += 1
for task, owner, f in bad:
print(f" VIOLATION {task}({owner}) 改动了 {f},超出其声明范围")
else:
print(" OK")
print(f"\n结论:{'存在 ' + str(problems) + ' 类问题,需人工确认后再合' if problems else '全部合规,可进入合入流程'}")
return 1 if problems else 0
if __name__ == "__main__":
sys.exit(main())
实测输出(三个 Agent 各提交了一次改动之后):
text
任务 分支 负责人 状态 未提交 超前main 改动文件
----------------------------------------------------------------------------------
task-a feat/api agent-a IDLE 0 1 config/ports.yaml,src/api/routes.py
task-b feat/ui agent-b IDLE 0 1 src/ui/Button.tsx
task-c feat/report agent-c IDLE 0 1 src/api/summary.py,src/report/build.py
[1] 独占资源冲突:
CONFLICT config/ports.yaml 被 2 个任务同时声明 -> task-a, task-c
[2] 声明边界检查(改动是否越界):
VIOLATION task-c(agent-c) 改动了 src/api/summary.py,超出其声明范围
结论:存在 2 类问题,需人工确认后再合
3.3 越界检测:把"顺手改了一下"抓出来
上面输出里第二类问题更值得说。
task-c 声明的是 src/report/,但它顺手在 src/api/summary.py 里加了一个接口。从单个 Agent 的视角看,这是"顺手把活干得更完整";放到一队 Agent 里看,这是踩进了别人声明的领地 ------task-a 负责 src/api/,它可能正在重写同一批路由。
这类问题在人工复核时最容易被放过,因为 diff 看起来很合理。脚本比对声明范围,才能稳定地报出来。
判断规则很简单,就是路径前缀匹配:
- 改动文件落在自己声明的路径下 → 合规
- 不在任何声明路径下 → 越界,报出任务名与负责人
- 脚本退出码非 0 → 阻断合入流程
退出码是这套东西能接进 CI 的关键。 打印好看不重要,能返回非 0 才能在流水线里自动拦住。
四、回流:让失败信号回到责任 Agent
4.1 归属判定:一个文件一个文件地算
第三步要回答的问题很具体:CI 报错指向三个文件,该把日志发给谁?
思路是把"文件"和"改动过它的分支"建一张反向索引,然后对每个失败文件查一次:
python
#!/usr/bin/env python3
"""route_feedback.py ------ 把失败信号(CI 报错文件)路由回责任 Agent
环境:Python 3.13 / git 2.50.1(实测 2026-09-18)
用法:python route_feedback.py <仓库路径> <任务清单> <失败文件> [<失败文件> ...]
"""
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
ACTION = {
"single": "回写该 Agent 会话:附失败日志 + 要求只改自己声明范围内的文件",
"multi": "多 Agent 同时改动过,先人工定责(或按最后提交者),再回写单一会话",
"none": "无归属(基线文件或未纳入任务),不派给 Agent,转人工排查",
}
def git(repo: Path, *args: str) -> str:
out = subprocess.run(["git", "-C", str(repo), *args],
capture_output=True, text=True, check=True)
return out.stdout.strip()
def load_branches(path: Path) -> dict[str, list[tuple[str, str]]]:
m: dict[str, list[tuple[str, str]]] = defaultdict(list)
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
task, branch, owner, *_ = line.split("|")
m[branch.strip()] = (task.strip(), owner.strip())
return m
def blame(repo: Path, branches: dict) -> dict[str, list[tuple[str, str]]]:
"""文件 -> 改动过它的 (任务, 负责人) 列表"""
owners: dict[str, list[tuple[str, str]]] = defaultdict(list)
for branch, (task, owner) in branches.items():
diff = git(repo, "diff", "--name-only", f"main...{branch}")
for f in diff.splitlines():
if f.strip():
owners[f.strip()].append((task, owner))
return owners
def main() -> int:
repo = Path(sys.argv[1]).resolve()
branches = load_branches(Path(sys.argv[2]))
failed = sys.argv[3:]
owners = blame(repo, branches)
print("失败文件 → 责任归属")
print("-" * 74)
escalations = 0
for f in failed:
who = owners.get(f, [])
if len(who) == 1:
task, owner = who[0]
kind = "single"
print(f" {f}\n -> {task} / {owner} [{kind}]")
elif len(who) > 1:
kind = "multi"
names = ", ".join(f"{t}({o})" for t, o in who)
print(f" {f}\n -> 多责任:{names} [{kind}]")
else:
kind = "none"
print(f" {f}\n -> 无归属 [{kind}]")
print(f" 建议动作:{ACTION[kind]}")
if kind != "single":
escalations += 1
print(f"\n统计:失败 {len(failed)} 项,可直接派回 {len(failed) - escalations} 项,需人工介入 {escalations} 项")
return 0
if __name__ == "__main__":
sys.exit(main())
实测输出(传入三个失败文件,分别对应单责任、多责任、无归属三种情况):
text
失败文件 → 责任归属
--------------------------------------------------------------------------
src/api/routes.py
-> 多责任:task-a(agent-a), task-c(agent-c) [multi]
建议动作:多 Agent 同时改动过,先人工定责(或按最后提交者),再回写单一会话
src/report/build.py
-> task-c / agent-c [single]
建议动作:回写该 Agent 会话:附失败日志 + 要求只改自己声明范围内的文件
src/legacy/old.py
-> 无归属 [none]
建议动作:无归属(基线文件或未纳入任务),不派给 Agent,转人工排查
统计:失败 3 项,可直接派回 1 项,需人工介入 2 项
三个失败文件,只有一个是能直接派回去的。这个比例本身就是信息。

4.2 为什么要先分类、再派回
如果不分类,把三个失败文件一股脑丢给某一个 Agent,会发生三件不太好的事:
第一件,多责任的问题被随机指派。 src/api/routes.py 是 task-a 和 task-c 都改过的。指给谁都是猜。猜错的结果是:被指派的那个 Agent 会去改一个它并不完全了解的改动------而它自己的改动本来就是对的。
第二件,无归属的问题变成"流畅的解释"。 让 Agent 去修 src/legacy/old.py,它修不了,但它会给你一段解释:可能是环境问题、可能是依赖版本、建议检查配置。这段话读起来很合理,问题却没解决,还多烧了一轮 token。
第三件,责任链断了。 一旦出现"Agent 修不动 → 人再接手",如果不记录这次升级,下一次同样的失败还会被派回 Agent,再解释一遍。
这也正是把三类分开的原因:单责任的派回,多责任的定责,无归属的转人工。 分类不是为了让输出好看,是因为三类问题的正确处理方式根本不同。
4.3 回流要带什么信息
派回给 Agent 的会话里,至少要带三样东西:
| 信息 | 为什么必须有 |
|---|---|
| 失败日志原文 | 不要摘录、不要转述,原样给它,转述会丢关键栈帧 |
| 归属依据 | 告诉它"这个文件是你改的",它才知道上下文在哪个会话里 |
| 边界提醒 | 明确要求只改自己声明范围内的文件,否则它会顺手再越一次界 |
第三样最容易被忽略。如果这次失败正是越界引起的,而回流时不提边界,Agent 的下一次修复很可能再次越界------同一个问题会以同样方式复发。
五、实测账:一队 Agent 跑一轮是什么样
5.1 实测环境
| 项 | 值 |
|---|---|
| git | 2.50.1(Apple Git-155) |
| Python | 3.13 |
| 仓库 | 单仓库,main 为基线,src/ 下三个模块目录 |
| 并行任务 | 3 个(task-a / task-b / task-c),各一个 worktree |
| 脚本 | spawn_agents.sh / agent_status.py / route_feedback.py,均实跑 |
整个过程是:建工作区 → 三个 Agent 各提交一次改动 → 跑状态与边界检查 → 模拟 CI 传入三个失败文件 → 跑归属路由。
5.2 三轮输出对照
| 轮次 | 跑什么 | 输出要点 | 退出码 |
|---|---|---|---|
| 第 1 轮 | spawn_agents.sh |
3 个工作区建立,worktree list 显示 4 条(含 main) |
0 |
| 第 2 轮 | agent_status.py |
检出 1 处独占资源冲突 + 1 处越界改动 | 1 |
| 第 3 轮 | route_feedback.py |
3 个失败文件 → 可派回 1 项,需人工 2 项 | 0 |
值得留意的是第 2 轮那个退出码:它是 1。 也就是说,这套检查可以直接挂在合入前,冲突和越界在开工阶段就把流水线拦住,不会等到合并时才发现代码已经对不上。
5.3 这次实测没解决的问题
诚实说三条局限:
第一,越界检测是路径级的,不是语义级的。 它只能判断"文件路径在不在声明范围内"。如果两个 Agent 改的是同一路径下的不同文件、但业务逻辑耦合(比如一个改接口签名、一个调这个接口),脚本报不出来。这类要靠测试和评审兜。
第二,冲突检测只覆盖显式声明的资源。 端口、数据库行、外部 API 配额这些如果没写进清单,脚本一样看不见。清单写得越粗,检查越像摆设。
第三,归属判定用的是"文件被哪些分支改过"。 如果某个分支改动量极大、覆盖了半个仓库,它会在几乎所有失败文件上被判为责任方。这种情况下"责任"其实已经失去分辨力------需要更细的归属手段(按行级 blame),本文没有做。
六、与现成工具的分工
6.1 现成的元 harness 已经覆盖了什么
这类"同时监督多个 Agent"的工具,2026 年下半年明显多了起来。两个信号值得先记下来:
- 2026-09-17,GitHub Trending 上出现一个 Go 写的元 harness------它把每个 Agent 放进独立工作区,并把 CI 失败、评审意见、合并冲突自动路由回"责任 Agent"(项目说明见其仓库 README)。
- 同一天,OpenAI 的 Agents API 转入公测:官方说明里明确支持长期运行任务、并行执行与多个子智能体协作,且不额外收服务费,只按 token 与工具调用计费。
两条消息指向同一件事:"跑一队 Agent"正在从个人技巧,变成有工具支撑的工作方式。 挑几个有代表性的放一起看:
| 工具 | 形态 | 隔离方式 | 反馈回流 | 适合场景 |
|---|---|---|---|---|
| agent-orchestrator(Go,Apache-2.0) | 元 harness | 每个 Agent 一个 git worktree | CI 失败 / 评审意见 / 冲突自动路由回责任 Agent,带看板 | 已经在一台机器上跑多个终端 Agent |
| 像素化编排类桌面工具 | 图形界面 | 各终端独立会话 | 以可视化看板为主 | 想用界面管多个会话、不想敲命令 |
| 通用多智能体框架 | SDK / 框架 | 由开发者自行实现 | 需自己接 CI 与评审 | 把 Agent 编进自己的应用 |
| 企业级环曜 Agent 之类的私有化平台 | 平台 | 平台侧提供工作区与会话隔离 | 提供运行记录与审计 | 需要内网部署、数据不出域、统一治理 |
这张表的读法不是"谁更好",而是站在哪一层:编排工具解决"一队终端 Agent 怎么管",框架解决"Agent 怎么编进业务系统",私有化平台解决"内网里怎么统一纳管与留痕"。三者能叠,不冲突。
6.2 自建的部分该保留什么
不管用不用现成工具,有三样建议自己留一份:
一是任务清单。 它是"谁能改哪块"的单一事实来源。工具换了、平台换了,这份声明还在,迁移成本就低。
二是归属判定的规则。 单责任 / 多责任 / 无归属怎么分、各自怎么处理,这是团队约定,不该藏在某个工具的实现里。
三是退出码。 检查能不能拦住流程,取决于它返回什么,而不是它打印什么。这一点自建脚本反而更可控。
这份自建清单不必从零造。企业级环曜 Agent 这类私有化平台已把会话隔离与运行记录做在平台侧,自建时至少保留一份等价的最小实现,后续换工具或换平台都不会卡住。
七、落地的三个边界条件
7.1 安全边界:工作区隔离不等于执行隔离
前面提过,这里再强调一次,因为它最容易在真实项目里出事。
git worktree 让两个 Agent 看不到对方的工作区,但它们在同一台机器、同一个账号下运行------凭证、网络、可访问的路径完全一样。 如果给 Agent 开放了执行命令的能力,它的影响范围不会因为 git worktree 而缩小。
所以部署时的顺序建议是:先有执行隔离(容器、独立账号、受限网络),再谈工作区隔离。反过来做,会得到一个"文件不打架、但权限敞开"的当前状态------看起来整洁,实际风险敞口没变。
7.2 管理边界:一个人带几个 Agent
并行数量不是越多越好,瓶颈在"单位时间内需要人工介入的次数"。
本文的实测里,3 个任务产生了 2 类需要人工确认的问题。如果把任务加到 8 个,人工确认的次数通常不是线性增长------因为越界的组合、共享资源的冲突会成对增加。在任务清单还没写细的时候,先控制并行数,比急着扩规模更有价值。
并行规模真要往上走,靠人工确认会先到极限。此时接入统一的会话与运行记录(环曜 Agent 这类私有化平台、或自建的看板都行)比继续加人盯更划算。
7.3 成本边界:并行不等于更快
并行省的是"等待时间",不省"评审与返工时间"。如果任务之间本来就有强依赖(B 必须等 A 的接口定下来),拆成并行只会制造等待加冲突。
判断标准可以很朴素:任务之间如果没有共享文件、没有前置依赖,才值得并行。 有依赖的,串行反而更快。
八、FAQ
Q1: git worktree 和普通的 feature 分支,实际差别在哪? **A1:**差别在"工作目录"这一层。普通分支切换时,工作目录是同一个,未提交的改动、构建缓存、依赖目录都跟着切走;git worktree 给每个分支一个独立目录,同一时刻能有多份工作区并存。一队 Agent 并行时,后者才谈得上隔离。
**Q2:**这套东西必须自己写脚本吗,有没有现成工具可以用? **A2:**2026 年下半年这类元 harness 工具已经不少,能覆盖隔离、看板、反馈路由。自建的价值主要在两处:任务清单的声明格式由自己定、检查的退出码可控。如果只想要快速上手,先用现成工具跑通流程,再把清单和规则抽出来自持,是更省的路子。需要内网部署、数据不出域的场景,也可以直接选环曜这类私有化方案,把工作区隔离与审计一起纳管。
**Q3:**越界检测报了,但 Agent 说它是"顺手把活干完整",该不该拦? **A3:**建议拦。多 Agent 场景下,"顺手"是冲突的主要来源。真正需要的改动,应该回到任务清单里补一条声明,让改动有归属------而不是让某个 Agent 自行扩大范围。
**Q4:**失败文件"无归属"的情况,一般是什么原因? **A4:**常见三种:改动来自基线代码(没人动过)、文件由人工手工改的、任务清单本身没覆盖这部分。三种都说明"这次失败不在 Agent 的责任范围内",正确做法是转人工,不要勉强派回。
**Q5:**并行任务数该定多少?有没有参考值? **A5:**没有通用数字,取决于两点:任务之间共享资源的多少、你们对越界问题的容忍度。经验做法是先从 2--3 个起步,观察每轮的"人工介入次数";如果每轮都有需要定责的冲突,说明任务清单写得还不够细,此时扩规模只会放大问题。
**Q6:**CI 已经能跑测试了,为什么还要单独做一层归属判定? **A6:**CI 回答的是"哪里坏了",归属判定回答的是"该谁修"。在一队 Agent 并行的情况下,后者才是决定"能不能自动修复"的那一步------不知道发给谁,就只能人工逐个比对 diff。
总结
一队 Agent 并行改同一个仓库,工程上要补的三件事是:隔离 (git worktree + 独立分支)、路由 (任务清单声明边界、开工前查冲突与越界)、回流(失败按 diff 归属路由回责任会话)。这三件事自建脚本能做,编排工具与环曜这类私有化平台也各自覆盖了一部分,选哪条路取决于要不要把执行隔离与审计一并纳管。
本文的三个脚本都在 git 2.50.1 + Python 3.13 下实跑过,输出原样贴出。其中最值得先落地的是退出码------打印好看的检查到处都是,能拦住流程的检查才少。
适用边界与风险提示:
- 本文的隔离方案只覆盖工作区一层,执行隔离(容器 / 账号 / 网络)需要另行设计,尤其当 Agent 拥有执行命令或写数据库的能力时。
- 越界检测是路径级的,不做语义耦合判断;分支改动量过大时,归属判定的分辨力会下降。
- 所有脚本按 POSIX 环境编写并在 macOS 实跑,Windows 下
IFS读取与路径处理需另行调整;生产使用前建议先在隔离仓库里跑一轮完整流程。 - 文中提到的第三方工具信息来自其公开仓库与文档(2026-09-17/18 检索),版本与能力会随项目演进变化,选型前请以项目当前状态为准。
如果你的团队已经在并行跑多个 Agent,欢迎在评论区说说遇到的是哪一类问题------是冲突多、还是失败找不到人。这两类问题的解法差别挺大。
