At 03:38 UTC on 16 March, two commits landed in waytohire.ai, two seconds apart. The first added a changelog to the backend: the data model, the API endpoints, the admin screen. The second added the pages that show that changelog to users. Two repositories, one feature. Both commits have the same author, and it isn’t anyone on our team. The author field says Claude.
Nobody had to type out what the API returns so the frontend could use it. The agent that wrote the pages had written the endpoints a moment earlier, and could simply read them.
That is what this post is about. An AI agent can only work with what it can see, and where your code lives decides what it sees.
A repository boundary is a wall
Most products are at least two codebases: a backend that stores data and enforces the rules, and a frontend that people actually click on. They often live in separate repositories, for reasons that made sense when only humans read them. Different languages, different release schedules, different people.
A human developer handles the split without noticing. They remember what the API returns, or they ask whoever wrote it. An agent opened inside the frontend repository can do neither. Say the backend sends a field called salary_from and you ask for it on a screen. The agent has to guess the name, the type, and whether it can be empty. It will guess something sensible, like minSalary. That is the trouble: a sensible wrong guess compiles, survives a quick look, and fails later in front of a user.
The usual workaround is a person pasting API responses into the chat. It works, and it turns a developer into a courier.
Put both codebases where the agent can read them and the guess becomes a lookup. Claude Code’s documentation puts it in one line: where you start the tool “determines which files Claude can read and edit without an additional permission grant”. The rest of this post is that sentence, applied to two of our own projects.
The agent sees the frontend only
minSalary: numberCompiles. Wrong name, never empty. Fails in front of a user.
The agent sees both sides
salary_from = models.IntegerField(
blank=True, null=True)salary_from: number | nullSame name, same type, and it can be empty.
What we run on waytohire.ai
waytohire.ai is our own recruitment product, in development since spring 2024. The backend is Django: about 31,000 lines of Python and some 220 URL routes. The frontend is five web apps in one TypeScript workspace, roughly 75,000 lines. Two languages, two git histories.
We never merged them into one repository. What we did is smaller. Both are pulled into a single workspace as git submodules, and the agent starts at the top of it. In one session it can open a Django serializer and the React component that renders its output.
To see whether that changed anything, we went through the history. For every backend commit we checked whether a frontend commit landed within ten minutes. It is a crude signal that both halves of a change were made in one sitting, but it is one you can count.
In 2024 that held for about a quarter of backend commits, and in 2025 for 30%. In 2026 it is 70%. To make sure we weren’t measuring coincidence, we shifted the backend timestamps by a few days and counted again. Chance alone gives between 1 and 4%.
A few caveats, because the chart looks more scientific than it is. This year is a small sample so far. Ten minutes is a proxy, and two commits close in time are not always the same feature. The models also got much better over those two years, so the workspace is not the only thing that changed. Treat it as a picture of our own history. Still, the picture matches those two commits from March: a feature now tends to arrive whole, on both sides at once.
What full context does not give you
Seeing both sides is not the same as the two sides being unable to disagree.
On waytohire the frontend carries its own description of the API: 193 endpoints, each with hand-written TypeScript types that say what the backend returns. When a backend response changes, those types stay correct only if someone updates them in the same sitting. With both codebases in view, the agent usually does. Usually is not always, and nothing stops the build when it forgets.
So submodules solve the context problem and leave the contract problem where it was. The agent knows more. It still isn’t told when it is wrong.
The newer project started as a monorepo
In late September we started a new product. It isn’t released yet, so it stays unnamed here, but the structure is the relevant part: a mobile app, a small serverless backend and an internal web tool, all in one repository managed with Nx.
About half of the code sits in shared packages rather than inside any one app. That is the first thing you notice in the folder tree, and it is the point of the whole arrangement.
It was not all tidy. On day one the project scaffold generated configuration for six different AI tools, close to 12,000 lines of it. We deleted the lot five minutes later. What the agent actually works from is plainer: a written plan split into stages, one stage per session, with decisions noted back into the plan as they are made.
What a monorepo lets you share
Those shared packages are where the advantages come from. Four of them show up in daily work.
| Part | Contract | Catalogue | UI kit |
|---|---|---|---|
| Mobile app | Imports it | Imports it | Imports it |
| Server function | Gets a generated copy | Gets a generated copy | Does not use it |
| Internal web tool | Does not use it | Imports it | Does not use it |
Lines of code
Types, defined once. The shape of a server reply is written in one place. The same definition checks what the server sends and parses what the app receives. Change it, and either both sides change with it or something fails loudly. On waytohire the same job is done by hand-written types for 193 endpoints, and someone has to remember to update them.
Data and limits, defined once. The catalogue the whole product is built around is its own package. The app and the internal web tool import it, and so, indirectly, does the server. The product also has an AI feature that picks items from that catalogue. The schema its replies have to follow is built from the same package, so the model cannot pick an item the app does not have. The limits sit next to it: how long a message can be, how many messages go to the server. The app trims to those numbers before it sends anything.
One command checks everything. nx run-many -t test typecheck checks every project in the repository. An agent that edits the backend contract finds out in the same session whether the app still compiles. On waytohire the agent has to remember. Here it gets told.
One change, one commit. The two waytohire commits at the top of this post were one feature, split across two repositories two seconds apart. Here a feature that touches the server and the app is a single commit, and whoever reviews it sees both halves side by side.
One rough edge is worth admitting. The serverless runtime cannot import our shared packages directly, so a script generates the files it needs and a test fails when they go stale. A few limits are still copied by hand on the server side. A monorepo narrows the gap between two sides of a product. It does not always close it.
Why a monorepo is good for AI
An agent works in a loop: it reads the code, changes it, and checks the result. A monorepo helps with each step, though not equally.
Reading is the part you can get without one. Submodules in a shared workspace give the agent on waytohire the same view of both sides. If the view is all you are missing, you do not need a monorepo.
Changing is where shared definitions pay off. When a type lives in one place, there is no second copy for the agent to forget. Claude Code’s documentation gives the same advice for changes that cross packages: hand the agent the shared edit and every place that uses it in one session, so the decisions stay consistent.
Checking is what closes the gap the waytohire types leave open. There the agent usually updates them, and when it forgets, nothing fails. In a monorepo the same slip breaks the typecheck, in the same session, before anyone else sees it.
There is also a plainer reason. A developer carries context from one week to the next. An agent starts every session with what is in the repository and what you tell it. The more of the product the repository holds, and the more of its rules are written as code instead of remembered, the less you have to tell it.
Three levels, and what each one costs
Side by side, the options look like this.
Separate repositories
- What the agent sees
- One side at a time. The other side is a guess.
- What keeps the two in step
- People remembering.
- Cost to adopt
- None. It is the default.
Submodules in one workspace
- What the agent sees
- Both sides, in one session.
- What keeps the two in step
- The agent, and code review.
- Cost to adopt
- An afternoon.
Monorepo with a shared contract
- What the agent sees
- Both sides, plus the definitions they share.
- What keeps the two in step
- The build. A mismatch fails a check.
- Cost to adopt
- Small on day one. A migration later.
Level one costs nothing because it is where most projects already are. Level two is an afternoon: a parent repository, two submodules, and a short instruction file at the top. Level three is nearly free on the first day of a new project and a real migration on an existing one, especially when the two sides are written in different languages. A Django backend and a TypeScript frontend cannot share a type by importing it; the frontend’s types would have to be generated from the API schema instead. That is the distance between waytohire, which sits at level two, and the new project, which started at three.
When a monorepo gets too big
Bigger is not automatically better. Claude Code’s documentation spends most of its length on the opposite problem: in a large repository the agent’s context fills up with instructions and files that have nothing to do with the task. That costs money, and the work gets worse.
The fixes are about scope. Instructions can be split by directory, so the agent loads the rules for the code it is working in instead of one file that covers everything. And the agent can be started in the part of the repository a task touches rather than at the top. One repository is a way to give the agent the right view. It is not a licence to show it everything.
Does a monorepo make development cheaper?
Partly. It removes one specific cost: the time spent finding and fixing mismatches between the backend and the frontend. It does not make the code itself cheaper to write, and on an existing product the move is a migration with its own price.
How much the saving is worth depends on how often a change touches both sides. On waytohire, 70% of this year’s backend commits came with a frontend commit within ten minutes, so most of the work crosses that line. On a product where the two halves rarely change together, the saving is small.
That is the commercial point. AI makes writing code faster. Whether that speed reaches you depends on how much of it gets spent putting the two halves back together afterwards.
Start with what the agent can see
If you do one thing after reading this, find out what your AI tools can see when they work on your product. Open the project the way the agent does. If half the product is missing from that view, fixing it is an afternoon of setup, and a few months later it shows up in the history.
→ What AI-built code costs to inherit is what happens when nobody checks.
→ How we add AI to a product that already exists.
→ See how we run a project from scope to release.
Sources
- Commit figures: git history of the waytohire.ai backend and frontend repositories, develop branch, merge commits excluded, counted in October 2026.
- File access and scoping in large repositories: Set up Claude Code in a monorepo or large codebase (Anthropic).
Last updated: October 5, 2026. The tools change quickly — the point about what an agent can see will outlast the tool names.




