codebase-design is a shared vocabulary for the shape of code. It's a reference, not a session: Skills and agents load it so everyone describes modules with the same words. Its goal is deep modules: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface.
The words, used exactly (never "component", "service", "API" or "boundary"):
Module: anything with an interface and an implementation, from one function to a whole package.
Interface: everything a caller must know to use the module correctly: the signature, but also its rules, error cases and required setup.
Depth: how much behaviour a caller gets for each bit of interface they learn. A module is deep when a lot sits behind a small interface, and shallow when the interface is nearly as complex as the code behind it.
Seam: the place where a module's interface lives, where behaviour can be swapped without editing there.
Adapter: a concrete thing that fills a seam, such as a Postgres repository or an in-memory fake for tests.
Leverage and locality: what depth buys. Callers get more for less, and maintainers fix a bug once and it's fixed everywhere.
deep
shallow
a lot sits behind a small interface
the interface is nearly as complex as the code behind it
Depth: how much behaviour a caller gets for each bit of interface they learn.
When to use it
When you're designing or reshaping one module's interface, such as the booking module of a FastAPI backend.
When you're deciding where a seam goes, or how to make code easier to test.
When another Skill needs the words: tdd calls it when an interface's shape is in question, and improve-codebase-architecture uses it to describe candidates.
Agents can also load it on their own.
When not to use it
To find which parts of a codebase need reshaping, use improve-codebase-architecture. That's the survey that finds candidates; codebase-design is the bench where you design the chosen one.
For the words of your project's domain (Booking, Customer, Invoice), use domain-modeling. codebase-design is about the shape of code, not the language of the domain.
What it needs to start
A module to talk about.
What it creates
Nothing on disk. It gives you principles to judge a design by:
The deletion test. Imagine deleting the module. If complexity vanishes, it was only passing calls through. If complexity reappears across many callers, it was earning its keep.
The interface is the test surface. Callers and tests cross the same seam.
One adapter means a hypothetical seam; two adapters mean a real one. Don't add a seam unless something actually varies across it.
And one way of working, design it twice: when you want alternatives, it spawns three or more sub-agents (helper agents, each with a fresh context window) that each design a radically different interface. It compares them and recommends one.
Going deeper
6 min read · Going deeper not read
Why it exists
Matt's starting point is agents. On his page "How To Make Codebases AI Agents Love" (updated 2026-02-26) he writes: "AI is not a super-powered developer. It's a new starter with no memory." He adds that your codebase, "way more than your prompt or your AGENTS.md file, is the biggest influence on AI's output."
What an agent sees, he says, is "A bunch of disparate modules that can all import from each other." His fix is deep modules, an idea he takes from the book A Philosophy of Software Design. He splits the work this way: "You own the interface. AI owns the implementation. Tests keep it honest."
Matt's picture of the map you hold in your head: modules in loose groups, such as authentication. From aihero.dev, 30 October 2026.What Matt says an agent sees in a codebase: modules that can all import from each other. From aihero.dev, 30 October 2026.
Matt's Skill page (updated 2026-08-24) calls codebase-design "The vocabulary for designing deep modules." It "replaced design-an-interface and took over its content."
From snapshot/skills/codebase-design/SKILL.md, line 16: “Interface: everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics.”
Depth comes from John Ousterhout. In a series of discussions with Robert Martin, written up as one document (September 2024 to February 2025), Ousterhout explains his word. The best methods "replace a large cognitive load (reading the detailed implementation) with a much smaller cognitive load (learning the interface). I call these methods "deep"." When splitting goes too far, less sits behind each interface while the interface often grows more complex. He calls those interfaces "shallow". He likes the pair because "it will tell you when a decomposition is good and also when decomposition makes things worse."
Matt's summary picture: a web of interconnected shallow modules, the shape deep modules replace. From aihero.dev, 30 October 2026.
Both the Skill as of 2026-10-29 and Matt's page (2026-08-24) credit a lines ratio to Ousterhout, and the Skill rejects it because it "rewards padding the implementation". Ousterhout's own words above (2024 to 2025) describe depth by the load of learning an interface, not by lines.
Seam comes from Michael Feathers. Martin Fowler (2024-01-04) writes that Feathers coined the term in his book on legacy code, and quotes his definition: "a seam is a place where you can alter behavior in your program without editing in that place". Fowler adds that each seam has an enabling point: "a place where you can make the decision to use one behavior or another".
Dependencies decide how you test. The Skill's DEEPENING.md sorts what a module depends on into four kinds:
In-process: no I/O, so you test through the new interface directly.
Local-substitutable, such as Postgres with a local stand-in: tests run the stand-in, and the seam stays internal.
Remote but owned: define a port, with an HTTP adapter for production and an in-memory adapter for tests.
True external, such as Stripe: inject it as a port, and tests use a mock adapter.
Once tests at the new interface exist, the old tests on the shallow modules "become waste". The rule is replace, don't layer: delete them.
Remote but owned
True external
tests use
an in-memory adapter
a mock adapter
Dependencies decide how you test.
Design it twice is Ousterhout's too. DESIGN-IT-TWICE.md credits him: "your first idea is unlikely to be the best." Each sub-agent gets a different constraint: one minimises the interface, one maximises flexibility, one makes the common caller's case trivial.
A real example
One line of DESIGN-IT-TWICE.md shows how the Skill changes, and why the current file decides.
Until 2026-08-06 it said "Spawn 3+ sub-agents in parallel using the Agent tool." Issue #564 (2026-07-14) pointed out that other harnesses "may not expose an Agent tool".
Matt's commit of 2026-08-06 dropped the tool name. His message says the text now "leaves the mechanism to the harness."
Pull request #1341 (2026-10-29) added a fresh context window, because "Some Codex models fork the parent conversation when they spawn a subagent." A fork inherits the parent's context window, which throws away the point of a sub-agent: a clean context that sees only its brief.
Matt's Skill page, updated 2026-08-24, still quotes the "Agent tool" wording and calls #564 open. The file as of 2026-10-29 is newer, so it wins: today the line says "each with a fresh context window".
Where it goes wrong
Pointed at as a driver, it runs away. Matt's page warns: "If you point a session at a skill with no process and no stopping rule and say "go", the agent invents a process." Issue #449 (2026-07-04) is that story. A handoff note said "resume in /codebase-design to drive the open decisions". The fresh session re-explored code already mapped and "ran to ~105k tokens before asking the user anything." The fix is to name a driver Skill, such as grill-with-docs or tdd, and let it use codebase-design's words. A triage note marked as generated by AI closed the issue on 2026-10-06 without changing the Skill, so the fix stays yours to apply.
Nothing enforces the interface. In issue #458, a reader asked how to stop other code importing past a deep module's interface. The Skill doesn't say. Matt replied on 2026-07-07 with three options, each with a catch. Wrap it in a class or IIFE, "but the class/IIFE will get enormous". Make it a package in a monorepo, "but the tooling of a monorepo is rarely worth it". Or use a linter like dependency-cruiser. Of that one he wrote: "haven't tried this but might be the closest thing to what you want". A day earlier (2026-07-06), answering a note about nesting modules in folders, he had written: "I think you might be tying the concept of modules too closely to the file system."
Matt's page (2026-08-24) said his in-progress Skill setup-ts-deep-modules shipped no lint rule. The Skill as of 2026-10-29 is newer, so it wins. Its description now begins "Wire dependency-cruiser into a TypeScript repo", the linter Matt named in #458. The Skill "installs dependency-cruiser and the rules that make the entry points the only way in".
Try it
In your practice project (set it up first with keeping-track's Try it step if you haven't), open the booking code in api/. Start a session with grill-with-docs as the driver, and ask it to describe the booking module in codebase-design's words: its interface, its seam, and which of DEEPENING.md's four kinds Postgres falls under. Then do the deletion test on one helper it calls: imagine deleting it, and note whether complexity vanishes or reappears in its callers. Write the answers in a comment on one of the project's issues. Change no code yet.
Going deeper's Check questions are asked in Next up.
From snapshot/skills/codebase-design/SKILL.md, line 20: “A module is deep when a large amount of behaviour sits behind a small interface, shallow when the interface is nearly as complex as the implementation.”
From snapshot/skills/codebase-design/SKILL.md, lines 26–28: “Locality: what maintainers get from depth. Change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere.”
From snapshot/skills/codebase-design/SKILL.md, line 3: “Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.”
From snapshot/skills/tdd/SKILL.md, line 26: “When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), call the Skill tool with "codebase-design" for the vocabulary.”
From snapshot/skills/ask-matt/SKILL.md, lines 66–67: “/codebase-design is the deep-module vocabulary (module, interface, depth, seam, adapter, leverage, locality) for designing a module's shape”
From snapshot/skills/codebase-design/SKILL.md, lines 62–65: “The deletion test. Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.”
From snapshot/skills/codebase-design/SKILL.md, line 107: “Depth as ratio of implementation-lines to interface-lines (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead.”
From snapshot/skills/codebase-design/DEEPENING.md, line 15: “Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal”
From snapshot/skills/codebase-design/DEEPENING.md, line 19: “Define a port (interface) at the seam. The deep module owns the logic; the transport is injected as an adapter. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter.”
From snapshot/skills/codebase-design/DEEPENING.md, line 25: “Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter.”
From snapshot/skills/codebase-design/DESIGN-IT-TWICE.md, lines 25–27: “- Agent 1: "Minimize the interface: aim for 1–3 entry points max. Maximise leverage per entry point." - Agent 2: "Maximise flexibility: support many use cases and extension." - Agent 3: "Optimise for the most common caller: make the default case trivial."”
From snapshot/web/github.com/mattpocock-skills-pull-1341.md, line 31: “Some Codex models fork the parent conversation when they spawn a subagent. The fork inherits the parent's context window, which throws away the reason for using a subagent: a clean context that sees only its brief.”
From snapshot/web/aihero.dev/skills-codebase-design.md, line 89: “There is a setup-ts-deep-modules skill in the repo's in-progress/ bucket that lays down a src/packages/<name>/index.ts convention, but it is a beta-channel skill with no docs page, and it ships no lint rule.”
From snapshot/skills/setup-ts-deep-modules/SKILL.md, line 3: “description: Wire dependency-cruiser into a TypeScript repo so each package is a deep module, with implementation hidden in subfolders and reachable only through its entry-point files.”
From snapshot/skills/codebase-design/SKILL.md, line 63: “The deletion test. Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.”