We keep almost everything we build in one monorepo: customer-facing applications, hosted customer websites, marketing surfaces, internal tools, shared design systems, database schemas, configuration and AI infrastructure.
Monorepos were already useful for humans. Coding agents make the tradeoff even more interesting.
Context is infrastructure
Coding agents do better when the code, instructions, tools and feedback required to complete a task are easy to discover and operate.
A monorepo does not magically put the entire codebase into a model's context window. It gives the agent a coherent source graph it can search and traverse.
When an agent works on a feature at Durable, it can inspect the application, shared components, database schema, related internal tooling and other consumers of the same packages without crossing repository boundaries or resolving independently versioned codebases.
This becomes more valuable as agents take on larger changes. A database schema change might also require updates to shared types, an API, the customer-facing product and an internal operations tool. Keeping those systems in the same source graph makes the complete change easier to discover, implement and review.
The inverse matters too. More accessible code is not automatically better context. As the repository grows, clear package boundaries, local documentation and scoped agent instructions become increasingly important.
One change, one graph
Cross-cutting changes are one of the strongest reasons we use a monorepo.
A change to a shared contract can update the contract and every known consumer in the same branch and pull request. We do not need to publish an internal package, update versions across several repositories and coordinate a sequence of merges while the system temporarily exists in multiple states.
The same property makes large refactors easier. Humans and agents can search the repository for the real dependency surface, change an interface and update its consumers together.
Git remains the unit of change even when the applications themselves remain independently deployable.
Share stable primitives
We share primitives that should remain consistent across the product: design systems, database schemas, types, validation, configuration, AI tooling and common application infrastructure.
We use TypeScript across the stack, Next.js for our applications, the Vercel AI SDK for AI products, and pnpm workspaces with Turborepo to organize packages and coordinate tasks across the repository.
That does not mean everything reusable belongs in a shared package. Shared abstractions create coupling too. Product-specific behavior should usually stay with the product that owns it.
The goal is not maximum reuse. It is one canonical implementation for the things that actually need to stay consistent.
The repository is executable context
Source code is only part of what makes the repository useful to an agent.
The repo also contains instructions, dependency relationships, tests, build commands, lint rules, task definitions and development tooling. Those give an agent ways to validate its understanding against the actual system.
We use repository-level agent instructions and can scope instructions further as applications and packages develop different constraints.
That turns architectural knowledge from something an agent has to infer into something the repository can explicitly teach.
One development surface
pnpm workspaces give us a single workspace and dependency graph for our TypeScript packages. Turborepo sits above that graph and coordinates builds, tests, linting, type checking and other tasks.
A monorepo does not mean every command needs to run against the entire repository.
Turborepo can use the dependency graph and task inputs to scope work to the affected parts of the repository, while caching lets unchanged work be reused locally and in CI. pnpm filtering gives us another way to target individual applications, packages or parts of the workspace.
That lets us retain many of the performance characteristics people reach for separate repositories to get: selective builds and tests, parallel execution, isolated application development and independently deployable services, without giving up a shared source graph.
Turbopack handles the Next.js bundling side and keeps the application development loop fast. It solves a different problem from Turborepo, but together the tooling gives us a consistent development surface across our Next.js applications.
The result is that an engineer or coding agent can move between applications while keeping the same package model, commands and development conventions.
Branches become environments
Our branch workflow compounds the value of the monorepo.
A branch can contain changes across an application, shared packages and database schema. That branch gets live preview deployments and an isolated fork of the production database, so the result can be exercised as running software before it is merged.
This is also what allows product managers and designers to work directly in the repository. They can start a branch, use the real design system and data model, and work against running software instead of describing the change through mocks and specifications.
Sometimes engineering takes over the implementation. Sometimes they make a few changes. Sometimes they only need to review the pull request.
The shared repository makes all three workflows possible.
Optimize for shipping
There are good reasons not to use a monorepo. Hard source-code access boundaries, unrelated organizations, radically different development environments or systems with almost no meaningful shared change surface can justify separate repositories.
A large monorepo still requires discipline. Dependency boundaries, ownership, task scoping, caching and agent instructions matter more as the graph grows.
For us, those costs are worth paying.
Our products and internal systems operate on the same customers, data models, design language and AI infrastructure. Changes regularly cross those boundaries.
Keeping that system in one repository means humans and agents can reason about more of the real change at once, while modern workspace and build tooling lets most individual operations stay scoped to the part of the graph that actually changed.
We optimize the repository for shipping.