Written by Waseef Khan, Co-President of KCL Tech.
A codebase rarely stays as one application forever.
Imagine a team building a public marketing site, an internal admin platform and a customer-facing learning platform. Each product has its own users and responsibilities, but they share the same organisation, visual language, authentication patterns and engineering team.
That creates a structural question:
Should every product live in its own repository, or should they live together?
A monorepo can be the most useful answer.
KCL Tech is committed to making technology accessible, so this guide assumes no previous experience with monorepos. I will explain the basic language first, then show how the tools fit together. We will also explore how the same structure can make a codebase easier for AI coding agents to navigate safely.
Five terms to know before we begin
You only need a few ideas to follow the rest of this guide:
- Repository: A project folder tracked by a version-control tool such as Git. It contains the code and a history of changes.
- Application: A product people can use, such as a website, mobile app or admin dashboard.
- Package: A reusable collection of code. One package might provide buttons, while another handles authentication.
- Dependency: Code that another piece of code relies on. If an app uses a button package, the button package is one of its dependencies.
- Build: The process of turning source code into a version that can be tested or deployed for people to use.
If those terms make sense, you already have enough context to understand a monorepo. If Git or package managers are completely new to you, the Pro Git introduction and MDN's package-management basics are friendly starting points.
What is a monorepo?
A monorepo is one repository containing multiple applications or packages.
Think of it as one building with several rooms. Each application has its own room and purpose, while shared facilities sit in the same building. The rooms do not become one giant room simply because they share an address.
It is not necessarily one application, one deployment or one giant bundle. A monorepo can contain several independently deployed products:
platform/
├── apps/
│ ├── marketing/
│ ├── admin/
│ └── learning/
├── packages/
│ ├── ui/
│ ├── auth/
│ ├── database/
│ ├── eslint-config/
│ └── typescript-config/
├── design/
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
In this example:
-
apps/marketingis the public website. -
apps/adminis the internal platform used to manage content, events and programmes. -
apps/learningis the customer-facing learning platform. -
packages/uicontains reusable interface primitives. -
packages/authcontains shared authentication logic and types. -
designdocuments how the product family should feel: visual direction, motion, interaction principles, accessibility and content style.
The applications remain separate products. They can have different users, web addresses and release schedules. The repository simply gives their code a shared home.
How the pieces are connected
The folder tree shows us where the code lives. A dependency graph shows us which parts rely on other parts.
Imagine the following relationships:
marketing ──┐
admin ──────┼──> ui
learning ───┘
admin ──────┐
learning ───┼──> auth ──> database
In technical language, each application or package is a node, and each connection is an edge. In plain English, each box is a piece of the system and each arrow means “uses”.
This matters because the graph tells our tooling:
- what must be built first;
- which products may be affected by a change;
- which tests need to run;
- which previous outputs can be reused from a cache;
- how many places might be affected by a shared change.
Tools such as Turborepo and Nx use these relationships to decide what work is necessary. The tools differ, but the underlying idea is simple: understand what depends on what before running commands.
How workspace packages connect
We now need a way to tell our package manager which folders belong to the same workspace. A package manager installs dependencies and runs project commands. In this example, we use pnpm.
The root workspace file lists the directories that belong to the monorepo:
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
Each project still has its own package.json, which acts like an identity card and instruction sheet for that project. An application can list another package from the same repository as a dependency:
{
"name": "@platform/learning",
"dependencies": {
"@platform/ui": "workspace:*",
"@platform/auth": "workspace:*"
}
}
The workspace:* value tells pnpm to use the local package from this repository. We do not need to publish @platform/ui publicly just to use it in the learning app.
The result is a clean import:
import { Button } from "@platform/ui/button";
instead of a fragile relative path:
import { Button } from "../../../../packages/ui/src/button";
The first version clearly says, “this app uses the shared UI package”. The second reaches through several folders and makes that relationship harder to understand or control.
How commands run in the correct order
A dependency graph describes which projects rely on each other. A task graph describes the order in which commands such as build, test and lint should run. Linting is an automated check for code-quality and style problems.
For example, the learning app cannot complete its production build until the internal packages it consumes are ready. A minimal Turborepo configuration can encode that relationship:
{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"typecheck": {
"dependsOn": ["^typecheck"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
You do not need to understand every line yet. The important one is ^build, which means: build this package's dependencies before building the package itself.
The root package.json then becomes a simple front door to the entire repository:
{
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"lint": "turbo lint",
"typecheck": "turbo typecheck",
"test": "turbo test"
}
}
We can run everything:
pnpm build
Or focus on one product:
pnpm --filter @platform/learning dev
A task runner such as Turborepo reads this configuration. It can run unrelated work at the same time, respect the correct order and reuse previous results through a cache. A cache is simply stored work that does not need to be repeated when nothing relevant has changed.
That is what prevents a growing monorepo from automatically becoming a slow monorepo. To go further, Turborepo's guides explain internal packages and package and task graphs in more detail.
What belongs in a shared package?
The easiest monorepo mistake is sharing code simply because two files look similar.
Good shared packages usually represent stable capabilities:
- design-system primitives such as buttons, inputs and dialogs;
- authentication clients, session types and permission helpers;
- database schemas and generated types;
- analytics wrappers;
- linting, formatting and TypeScript configuration;
- utilities with the same meaning across every application.
Code should normally remain inside an application when it represents that product's domain:
- a learning-platform lesson timeline;
- an admin moderation queue;
- marketing-page campaign sections;
- a workflow that merely happens to resemble another workflow today.
A useful rule is:
Share stable capability, not accidental similarity.
Sharing too early can create an overcomplicated component with too many options, unclear ownership and several products afraid to change it. A small amount of duplication can be safer until the correct shared solution becomes obvious.
One design language does not mean one interface
A shared UI package should contain implementation. The design directory should explain intent.
For a multi-product repository, that directory can stay deliberately simple:
design/
├── README.md
├── foundations.md
├── components.md
├── motion.md
├── accessibility.md
└── content.md
It answers questions that code alone cannot:
- Should the product feel editorial, playful, technical or institutional?
- When should motion communicate state, and when would it become decoration?
- How dense should the admin platform feel compared with the marketing site?
- Which behaviours must remain consistent across all three products?
The products can feel related without looking cloned. Marketing may be expressive, Learning may be encouraging and progress-led, and Admin may prioritise density and speed. Shared foundations should create family resemblance, not forced uniformity.
A change moving through the monorepo
Suppose we add a new Avatar component to @platform/ui and use it in the learning app.
The flow looks like this:
- Add the component and its tests inside
packages/ui. - Export it through the package's public API, meaning the approved way other projects can access it.
- Import it inside
apps/learning. - Run code-quality checks, type checks and tests for the projects that could be affected.
- Build the learning app and any other consumer affected by the shared package change.
- Deploy the learning app independently.
The shared component and the app using it can be reviewed together in one proposed change, often called a pull request. The products can still be released independently: changing Learning does not require deploying Marketing or Admin unless they are genuinely affected.
What is an agentic monorepo?
A coding agent is an AI system that can do more than suggest a line of code. Depending on the tool and the permissions it receives, it may be able to inspect files, edit code, run commands and check its own work.
“Agentic monorepo” is not a separate build tool or formal repository type. I use it to describe a monorepo deliberately designed so coding agents can understand it, make limited and relevant changes, and verify their own work.
An ordinary monorepo gives an agent access to a lot of code. An agentic monorepo gives it a map, boundaries and a safe route through that code.
That distinction matters. The biggest advantage of a monorepo for an agent is context: the applications, rules, design system and tests are available together. Its biggest risk is also context: an agent working without clear limits could change a shared package and unintentionally affect every application.
1. Give the repository a clear map
Create a root instruction file that explains the repository in plain language. Some coding tools read files such as AGENTS.md automatically. The exact filename is less important than the principle: important repository knowledge should live beside the code instead of only in someone's head.
# Repository engineering guide
## Repository map
- apps/marketing: public website
- apps/admin: internal operations platform
- apps/learning: customer learning platform
- packages/ui: shared interface primitives only
- packages/auth: authentication and authorisation contracts
- design: product-wide design principles
## Commands
- Install: pnpm install
- Run Learning: pnpm --filter @platform/learning dev
- Validate affected work: pnpm lint && pnpm typecheck && pnpm test
## Rules
- Do not import directly between apps.
- Consume shared code through package public APIs.
- Do not place product-specific workflows in packages/ui.
- Never commit secrets or real customer data.
- Update tests when behaviour changes.
This avoids forcing an agent to infer critical architecture from thousands of files.
2. Scope instructions to the work
Root guidance should be short and universal. Product-specific knowledge should live closer to the product:
AGENTS.md
apps/
├── admin/
│ └── AGENTS.md
└── learning/
└── AGENTS.md
The Learning instructions might explain enrolment rules and customer privacy. The Admin instructions might explain role permissions and audit logging. This keeps the information given to the agent relevant instead of loading every rule for every task.
Agent products support different instruction filenames and scoping rules, so check the tool you use. The architectural principle remains portable: global rules at the root, local rules near the code they govern.
3. Make the correct action the easy action
An agent should not need to invent the setup process or assemble a validation command.
Prefer predictable entry points:
pnpm install
pnpm --filter @platform/learning dev
pnpm --filter @platform/learning test
pnpm --filter @platform/learning typecheck
Also provide:
-
.env.examplefiles containing names, never secrets; - predictable sample data containing no personal information;
- scripts that work without interactive prompts;
- clear package names and ownership;
- one command for formatting and one command for validation;
- fast unit tests plus focused end-to-end tests for critical journeys.
Agents become more reliable when the feedback loop is short enough to run after every meaningful change.
4. Encode boundaries in tools, not only prose
Instructions can be ignored or misunderstood. Architectural rules should also be enforced mechanically.
Examples include:
- automated code rules that forbid imports from one app into another;
- TypeScript project references and package exports;
- required human reviewers for sensitive packages;
- schema validation at API boundaries;
- protected automated checks before changes can be merged;
- permission tests for Admin and Learning;
- secret scanning and dependency auditing.
An instruction saying “do not bypass the authentication package” is helpful. An automated rule and a failing test are stronger because they actively stop an unsafe change.
5. Optimise for selective context
More context is not always better. An agent working on a Learning dashboard should not need the full Marketing application in its prompt.
A well-structured monorepo allows the agent to inspect:
- the root map;
- the target application's instructions;
- its direct package dependencies;
- relevant tests and design documentation;
- the affected task graph.
This is one reason clear package boundaries matter beyond speed. They also define what humans and agents need to understand for a particular task.
6. Define the verification loop
Every task given to an agent should end with evidence, not confidence.
A sensible definition of done is:
- the requested behaviour is implemented;
- existing architectural boundaries remain intact;
- linting and type checks pass for affected projects;
- relevant tests pass;
- user-facing changes are visually inspected;
- migrations and environment changes are documented;
- the final summary names changed packages and remaining risks.
The agent should be able to discover and run this loop from the repository itself.
A practical agent workflow
For a task such as “add role-based progress editing to Learning”, an agent-ready workflow would be:
Read root instructions
↓
Read Learning instructions
↓
Inspect Learning's dependency boundary
↓
Plan the smallest valid change
↓
Implement inside Learning where possible
↓
Run relevant code-quality checks, type checks and tests
↓
Review the affected graph and report evidence
If the work requires changing packages/auth, the task expands deliberately because Admin may also consume that package. The agent should re-check both consumers instead of treating the shared edit as a local implementation detail.
That is the central idea of an agentic monorepo: an agent can work independently, but only within boundaries that everyone can see and check.
Common failure modes
The “shared everything” package
One enormous common package becomes a dumping ground for unrelated code. Prefer packages named after clear, stable responsibilities.
Invisible dependencies
Imports that climb through several folders and duplicated configuration hide relationships. Use named packages, approved exports and automated import rules.
Running every check for every change
This works at first and becomes painful as the repository grows. Run focused checks for the projects a change could affect, while keeping a complete automated test path for high-risk changes and releases.
Treating all apps as one deployment
Repository strategy and deployment strategy are different choices. Each application should have explicit build outputs, environment variables and deployment ownership.
Using instruction files as a substitute for architecture
An excellent prompt cannot rescue circular dependencies, ambiguous ownership or a test suite that does not run. Agents amplify the structure already present.
Should you use a monorepo?
A monorepo is a strong choice when:
- several products are maintained by the same team;
- changes frequently cross application and package boundaries;
- products share types, tooling or stable capabilities;
- reviewing related cross-project changes together is valuable;
- consistent engineering standards matter.
Separate repositories may be better when:
- products have unrelated teams, technology stacks and release processes;
- access must be isolated at the repository level;
- almost no code or operational knowledge is shared;
- independent ownership matters more than cross-project coordination.
Do not choose a monorepo simply because large companies use one. Choose it when your projects already share code, people and responsibilities closely enough to benefit from one home.
Final takeaway
A monorepo is not primarily about putting files together. It is about making relationships explicit.
That means a marketing site, admin platform and learning product can remain distinct while sharing the foundations that genuinely belong to the whole organisation.
Making the repository agentic takes the same idea further. We explain its map, enforce its boundaries, standardise its commands and require proof that changes work. Humans gain a clearer codebase, and agents gain the information they need without being given permission to wander.
One repository can hold many products. The quality of the architecture depends on whether everyone working inside it, human or agent, can tell where one responsibility ends and another begins.
Choose what to read next
You do not need to read every link. Pick the level that matches where you are now.
If the foundations are new
If you want to understand monorepos more deeply
- pnpm Workspaces
- Nx: What is a monorepo?
- Nx: Monorepo or polyrepo?
- Turborepo: Package and Task Graphs
- Turborepo: Running Tasks
Top comments (0)