How an empty host repo binds two codebases for an AI coding agent: instructions, skills, memory, and verification that ends on a device.
A native module moved from the Android shell into the React Native repo, and one method got a new name on the way. The JS kept calling the old one. The build passed, the app started, and a whole screen quietly stopped working. Nothing in either repo could have flagged it, because the name lived in both and neither side knew about the other.
That is the failure I built this setup to catch. It starts from a simpler observation.
AI-assisted coding is great inside one repo. The agent reads the repo's instructions, sees the whole tree, and its mistakes stay local.
But what if your app ships from two repos, and every real change has to land in both, in the right order? Open the agent one level up and it sees every project on your machine, with nothing telling it how these two relate.
My answer is a third repo. Empty of application code. It holds the binding between the other two, the instructions that only make sense across them, the skills, and the memory. The agent runs from there. I use Claude Code. The pattern ports to any agent that reads instruction files.
The setup, and the gap
One Android APK, built from a React Native monorepo and a native Kotlin shell. The React Native side publishes its bundle and its bridge modules' native code as an npm package. The Android side pins that package by exact version and points Gradle into node_modules. No over-the-air updates. The JS a user runs is whatever version the pin says.
Between them sits a hand-maintained bridge. Module names, method names, event names, and storage keys exist on both sides and must match exactly. No codegen checks them. When one side drifts, nothing fails at build time. The call resolves to undefined at runtime. That is the story at the top.
Each repo's docs cover its own half. Nobody owns the gap.
The host repo
The whole thing fits in one screen:
host/
├── CLAUDE.md the contract between the two repos
├── notes/ cross-session state, written for the agent
└── .claude/
├── settings.json points at the two repos
├── skills/ bridge wiring, local builds
├── agents/ the review agent
└── agent-memory/ what that agent has already learned
The settings file at .claude/settings.json is the whole trick:
{ "additionalDirectories": ["../rn-app", "../android-shell"] }
A session started here sees exactly those two trees and nothing else. The host's CLAUDE.md imports each repo's own CLAUDE.md, so repo-internal rules stack underneath. On top, the host adds what only makes sense across the boundary: how the repos bind, the four identifier kinds that must match, where native modules live during an ongoing migration, areas owned by other teams, and a list titled "things this file does not yet cover" so the agent says "not specified" instead of inventing.
Plus the one rule that exists only because there are two repos: plan before editing, because order matters.
Skills that compose
Procedures live in skills, markdown files the agent pulls in when a task matches their description. The interesting part is how they chain.
The bridge-wiring skill knows the two paths for a native module: the short one entirely inside the React Native repo, and the legacy one that touches the shell. It defaults to the short path and flags the legacy one. It also carries the migration checklist, with one rule: never rename a module while moving it.
The local-build skill knows how to run a debug build against Metro, and how to build a release-style APK against an unpublished bundle by pointing the shell's package.json at a local path instead of a published version.
A review agent, with its own memory of past pitfalls, reads a pull request through one team's ownership boundaries and comments only on changed lines that team owns.
On their own, each is a checklist. Together they close a loop the repos cannot close by themselves.
Verification that reaches the device
Ask for a new native module. The wiring skill writes the TS wrapper and the Kotlin class and registers it. Then, instead of stopping at "done", the local-build skill takes over: build the bundle locally, link it into the shell, assemble the APK, install it, and exercise the feature.
That last step is where the silent undefined would otherwise surface in QA days later. Now it surfaces in the same session, on the actual APK a user would get. The agent reports what it saw, not what it expects.
Memory
Because the agent runs from the host, its memory is scoped to cross-repo work rather than to either repo. Notes on long-running efforts that span both sides sit next to the instructions, each with a status and a last-updated date. The review agent keeps its own store of pitfalls it has already found. The next session starts from what was verified, not from zero.
What it does not fix
The bridge is still hand-maintained. The loop catches mismatches. It does not prevent them. Codegen would, and that is a bigger change than a markdown file.
The rules only work if they are read. When the agent slips back into one-repo thinking, the fix is usually a sharper sentence, not a longer file.
Takeaway
If one artifact is built from more than one repo, the contract between them lives nowhere. Give it a home your tools read, scope the agent to exactly the repos involved, and let the skills compose into a loop that ends on a device.
The empty repo is just that, with a working directory attached.
Top comments (0)