并行编程 Agent 的 Worktree 所有权围栏:阻断越界改写与迟到合并
Git worktree 能隔离索引和工作目录,却不会自动解决文件、生成目标与共享契约的并行所有权。本文用版本化 claim、单调 generation、差异准入和 ref CAS 建立可回收且不可被迟到 Agent 覆盖的集成协议。
本文要点
- 主题聚焦:AI Agent、DevOps、开源
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
给每个编程 Agent 创建独立 worktree,只隔离工作目录与索引。多个 worktree 仍共享对象库和 refs;两个 Agent 也可能分别修改同一接口的实现与调用方,直到集成时才发现逻辑冲突。更危险的是,旧 Agent 暂停后租约到期,新 Agent 已接管并合并,旧进程恢复后仍能提交一份语法上可合并的过期补丁。
这里治理的是“哪次执行可把哪些代码资源送入集成队列”,不是生成目录由谁维护。生成代码所有权边界定义源、模板与产物的长期 owner;多 Agent 任务 Claim解决调度节点归属。本文在两者之间增加一次变更的路径和语义资源围栏。
冻结 ownership.v1 合同
协调器签发不可变的 workspaceOwnership.v1。合同至少包含 taskId、attemptId、仓库身份、baseCommit、ownershipGeneration、允许与禁止的规范化路径、语义资源、worktree ID、租约、最大补丁字节数及策略摘要:
{
"schema": "coding.workspace.ownership.v1",
"repo": "sha256:9d2a...",
"baseCommit": "81f0c7a...",
"attemptId": "a-184",
"ownershipGeneration": 7,
"paths": ["packages/billing/**", "tests/billing/**"],
"resources": ["api:Invoice.v2", "migration-slot:billing"],
"deny": [".github/**", "pnpm-lock.yaml"],
"policyDigest": "sha256:64be..."
}
路径先按仓库根目录、大小写规则和 Unicode 规则规范化;rename 的旧、新路径都要授权,删除也不能漏检。符号、数据库迁移序号、公共 schema、锁文件和代码生成目标属于语义资源,不能仅靠 glob 判断。它们可由仓库依赖图改动规划映射到共享消费者;无法解析的动态生成或子模块边进入 UNKNOWN,默认转人工或扩大 claim。
合同版本要规定 unknown 字段、路径匹配和 generation 比较语义。v1 reader 遇到要求 v2 才能解释的资源类型必须拒绝,而不是忽略。租约到期只允许协调器回收;每次接管都单调增加 generation,续租不能改变资源集合。worktree 与合同一一绑定,Agent 不得把 token 搬到另一个 checkout。
Agent 发现必须触碰未声明路径时,要在写入前暂停并提交扩张请求,携带合同摘要、expected generation、当前 tree/diff 摘要和新增资源。协调器以 CAS 比较 generation 与现场摘要,再原子检查其他活跃 claim;无冲突才签发包含完整新写集的 generation+1 合同,旧 token 立即 fenced。有冲突就保持未写状态并转人工,禁止先改文件、再补 claim 把越界事实合法化。
写入拦截和最终围栏缺一不可
工具代理在每次文件写入、移动、删除前检查合同,可尽早阻止误操作;网络、命令和仓库外路径仍由编程 Agent 沙箱权限控制。但本地拦截不是安全边界:进程可能缓存旧授权,插件也可能绕过代理。因此 canonical ref 前的集成服务必须重新计算 Git diff,验证所有路径、mode、子模块 OID、生成资源和实际补丁大小,并读取当前 generation。
集成输入固定为 baseCommit、候选 commit、合同摘要和 generation。先确认候选历史可追到声明的 base,再用 git merge-tree 对准确 OID 计算无工作树合并结果;文本无冲突不等于资源无冲突,同一 migration slot、API 名或 lockfile 仍需策略仲裁。候选不得直接 push 主干,集成器只把验证后的 tree 写成 staging commit。
若 claim 保存在外部数据库、Git ref 另行更新,两次操作之间仍有接管窗口。canonical Git 端应以私有 ref 指向当前 claim 对象;集成器通过 git update-ref --stdin 在同一事务中校验并消费预期 claim OID,同时把队列 ref 从预期旧 OID 推到 staging commit。外部注册表只镜像结果,不参与最终授权。claim ref、generation、策略摘要或目标 ref 任一变化都返回 FENCED,留下候选供审阅但不自动重放。这样旧 Agent 即使持有完整 Git 对象,也无法覆盖接管者。事务提交后进程崩溃时,恢复器按 commit OID 幂等确认,不创建第二次合并。
用冲突演练证明边界
故障注入至少包含六组:A 获得 generation 7 后暂停,租约回收给 B=8,再恢复 A;两个 Agent 分别 rename 与编辑同一文件;一方改 schema、另一方只改生成产物;未声明的构建脚本生成 lockfile;集成检查后策略升级;ref CAS 成功后服务被杀。还要注入大小写路径碰撞、符号链接逃逸、浅克隆缺 base 和注册表暂时不可用。预期是越界在本地或集成门被拒绝,旧 generation 永不推进 canonical ref,未知状态保持待审而非自动放行。
首个强制阶段可设置量化门:100% diff 路径和 rename 双端有 claim;100% 语义资源有 OWNED/SHARED/UNKNOWN 判定;旧 generation 的 10,000 次恢复提交接受数为 0;同一旧 ref 的并发集成最多一次 CAS 成功;注入中 canonical tree 与审定候选摘要一致率 100%;围栏服务 p95 增量低于 2 秒。任何越界接受、漏记 generation 或无法解释的 ref 变化都阻断发布,而不是用平均成功率稀释。
分阶段启用并保留撤销路径
先在 observe 模式生成合同并记录本应阻断的 diff,不改变现有合并;误报归零后,只强制迁移、公共 schema、生成目标和 CI 配置,再扩到普通源码。新旧集成器双算结果至少一周,要求 winner、tree OID 和资源判定完全一致。旧 runner 不携带 generation 时只能进入人工队列,不能以兼容为由绕过围栏。
出现误阻断可把 enforcement 切回 observe,并暂停新 claim;已经验证的 commit 不重写历史。若错误补丁已合并,按原 ownership 证据生成显式 revert commit,并通过同一门禁,而不是强推旧 ref。恢复服务时提升 policy generation、撤销未完成合同,再逐个重签。最终的编程 Agent 改动验证门仍负责证明行为正确;所有权围栏只保证进入验证的补丁来自当前、获准的执行者。
Related