Before Your Antigravity Agents Fight Over the Same File — Ownership Manifests and Conflict Detection
Multi-agent workflows do not break at the design stage. They break at runtime. Here are the field notes: an ownership manifest that pins each agent's editable region, a git-only conflict detector, and a three-part handoff contract.
Most articles on multi-agent boundaries end at "separate the responsibilities." I believed that too, at first. The problem is that separating responsibilities on paper gives you no guarantee the separation holds at runtime.
Running two or three Antigravity agents in parallel as an indie developer on my own projects, I would split the regions cleanly in the design, and still wake up to find the same file under src/lib/ rewritten by both agents, with one set of changes quietly swallowed by the other. The design was correct. There was simply nothing forcing the agents to obey it.
This is a field guide to boundaries that bite at runtime, built from that mistake. Not concepts, but three things you can use immediately: pinning ownership in one file, finding conflicts with git alone, and passing work as a contract.
Why a "design-time boundary" dissolves at runtime
On a human team, you say something before touching a file you do not own. Agents do not. A boundary you have not made explicit does not exist for them. Worse, an agent will infer its own scope from the task context.
Tell it "implement the auth flow" and it will happily reach into shared utilities that feel auth-adjacent. From its point of view that is reasonable. But if another agent is editing those same utilities at the same time for a different reason, that is where the boundary dissolves.
So a boundary written only inside a natural-language prompt will not be respected. It has to live somewhere a machine can read it and a machine can reject violations of it. What I landed on was a single file: an ownership manifest.
The ownership manifest — pinning regions in one file
The idea is simple. Declare which agent may touch which paths in one file at the repository root. Put it there as a fact about the repo, not as an instruction buried in a prompt.
// agents.ownership.json{ "agents": { "auth-agent": { "owns": ["src/features/auth/**"], "deny": ["src/lib/**"] }, "billing-agent": { "owns": ["src/features/billing/**"], "deny": ["src/lib/**"] }, "shared-agent": { "owns": ["src/lib/**", "src/hooks/**"] } }, "rule": "Shared code (src/lib, src/hooks) is owned only by shared-agent. Cross a path boundary and stop."}
The key move is carving the shared directories out to a dedicated third owner (shared-agent). My first painful lesson came from treating shared code as an implicit "anyone may edit" zone. Shared code is where conflicts breed, so I force it onto a single owner; if another agent needs a change there, it files a request to shared-agent instead of editing directly. One-way traffic.
When I launch each agent, I feed only the paths from its owns list as task context: "The only thing you may touch is src/features/auth/. If you need to change anything else, do not change it — report the reason and stop." The instruction is natural language, but its authority points at one fact, the manifest, so agents never disagree about who owns what.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to pin each agent's editable region in a single ownership manifest and reject out-of-region changes mechanically before commit
✦A lightweight conflict detector built on nothing but git diff, plus the recovery procedure when two agents touch the same file
✦A three-part handoff contract (decided / requested / undefined) and a clear rule for when a supervising agent is worth its cost
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
A pre-commit guard — rejecting out-of-region changes mechanically
Even with the manifest in place, an agent can still wander out of its region. So I check, before commit, whether the changed files fall inside that agent's owns. Do not rely on the agent's good intentions; stop it with a machine at the end.
# guard_ownership.py — verify an agent's changes stay inside its owned regionimport json, subprocess, sys, fnmatchagent = sys.argv[1] # e.g. auth-agentmanifest = json.load(open("agents.ownership.json"))["agents"][agent]owns = manifest["owns"]deny = manifest.get("deny", [])changed = subprocess.check_output( ["git", "diff", "--name-only", "HEAD"], text=True).splitlines()violations = []for path in changed: in_owns = any(fnmatch.fnmatch(path, p) for p in owns) in_deny = any(fnmatch.fnmatch(path, p) for p in deny) if in_deny or not in_owns: violations.append(path)if violations: print(f"❌ {agent} changed files outside its region: {violations}") sys.exit(1)print(f"✅ all of {agent}'s changes are inside its owned region")
Wire this into each agent's completion hook and the moment an out-of-region change sneaks in, it stops with exit 1. After adding this guard, the "wait, the shared file got rewritten overnight" class of rework almost disappeared for me. It works because it moves the detection point from "morning review" to "just before commit." The damage stays contained to one diff.
Conflict detection — finding the fight with git diff alone
Even with regions split, two agents can touch the same file — when you run phases on a time offset, or when two requests into the shared region overlap. You can find this with no special tooling, using git alone.
Run each agent in its own worktree or branch, then at the end check whether the two changed the same file.
# detect_conflict.sh — did two agent branches touch the same file?A_BRANCH="agent/auth"B_BRANCH="agent/billing"BASE="$(git merge-base "$A_BRANCH" "$B_BRANCH")"A_FILES="$(git diff --name-only "$BASE" "$A_BRANCH")"B_FILES="$(git diff --name-only "$BASE" "$B_BRANCH")"OVERLAP="$(comm -12 <(echo "$A_FILES" | sort) <(echo "$B_FILES" | sort))"if [ -n "$OVERLAP" ]; then echo "⚠️ files touched by both agents:" echo "$OVERLAP" exit 1fiecho "✅ no conflict. safe to merge."
comm -12 just takes the intersection of the two branches' changed-file sets. A non-empty intersection means that file is being fought over. The important part is that you do not attempt the merge here. Merging both sets of changes almost always smuggles in a semantic contradiction — the types line up but the assumptions diverge.
My recovery procedure when a conflict shows up: lock in one agent's output with git checkout, then have the other agent redo its work using the finalized state as input. Throw one side away and re-run rather than merge. It feels wasteful, but after half a year it is consistently faster than merging a contradiction and hunting for the cause later.
The handoff contract — decided / requested / undefined
When a phase hands its output to the next, the most common accident is the next agent misreading something the previous agent decided tentatively as a fixed decision — or the reverse, overturning a real decision on its own. To prevent it, write the handoff as a contract, not prose. The trick is splitting items into three buckets.
# Handoff contract: AUTH-014## Decided (do not change)- The useAuth hook interface stays as-is. Three other call sites depend on it.- Errors are returned as a Result type. Do not throw.## Requested (please implement this)- Target: src/features/auth/loginFlow.ts- Input spec: docs/auth-spec.md, section 3- Done when: all existing tests green, plus a unit test for loginFlow## Undefined (your call — but leave a reason)- Wording of error messages- How the loading state is rendered
"Decided" is what breaks if touched, "Requested" is the job, "Undefined" is what you delegate. Explicitly naming the undefined items is what pays off. Anything you do not mark as undefined, the next agent silently forces into either "decided" or "free to choose" — and that silent choice is where implementations drift from the upstream intent. Declare undefined as undefined, and require a reason once it is settled. That alone cut a lot of downstream overreach.
When a supervising agent earns its keep
Should you add one supervising agent that watches the whole thing? I answer it with a rule. Add it only when three or more agents run at once and completion order matters (a later phase cannot start until an earlier one finishes). In that case I give the supervisor a hard constraint: "Make no individual change decisions. You only manage progression and run the conflict-detection scripts above." Let it make judgments and its own context gets heavy and its quality drops.
Conversely, with two agents on fully independent regions, you do not need a supervisor; the extra layer just slows you down. If you can watch the diffs in real time yourself, you are the lightest supervisor available. On my own setup, two-to-three agents combined with an ownership manifest and the conflict-detection scripts is where the effort-to-payoff balance settles.
The investment is not agent count
After half a year of running multiple agents, I am convinced the shortcut to throughput is not adding agents — it is investing in the machinery that enforces boundaries at runtime. Pin ownership in one manifest, reject out-of-region changes before commit, find conflicts with git, and hand off work as a three-part contract. Once those four are running, adding agents stops breaking the whole.
If you try one thing next, write a single agents.ownership.json and declare the owns list for each agent currently running. If there is an agent you cannot write a region for, that is your sign the boundary was never designed. Putting it into words is often all it takes to change how stable next week's output feels. I hope it helps anyone working through the same problem.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.