5.4 The Road to MAGE
This chapter illustrates
✓ The Modeling Thesis · ✓ The Governed Engineering Environment · ✓ Governance Conversion
The MAGE methodology emerged from months of work with coding agents. I initially expected to prompt the agents and have them make what I wanted, and for a little while that sufficed. But the more my ambition grew, the less mere prompting could carry, and I accumulated more and more of the method. Every model, every constraint, every sensor in the built system arrived the same way: a growing codebase and a rising quality bar demanded it, and I minted the smallest thing that met the demand. Then the system grew again, the bar rose again, the current system proved inadequate, and the next thing was demanded. MAGE is the name for what was left standing after enough of those rounds.
What follows walks the order in which the pieces appeared, and I suggest you read it as a staircase. Each step answers a load the step before it could no longer carry. Watch the job title climb as you go — co-coder, QA, review lead, tech lead, architect — each rung a job the one before it could no longer do, and each one handed off only because the step below had made it safe to let go. The thread through every rung is the same: the seat climbs only because each step first minted the model or the control the next rung would need. The models did accrete along the way. But the road this chapter walks is the climb of the seat, and the governance that made each rung safe to let go.
Why the job title climbs
The whole ladder rests on one hinge, and it is governance. Every rung is a successive delegation. I hand the agents a piece of the tactics — write this code, audit that diff, run this zone — and keep the strategy for myself. That handoff is only safe because a control caught what the delegation would otherwise have dropped. A lint reddens on the boundary I stopped watching; a gate refuses the commit I stopped reading; a model the fleet reasons through means I no longer have to hold the map in my head. Governance is what makes it safe to let go — so delegation climbs exactly as far as the controls will carry it, and no farther.
Get the governance wrong and the ladder runs in reverse. The controls that were supposed to catch the dropped thing miss it, the misses surface days later in some downstream gate, and I am pulled back down into the tactics to firefight. I am reading diffs again, chasing a drift the map no longer shows, with no one left minding the strategy. That is the Claude is the fastest road to hell principle at fleet scale: the same speed, now pointed through a governance gap instead of riding atop, carrying the whole crew downhill as fast as it once carried them up.
5.4.1 Co-coder Step one — I reviewed everything myself
At the start there was one document format, a few hundred lines, and me. When the agent proposed a change I read the diff, decided it was right or wrong, and merged it. That is the whole method in the first week: a human is the quality gate. It works, and it works precisely because the volume is small enough that one person can hold the whole thing in his head and say yes or no to every line.
The obvious trouble with that gate is that it does not scale. A real system takes more code than one person can read, and agents write code far faster than a human can review it — the more they wrote, the further behind I fell. The first crack was thus a queue: work finished faster than I could bless it, and I became the bottleneck.
5.4.2 QA Step two — I dispatched an agent to audit the work
The first offload was to hand the review itself to an agent. Instead of reading every diff, I dispatched a second agent to audit the first one's work and report what it found. Now the checking of the work had left my hands and entered the fleet. The human stopped being the reviewer of record and started being the reviewer of the reviewer.
That move taught a lesson. A test or an audit is not a chore bolted onto the work — it is a control, a discrete artifact that fires on a violation so a failure caught once is caught every time after. An audit is the weakest kind of control, because it is expensive, deferrable, and after-the-fact. It runs when I remember to run it, over a codebase that has already drifted. The audit worked, but it planted a question that drove everything downstream: which of these audit findings could become a lint — deterministic and cheap static analysis — so I never had to look for that class of failure again? Today's audit finding is tomorrow's lint.
5.4.3 Review Lead Step three — one audit became a crew
The single reviewing agent worked, but it had a flaw I could not brief away. Pointed at a hundred thousand lines, one audit agent always found something to complain about — it never came back empty. And it could not do two things I needed. It could not rank its own findings, so a cosmetic nit and a real architectural break arrived side by side with equal weight. And it could not tell one part of the codebase from another, so a rule that mattered in the PDF model got applied, out of place, to the web layer. A single generalist reviewer over a large codebase is a firehose with no aim.
The fix was to stop dispatching one reviewer and start dispatching a crew — a set of specialists, each with a narrow charter. I split the one audit into distinct briefs by type of concern: a DRY audit that hunts duplicated logic, an architecture audit that checks the code against its own stated rules, a naming audit, a security audit. Each brief knows one thing well and ignores everything else, so its findings are all of one kind and can be ranked against each other instead of against apples.
Aiming the crew took a second idea, and it is the one that matters for the rest of this chapter: architectural zones. I partitioned the codebase into named regions and let each audit run per zone. Now a finding carries both coordinates — which kind of concern and which part of the system — and the firehose becomes a grid of aimed jets. The zone idea is the seed of the first real model, and step four is where it grows up; here it is just the thing that made the crew usable.
A crew brief is a standing artifact the orchestrator re-dispatches per zone. Here is the head of the real architecture audit — the one that checks code against the architecture rules the project has written down for itself:
A standing brief — the architecture audit
# Architecture Compliance Audit Agent
**Purpose:** Verify that code follows the architectural rules and
invariants documented in CLAUDE.md, DESIGN.md, and ORCHESTRATION.md.
Catches violations before they become production bugs.
## What to check
For each rule below, verify the code matches what the docs say. If
the docs have been updated but the code hasn't, that is a finding.
- **Library discipline:** Verify PDF production code uses the one
sanctioned PDF library only, that the tag-tree-stripping library is
absent from tag-tree operations, and that GenAI calls go through the
single client factory.
- **State machine discipline:** Verify the async job lifecycle uses the
explicit state machine with a terminal-state guarantee in `finally`,
and that metric hooks are wired as state-entry callbacks, not
scattered calls.
- **Redis vs DB responsibility:** Flag durable state kept in Redis, or
dispatch queues kept in the database.
## Output
Each finding: file:line, rule violated, 1-2 sentence description.
Cap at 15 findings. Prioritize by production-incident risk.Read the brief and the two ideas are both visible. The ## What to check list is the type charter — this agent reasons about architectural rules and nothing else. The Cap at 15 findings. Prioritize by production-incident risk. line is the fix for the firehose: a narrow charter can rank, because everything it returns is the same kind of thing. And this brief runs once per zone, so the same rules get checked against the PDF model, the web layer, and the dispatch plane as separate passes — the target coordinate.
The crew taught the lesson that drove everything after it. Once I was paying agent tokens to run these audits on a cadence, the waste was obvious: an audit is a probabilistic agent re-reading the code every time, and much of what it checks is deterministic. "Does any file outside the PDF zone import the PDF library?" does not need a language model — it needs a lint. So I leaned hard into two cheaper controls that match the crew's briefs one-for-one. First, commodity lints — the off-the-shelf static-analysis rules that catch the generic smells for free. Second, custom lints written to mirror each audit brief: the architecture audit's "one sanctioned PDF library" check became a boundary lint that reads the zone declaration and reddens on a cross-zone import, deterministically, at commit time. The audit crew did not disappear; it retreated to the findings a lint genuinely cannot make — the ones that need judgment. Everything a machine could decide moved down to the machine. This is the audit-to-lint move — today's audit finding is tomorrow's lint — run at fleet scale, and it is what freed the crew to be worth its tokens.
5.4.4 Tech Lead Step four — zones, so an agent can reason locally
A crew that coordinates is still a crew of agents each of whom has to understand the code it touches. And here the agent's nature bites. A cold-start agent with a finite context window cannot reach the tacit knowledge a human team keeps in its collective head — which file is canonical, what this module means, which of two look-alikes is the live one. A human learns that by working in the codebase for months. Think of the agent instead as a contractor with no memory of yesterday: it arrives knowing nothing and has to be told, in the code, every time.
The answer was to partition the system into named zones — a model that says, out loud and in a queryable form, what the parts are and where their boundaries lie. Once the system is carved into zones, an agent can be handed one zone, reason inside it locally, and be fenced from the rest. A boundary lint can then refuse an edit that reaches across a zone it has no business touching. The zone model turns "understand the whole 280-thousand-line codebase" — which no finite-context agent can do — into "understand this zone," which it can.
A zone is a small typed record. Here are two real ones — the PDF and PowerPoint object models, side by side:
Two zones, side by side
Component(
name="pdf-model",
focus_dirs=(RepoRelPath("backend/src/AdaTool.PdfModel"),),
boundary_kind="internal",
external_seams=("pdf-lib",), # ONLY this zone may call the PDF library; banned in every other zone
description="PDF object model + typed mutators",
),
Component(
name="slides-model",
focus_dirs=(RepoRelPath("backend/src/AdaTool.SlidesModel"),),
boundary_kind="internal",
external_seams=("openxml",), # ONLY this zone may call the Office library; banned in every other zone
description="PPTX Office-XML model + typed mutators",
),Read external_seams and the whole discipline is legible. The pdf-model zone is the only one allowed to reach the canonical PDF library; slides-model is the only one allowed to reach the Office library. A boundary lint reads that field at check-time and reddens on any file outside the pdf-model zone that imports the PDF library directly — the "one structured model per format" rule from the built-system chapter, enforced not by vigilance but by a zone declaration a machine walks. The agent working the PDF zone is handed focus_dirs as its lane and external_seams as its permission slip, and it cannot wander.
The zone model is the first piece of MAGE proper, because it is the first time I wrote down a model of the system's own structure for the fleet to read, rather than a piece of the product. Its audience is the fleet; a user never sees it.
5.4.5 Architect Step five — the codebase modeled, from components.py to MAGE
By this step I never looked at code at all. What changed is that I stopped holding the system in my head and started committing it into explicit models — the system the fleet reasons through. It has one shape with three movements: a first model the fleet reads at runtime, a set of sibling models that grew up around it, and a substrate that keeps all of them honest. Together they are MAGE.
The first movement was to make the zone model a file the fleet reads at runtime. That is where the method crosses from documenting the structure to executing on it.
A typed registry of the system's own components — its zones, their focus directories, the seams each one is allowed to cross — is not documentation about the architecture. It is the architecture, in a form a tool can query. The zone I showed above is one row of it; the row's shape is a frozen dataclass:
The registry the fleet reads
@dataclass(frozen=True)
class Component:
"""One system component."""
name: str
focus_dirs: tuple[RepoRelPath, ...]
tags: frozenset[str] = field(default_factory=frozenset)
kind: str = "leaf" # leaf | group | meta
external_seams: tuple[ExternalSeam, ...] = () # the powerful libs it may reach
import_allowed_components: tuple[ComponentName, ...] = () # who it may importEvery field is a machine-readable answer to a question an agent would otherwise have to guess. focus_dirs answers "what files are in my lane?" external_seams answers "which powerful library am I permitted to touch?" import_allowed_components answers "who am I allowed to depend on?" When a lint needs to know "which components may cross which external boundary," it does not hardcode a list that rots — it reads external_seams at lint-time. When the dispatcher needs to know which files a brief may touch, it reads focus_dirs. When context has to be injected into an agent's prompt, the question stops being "which files do I paste?" and becomes "what durable artifact should exist so the right context is selectable deterministically?"
The payoff was concrete and it compounded. Adding a single typed field to that registry — one field naming which components cross an external boundary — replaced four hand-maintained allowlists that were quietly drifting and unlocked three new lints all reading the same field. On its first run the new boundary lint surfaced a large backlog of latent violations no one had had a way to ask about before. The type named a previously-anonymous shape, and the named shape became a question you could pose to the whole codebase at once. The reflective codebase is the agent substrate. The more the system knows about itself, the less the agent has to guess.
The refactoring got cheaper because the models told me exactly where to look and where to stop looking. They took the guesswork out of migration planning: instead of a fake-confident Claude analysis I got a real-confident one I could trust. A cross-format migration that touched fifty-eight files and rewrote five thousand lines — the kind of change I might once have deferred — took about five hours of agent time over a dinner break. When the mechanical cost of restructuring collapses, the economics of architecture invert: postponing the right shape becomes the expensive choice, and building the right model becomes something you can do the moment you see you need it. The hard part was never touching fifty-eight files; it was knowing that fifty-eight files should be touched. The mechanics went free. Deciding which model to build did not.
A view per property I had to guarantee
Once that first model paid off, the others followed — but the right way to see the set is not "more models." Each model is a view of the system, and each view exists to answer for one property or quality metric I had to guarantee: a property like deployability, a quality metric like security. The set was never planned. What grew was the demand. The faster the fleet moved, the higher the bar on each property rose, and a rising bar on a property I could no longer hold in my head is exactly what forced a view to carry it. The models accreted on demand, one per quality the growing system made me answer for.
- A service-flow model — the view that guards event-wiring correctness. It appeared when the back end became enough services that no one could hold the wiring in their head: which handler reacts to which topic, what the durable-versus-transient split is. That view is what later made a hard re-platforming close to a change of deployment target.
- Synchronization contracts — the view that guards concurrency safety. They appeared when concurrent agents and concurrent workers started tripping over shared state, and the invariants that had lived only in prose had to be encoded where a checker could walk them.
- A deployment-topology model — the view that guards deployability. It appeared when the system ran in more than one place and the boot-time environment each service needs stopped fitting in anyone's memory.
- Domain registries — the view that guards vocabulary closure. The closed edit vocabulary, the remediation verbs, the mutator stamps: each appeared when a set of strings needed to become a typed, enumerable thing a lint could hold closed.
None of these was drawn from a master diagram. Each was the smallest view that answered for one rising property. But a stack of views has a failure mode of its own, and it is the one that turns a map into a lie: drift. A view that names a code symbol goes stale the moment that symbol is renamed, deleted, or moved, and nothing notices — the view still looks authoritative while pointing at a ghost. The fleet reads the ghost as truth, and the blow-up lands days later in a downstream gate instead of at the change that caused it.
The substrate that keeps the models honest
So the models grew a nervous system. A traceability substrate links every model to its lint, its code entry-point, its proof, and its sibling models as a typed graph whose every edge is a derived obligation a lint re-checks against a resolvable symbol — never a snapshotted line number. Derived edges defend; snapshotted ones drift.
Drift comes in two kinds, and the substrate catches each a different way, because the two are not the same problem. The first kind is deterministic. The code moved: a symbol was renamed, deleted, or hoisted into a different module, and a view's edge now points at nothing. That is mechanically decidable, so a machine decides it. A lint walks every edge in the graph and re-resolves its anchor against the live code; an edge that no longer resolves reddens on the spot. There is no judgment in it — the symbol is either there or it is not — so this is a lint, run deterministically, and a broken edge fails at the change that broke it rather than days downstream. The second kind is judgment. The code still resolves — every anchor points at a real symbol — but the change altered what the view claims. A function still exists, but it no longer does what the invariant says it does; a checker was quietly made stricter than the producer it now rejects. No lint can see this, because nothing on disk is broken; the map matches the territory symbol-for-symbol and lies anyway. So this drift is caught by a review gate, not a lint: the definition-of-done audit at the close of every unit of work asks, in as many words, did this change alter a model? — and routes a "yes" to whoever owns that view. Deterministic drift is a wall; semantic drift is a question you have to keep asking.
That pairing — a lint that walks the anchors for the drift a machine can decide, and a review gate that asks the human-or-agent question for the drift it cannot — is the whole mechanism, and it is instructive enough to have its own catalogue entry: symbol-anchored traceability graph. The graph is what keeps the map equal to the territory, which is the only condition under which a context-bounded agent can safely operate a context-exceeding codebase through the models. The structured models, the substrate that keeps them honest, and the query surface the fleet reasons through — together these are MAGE. Figure 5.4-1 draws the five-rung climb.
The Architect's newest instrument: measuring what is still unmodeled
The Architect rung is where the ladder ends, but the work on it does not. Every model so far was built the same way: I noticed a load and modeled it. That is a supply-side method — it models what I happen to see. Once there are a dozen models and a traceability graph, the obvious question is the one I could not answer by looking harder: what is still unmodeled? That is the Brownfield problem, turned inward on my own system. A greenfield method invents the models it wants up front; a brownfield method inherits a large, already-built system and has to discover the models the system already implies but nobody has drawn yet. That is the situation I was actually in, and it needed a different instrument.
The instrument is a metric that is the dual of test coverage — the Missing Model Metric. Ordinary coverage walks model-to-code: for each model node, is it tested? This one walks the other way, code-to-model. Start from a test, follow the production code it exercises by static reachability, and ask whether any of that code carries a model anchor — an invariant, a state machine, a service edge. A test whose exercised code reaches no anchor is an orphan, and the orphans, clustered by the unmodeled code they run through, are a candidate list of missing models. It is negative-space modeling: instead of "is my model tested," it asks "what am I running in production that no model has ever named?" On its first run over the dispatch subsystem — the most heavily modeled corner of the codebase, where orphans should have been rare — more than half the tests came back orphaned. A sharpening pass turned that scolding into a map: every orphan reached code the model already knew at the coarse structural level, but that carried no fine behavioral model. The structural map was complete; the behavioral map covered the concurrency-critical spine and stopped. The metric had found a granularity gap, and it named exactly where to close it — three genuine candidate models: the durable-state persistence layer, the cost-and-subprocess pipeline, and the chunking-merge-and-validation path.
That map drove a loop, and the loop has run. Measure the orphan rate; rank the biggest clusters; for the largest, found a modeling Epic and drive it to a strong structured model under a real design review; re-measure; take the next cluster. The key property is the one that makes it a modeling instrument rather than a testing one: you do not move the number by adding a test — you move it by adding a model. Wave by wave, the loop worked as designed. The three seed models drove the dispatch subsystem's orphan rate down from the low-fifties percent to the high-thirties, and the next cluster — the sidecar persistence seams — took it into the low thirties. Each closed model converted exactly the tests that reach its new anchors from orphan to modeled, which is the falsifiable signal that the metric is measuring what it claims.
The loop was not bookkeeping. Building those models found real, shipping bugs the green tests had missed — the payoff that justifies the whole method. Modeling the cross-language pipeline seam caught a subprocess exit code that one runtime emitted and the other had never enumerated, so a "the service should have worked" failure was being silently misfiled as a generic error. Anchoring the chunking model to its one canonical cleanup seam exposed a failure path that purged a job's database rows and blobs but skipped the queue structures its sibling path always cleaned — a chunk leak on a rare redelivery edge. And modeling the interactive editor's save round-trip surfaced an unfenced last-write-wins race in which two concurrent edits silently discarded one's work. None of these was a hypothetical the model raised; each was a live defect the act of modeling forced into view.
So the method that began with a human reviewing every line arrived at a system that measures its own modeling completeness and dispatches work to close the gap it finds — a codebase modeling itself, brownfield, in a loop. MAGE was never designed. It was demanded, step by step, by a system that kept growing and a bar that kept rising — and the last thing it demanded was that the method turn its own instruments back on the parts of itself it had not yet named.
5.4.6 The whole method, made real
This book opened on one diagram — The MAGE Method at a Glance — that named every part before the argument had earned it. Walk its boxes one last time, and each is now a real thing in a system that ships:
- The Printer ✓ — a fleet of agents printing production code faster than any human could read the diffs.
- The Modeling Thesis ✓ — the system committed to explicit models the fleet reasons through, from the zone registry to the view-per-property stack.
- The Alignment Thesis ✓ — every probabilistic part, the vision model and the coding agents alike, sealed in a bounded, typed, validated call.
- The Governed Engineering Environment ✓ — the models, the controls, and the query surface, grown until the apparatus outweighed the product it guards.
- Governance Conversion ✓ — every control minted the moment a rising bar demanded it, one failure at a time.
- Trustworthy software ✓ — DocAble, live in beta, remediating a real university's documents for dollars a deck.
Six boxes, all checked. The case study was the evidence; the diagram was its table of contents.