The Hidden Prerequisite
Last week I had three AI agents running in parallel. One was building a frontend component. One was implementing the corresponding backend handler. A third was writing contract tests to verify they'd talk to each other correctly. All three finished, all three produced working code, and the system worked on the first deploy.
This isn't typical. Sometimes I'm still iterating multiple times, fixing what agents get wrong, re-running failed CI (automated). But the fact that it's possible at all is what made me start writing this down.
The interesting part isn't the clean run. It's why it worked when it did.
It wasn't better prompts. It wasn't a smarter model. It was the architecture. The agents succeeded because the codebase was structured so that each one could hold an entire bounded context in its context window, know exactly where to put its code, and verify its work against an explicit contract -- without needing to understand anything else.
This post is about that connection: a serverless architecture built on Domain-Driven Design principles turns out to be a precondition for effective agentic software development. And the reverse holds too -- agents reinforce good architectural discipline in ways that human developers often don't.
Architecture Is the Agent's Onboarding
When a new developer joins a team, they compensate for messy architecture. They ask questions in Slack. They read the wiki (if it exists). They sit with a colleague who explains that "oh, the billing logic is actually split across three services because of a migration we never finished." They build tribal knowledge over weeks and months, and that knowledge lets them navigate a codebase that doesn't explain itself.
AI agents have none of that.
An agent has exactly what's explicit: file names, folder structure, interfaces, types, tests, and whatever context you pass in the prompt. There is no Slack. There is no "let me ask Sarah about that module." There is no institutional memory beyond what's written down in code and configuration.
Architecture becomes the agent's onboarding. There is no other onboarding. A codebase that's hard for a new hire to navigate on their first day is impossible for an agent. A codebase where you need to "just know" that the payment logic lives in a shared utility folder three directories up? The agent won't find it.
It goes beyond navigation. Agents have finite context windows. They can only "see" a limited amount of code at once. A monolith with 50,000 lines of tightly coupled code forces the agent to load far more context than it needs for a single change -- and the more irrelevant context it gets, the worse it performs. In my experience, it hallucinates connections between unrelated modules, touches files it shouldn't, and loses the thread.
Small, self-contained units of code solve this. That's what Domain-Driven Design's bounded contexts provide: one domain concept, one repository, clear edges. Add serverless deployment and you get independent functions with explicit inputs and outputs, each deployable in isolation. An agent doesn't need to understand the whole system -- just its bounded context. A well-designed bounded context fits entirely in a context window.
What This Looks Like in Practice
Abstract architecture principles are easy to agree with. The hard part is making them concrete enough that agents can actually navigate them. Here's an example of how this can work.
The Planning Repository
The first structural decision is separating requirements from implementation entirely. A single planning repository contains zero application code. Its job is to be the product brain: the place where all requirements, specifications, and the roadmap live, independent of any codebase. I use this pattern in my own projects, and the structure below illustrates the idea.
Imagine you're building an e-commerce platform with domains like Catalog, Orders, Payments, Shipping, and Notifications. The planning repo might look like this:
project-planning/
roadmap.md
phase-1/
E1.1-product-catalog/
list-products.feature
add-product.feature
update-product.feature
E1.2-shopping-cart/
add-to-cart.feature
update-quantity.feature
checkout.feature
phase-2/
E2.1-order-management/
place-order.feature
view-order-history.feature
cancel-order.feature
E2.2-payment-processing/
process-payment.feature
refund-payment.feature
phase-3/
E3.1-shipping/
...
architecture/
decisions/
001-vertical-slice-architecture.md
002-infrastructure.md
...
The naming convention matters. Each directory follows a pattern like E{phase}.{epic}-{slug}. This creates a computable mapping from requirement to implementation repository. E2.1 means Phase 2, Epic 1: Order Management. An agent (or a human) can resolve that to order-ui and order-service without ambiguity.
Inside each epic directory, feature files describe the expected behavior as structured specifications:
Feature: Place Order
As a customer
I want to place an order from my shopping cart
So that I can purchase the products I selected
Scenario: Place order with valid payment
Given I have 2 items in my cart
And I have entered a valid shipping address
When I submit my order with a valid credit card
Then I should see an order confirmation with an order number
And my cart should be empty
And I should receive a confirmation email
Scenario: Place order with insufficient stock
Given I have 1 item in my cart
And that item is out of stock
When I try to submit my order
Then I should see an error that the item is unavailable
And my order should not be placed
These aren't only documentation. They're acceptance criteria in a structured format an agent can parse. An agent receiving a feature file knows the inputs, the expected outputs, and the edge cases -- without needing a product manager in the loop.
The planning repo also holds the roadmap with a dependency graph across phases, priority tiers, and architecture decision records. It's the one place where the entire product is visible, even though the code is spread across many repositories.
The Implementation Repositories
Each bounded context becomes a repository pair:
| Domain | Frontend | Backend |
|---|---|---|
| Catalog | catalog-ui |
catalog-service |
| Orders | order-ui |
order-service |
| Payments | payment-ui |
payment-service |
| Shipping | shipping-ui |
shipping-service |
| Notifications | notification-ui |
notification-service |
Each repo is self-contained: its own CI pipeline, its own tests, its own deployment. Each is small enough that an agent can hold the entire codebase in context.
Repos never import each other's code. No shared libraries, no monorepo, no cross-repo dependencies at the code level. Code duplication across bounded contexts is preferred over coupling. When an agent works in order-service, it doesn't need to know that shipping-service exists. Its world is the files in that repo, and that's enough.
Shared conventions across repos reduce the cognitive overhead further. Every frontend follows the same folder structure. Every backend follows the same handler pattern. An agent that has worked in one repo can apply the same patterns in any other. The convention becomes transferable context.
Contracts as the Boundary Layer
If repos never import each other, how do they agree on interfaces? Consumer-driven contracts.
This isn't a new idea -- tools like Pact have been around for years. I'm not claiming novelty here, just applying it as designed. The frontend repo (the consumer) writes contract tests that define what API requests it will make and what responses it expects. These contracts are published to a broker. The backend repo (the provider) fetches the contracts and runs verification tests to prove it satisfies them.
The deploy gate matters most. Before any release, a check verifies that all contracts between a service and its consumers have been verified. If a backend developer changes a response shape, the gate blocks the release until the contract is updated and re-verified. This works regardless of whether the developer is a human or an agent.
For agents specifically, contracts replace tribal knowledge. A human developer might know from experience that "if you change the order response format, you need to update the order-ui's API client too." An agent doesn't know that. But the contract test fails, the deploy gate blocks, and the problem surfaces automatically.
A Worked Example: From Ticket to PR
Here's a concrete path through the system, end to end.
Step 1: The issue. A ticket exists in the planning repo: "Implement order placement." It links to the feature file at phase-2/E2.1-order-management/place-order.feature.
Step 2: Resolving the target repos. The epic is E2.1 -- Phase 2, Order Management domain. The naming convention maps this to order-ui (frontend) and order-service (backend). No lookup table needed, no human clarification required.
Step 3: The contract. An agent working in order-ui writes a consumer contract test: "When I POST to /api/orders with a cart ID and shipping address, I expect a 201 response with an order ID and status." It generates the contract and publishes it to the broker.
Step 4: The backend. A separate agent working in order-service fetches the contract from the broker. It now has an explicit specification: this endpoint, this method, this request body, this response shape. It implements the handler, the validation, the database layer, and the tests. It verifies against the contract. Green. It checks the deploy gate. Safe.
Step 5: The frontend. Back in order-ui, the agent implements the checkout page, wires it up to the API client function that matches the contract, adds the route, writes unit tests.
Step 6: The PRs. Each repo gets its own PR. Each PR's description links back to the planning repo issue. Each PR runs its own CI: lint, unit tests, contract tests, build. Each passes independently.
Step 7: Traceability. From the planning repo issue, you can follow links to both PRs. From either PR, you can follow the link back to the requirement. From the contract in the broker, you can see which consumer defined it and which provider verified it. The full chain -- requirement to specification to contract to code to PR -- is navigable.
No step in this chain required an agent to understand more than one repository at a time. That's the point.
The Reinforcing Loop
Once you start using agents against a well-structured architecture, something happens: the architecture gets better. Not because you decided to refactor, but because agents surface bad architecture immediately.
When a bounded context is clean -- small repo, explicit interfaces, consistent naming -- agents work well. They find the right files, make the right changes, and produce working code. This success reinforces the discipline of keeping things small and isolated. You don't merge that "quick shortcut" that adds a cross-repo import, because you know it'll break the agent's ability to work in that repo independently.
When boundaries are sloppy -- a shared database table, an implicit dependency, a convention that exists only in someone's head -- the agent fails. It doesn't fail gracefully. It fails in ways that take longer to fix than doing the work manually. A shared database table means the agent in one service might unknowingly write data that another service depends on, and neither agent's tests catch it. An implicit dependency means the agent produces code that compiles and passes tests locally, but breaks in production because some other service was expected to be called first.
These problems exist with human developers too, of course. But humans learn to work around them. They develop instincts. They ask colleagues. The bad architecture persists because humans compensate for it.
Agents don't compensate. They expose.
This creates a positive feedback loop: well-bounded contexts let agents work effectively, which incentivizes keeping contexts well-bounded. Sloppy boundaries break agent workflows immediately, which creates pressure to fix the boundaries rather than working around them. The architecture becomes self-reinforcing.
The architecture that works best for agents -- small repos, explicit contracts, consistent naming, independent deployments, no hidden coupling -- is the architecture we've always said we should build. It's in every DDD book. It's in every microservices talk. Most teams don't build it because the cost of doing it right seems high and the cost of doing it wrong accumulates slowly. In my experience, agents change that calculus. The cost of bad architecture goes from "gradual tech debt" to "the agent can't do its job today."
Honest Caveats
This isn't autopilot. Here's what still doesn't work well.
Cross-cutting concerns are hard. A feature that spans multiple bounded contexts -- say, "when an order is placed, reserve inventory, process payment, schedule shipping, and send a confirmation" -- requires coordination across four repos. No single agent can do this. It requires orchestration, sequencing, and a human who understands the domain interactions well enough to decompose the work correctly.
Agents follow structure but can't design it. An agent excels at implementing a handler in an existing repo with established patterns. It cannot decide that a new domain concept deserves its own bounded context, or that two contexts should be merged. Architecture is still a human job. The agent is a skilled builder, not an architect.
The planning repo requires curation. Feature files, roadmap updates, priority changes -- these are human decisions. The planning repo is consumed by agents, but maintained by humans. If the specs are stale, the agents build the wrong thing. Garbage in, garbage out, but faster.
Contracts cover the interface, not implementation verification. A contract verifies that the response has the right shape. An agent — like any developer — can produce a handler that satisfies the contract by returning hardcoded data. The contract passes, the deploy gate is green, and the feature is broken. Contracts are necessary but not sufficient -- you still need proper unit and integration tests behind them.
This only works if the boundaries are real. A folder structure that mimics bounded contexts but shares a database underneath isn't fooling anyone -- especially not an agent. If one service reads directly from another service's database instead of calling its API, the contract is bypassed and the boundary is fake. The discipline has to be genuine all the way down, not just at the directory level.
What I'm Least Sure About
Everything above comes from one developer working on one product. Are there other (better?) approaches or am I just rediscovering microservices and putting 'agents' in the title ?
That's why I'm publishing this -- to find out.
Has anyone tried running agents across a multi-repo architecture with more than one human developer? Does the planning repo hold up when multiple people / agents curate it, or does it become another stale wiki? At what repo count does the naming-convention-to-repo mapping start to crack? I'd genuinely like to know.
Top comments (0)