September 15, 2026

Moving Taproot from multiple repositories into a monorepo substantially improved the pace and quality of our work. We had fewer releases to coordinate, spent less time waiting for CI/CD, and could make related changes together. The AI assistant could inspect the whole solution, and the changes it proposed became easier to integrate.

It also helped us avoid a familiar chicken-and-egg problem: one piece needed an update before another could use it, while finishing the update required working through both pieces together.

I don't have a controlled before-and-after measurement that puts a percentage on the improvement. I do have the experience of working both ways, along with Git history showing the changes we could make together and the release steps we eventually removed.

For a small team building one connected product with AI assistance, I think those details are more useful than a blanket recommendation to put everything in one repository.

Big File Upload.png

One repository, several services

A monorepo keeps several projects in one version-controlled repository. A polyrepo, or multi-repo setup, gives those projects separate repositories.

Taproot is a publishing platform. Its authoring interface, backend API, site generator, and image processor have different responsibilities. The frontend and Node services use TypeScript; the backend uses C#. Shared protobuf definitions describe the service contracts.

Our main repository brought the API, frontend, generator, and contracts together in December 2025. We continued consolidating related code afterward. Today, those parts can be inspected alongside their tests, development configuration, and task history.

The services still have their own jobs and build processes. Putting them in one repository did not turn them into one running application. It gave us a common place to make and review changes that involve several of them.

That distinction matters when considering a monorepo: source organization and deployment design are separate decisions.

A package boundary that was costing us time

One later consolidation gives a concrete example of what improved.

I had put Taproot's generator-owned web components in a separate repository because I thought keeping them separate might make things easier. In retrospect, that choice added work.

We published those components as a package used by the site generator and by previews in the authoring interface. A component change could therefore affect both what a creator saw while editing and what appeared on the published site.

The package had one consuming codebase: Taproot. It was also tightly connected to Taproot's templates, design system, and published assets. The separate package wasn't providing the useful independence its original rationale had anticipated.

Our migration notes recorded 23 published versions and more than a dozen dependency-version updates in the generator. There were publishing workflows and a cross-repository release trigger to keep the pieces connected.

We moved the component source into the main repository and updated the generator and editor preview to consume it there.

Part of the workBefore this consolidationAfterward
Make a component change available to its consumersPublish the package and update dependency versionsChange the source and affected consumers in the same checkout
Review the implementation and integrationFollow changes across repositories and package versionsInspect the related changes together
Coordinate the component releaseMaintain a separate package publication and notification pathInclude the component source in the product build

We retained versioning for the runtime delivered to published websites. That version identifies an actual deployed artifact and still serves a purpose.

The improvement was specific: we removed an internal publication step from code that we were developing as part of the same product.

What became easier for the AI assistant

A feature often crosses the boundaries in a repository diagram.

An early example in our Git history is the January 2026 work on setting a site's favicon, the small icon associated with a website. The same commit updated the settings contract, the backend logic and data structures, the generated TypeScript client, and the interface that selected the image and retrieved its resulting URL.

That was one product capability spanning several layers. We could change the contract and its consumers together in a single commit. The frontend work could be developed against the corresponding backend work in the same checkout, before either was released separately.

That is the sort of coordination that had produced the chicken-and-egg problem. Bringing the work into one repository let us prepare the combined change before putting it through the release process.

In our monorepo, the assistant can follow that path through the actual code. It can inspect the contract, find its consumers, look at an existing example, and propose the related changes together. I can review whether the pieces agree as part of the same change.

That makes it practical to describe the intended behavior and have the agent investigate the implementation across layers. A request still needs clear scope and acceptance criteria. The checkout supplies the code against which those requirements can be checked.

I saw the quality of the proposed solutions improve as the assistant gained that broader view. It could inspect how the parts actually connected, and the resulting changes were easier to integrate.

AI agents can also work across multiple repositories when the tools and permissions make those repositories available. Access to several repositories would address part of our problem. Separate commits, dependency versions, and releases would still need coordination. For Taproot, bringing related work together simplified that process for me as well as for the assistant.

