Worktree Ownership Fencing for Parallel Coding Agents: Blocking Out-of-Scope Rewrites and Late Merges
Git worktrees isolate indexes and working directories, but do not automatically resolve parallel ownership of files, generated targets, and shared contracts. This article uses versioned claims, monotonic generations, diff admission, and ref CAS to create a reclaimable integration protocol that late agents cannot overwrite.
Key Points
- Topic Focus: AI Agent、DevOps、开源
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Creating a separate worktree for each coding agent isolates only the working directory and index. Multiple worktrees still share the object store and refs; two agents may also modify an interface implementation and its callers separately, discovering the logical conflict only during integration. More dangerously, an old agent may pause, let its lease expire, and then resume after a new agent has taken over and merged, still able to submit a stale patch that is syntactically mergeable.
This governs which execution attempt may submit which code resources to the integration queue, not who maintains generated directories. Generated Code Ownership Boundaries defines the long-term owners of sources, templates, and artifacts; Multi-Agent Task Claims resolves ownership of scheduling nodes. This article adds path and semantic-resource fencing for an individual change between those two layers.
Freeze the ownership.v1 Contract
The coordinator issues an immutable workspaceOwnership.v1. The contract must include at least taskId, attemptId, repository identity, baseCommit, ownershipGeneration, allowed and denied normalized paths, semantic resources, the worktree ID, lease, maximum patch byte count, and policy digest:
{
"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..."
}
Paths are first normalized against the repository root, case rules, and Unicode rules. Both the old and new paths of a rename must be authorized, and deletions must not escape detection. Symbols, database migration sequence numbers, public schemas, lockfiles, and code-generation targets are semantic resources that cannot be evaluated using globs alone. Repository Dependency Graph Change Planning can map them to shared consumers; dynamic generation or submodule edges that cannot be resolved enter UNKNOWN and default to human review or an expanded claim.
The contract version must define the semantics of unknown fields, path matching, and generation comparison. A v1 reader encountering a resource type that requires v2 to interpret must reject it rather than ignore it. Only the coordinator may reclaim an expired lease; every takeover monotonically increments the generation, while renewal cannot change the resource set. A worktree and contract are bound one-to-one, and an agent must not move a token to another checkout.
When an agent discovers that it must touch an undeclared path, it must pause before writing and submit an expansion request containing the contract digest, expected generation, current tree and diff digests, and added resources. The coordinator uses CAS to compare the generation and live digest, then atomically checks all other active claims. If no conflict exists, it issues a generation+1 contract containing the complete new write set, immediately fencing the old token. If a conflict exists, the workspace remains unwritten and is routed to human review. Editing files first and adding a claim afterward to legitimize the boundary violation is prohibited.
Write Interception and Final Fencing Are Both Required
A tool proxy checks the contract before every file write, move, or deletion, stopping mistakes early; network access, commands, and paths outside the repository remain controlled by Coding Agent Sandbox Permissions. Local interception is not a security boundary, however: a process may cache stale authorization, and plugins may bypass the proxy. The integration service must therefore recompute the Git diff before the canonical ref, verify every path, mode, submodule OID, generated resource, and actual patch size, and read the current generation.
Integration input is fixed as baseCommit, the candidate commit, the contract digest, and the generation. The service first confirms that the candidate history is traceable to the declared base, then uses git merge-tree to compute a worktree-free merge result over the exact OIDs. A textually conflict-free merge does not imply that resources are conflict-free: the same migration slot, API name, or lockfile still requires policy arbitration. A candidate cannot push directly to the main branch; the integrator writes only the verified tree as a staging commit.
If the claim is stored in an external database while the Git ref is updated separately, a takeover window still exists between the two operations. On the canonical Git side, a private ref should point to the current claim object. Using git update-ref --stdin, the integrator validates and consumes the expected claim OID in the same transaction while moving the queue ref from the expected old OID to the staging commit. The external registry only mirrors the result and does not participate in final authorization. Any change to the claim ref, generation, policy digest, or target ref returns FENCED, retaining the candidate for review without replaying it automatically. Even if an old agent still holds all Git objects, it cannot overwrite the successor. If the process crashes after the transaction commits, the recovery service confirms the result idempotently by commit OID and does not create a second merge.
Prove the Boundary with Conflict Drills
Fault injection must cover at least six groups: A pauses after obtaining generation 7, the lease is reclaimed by B=8, and then A resumes; two agents respectively rename and edit the same file; one side changes a schema while the other changes only generated artifacts; an undeclared build script generates a lockfile; the policy is upgraded after the integration check; and the service is killed after ref CAS succeeds. Tests must also inject case-colliding paths, symlink escapes, shallow clones missing the base, and temporary registry unavailability. The expected result is that out-of-scope changes are rejected locally or at the integration gate, an old generation never advances the canonical ref, and unknown states remain pending review rather than being admitted automatically.
The first enforcement stage can set quantitative gates: 100% of diff paths and both endpoints of every rename have claims; 100% of semantic resources receive an OWNED/SHARED/UNKNOWN determination; 10,000 recovery submissions from old generations have an acceptance count of 0; concurrent integrations against the same old ref produce at most one successful CAS; during fault injection, the canonical tree matches the approved candidate digest 100% of the time; and the fencing service adds less than 2 seconds of p95 latency. Any admitted boundary violation, missing generation record, or unexplained ref change blocks the release instead of being diluted by an average success rate.
Enable It in Stages and Preserve a Rollback Path
Start in observe mode by generating contracts and recording diffs that should have been blocked without changing the existing merge path. Once false positives reach zero, enforce the contract only for migrations, public schemas, generation targets, and CI configuration, then expand it to ordinary source code. Run the old and new integrators in parallel for at least one week, requiring their winner, tree OID, and resource determinations to match exactly. Legacy runners that do not carry a generation may enter only the human-review queue; compatibility cannot justify bypassing the fence.
For false blocks, switch enforcement back to observe and pause new claims; do not rewrite the history of commits that have already been verified. If an incorrect patch has already merged, use the original ownership evidence to create an explicit revert commit and send it through the same gate instead of force-pushing an old ref. When restoring the service, increment the policy generation, revoke unfinished contracts, and reissue them one at a time. The final Coding Agent Change Validation Gate still proves behavioral correctness; the ownership fence only guarantees that the patch entering validation came from the currently authorized execution attempt.
Related
Related Content
Article
What Is Vibe Coding? Will It Change How Programmers Work?
Technical trends, engineering practices, and an indie developer perspective on Vibe Coding, AI, and independent development.
Article
From Copilot to Codex: AI Coding Tools Enter the Agent Era
Technical trends, engineering practices, and indie-developer perspectives around AI, AI Agents, and Vibe Coding.
Article
Why Does MCP Matter? Understanding the AI Tool Protocol
Technical trends, engineering practices, and an independent developer perspective on MCP and AI Agents.
Article
Core Architecture of AI Agents: Planning, Memory, Tools, and Execution
Technical trends, engineering practices, and independent developer perspectives around AI Agents and large language models.