多 Agent 并行不打架:worktree 隔离与反馈回流落地(附脚本)

摘要:一队编码 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,欢迎在评论区说说遇到的是哪一类问题------是冲突多、还是失败找不到人。这两类问题的解法差别挺大。

相关推荐
亦暖筑序1 小时前
AgentScope Java 实战:Agent 的状态存在哪、怎么恢复、怎么隔离?
人工智能·后端·agent
jsl_jsl_jsl1 小时前
《Bun 后端怎么变桌面软件:Tauri 2 三进程架构与崩溃自愈》
人工智能
lucas_AI1 小时前
第 10 讲 · PGO 实战:让程序的真实运行数据指导编译
人工智能
lucas_AI1 小时前
第 8 讲 · oeAware 与中断绑核:把手工调优自动化
人工智能
hyunbar7771 小时前
LangChain 实战:上下文工程长期记忆管理
人工智能
lucas_AI1 小时前
第 11 讲 · KAE 硬件加速:不改一行业务代码的性能提升
人工智能
hanbon1 小时前
技术参数响应表怎么写?
人工智能·招投标·ai写标书·评分表
byte轻骑兵1 小时前
【BlueZ 】sdp 模块:服务发现协议的用户态实现基础
linux·人工智能·bluez·电脑蓝牙·嵌入式蓝牙
小白学大数据1 小时前
Python 项目实战:用 Flask 构建生产级 MySQL 增删改查 REST API
开发语言·人工智能·python·mysql·flask