We keep the task's explanation in the repository too. I've written about why we put our backlog in Git. The intended behavior, the implementation, and the verification record can travel together.

A larger repository still needs focused context

Having access to the repository does not mean an agent should read all of it for every task.

The useful context for a change is usually a smaller set: the requirement, relevant contracts, implementations, consumers, tests, and architectural rules. A repository full of unrelated files can make that investigation harder if the agent has no guidance about where to begin.

Anthropic's guidance for Claude Code explicitly discusses context limits and recommends specific file references, relevant examples, and managing unrelated material in a session. A monorepo doesn't remove those constraints. Claude Code best practices.

In Taproot, service directories and READMEs provide starting points. Our project instructions describe the architectural rules and point to examples. Task records explain what needs to change and what would count as completion.

The assistant still needs to search, follow references, and choose what to read. Keeping related code nearby makes that investigation easier to arrange; it doesn't guarantee that the agent will discover every dependency.

Whether that reduces token usage depends on the task and how the agent retrieves context. We haven't measured token savings. Our reason for keeping the arrangement is the simpler workflow and the improvement in the work we're producing.

Moving the files was only part of the migration

The component consolidation also exposed details that needed care.

Our editor and generator had their own dependency installations. When the editor began importing source from the generator directory, module resolution could find a different physical copy of the design-system dependency. Bundling both copies would cause duplicate custom-element registration. We configured resolution so the editor bundle used one copy.

The Docker build needed attention as well. A local checkout contained the new shared source, but the editor's image build only had the files its Dockerfile copied. We had to include the component source in that build. TypeScript, the bundler, and the test runner also needed matching import paths.

Those are migration costs worth including in the decision. A successful local import doesn't prove that tests, container builds, and release workflows all understand the new layout.

Keeping the convenience without losing the boundaries

Putting code nearby makes it easier to reuse. It can also make an inappropriate dependency easier to introduce.

Taproot still has rules about which layers can import which others, how API contracts are defined, and where mutations and authorization belong. Automated guards enforce parts of those rules. The agent's ability to find a file is not permission to connect it to anything else in the repository.

Verification needs similar care. Our root verification script selects checks based on the affected paths. Shared contracts can require checks across several services. That mapping needs maintenance as the relationships change.

A monorepo also doesn't make production updates atomic. Services and published sites may run different versions during a rollout. Compatibility and release ordering still need deliberate handling, even when their source changes share a commit. The published-site runtime is one reason we preserve versioned release contracts inside Taproot's monorepo.

The common checkout makes coordinated work easier to review. Builds, tests, and deployment controls establish whether that work is ready to use.

When I would keep separate repositories

For a small team, I would start by examining how often a useful product change crosses repository boundaries.

If the frontend, backend, and shared contracts routinely change together, a common repository deserves serious consideration. The same is true when an internal package needs frequent publication just to get an update into its only product.

Separate repositories can be a better fit when access needs to be restricted by repository, products share little code, or a library has independent consumers that need a stable release contract. Those are concrete reasons to retain a boundary.

I would also consider improving a multi-repo workspace before migrating if finding related code were the main difficulty. A repository move has to earn back the effort of changing builds, CI, permissions, and documentation.

For someone planning a product, the practical question is how much work a normal feature takes to coordinate. Look at a recent change: where did it start, which components needed updating, how many releases did it require, and how was the combined result checked? That gives you a useful basis for choosing a structure.

Why I'm keeping the monorepo

For Taproot, the monorepo fits the way we build. I can describe a product change, the assistant can investigate the relevant layers, and we can develop and verify the result in a shared working environment.

It has also let us remove machinery whose main purpose was carrying changes between closely related parts of the same product. That has made the work considerably easier.

This is one way I'm applying 26 years of full-stack engineering experience to working with AI: examining the process around the code and changing the parts that get in the way of delivering it.

If you're building a product and want help making that process more effective, I'd welcome a conversation about the software, the constraints, and where the work is getting stuck.