Protecting Architecture Boundaries
Guides agents to preserve module ownership and avoid dependencies across protected boundaries.
Scenario
You maintain a larger application divided into business domains.
Each domain owns its business logic and persistence access.
The application also contains shared infrastructure.
The intended dependency direction is:
Application entry points
↓
Business domains
↓
Shared infrastructure
However, coding agents working on local tasks can accidentally introduce shortcuts that violate those boundaries.
This example uses AGENTS.md to protect architectural relationships without describing the entire system architecture.
Repository Structure
platform/
├── src/
│ ├── app/
│ │ └── server.ts
│ ├── domains/
│ │ ├── users/
│ │ │ ├── index.ts
│ │ │ ├── services/
│ │ │ └── repositories/
│ │ ├── billing/
│ │ │ ├── index.ts
│ │ │ ├── services/
│ │ │ └── repositories/
│ │ └── projects/
│ └── infrastructure/
│ ├── database/
│ ├── logging/
│ └── messaging/
├── tests/
└── AGENTS.md
AGENTS.md
# Project Instructions
## Domain Boundaries
- Keep business logic inside the domain that owns it.
- Consume another domain through its public exports.
- Do not import another domain's internal services or repositories directly.
- Do not move domain-specific logic into shared infrastructure to bypass a domain boundary.
## Persistence
- Domain repositories own persistence operations for their domain.
- Do not query another domain's tables directly from a service unless the existing architecture explicitly supports that interaction.
- Follow existing cross-domain workflows when data from multiple domains is required.
## Infrastructure
- Keep infrastructure modules free of product-specific business rules.
- Domains may depend on shared infrastructure.
- Shared infrastructure must not depend on domain implementation modules.
## New Dependencies
Before introducing a dependency between domains:
- inspect the existing public interfaces;
- check whether an established integration path already exists;
- prefer extending an appropriate public interface over importing internals.
## Validation
For boundary-affecting changes:
- run tests for the modified domain;
- run tests for affected consumers;
- run `pnpm lint`;
- run `pnpm typecheck`.
What This Does
This gives the agent a dependency model.
For example:
billing
may need information owned by:
users
The quickest implementation could be:
import { userRepository } from "../users/repositories/user-repository";
But that creates a dependency on an internal implementation detail.
The instructions tell the agent to first look for something intentionally exposed by the users domain:
import { getUser } from "../users";
The exact interface depends on the repository, but the boundary is clear.
What This Does NOT Do
The instructions do not prohibit domains from communicating.
That would make many real workflows impossible.
Instead, they distinguish:
intentional integration
from:
internal implementation coupling
The file also does not contain a complete architecture specification.
Detailed domain relationships may belong in architecture documentation or diagrams.
AGENTS.md contains the working rules an agent needs while modifying code.
Why These Instructions Matter
Architecture often degrades through individually reasonable shortcuts.
Imagine billing needs a user's country.
The agent discovers that the users repository already has:
findUserById()
Importing that repository directly seems efficient.
Later, billing begins using more users internals:
user repository
user database types
user persistence models
Now changes inside the users domain can unexpectedly break billing.
The architecture has become coupled even though no single change looked especially dangerous.
An instruction such as:
- Consume another domain through its public exports.
creates a simple decision boundary.
Infrastructure has a similar risk.
Suppose several domains need billing-related logic.
Moving that logic into:
infrastructure/
does not make it generic.
It merely hides domain logic inside a shared layer.
That is why the instructions explicitly say:
- Do not move domain-specific logic into shared infrastructure to bypass a domain boundary.
Key Decisions
Protect dependency direction
A useful architecture usually has an intended direction of dependency.
AGENTS.md can state that direction in terms the agent can act on.
Public interfaces are deliberate boundaries
A public domain export communicates:
Other parts of the application may depend on this.
An internal repository communicates something different:
This exists to implement this domain.
Agents should respect that distinction.
Avoid fake sharing
Moving code into a shared directory does not automatically make the code generic.
Ownership should be based on responsibility.
Cross-domain changes deserve broader validation
Changing a public domain interface may affect multiple consumers.
Validation should therefore include those consumers rather than only the domain where the change originated.
Use tooling when possible
If architecture boundaries are critical and mechanically enforceable, consider also enforcing them with:
- ESLint import restrictions;
- package boundaries;
- TypeScript project references;
- dependency-analysis tooling.
AGENTS.md provides guidance, but important invariants should not rely solely on written instructions when tooling can enforce them reliably.
When to Use This Pattern
Use this pattern when:
- an application contains multiple business domains;
- modules expose intentional public interfaces;
- cross-domain dependencies need control;
- shared infrastructure has a defined responsibility;
- architectural shortcuts create long-term maintenance problems.
This is an advanced pattern.
For a small application, a few simple directory rules may be enough. Add stronger boundary guidance when the architecture actually requires it.