When Parallel Antigravity Agents Fight Over the Same File — Preventing Conflicts and Surviving Real-World Race Conditions
Running Antigravity agents in parallel surfaces five concrete kinds of conflict and three classic race conditions. This guide walks through each one with reproducible failures and the locking, optimistic, and queue-based fixes I actually run in production.
The first week I ran agents in parallel, I broke a build at midnight. Three Antigravity agents were each working on separate features. All three modified package.json. The last write erased what the other two had added — git history showed three clean PRs, but the actual package.json only carried one of the three new dependencies.
The real difficulty of running agents in parallel is that you can no longer trace, with the human eye, who touched what at which moment. This article is a structured account of the failures I hit while taking Antigravity parallelism seriously, organized by the kind of conflict, with reproductions and the fixes that hold up in production. If you are at the stage of believing "more agents will be faster," this is the moment to read it.
Five resources where parallel agents collide
After a week of operating in parallel, the conflicts converge onto roughly five hot spots:
Writes to the same file — package.json, tsconfig.json, lockfiles, shared schemas.
Commits to the same branch — when two or more agents diverge from the same parent, the late one falls into rebase hell.
Shared caches and build artifacts — .next/, node_modules/.cache/, dist/ — one agent reads while another writes.
External API rate limits — three agents calling Gemini or Stripe with the same key cascade into 429s.
Working directories and tempfiles — /tmp/ collisions, log file appends, SQLite WAL contention.
The first thing to internalize: do not design for "conflicts might happen." Design for conflicts will happen. The probability scales with the number of agents and time. Even a one-percent collision rate, run a hundred times a day, will fail at least once.
A reproducible minimum: the package.json race
Let me show the most common failure shape in a form you can paste into Node and run. Two parallel "read-then-write" operations easily produce a state where the last writer erases the earlier one.
Run it and only the last write survives. The reads overlapped, so each writer overwrote a stale snapshot.
Parallel agent execution is structurally identical to this code. The only difference is that the "thinking" phase is seconds or minutes, not 200 milliseconds. The longer window makes collisions more likely, not less.
✦
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
✦The five resources parallel agents collide over, and how to decide between pessimistic locks, optimistic locks, and queues for each
✦A worktree-based partitioning design where each agent declares its write scope and a script verifies it before push
✦How far v2.3.0 message queuing and v2.2.1 unified permissions actually go as conflict prevention
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.
Fix 1: file locks — the most practical single-host choice
For agents running on a single machine, the simplest robust fix is an OS-level file lock. In Node, proper-lockfile is the de-facto choice and provides a lockfile-based mutual exclusion.
One caveat: always set a stale timeout. If an agent crashes mid-edit, the lock would otherwise sit forever and every subsequent run would hang. I keep it at 60 seconds, longer for jobs with known long-running steps.
Fix 2: optimistic locking — the right answer for branch conflicts
For conflicts at the branch level, locks are wrong. Optimistic locking — remember the version you read, retry if it changed by write time — is the practical fit, and Git is already an optimistic locking system. I encode the rules at the agent definition layer:
branch_per_task is dumb and powerful. The agent works on a dedicated branch, attempts git pull --rebase origin main right before pushing, and aborts and reports if conflicts cannot be resolved cleanly.
I deliberately disable "auto-resolve conflicts." There was a night when an agent formatted around a conflict marker in package.json and shipped a broken build. Auto-resolution of merge conflicts is a mechanism for code to be merged without a human reading it. I keep it off, by default, on every project.
Fix 3: queues — for shared APIs and global rate limits
External API rate limits cannot be fixed by locks or branches. The right place to absorb them is outside the agents, in a queue. I work as an indie developer, at a scale where a shared key is easy to underestimate, and even so every Gemini API call goes through a thin Cloudflare Workers Queue.
The agents follow one rule: never call the API directly. The 429 rate becomes a property of the worker's configured concurrency, not the number of agents. The first time I doubled the agent count and saw the 429 rate stay flat, parallel operation finally felt scalable.
Fix 4: don't resolve conflicts — partition writes so they never happen
The three fixes above are all about surviving a collision. Once I passed six months of running this setup, my center of gravity moved one step earlier: make sure two agents never reach for the same file in the first place.
There are only two moving parts. Give each agent an independent working directory via git worktree, and make it declare which paths it is allowed to touch.
# One checkout and one dedicated branch per agentgit worktree add ../wt-billing -b agent/billing origin/maingit worktree add ../wt-editor -b agent/editor origin/maingit worktree add ../wt-docs -b agent/docs origin/main
A worktree shares .git but splits the working tree, so .next/ and dist/ land in separate directories per agent. Two of the five hot spots — shared caches and tempfiles — mostly evaporate with this single move. This, not locking, is what I should have done first.
That leaves same-file writes. One ownership table handles it:
# agents/write-scope.yamlagents: billing: allow: ["src/billing/**", "src/config/pricing.ts"] editor: allow: ["src/editor/**", "src/components/editor/**"] docs: allow: ["docs/**", "README.md"]shared: # Nobody writes these directly. Agents propose diffs; a coordinator applies them in order. coordinator_only: ["package.json", "pnpm-lock.yaml", "tsconfig.json"]
A declaration nobody checks is a wish. Verify it mechanically, right before push:
#!/usr/bin/env bash# scripts/check-write-scope.sh <agent-name>set -euo pipefailAGENT="$1"BASE="$(git merge-base HEAD origin/main)"# Keep the decisive command out of a pipe — a pipe swallows the exit code.CHANGED="$(git diff --name-only "$BASE"..HEAD)"VIOLATIONS=0while IFS= read -r f; do [ -z "$f" ] && continue if ! yq -r ".agents.${AGENT}.allow[]" agents/write-scope.yaml \ | while read -r pat; do case "$f" in $pat) exit 9;; esac; done; then continue # exit 9 = matched an allow pattern fi echo "❌ scope violation: $AGENT touched $f" VIOLATIONS=$((VIOLATIONS + 1))done <<< "$CHANGED"[ "$VIOLATIONS" -eq 0 ] || { echo "$VIOLATIONS write-scope violations. Aborting push."; exit 1; }echo "✅ write scope OK ($AGENT)"
The value of this script is not that it fixes conflicts. It is that you find out the moment the repo enters a state where a conflict becomes possible — before the push, not after the incident. Locks make contention wait its turn safely, but waiting is parallelism you paid for and did not get. Partitioning never creates the wait at all.
To be honest about the limits: this does not scale to every kind of work. On days when a feature spans multiple areas, proposals pile up against the files marked coordinator_only, and that path serializes anyway. My workaround is a scheduling rule rather than a design one — only one agent per day gets tasks that add dependencies. Serialization you cannot design away, you schedule away.
Where queuing and unified permissions actually fit
Antigravity's July 2026 updates sit directly on top of this problem. v2.3.0 (July 13) added message queuing and a Send Now option, so you can stack the next instruction while an agent is still working.
One distinction matters here. What v2.3.0 queuing serializes is the order of instructions to a single agent — not writes across multiple agents. Instructions lining up neatly and file writes lining up neatly are different problems. Read the release note as "conflict handling is solved now" and you get the same midnight this article opened with.
The same release's automatic retry on backend overload deserves the same care. A retry re-runs work that already partially happened, so any write that is not idempotent gains a fresh double-application window. I gave my diff-application step a task_id and made it return success silently when the id has already been applied.
The unified permission model from v2.2.1, by contrast, is prevention in the cleanest sense. It lets the ownership table stop being a declaration and become a boundary the agent cannot cross. Generate the write-scope yaml and the permission config from the same source of truth, and the declaration can never drift from the real grant. Anything maintained in two places will diverge — that was the most expensive lesson of the last six months.
The three classic race conditions, in agent form
On top of the five hot spots, there is another layer: race conditions that look almost the same shape every time. The three I see repeatedly:
1. Read-Modify-Write
The most classic and the most common. The package.json race above is exactly this. A reads → B reads → A writes → B writes and B's write erases A's. Fix with a file lock, or with a single coordinator process: agents propose diffs, one process serializes the application of those diffs.
2. Check-Then-Act
A check (does the file exist?) and an action (write it) are separated by enough time for someone else to slip in between. Antigravity agents trying to create a "fresh" branch and colliding with one that already exists are this pattern. Fix by collapsing check and act into a single atomic operation: git rev-parse --verify new-branch && exit 1 || git checkout -b new-branch runs as one line and either succeeds atomically or fails fast.
3. ABA
An agent forks from branch A, the branch is recreated as a different "A," and the agent comes back assuming everything is the same. The fix is to remember the commit hash, not the branch name.
My rule of thumb: if the resource is a file, lock it; a branch, rebase it; an API, queue it; a working tree, split it. Two exceptions: SQLite, even in WAL mode, only allows one writer at a time and deserves a pessimistic lock; long-running migration jobs go to a dedicated job runner with single-worker concurrency.
Observability: noticing that contention happened
A conflict you do not detect becomes a production incident. The five signals I watch:
File lock acquisition time — warn at 1s, BLOCK at 10s.
Rebase failure count — auto-pause an agent after 3 consecutive rebase failures.
429 ratio — if it crosses 5 percent, halve queue worker concurrency automatically.
Job de-duplication — if the same task_id is enqueued twice, drop one.
Write-scope violations — even one means the parallelism level does not go up that day.
Surface these on a Grafana or Cloudflare dashboard and you can review a few hours of data right after raising the parallelism level to confirm whether the new degree is safe. The fifth is less a metric than a line in the sand: a violation means the design has fallen behind reality.
A pre-flight checklist before turning the dial up
Closing with the checklist I run before bumping parallelism. Even a single pass through this list catches surprising risks.
[ ] Does each agent have its own worktree and a declared set of writable paths?
[ ] Are shared files that agents write protected by a lock (or a coordinator)?
[ ] Does each agent work on its own branch and rebase before pushing?
[ ] Is the external API key shared, and if so, is there a per-key rate budget?
[ ] Is any step that can be auto-retried idempotent (guarded by task_id)?
[ ] Can logs and traces be filtered by agent_id?
Parallel agents do bring speed. But speed only pays off after the scaffolding is in place to catch what falls during a collision — that was the lesson from the night I had to rebuild package.json by hand. If your finger is currently hovering over the button to spawn a third agent, cut three worktrees first and write down, in one yaml file, what each one is allowed to touch. That order turns out to be the right one.
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.