Coding agents are fast, confident, and unaware of your architecture until you give them architecture to read. If you hand Claude Code, Codex, or Cursor a vague ticket, it may produce code that compiles and passes tests while quietly breaking your layering rules, idempotency model, or API contract. A DESIGN.md file reduces that failure mode by putting architectural intent in the repository, where agents already look.
TL;DR
DESIGN.md is a repo-level Markdown file that records architectural intent, constraints, invariants, and design decisions so coding agents generate code that fits your system.
Use it to answer:
- Why is the code shaped this way?
- What rules must new code follow?
- What should agents avoid changing?
- Where should new logic go?
It complements AGENTS.md, CLAUDE.md, and README.md:
-
README.md: what the project is and how humans start -
AGENTS.md: how agents build, test, lint, and commit -
CLAUDE.md: Claude-specific instructions -
DESIGN.md: architecture, invariants, rationale, and rejected alternatives
Introduction
A common failure mode appears quickly when teams start using coding agents.
You ask an agent to add a refund endpoint to a payments service. It returns a working handler that:
- calls the database directly from the controller
- swallows the gateway error
- creates a new money representation
- bypasses an existing domain aggregate
- still compiles
- still passes a quick test
The diff looks clean, but it violates decisions only an engineer familiar with the system would know.
The problem is not that the agent cannot code. The problem is that it cannot infer decisions that live in a Notion page, Slack thread, incident review, or senior engineer’s memory.
DESIGN.md fixes that by committing those decisions to the repo root.
It should describe the load-bearing facts of the system:
- layering rules
- domain invariants
- transaction boundaries
- idempotency rules
- API contract ownership
- generated-code rules
- rejected approaches
- places agents should not modify
It is not a formal standard. It is a practical convention, like ARCHITECTURE.md, CONTRIBUTING.md, or ADRs. But it works especially well with coding agents because it gives them architectural context before they start editing files.
What DESIGN.md actually is
DESIGN.md is a plain-text record of why your code looks the way it does.
It is not a replacement for:
-
README.md, which explains what the project does -
AGENTS.md, which explains how to build and test -
CLAUDE.md, which gives Claude-specific instructions - ADRs, which record individual decisions over time
Instead, DESIGN.md gives agents and engineers the architecture-level rules they need before modifying the system.
Examples of useful DESIGN.md rules:
- Domain logic never imports the web framework.
- HTTP handlers never call the payment gateway directly.
- Money is always represented as integer minor units plus currency.
- Ledger entries are immutable after creation.
- Every external payment request must use an idempotency key.
- Generated API types must not be hand-edited.
These are rules an agent cannot reliably infer from source code alone.
The source code may show that Account.post_entry() exists. It does not prove that Account.post_entry() is the only allowed write path to the ledger. Without that context, an agent may create a second write path that works locally but breaks the design.
A good DESIGN.md gives the agent the missing intent.
DESIGN.md vs AGENTS.md vs CLAUDE.md vs README.md
These files should not all contain the same information.
| File | Audience | Answers | Change rate | Recommended length |
|---|---|---|---|---|
README.md |
Humans | What is this project? How do I start? | Changes with features | Medium |
AGENTS.md |
Coding agents | How do I build, test, lint, and commit? | Changes with tooling | Short |
CLAUDE.md |
Claude Code | Claude-specific instructions and imports | Changes with tooling | Short |
DESIGN.md |
Agents, engineers, reviewers | Why is the system shaped this way? What must never break? | Changes with architecture | Medium, dense |
The agents.md project describes AGENTS.md as “a simple, open format for guiding coding agents.” Its job is operational: test commands, build steps, linting, formatting, style, and workflow rules.
Anthropic’s Claude Code memory documentation describes CLAUDE.md as Claude’s instruction file. If you already use AGENTS.md, the docs recommend importing it from CLAUDE.md with:
@AGENTS.md
That keeps one source of truth for operational agent instructions.
But architectural rationale should not be squeezed into those short files. Long instruction files consume context and reduce adherence. Keep the operational files short and point them at DESIGN.md.
A clean setup looks like this:
repo/
README.md
AGENTS.md
CLAUDE.md
DESIGN.md
Example AGENTS.md:
# AGENTS.md
## Build and test
- Install dependencies with `pnpm install`.
- Run unit tests with `pnpm test`.
- Run type checks with `pnpm typecheck`.
- Run linting with `pnpm lint`.
## Architecture
Before making structural changes, read `DESIGN.md`.
Do not change generated files. If API types are wrong, update the OpenAPI spec first.
Example CLAUDE.md:
@AGENTS.md
Before editing domain, persistence, or API boundary code, read @DESIGN.md.
If a requested change conflicts with DESIGN.md, explain the conflict instead of working around it.
For more on structuring Claude context, see Claude Code workflows.
What to put in DESIGN.md
A useful DESIGN.md should answer questions an agent cannot infer reliably from code.
Include these sections.
1. System shape
Describe layers, modules, and dependency direction.
Example:
## System shape
Dependencies point inward:
http -> app -> domain <- infra
- `domain/` has no imports from `http/`, `app/`, `infra/`, or framework packages.
- `http/` handles routing, auth context, request parsing, and response formatting only.
- `app/` owns use cases and transaction orchestration.
- `infra/` implements interfaces declared by `app/` or `domain/`.
2. Invariants
State rules that must always hold.
Example:
## Invariants
- Ledger entries are immutable after creation.
- Money is represented as integer minor units plus ISO-4217 currency.
- External payment calls are idempotent by `idempotency_key`.
- Every query is scoped by `tenant_id`.
- Domain logic does not depend on HTTP request objects.
Use absolute language. Avoid vague phrases like “usually,” “prefer,” or “try to.”
3. Key decisions and rationale
Explain decisions that may look arbitrary.
Example:
## Key decisions and rationale
- Gateway calls use the outbox pattern.
Rationale: inline gateway calls caused request timeouts and inconsistent retry behavior.
Do not call the gateway directly from request handlers.
- The ledger is append-only.
Rationale: we need a complete audit trail for financial operations.
Corrections must be compensating entries, not updates or deletes.
The rationale matters. It prevents agents from “simplifying” something that exists because of a past production problem.
4. Rejected alternatives
Tell agents what not to reintroduce.
Example:
## Rejected alternatives
- Do not store account balance as a mutable column.
Balance is derived from ledger entries.
- Do not use floats for money.
Use `domain/money.ts`.
- Do not add synchronous merchant webhooks to request handlers.
Use the notification queue.
This section prevents agents from presenting rejected ideas as new suggestions.
5. Data and domain rules
Document rules around identifiers, time, tenancy, deletion, and consistency.
Example:
## Data and domain rules
- All timestamps are UTC.
- API timestamps are formatted as RFC 3339.
- IDs are ULIDs generated in the application layer.
- Soft delete is not used.
- Repository methods must include tenant scope.
6. API contract source of truth
For backend and API services, this is one of the highest-value sections.
Example:
## API contract source of truth
- `api/openapi.yaml` is authoritative.
- Request and response types are generated from the OpenAPI spec.
- Do not hand-edit files in `src/http/generated/`.
- To change an endpoint, update the OpenAPI spec first, regenerate types, then implement.
- Error responses use `application/problem+json`.
If your team designs APIs first in Apidog, say that explicitly:
- API changes are designed and reviewed in Apidog first.
- The exported OpenAPI document in `api/openapi.yaml` is the source of truth.
7. Where new code goes
Give agents a placement map.
Example:
## Where new code goes
- New endpoint: `src/http/routes/`
- Request/response mapping: `src/http/dto/`
- Use case: `src/app/usecases/`
- Domain logic: `src/domain/`
- Database implementation: `src/infra/db/`
- External API client: `src/infra/clients/`
This reduces random scattering of logic across layers.
8. Out of scope / do not touch
Tell agents which files are generated, frozen, or under migration.
Example:
## Out of scope / do not touch
- `src/http/generated/`: generated from OpenAPI.
- `legacy/billing_v1/`: frozen during migration.
- Applied migrations must not be edited. Add a new migration instead.
9. Conflict handling
Add a rule for what the agent should do when the ticket conflicts with the design.
Example:
## When in doubt
If a requested change conflicts with this file, stop and explain the conflict.
Propose the smallest design-consistent alternative.
Do not silently work around the rule.
This turns DESIGN.md from passive documentation into an active guardrail.
Full DESIGN.md template for an API service
Copy this, delete what does not apply, and fill in your project-specific rules.
# DESIGN.md: Payments API Service
This file records architectural intent and the decisions behind it.
Read this before generating or modifying code.
If a requested change conflicts with a rule here, stop and flag the conflict
instead of silently working around it.
## System shape
Layered architecture. Dependencies point inward only:
http -> app -> domain <- infra
- `domain/` contains entities, value objects, and invariants.
- `domain/` has zero imports from `http/`, `app/`, `infra/`, or framework packages.
- `http/` handles routing, auth context, request parsing, and response formatting.
- `http/` never calls the database or payment gateway directly.
- `app/` owns use cases, transactions, and orchestration.
- `infra/` implements interfaces declared by `app/` or `domain/`.
## Invariants
- A ledger entry is immutable once written.
- Corrections are new compensating entries, never updates or deletes.
- Account balance is derived from ledger entries.
- Money is represented as integer minor units plus ISO-4217 currency.
- Money is never represented as a float.
- Currencies are never mixed in one operation.
- Every external payment gateway call is idempotent by `idempotency_key`.
- Retries must not double-charge.
- Balances never go negative unless an explicit `OverdraftPolicy` authorizes it.
- Every query is scoped by `tenant_id`.
## Key decisions and rationale
- **Outbox pattern for gateway calls.**
Request handlers write an intent row in the same DB transaction as the business change.
A worker later calls the payment gateway.
Rationale: gateway timeouts made inline calls unreliable.
Do not call the gateway from request handlers.
- **Single write path per aggregate.**
Only `Account.post_entry()` writes to the ledger.
Rationale: a second write path caused balance drift.
Add new behavior as aggregate methods, not direct SQL writes.
- **Event sourcing for the ledger only.**
The rest of the system is CRUD.
Rationale: the ledger requires a complete audit trail; other modules do not.
## Rejected alternatives
- Do not use ORM lazy-loading across aggregates.
Repositories return fully-loaded aggregates.
- Do not store balance as a mutable column.
Balance is always derived from ledger entries.
- Do not import a generic money package.
Use `domain/money.ts`.
- Do not send merchant webhooks synchronously from request handlers.
Use the notification queue.
## Data and domain rules
- All timestamps are UTC.
- API timestamps are formatted as RFC 3339.
- No naive datetime crosses a function boundary.
- IDs are ULIDs generated in the application layer.
- Soft delete is not used.
- Records are active or moved to archive tables by explicit use cases.
- Repository methods must include tenant scope.
## API contract source of truth
- `api/openapi.yaml` is authoritative.
- Request and response types are generated from the OpenAPI spec.
- Do not hand-edit generated files in `src/http/generated/`.
- To add or change an endpoint:
1. Update `api/openapi.yaml`.
2. Regenerate API types.
3. Implement the route and use case.
4. Add contract and integration tests.
- API changes are designed and reviewed in Apidog before implementation.
- Error responses use RFC 9457 `application/problem+json`.
- Use the shared `problem()` helper for API errors.
## Where new code goes
- New route: `src/http/routes/`
- DTO or mapper: `src/http/dto/`
- Use case: `src/app/usecases/`
- Domain behavior: `src/domain/`
- Repository interface: `src/app/ports/`
- Repository implementation: `src/infra/db/`
- External API client: `src/infra/clients/`
- Middleware: `src/http/middleware/`
## Out of scope / do not touch
- `src/http/generated/`: regenerated from OpenAPI.
- `legacy/billing_v1/`: frozen during migration.
- `migrations/`: never edit an applied migration. Add a new one.
## When in doubt
If a requested change requires breaking a rule above, say so.
Propose the smallest design-consistent alternative.
Do not silently route around this file.
How coding agents consume DESIGN.md
Agents do not have a special DESIGN.md parser. They read it like any other repo file.
The practical pattern is:
- Put
DESIGN.mdin the repo root. - Reference it from
AGENTS.md. - Import or reference it from
CLAUDE.md. - Ask agents to read it before structural changes.
- Use PR review to enforce it.
Example AGENTS.md pointer:
## Architecture
Read `DESIGN.md` before changing domain logic, persistence, transactions,
API contracts, generated types, or cross-layer dependencies.
Example CLAUDE.md pointer:
@AGENTS.md
@DESIGN.md
Or, if you want to keep DESIGN.md loaded only when needed:
Before making structural changes, read @DESIGN.md.
Three implementation details matter.
1. The file is guidance, not enforcement
Agent instructions are context, not hard constraints.
That means this is weak:
We prefer clean layering where possible.
This is stronger:
`domain/` must not import `http/`, `infra/`, or framework packages.
If a change requires that import, stop and explain the conflict.
2. Structure improves adherence
Agents follow headers, bullets, and explicit rules better than long prose.
Prefer:
## Invariants
- External API calls never happen inside a DB transaction.
- Every write endpoint must be idempotent.
- Generated files must not be hand-edited.
Avoid:
In general, the system was designed with a fairly strong preference for separating
concerns between persistence, business logic, and delivery mechanisms...
3. It improves review loops
Even when the agent misses a rule, reviewers can point to the violated section:
This breaks the single-write-path rule in DESIGN.md.
Move the ledger write into Account.post_entry().
That usually produces a better second pass than a vague review comment.
For teams building custom agent loops, this written feedback cycle is important. See build your own Claude Code for more on wiring agent workflows.
Anti-patterns to avoid
A bad DESIGN.md can be worse than none because agents will trust outdated or vague information.
Avoid these patterns.
Restating the code
Bad:
`UserService` handles users.
The agent can read that from the code.
Better:
User creation must go through `CreateUserUseCase` because it enforces tenant limits,
email uniqueness, and audit logging in one transaction.
Turning it into a tutorial
Do not put shell commands, setup steps, or feature walkthroughs in DESIGN.md.
Put those in:
README.mdCONTRIBUTING.mdAGENTS.md
DESIGN.md should change at architecture speed, not tooling speed.
Documenting aspirations as facts
Bad:
All writes go through use cases.
If that is not currently true, say so.
Better:
Target: all writes go through use cases.
Current exception: `legacy/` bypasses this rule.
Do not extend `legacy/`; new writes must use use cases.
No owner or review trigger
Add a PR checklist item:
- [ ] Does this PR change a design decision, layer boundary, API contract rule,
transaction boundary, or external dependency?
If yes, update DESIGN.md in the same PR.
Review DESIGN.md when a PR:
- adds a module
- changes a layer boundary
- introduces a new external dependency
- changes transaction behavior
- changes API contract rules
- modifies tenancy or auth boundaries
Synchronizing it line-by-line with code
Do not make DESIGN.md a map of every package and class.
Keep it focused on decisions that change a few times per year, not function signatures that change every week.
Contradicting other instruction files
Do not duplicate the same rule in multiple files unless one file clearly owns it.
Recommended ownership:
- Build/test/lint commands:
AGENTS.md - Claude-specific imports:
CLAUDE.md - Architecture and invariants:
DESIGN.md - Project overview:
README.md
DESIGN.md for API and backend codebases
Backend services benefit most from DESIGN.md because many critical constraints are invisible in a single file.
Document these explicitly.
API contract ownership
State where the OpenAPI spec lives and that it is authoritative.
Example:
## API contract
- `api/openapi.yaml` is the source of truth.
- Generated server/client types must match the spec.
- Do not hand-edit generated API files.
- New endpoints start with an OpenAPI change.
Agents often “fix” type errors by editing generated files. This rule prevents that.
If you design APIs first in Apidog and export OpenAPI into the repo, DESIGN.md should point directly to that file.
For a deeper look at contract-first API design for agents, see designing APIs for AI agents.
Transaction boundaries
Document what can and cannot happen inside a transaction.
Example:
- External HTTP calls never happen inside a DB transaction.
- Request handlers do not open transactions directly.
- Use cases own transaction boundaries.
- Gateway calls use the outbox worker.
Without this, agents often implement the most direct path: open transaction, write row, call external service, return response.
That is usually wrong for production systems.
Idempotency and retries
Document idempotency as an invariant.
Example:
- All payment, refund, order, and provisioning endpoints require an idempotency key.
- Retries with the same key must return the original result.
- Retry logic must not create duplicate side effects.
This is especially important for payments, orders, billing, provisioning, and webhook processing.
Error model
State the error format.
Example:
- API errors use `application/problem+json`.
- Use the shared `problem()` helper.
- Do not create endpoint-specific error envelopes.
Otherwise, agents tend to create inconsistent response shapes.
Auth and tenancy
Security boundaries should be explicit.
Example:
- Every repository query must include `tenant_id`.
- Tenant scope comes from the authenticated request context.
- Repository methods without tenant scope are bugs.
- Never trust tenant IDs from request bodies.
Versioning and breaking changes
Tell agents what counts as a breaking API change.
Example:
- Removing a response field is breaking.
- Renaming a response field is breaking.
- Changing field type or nullability is breaking.
- Adding an optional response field is non-breaking.
- Breaking changes require a new API version.
Agents will otherwise rename or reshape fields to make implementation easier.
A practical implementation checklist
Use this checklist to add DESIGN.md to an existing repo.
Step 1: Create the file
touch DESIGN.md
Start with the sections that matter most:
# DESIGN.md
## System shape
## Invariants
## API contract source of truth
## Where new code goes
## Out of scope / do not touch
## When in doubt
Step 2: Add only current truths
Do not document the architecture you wish you had.
Document:
- what is true now
- what is intentionally being migrated
- what agents must not extend
Step 3: Reference it from AGENTS.md
## Architecture
Before changing domain logic, persistence, API contracts, generated types,
transactions, auth, or tenancy behavior, read `DESIGN.md`.
Step 4: Reference it from CLAUDE.md
@AGENTS.md
Read @DESIGN.md before structural changes.
If a request conflicts with DESIGN.md, explain the conflict before editing.
Step 5: Add a PR checklist trigger
- [ ] This PR does not change architectural decisions.
- [ ] If it does, `DESIGN.md` was updated in the same PR.
Step 6: Use it in agent prompts
Example prompt:
Add a POST /refunds endpoint.
Before editing, read AGENTS.md and DESIGN.md.
Follow the OpenAPI-first rule.
Do not edit generated files.
If the request conflicts with the design, explain the conflict.
Step 7: Use it in code review
When reviewing agent-generated code, reference rules directly:
This violates DESIGN.md: HTTP handlers must not call the gateway directly.
Move the gateway call behind the outbox workflow.
Backend payoff
For API and backend teams, a good DESIGN.md leads to concrete improvements:
- fewer layer violations
- fewer direct database calls from handlers
- fewer generated-file edits
- more consistent error responses
- safer idempotency handling
- clearer transaction boundaries
- better tenancy enforcement
- contract-conformant handlers
- faster review cycles for agent-generated code
The biggest win is naming the OpenAPI spec as authoritative. Once the agent knows the contract is the source of truth, it can conform to the schema instead of inventing one.
If you want a design-first workspace for the API contract your DESIGN.md points to, Download Apidog to design APIs, export OpenAPI, and test whether agent-generated endpoints match the contract.
Conclusion
Use DESIGN.md when your codebase has architectural rules that agents cannot infer from files alone.
A useful DESIGN.md should:
- explain why the system is shaped the way it is
- state invariants as clear rules
- document rejected alternatives
- name the OpenAPI spec as the source of truth
- tell agents where new code belongs
- mark generated and legacy areas as off limits
- tell agents to flag conflicts instead of working around them
Keep AGENTS.md and CLAUDE.md short and operational. Put deeper architecture in DESIGN.md and reference it from those files.
For API teams, design the contract first. Download Apidog to create the OpenAPI contract your DESIGN.md can point agents toward.
FAQ
Is DESIGN.md an official standard like AGENTS.md?
No. AGENTS.md is a defined, broadly adopted format. DESIGN.md is a community convention, similar to ARCHITECTURE.md or ADRs.
Treat it as a useful pattern, not a strict standard.
Do I need DESIGN.md if I already have AGENTS.md or CLAUDE.md?
Yes, if your architecture has non-obvious constraints.
AGENTS.md and CLAUDE.md should stay short and operational. Put deep architecture, invariants, and rationale in DESIGN.md, then reference it.
For the operational file, see how to write AGENTS.md files.
How is DESIGN.md different from ARCHITECTURE.md?
They overlap.
ARCHITECTURE.md usually maps the system for human contributors. DESIGN.md is the same idea written for humans and coding agents: more declarative, more rule-based, and explicitly referenced from agent instruction files.
Some teams use one file for both. The principles are the same.
How long should DESIGN.md be?
Long enough to cover decisions agents keep getting wrong. Short enough that every line is useful.
A focused two to four pages of rules and rationale is better than a long narrative.
Cut anything that:
- restates code
- explains setup
- duplicates
README.md - duplicates
AGENTS.md - describes aspirations as current facts
How do I make an agent read it?
Reference it from the file the agent already loads.
For Claude Code:
@DESIGN.md
For AGENTS.md:
Before structural changes, read `DESIGN.md`.
For prompts:
Read AGENTS.md and DESIGN.md before editing.
Follow the architectural rules in DESIGN.md.
Will the agent always follow DESIGN.md?
No.
Agent instruction files are context, not enforcement. Improve adherence by writing rules as sharp absolutes and adding a conflict-handling instruction.
Example:
If a requested change conflicts with this file, stop and explain the conflict.
Do not silently work around it.
Does DESIGN.md help with API contract problems?
Yes. Its highest-value backend use is stating that the OpenAPI spec is authoritative.
Example:
`api/openapi.yaml` is the source of truth.
Generated types must not be hand-edited.
New endpoints start with an OpenAPI change.
Designing the contract first in Apidog gives the agent a clear target.
Where should DESIGN.md live?
Put it in the repository root, next to:
README.md
AGENTS.md
CLAUDE.md
DESIGN.md
In a monorepo, use:
- one root
DESIGN.mdfor system-wide rules - package-level
DESIGN.mdfiles for local architecture where needed
Top comments (0)