Keeping Agent Behavior Consistent Across Separate Repositories: Notes on Multi-Repo Governance
How to keep Antigravity agents behaving consistently across several independent repositories when a monorepo isn't an option. A layered governance design with a working distribution script and drift audit, drawn from running four sites in parallel.
I still remember the day I went from one repository to two. I carefully copied the AGENTS.md I had grown in the first repo and pasted it into the second. By the time I created the third, I could no longer tell which copy was the newest. The agents dutifully followed "the instructions" in each repo — but the instructions themselves had quietly diverged. At some point, the very same rule ("write in a polite register") had been updated in one repo and left stale in another.
I currently run four sites with similar tech stacks as independent, parallel repositories. Collapsing them into a monorepo would make sharing configuration trivial, but I deliberately keep them separate so that deployment units, release timing, and blast radius stay isolated. That requirement — "independent, yet I want the agents to operate under the same discipline" — is the design problem I call multi-repo governance.
This article lays out how to guarantee behavioral consistency when running Antigravity agents across multiple independent repositories, complete with the sync script and drift detection I actually use. The focus is a long-term operational problem the official docs don't touch: how conventions quietly rot over time.
Why separate repos instead of a monorepo?
Let me set the premise straight. "If you want to share config, just use a monorepo" is a fair point, and honestly the most natural solution. Put a single AGENTS.md at the root of a pnpm workspace or an Nx setup, and every project reads the same conventions.
Even so, there are sound reasons to choose independent repos. In my case there were three.
First, I wanted to physically isolate blast radius. A bad config or a broken build in one site should never drag the other three sites' CI down with it. Second, I wanted deployment permissions and tokens fully separated per site, so that a leaked token damages exactly one repository. Third, I wanted release timing and commit history to be independent. I grow each site as its own character, so mixing histories feels wrong — call it an indie developer's aesthetic.
In other words, multi-repo is the choice you make when you want isolation badly enough to sacrifice config sharing. And precisely because you sacrificed it, you need to win it back through some other mechanism. That mechanism is the governance layer.
Conventions start rotting the moment you copy them
Run agents across multiple repositories and drift is inevitable. The drift I observed fell into three kinds.
The first is rule version drift. You add a new rule to AGENTS.md in one repo and forget to copy it to the others. In the un-updated repos, the agent keeps running the old rule.
The second is gate script drift. You fix the quality-check Python script in one place, but the other N-1 copies stay stale. The same violation gets blocked in one repo and waved through in another.
The third is the nasty one: implicit convention drift. Rules that aren't written in AGENTS.md at all, but that the agent absorbs like "atmosphere" by reading a repo's commit history. You can't detect this with a file diff.
This article addresses the first two — drift you can make visible as files. The third I keep in check indirectly through "centralizing the source of truth," described below.
✦
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
✦Implement a system that distributes AGENTS.md and quality gates from a single source across N independent repos, and detects drift
✦Design a layered architecture that keeps agent behavior aligned even when a monorepo isn't feasible
✦Take home the exact sync script and diff audit that actually prevented breakage across four parallel sites
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.
The core design: split into layers, keep one source of truth
The design I settled on splits the conventions an agent reads into three layers.
Layer 1 is the shared canonical source. Rules common to every repo, the quality gates, the style conventions. This exists as the "source of truth" in exactly one place and is copied into each repo as a distributed artifact. The iron rule: only the canonical source may be edited by hand.
Layer 2 is site-specific config. Category definitions, deploy targets, site names — values that are necessarily different per repo. Each repo owns these itself.
Layer 3 is the composed AGENTS.md — the file the agent actually reads, generated by merging Layers 1 and 2. Because it's a generated artifact, you never hand-edit it.
The payoff of this separation is that "where you're allowed to touch" becomes unambiguous. Most drift is born from "editing the distributed artifact directly." Structurally separating the source from the artifact lets you tell the agent, too: "this is generated, don't touch it."
The directory layout looks like this. The canonical source lives in an operations-only location that belongs to none of the four site repos.
ops/ # operations-only home for the canonical source├── canonical/│ ├── AGENTS.base.md # rules common to all repos (Layer 1)│ ├── gates/│ │ ├── article_gate.py # quality gate (canonical)│ │ └── frontmatter_integrity.py│ └── manifest.json # defines target repos and placement paths└── scripts/ ├── distribute.sh # distribute the canonical source to each repo └── audit-drift.sh # audit the diff between artifacts and source
Declare "what goes where" in a manifest
The heart of distribution is the manifest. It declares, declaratively, which file goes to which repo at which path. If you hard-code the procedure into the script, you end up editing the script every time a repo is added — and that becomes a drift source in itself. Separating declaration from execution is the key.
artifacts are straight-copy distributables; agents_md is a file composed from "common base + site-specific overlay." Split into these two tracks, almost every convention asset falls into one or the other.
The distribution script: compose it, and mark it as generated
Here's the distribution implementation. The key point: always inject a header at the top of the composed AGENTS.md saying "this is generated, edit the canonical source." It tells agents and humans alike not to touch it directly.
#!/usr/bin/env bash# distribute.sh — distribute the canonical source to each repo per the manifestset -euo pipefailOPS_DIR="$(cd "$(dirname "$0")/.." && pwd)"MANIFEST="$OPS_DIR/canonical/manifest.json"repo_count=$(jq '.repos | length' "$MANIFEST")for i in $(seq 0 $((repo_count - 1))); do repo_path=$(jq -r ".repos[$i].path" "$MANIFEST") repo_name=$(jq -r ".repos[$i].name" "$MANIFEST") [ -d "$repo_path/.git" ] || { echo "skip (no repo): $repo_name"; continue; } # 1) straight-copy artifacts art_count=$(jq '.artifacts | length' "$MANIFEST") for j in $(seq 0 $((art_count - 1))); do src=$(jq -r ".artifacts[$j].src" "$MANIFEST") dest=$(jq -r ".artifacts[$j].dest" "$MANIFEST") mkdir -p "$repo_path/$(dirname "$dest")" cp "$OPS_DIR/$src" "$repo_path/$dest" done # 2) compose AGENTS.md from base + site overlay base=$(jq -r '.agents_md.base' "$MANIFEST") overlay=$(jq -r '.agents_md.site_overlay' "$MANIFEST") dest=$(jq -r '.agents_md.dest' "$MANIFEST") { echo "<!-- GENERATED FILE — DO NOT EDIT. Edit the canonical source in ops/canonical/ -->" echo "" cat "$OPS_DIR/$base" if [ -f "$repo_path/$overlay" ]; then echo "" echo "<!-- ===== site-specific overlay ($repo_name) ===== -->" echo "" cat "$repo_path/$overlay" fi } > "$repo_path/$dest" echo "distributed -> $repo_name"done
The essence of this script is that it demotes AGENTS.md from something you "write" to something you "generate." The moment it becomes a generated artifact, the motive to hand-edit it disappears, and the single largest source of drift is sealed off. My grandfather was a temple carpenter, and I'm told he valued the reference measure above all else — because there's one reference, you can cut any number of boards without them going crooked. Keeping a single canonical source is, I feel, the same idea.
Drift detection: audit that artifacts match the source
Distribution alone isn't enough. If someone (including me) edits a distributed artifact directly, you won't notice until the next distribution. So I run an audit script — comparing artifact hashes against the source — in CI and before every push.
#!/usr/bin/env bash# audit-drift.sh — verify each repo's artifacts match the canonical sourceset -euo pipefailOPS_DIR="$(cd "$(dirname "$0")/.." && pwd)"MANIFEST="$OPS_DIR/canonical/manifest.json"drift=0hash_of() { sha256sum "$1" | awk '{print $1}'; }repo_count=$(jq '.repos | length' "$MANIFEST")art_count=$(jq '.artifacts | length' "$MANIFEST")for i in $(seq 0 $((repo_count - 1))); do repo_path=$(jq -r ".repos[$i].path" "$MANIFEST") repo_name=$(jq -r ".repos[$i].name" "$MANIFEST") [ -d "$repo_path/.git" ] || continue for j in $(seq 0 $((art_count - 1))); do src=$(jq -r ".artifacts[$j].src" "$MANIFEST") dest=$(jq -r ".artifacts[$j].dest" "$MANIFEST") deployed="$repo_path/$dest" if [ ! -f "$deployed" ]; then echo "MISSING $repo_name:$dest"; drift=1; continue fi if [ "$(hash_of "$OPS_DIR/$src")" != "$(hash_of "$deployed")" ]; then echo "DRIFT $repo_name:$dest does not match the canonical source"; drift=1 fi donedoneif [ "$drift" -ne 0 ]; then echo "❌ Drift detected. Re-run distribute.sh to realign." exit 1fiecho "✅ All repos' artifacts match the canonical source."
Reducing the check to "hashes match" is deliberate. Once you start tolerating semantic differences ("same content, different formatting"), the check loosens, and you end up missing drift. An artifact must match the source byte for byte — that strictness pays off over the long run. Loose criteria collapse quietly.
The numbers that worked, and the trick that didn't
Let me report honestly on running this across four repos.
Before adopting it, reflecting a convention change into four repos took an average of 18 minutes of manual work — opening each clone one by one, editing, committing, repeating. After introducing distribute.sh, it became a matter of fixing the source once and running distribution: about 40 seconds. With six to eight convention changes a month, that erased roughly two hours of manual work per month.
Bigger than the numbers, though, was that "forgot to propagate" dropped to zero. In the three months before adoption, I had created a state where a rule was missing from at least one of the four repos five separate times. Once audit-drift.sh was wired into the pre-push gate, a mismatch stops the push outright, so forgetting became literally impossible.
There was also a trick that didn't work. Early on, I tried to build a clever comparison that "doesn't count it as drift if it's semantically equivalent." It failed. Since AGENTS.md is Markdown, trying to absorb formatting variance made the check complex and, if anything, a breeding ground for bugs. A textbook case of the smarter you make it, the more brittle it gets. In the end, settling for nothing but "byte-equal or not" made operations far more stable.
Make the agent itself respect the governance
So far it's all been about scripts, but since you're having Antigravity agents do the work, the agent side needs to understand the governance too. Here's a passage I always include in AGENTS.base.md (the canonical source).
## About this repo's convention files- `AGENTS.md` and everything under `_gates/` are **generated, distributed artifacts**. Do not edit them directly.- If you want to change a convention, state only the intent of the change. The canonical source lives in a separate operations repo (ops/canonical/) and is managed centrally there.- Before every push, a check equivalent to `audit-drift.sh` runs. Rewriting a distributed artifact will halt at that check.
With this in place, you prevent the agent from "rewriting a distributed artifact in an attempt to improve a convention" — well-intentioned drift. Agents are faithful to instructions, so simply stating "this is a no-touch place" gets them to honor it remarkably well. Conversely, if you don't state it, the agent will touch it with the best of intentions. A boundary that isn't written down may as well not exist.
Where to start
If you're using Antigravity across two or more independent repos right now and you're tired of copy-pasting conventions, the first step is just to "decide on a single source of truth." You don't need a perfect manifest or distribution script at the start.
Concretely: take the AGENTS.md from whichever repo has the most mature conventions, move it to an operations location, and declare it the canonical source. Re-frame the AGENTS.md files in your other repos, in your own mind, as generated artifacts of that source. Just deciding "which one is canonical" means that the next time you change a convention, you won't hesitate over "where do I edit?" Automating distribution is something you can add later, once you cross three repos and the manual work starts to hurt.
I'm still reconciling a contradictory wish — growing four sites separately while wanting to run them all under one discipline — and this layered design is what lets me hold both. Separate, but aligned: it's a design for not giving up on either. If it gives a starting foothold to anyone else struggling as their repos multiply, I'd be glad.
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.