Domain agents, system maps, and MCP.
Three outputs that together give an AI coding agent the context it needs to build for today and evolve tomorrow. This is the shape: what each one is, how they relate, and how to reason about them.
AI shipped the build phase. It did not ship the scale phase. The first ten thousand lines are cheap; the next hundred thousand are where the codebase decides whether to survive.
Every AI edit that lands without architectural context is a vote for entropy. The assistant does not know which seams are real, which modules are hotspots, which files are fragile. So it touches whatever the prompt points at, and the shape of the system drifts one commit at a time.
The answer is not microservices, a framework, or the annual rewrite. It is evolutionary architecture: a system shaped so each domain can evolve independently when the data says it's time — and a durable, machine-readable map of where the seams are, what each one owns, and who depends on whom. Something the next prompt, and the next agent, can actually read.
More on the thinking behind this on my blog at jonno.nz.
A single discovery pass over your codebase produces three artefacts. Each one is consumed differently — by humans, by AI editors, and by tool-calling agents.
Per-domain context, AI-ready
One Markdown file per business domain. Holds the complete working picture an agent needs when it edits code inside those boundaries.
- Purpose and ownership
- Exposed + consumed interfaces
- Tech debt register
- Observability + scaling triggers
- Evolution path
The org chart of the code
A single root document that binds the agent files together: dependency graph, cross-domain contracts, architecture rules, and maturity stages.
- All domains and their agents
- Dependency graph between domains
- Interface contracts + stability
- Global architecture rules
On-demand domain queries
A Model Context Protocol server so Claude Code and other tool-calling agents can ask for domain context at the moment they need it — no prompt-stuffing required.
domain_lookupdomain_contextdomain_fileslist_domains
| Handshake | Who does the work | Who reads it |
|---|---|---|
| Agent → Map | Each agent file declares its ownership and interfaces. | The system map aggregates them into one graph. |
| Map → Agent | The map links to each agent and declares global rules. | Agents defer to the map for cross-domain policy. |
| MCP → Agent | Serves agent file contents by domain name on demand. | AI editors fetch the right file, not the whole repo. |
| Health → all | domain-agents health compares repo vs. agents. |
CI, humans, and agents pick up drift and coupling. |
# install npm install -g domain-agents # 1. Discover domains in your codebase domain-agents discover ./my-app # 2. Review the proposal, then generate agent files domain-agents init ./my-app # 3. (Optional) Use Claude to enrich agent files with real code analysis domain-agents setup # save your Anthropic API key domain-agents init ./my-app --enrich # generates contextual descriptions # 4. Connect to your AI tools domain-agents hooks claude ./my-app # Claude Code: auto-activating rules + MCP domain-agents hooks cursor ./my-app # Cursor: glob-activated .mdc rules # 5. Check domain health over time domain-agents health ./my-app
The discovery engine does not ask you to reorganise your codebase. It analyses the signals that are already there.
| Pass | Signal | What it does |
|---|---|---|
| 1. Structure | Directories | Detects whether the codebase is feature-organised, layer-organised, or mixed. Reads workspace and path alias config. |
| 2. Imports | TypeScript AST | Uses the TS compiler API to resolve every import, builds the directed dependency graph, and clusters by connectivity. |
| 3. Naming | File names | Groups auth.controller, auth.service, auth.routes under a shared "auth" identity. |
| 4. Dependencies | package.json | Maps stripe → billing, @sendgrid/mail → email, jsonwebtoken → auth. External services become domain hints. |
| 5. Interfaces | Cross-domain edges | Finds boundary files imported by two or more domains and scores the coupling between them. |
src/utils, src/lib) often belongs to no domain. The proposal flags these rather than forcing them into a cluster — you assign them, or leave them floating until they earn a boundary.
Feature-organised
Directory names already map to domains. Confidence is high out of the box.
src/auth/ → auth (96%) src/billing/ → billing (94%) src/email/ → email (99%)
Layer-organised
Business capabilities are split across technical layers. Import graph is the primary signal.
controllers/auth.controller.ts ┐
services/auth.service.ts ├ auth
routes/auth.routes.ts │ (74%)
middleware/auth.middleware.ts ┘
Mixed / Next.js
Services + components + hooks + routes spread everywhere. Clusters emerge from graph + naming.
lib/services/orders/ ┐
components/orders/ ├ orders
hooks/use-order-* │ (52)
app/api/orders/ ┘
Every domain sits somewhere on this track. Moving forward is a decision triggered by metrics, not speculation — and in every case the public interface is supposed to survive the move.
email async · tech debt: 3 · metrics: 3
auth and billing.- Discovery proposes "email" with 91% confidence: a
src/emaildirectory, 87% internal imports,@sendgrid/mailin deps, and two inbound call sites from other domains. - Init generates the scaffold —
agents/email.mdwith sections for purpose, ownership, interfaces, debt, observability, and stage. - Enrichment fills in the body — Claude reads key files in the domain and rewrites the purpose ("Manages transactional email: delivery, templates, scheduling"), detects the stage as Async (fire-and-forget), and extracts specific debt like "no retry logic" and "provider hardcoded to SendGrid".
- The system map links it in —
AGENTS.mdlistsemailalongsideauthandbilling, with a dependency edge from each consumer. - Claude Code picks it up — a
.claude/rules/domain-email.mdwith aglobs:header auto-activates the context the moment you edit a file insrc/email. - Six months later,
healthflags drift — 11 new files, coupling withbillingis 0.41, one boundary violation. The agent file is stale and the map knows it.
Claude Code
domain-agents hooks claude ./my-app installs three layers:
| Per-domain rules | .claude/rules/domain-<name>.md with globs: frontmatter. Editing a file in a domain auto-loads that context; cross-domain dependencies tell Claude to consult siblings via MCP. |
| MCP server | The four tools (domain_lookup, domain_context, domain_files, list_domains) for on-demand queries. |
| SessionStart hook | Prints a domain summary at the start of every session so Claude opens with the map, not a blank slate. |
users and permissions, and consults those agents too — without any manual context loading.
Cursor
domain-agents hooks cursor ./my-app generates .cursor/rules/<domain>.mdc files with glob-based activation. Cursor picks up the right rule when the glob matches the file you're editing.
CI
# .github/workflows/domain-health.yml name: Domain Health on: pull_request: branches: [main] jobs: health: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: npx domain-agents health --ci .
| Command | Description |
|---|---|
domain-agents discover <path> | Analyse the codebase and propose domains. |
domain-agents show <path> | Visual overview of the proposal. |
domain-agents init <path> [--enrich] | Generate agent files and AGENTS.md. --enrich uses Claude to fill in real context. |
domain-agents setup | Save your Anthropic API key for enrichment. |
domain-agents health <path> [--ci] [--json] | Check domain boundaries, coupling, and agent staleness. --ci fails the build on drift. |
domain-agents hooks claude <path> | Install Claude Code integration: auto-activating rules + MCP server + session hook. |
domain-agents hooks cursor <path> | Generate Cursor .mdc rule files with glob activation. |
Observability is the most important input
Agent files have a strong preference for gathering data. Scaling decisions should be driven by metrics, not speculation — so every agent declares its current instrumentation, its gaps, and the thresholds that would justify evolving to the next stage.
When agents build features, they do so with observability in mind: new code paths get instrumented, feature flags include usage tracking, and performance-sensitive paths get latency metrics. The loop is: build → instrument → observe → decide → evolve → build.
Tech debt informs feature consensus
When a feature spans multiple domains, each domain's tech debt register is consulted. If the email domain has a known bottleneck and the feature will increase email volume, the agent surfaces this during planning, not after the incident.
The effect: cross-domain work opens with a joined-up view of what's fragile, not just what's required.
Start consolidated, split when needed
You don't need a domain agent per micro-concern on day one. For small codebases (< 50 files) the recommended start is 2–3 broad agents: core, integrations, infrastructure.
Split when an agent exceeds ~30–40 files, its debt register covers unrelated concerns, or different parts are at different scaling stages.
The operator resolves conflicts
Agents own their domains, but cross-domain decisions are surfaced for human judgment. The tool makes the trade-offs legible — it does not make them for you.
The interface survives the stage
Every evolution step — inline to async, async to queued, queued to separate service — is meant to be invisible to callers. That's what earns the right to defer the decision: the implementation can move, but the calling code does not. If a stage transition demands an interface change, that's a signal the original interface was wrong, not that the stage is wrong.
Does this only work for feature-organised codebases?
No. Feature-organised is the easy case, but the discovery engine was designed around the layered and mixed cases — because most real codebases are messy. The import graph and naming analysis carry the load when directories don't.
What does enrichment actually cost?
Enrichment reads key files from each domain and calls Claude to write the descriptive sections. Cost scales with the number of domains, not the size of the whole repo. Your API key stays at ~/.config/domain-agents/config.json — never in the project directory.
What happens to files that don't belong to a domain?
They're flagged, not forced. Utility code often has no natural owner; the proposal lists unassigned files so you can either assign them, leave them floating, or wait until they grow into something worth owning.
Does it support monorepos?
Path aliases and workspaces are read during the structure pass. Multi-package monorepo support — where each package is a domain or domains span packages — is an open area on the roadmap rather than a shipped feature.
How does it fit alongside AGENTS.md the open standard?
The generated AGENTS.md is plain Markdown and is consumed by any tool that reads the file at the repo root. The domain-specific agent files sit underneath it as linked context, so tools that don't understand domain structure still get a sensible top-level document.
Will it rewrite my code?
No. The tool writes Markdown, YAML frontmatter, and a small MCP server. It does not edit your application source. It's a context producer, not a refactor engine.