Oct 23, 20268 min read

Microservices vs Monolith for AI Coding Agents

How context limits, service boundaries and the ability to run the whole system locally change the choice when a coding agent writes much of the code.

A coding agent reads the files it thinks are relevant, edits them, runs a check and reads the result, all inside a context window that has a fixed size. Anthropic's engineering team describes context as a finite resource with diminishing returns, and Chroma's context rot report found that models become less reliable as input length grows, even on simple tasks.

Architecture decides how much the agent has to read for a given change and whether it can run what it changed, which is why the monolith and microservices question looks different when an agent does much of the work.

A monolith keeps everything readable in one place and runs with one command, but it gets harder to work in when the codebase is large enough that the agent reads the wrong half of it, or when two teams step on each other's deploys. Microservices give the agent smaller units to work in and get harder when a change crosses a boundary, because the other service is in a different repository, the contract is an OpenAPI file that may be out of date, and the only way to test the two together is a shared staging environment. The general tradeoffs are covered in monolith vs microservices; this article looks at the parts that matter for agents.

What to weigh when choosing:

  • How much code the agent has to read to make a typical change.
  • Whether service or module boundaries are typed, so a broken contract is a compile or type error.
  • Whether all the code is in one repository the agent can search.
  • Whether the agent can run the whole system, including its databases and queues, on one machine.

Architectures for AI coding agents at a glance

ArchitectureWhat the agent sees and runsBest fit
EncoreServices in one app with typed calls between them, all started locally by encore run with their infrastructureTeams that want service boundaries without losing the ability to run and trace everything locally
MonolithOne codebase and one processSmall and mid-sized apps, early projects
Modular monolithOne process with enforced module boundariesGrowing apps where teams own modules
Microservices in a monorepoMany services in one repository, usually started with Docker ComposeTeams that need independent deploys but want one codebase
Microservices across repositoriesOne service per repository, other services only through their APIsLarge organizations with separate teams per service
Compare the architectures in detail
CapabilityEncoreMonolithModular monolithMicroservices in a monorepoMicroservices across repositories
Code the agent can searchThe whole appThe whole appThe whole appAll servicesOne service
Boundary contractsTyped service clients generated from the API definitionsFunction callsModule interfaces, enforced by a linterHTTP or gRPC, typed if you generate clientsHTTP or gRPC, often documented separately
Run the whole system locallyencore run starts every service and its infrastructureOne processOne processA Compose file kept in sync by handUsually not possible
Cross-service tracing locallyBuilt inNot neededNot neededRequires OpenTelemetry setupRequires a shared environment
Independent deploys and scalingPer-service infrastructure and IAM roles, deployed together from one appNoNoYesYes
Agent instructions per areaPer-service CLAUDE.md or AGENTS.md filesOne filePer-module filesPer-service filesPer-repository files

Encore: service boundaries that run as one system locally

Encore

Encore combines the boundaries of microservices with the local workflow of a monolith: an app is one repository where every directory with a service definition is a service, each service declares its own databases, Pub/Sub topics and buckets in code, and calls between services are typed function calls that Encore turns into network calls. encore run starts all services locally with their infrastructure, so an agent that changes the orders service and the billing service in one task can call the endpoint and watch the request cross both, and a deploy provisions the services and their resources in your own AWS or GCP account.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.

In TypeScript, a service is a directory with an encore.service.ts file, and its endpoints and database live next to it:

// billing/encore.service.ts import { Service } from "encore.dev/service"; export default new Service("billing");

The billing service exposes an endpoint with typed request and response shapes, which is the contract other services compile against:

// billing/billing.ts import { api } from "encore.dev/api"; interface ChargeRequest { orderId: string; amountCents: number; } interface ChargeResponse { chargeId: string; } export const charge = api( { method: "POST", path: "/billing/charge" }, async (req: ChargeRequest): Promise<ChargeResponse> => { return { chargeId: `ch_${req.orderId}` }; }, );

The orders service calls it through the generated client in ~encore/clients, so renaming a field in ChargeRequest is a type error in the orders service rather than a failed request in staging:

// orders/orders.ts import { api } from "encore.dev/api"; import { billing } from "~encore/clients"; export const checkout = api( { expose: true, method: "POST", path: "/orders/:id/checkout" }, async ({ id }: { id: string }): Promise<{ chargeId: string }> => { const { chargeId } = await billing.charge({ orderId: id, amountCents: 4200 }); return { chargeId }; }, );

