Domain-Agents · Evolutionary architecture v0.1

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.

5discovery passes
3outputs
5scaling stages
180tests passing
01
Why — the map AI never had
thesis

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.

The bet. Context is not a prompt you retype. It is a file that loads itself. Every feature should either strengthen a boundary or surface a reason to move it — and the agents editing your code should see both, in the moment, without being asked.
#architecture<jonno>
has anyone actually tried generating an AGENTS.md per business domain straight from the import graph, so Claude Code / Cursor load the right context on glob match instead of you re-pasting it every prompt?
02
At a glance — how the pieces fit
diagram

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.

CODEBASE TS / Node repo src/ · package.json · tsconfig DISCOVERY 5 analysis passes structure · imports · naming deps · interfaces AGENT FILES agents/<domain>.md interfaces · debt · triggers SYSTEM MAP AGENTS.md graph · contracts · rules MCP SERVER on-demand context domain_lookup · domain_context CONSUMERS Claude Code Cursor humans · CI scan read / query
Discovery Agent files System map MCP server
03
The three outputs — what each artefact is, quickly
03 / 03
Agent files

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
Location
agents/<domain>.md
Shape
Markdown, tool-agnostic
Owner
The domain itself
System map

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
Location
AGENTS.md
Shape
Markdown + tables + graph
Owner
The operator
MCP server

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_lookup
  • domain_context
  • domain_files
  • list_domains
Transport
stdio (local)
Runtime
Node
Auth
none — local only
Handshake Who does the work Who reads it
AgentMap Each agent file declares its ownership and interfaces. The system map aggregates them into one graph.
MapAgent The map links to each agent and declares global rules. Agents defer to the map for cross-domain policy.
MCPAgent 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.
04
Quick start — from zero to enriched agents
~5 min
# 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
05
Discovery — five passes, one proposal
engine

The discovery engine does not ask you to reorganise your codebase. It analyses the signals that are already there.

PassSignalWhat it does
1. StructureDirectoriesDetects whether the codebase is feature-organised, layer-organised, or mixed. Reads workspace and path alias config.
2. ImportsTypeScript ASTUses the TS compiler API to resolve every import, builds the directed dependency graph, and clusters by connectivity.
3. NamingFile namesGroups auth.controller, auth.service, auth.routes under a shared "auth" identity.
4. Dependenciespackage.jsonMaps stripe → billing, @sendgrid/mail → email, jsonwebtoken → auth. External services become domain hints.
5. InterfacesCross-domain edgesFinds boundary files imported by two or more domains and scores the coupling between them.
Note on unowned files. Utility code (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.
06
Codebase patterns — three shapes, one engine
handles
The easy case

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%)
The hard case

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  ┘
The real-world case

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/       ┘
07
Scaling stages — the interface is the architecture
5 stages

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.

Stage 1
Inline
Synchronous, in-process. The default starting point for any domain.
Stage 2
Async
Fire-and-forget. Good enough when loss is acceptable.
Stage 3
Queued
Durable queue (BullMQ / SQS). Retries and back-pressure live here.
Stage 4
Separate service
Own deployment and database. Same interface, different runtime.
Stage 5
Distributed
Own infrastructure across regions. A strategic move, not a default.
08
Worked example — an email agent, proposal to enriched
6 steps

email async · tech debt: 3 · metrics: 3

A 140-file Next.js SaaS app. Stripe is installed; SendGrid is the provider. No queue yet. Email is called from auth and billing.
  1. Discovery proposes "email" with 91% confidence: a src/email directory, 87% internal imports, @sendgrid/mail in deps, and two inbound call sites from other domains.
  2. Init generates the scaffoldagents/email.md with sections for purpose, ownership, interfaces, debt, observability, and stage.
  3. 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".
  4. The system map links it inAGENTS.md lists email alongside auth and billing, with a dependency edge from each consumer.
  5. Claude Code picks it up — a .claude/rules/domain-email.md with a globs: header auto-activates the context the moment you edit a file in src/email.
  6. Six months later, health flags drift — 11 new files, coupling with billing is 0.41, one boundary violation. The agent file is stale and the map knows it.
Tech-debt consensus. When a feature touches email and billing, the tool surfaces both registers together: "add invoice reminder emails" will quietly pull in "no retry logic" from email and "no reminder_sent state" from billing — both, in one view, before the work starts.
09
AI tool integration — auto-activating context, not prompt gymnastics
Claude · Cursor · CI

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 serverThe four tools (domain_lookup, domain_context, domain_files, list_domains) for on-demand queries.
SessionStart hookPrints a domain summary at the start of every session so Claude opens with the map, not a blank slate.
Say "add a new email service". Claude sees you editing email files, auto-loads the email agent, notices it depends on 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 .
10
Commands — the CLI surface
7
CommandDescription
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 setupSave 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.
11
Key decisions — why the tool is opinionated where it is
5
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.

12
FAQ — questions worth asking
6
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.