Because Encore knows every service and every call between them from static analysis, the local development dashboard shows a service catalog and distributed traces across services, and the local MCP server exposes the same metadata and traces to the agent, so it can check which services a request touched. Python and Rust services follow the same model with an encore.service.json per service, and services in different languages can live in one app, as Python microservices shows.

Key features

  • One repository, with one service per directory and infrastructure declared per service.
  • Typed service-to-service calls checked at compile time.
  • encore run starts every service locally with its databases, topics and buckets.
  • Traces across services locally and in every environment.
  • Each service gets its own IAM role in production, scoped to the resources it uses.

Good to know

Splitting a service is a matter of moving code into a new directory with its own service definition, and the typed clients make every broken call site visible. Service clients cannot be passed around freely because the calls are found through static analysis, and the .ref() method on a client exists for those cases.

Try Encore →

Monolith: the least for the agent to coordinate

A single codebase that runs as one process is the easiest architecture for an agent to work in at small and medium size, since every caller of a function is a search away and a refactor is checked by one build, and it gets harder as the codebase grows, because the agent has more to search and more unrelated code competes for its context, and Claude Code's documentation notes that more specific and concise instructions are followed more consistently. Splitting instructions per directory helps, since CLAUDE.md files in subdirectories load only when the agent works there.

Modular monolith: boundaries a linter enforces

A modular monolith keeps one process and adds boundaries the build checks, which gives the agent smaller areas to reason about without a network in between. In Python, import-linter turns the boundaries into a contract that fails CI when a module reaches into another's internals:

[importlinter] root_package = shop [importlinter:contract:billing-boundary] name = Orders only uses the billing public API type = forbidden source_modules = shop.orders forbidden_modules = shop.billing.internal

Good to know

The boundaries are only as strong as the checks around them, so the contract file and the command that runs it belong in AGENTS.md. Microservices vs modular monolith covers how to keep these boundaries from eroding.

Microservices in a monorepo

Putting every service in one repository gives the agent the whole system to search, and a Docker Compose file can start the services and their dependencies together:

# compose.yaml services: orders: build: ./orders environment: BILLING_URL: http://billing:8080 DATABASE_URL: postgres://postgres:postgres@orders-db:5432/orders depends_on: [billing, orders-db] billing: build: ./billing orders-db: image: postgres:17 environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: orders

Good to know

The Compose file is a second description of the system that drifts from the code, every new service or database means editing it, and the HTTP contract between services is only typed if the team generates clients from a schema. Tracing a request across services locally needs OpenTelemetry wired into each one.

Microservices across repositories

One repository per service gives teams full independence but is the hardest setup for an agent, because the other services are visible only through their API documentation, a change to a contract means coordinated pull requests in several repositories, and running the full system usually means deploying to a shared environment. Claude Code can run subagents, each in its own context window, which helps split work, but each one still needs access to the code it changes and a way to run it.

How to choose

  • Encore when you want service boundaries with typed calls, every service in one repository, and the whole system running locally with its infrastructure, provisioned in your own AWS or GCP account on deploy.
  • A monolith for most new and mid-sized apps, with per-directory agent instructions as it grows.
  • A modular monolith when teams own areas of the code and the boundaries need checking in CI.
  • Microservices in a monorepo when services must deploy independently and the team accepts maintaining a Compose file.
  • Microservices across repositories only when organizational boundaries require it, accepting that agents will work on one service at a time.

Frequently asked questions

Are microservices or a monolith better for AI coding agents?

A monolith or a modular monolith is usually easier for an agent, because it can read the related code in one repository and run the whole app with one command. Microservices work well with agents when the services live in one repository, call each other through typed clients rather than hand-written HTTP, and can all run locally together, so the agent can test a change that crosses a service boundary before it is deployed.

How do I let an AI coding agent run all my microservices locally?

Keep the services in one repository and use tooling that starts them together with their infrastructure. With Encore, every directory with a service definition is a service, and encore run starts all of them locally with their databases, Pub/Sub topics and buckets, with service-to-service calls traced so the agent can see the whole request. Without Encore, a Docker Compose file that starts each service and its dependencies does a similar job, but has to be kept in sync by hand.

Do AI agents struggle with large codebases?

They have to work within a finite context window, and research such as Chroma's 2025 context rot report found that model performance becomes less reliable as input length grows. Clear module or service boundaries help, because the agent can work on one part while reading only the interfaces of the others, as long as those interfaces are typed and in the same repository.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders