4.2 Migrating to MAGE
Most readers do not begin on a blank page. You inherit some combination of code, tests, schemas, architectural documents, CI rules, operational lore, permissions, dashboards, and conventions. That estate carries liabilities and assets: technical debt, certainly, but also useful engineering structures that nobody has connected well enough to reuse. Migration is partly asset recovery — find the useful representations and controls already present, reconnect them to their territory, retire what has rotted, and invest where recurring costs justify stronger structure.
SOFTWARE ENGINEERINGInset — Software evolves; its engineering structure must evolve with it
Software engineering has long treated change as a normal property of successful systems rather than an exceptional maintenance phase. Lehman's work on software evolution observed that systems embedded in a changing real-world environment must continually adapt, while their complexity tends to increase unless engineers act to control it 11. Meir M. Lehman and L. A. Belady, Program Evolution: Processes of Software Change (Academic Press, 1985)..
MAGE extends that maintenance obligation beyond implementation. Models, validators, gates, runbooks, and other governance mechanisms describe or constrain a system that continues to change. They can therefore drift, preserve assumptions that no longer hold, or impose costs that no longer earn their keep. A governed engineering environment is not a finished artifact. Its engineering structure must evolve with the system it governs.
Two axes fix the starting move. The first is how much system already exists. A mature estate gives you implementation, history, and latent models to recover; a new system does not. The second is how settled the relevant intent is. A detailed contract can justify substantial modeling and authority before implementation; an exploratory product question cannot. These axes are independent. Greenfield does not imply uncertainty, and brownfield does not imply settled intent: a blank-page regulated system can carry highly settled obligations, while a mature product may still be exploring what its users need.
Figure 4.2-1 crosses how much of the system already exists with how settled the relevant intent is. Each cell suggests the starting move.
Brownfield work enters through many doors, but two are useful extremes. At one end is the fast-built system whose implementation outran its explicit engineering account: code exists, perhaps even a working product, but the architecture, obligations, and operating rules were never made durable. DocAble began near this end: a plausible product was built quickly, then migrated into a governed one. At the other end is the established organization with years of accumulated structure: ADRs, schemas, ownership files, developer portals, CI policy, tests, and operational playbooks. Its problem is not absence but fragmentation.
A fast-built estate must author more of its initial models. An established estate can recover and reconcile more of what it already knows. Both begin by inventorying the representations and controls already present, then investing where missing structure repeatedly consumes judgment.** A similar interpretation appears in recent developer-portal practice: a developer portal's value was never its UI but the curated context underneath — service and API catalogs, golden-path templates, ownership, CI/CD signals — and agentic engineering turns that human-facing surface into an execution substrate, "the portal isn't dying; it's becoming infrastructure" CITE0 .
Neither starting condition requires a complete governed environment before useful work begins. Preserve the obligations already worth preserving, surface the representations already worth trusting, and let implementation and operation expose what is still missing. The method is constant; the justified starting stock is not. A safety-critical project may begin with extensive explicit requirements and heavy validation; a product search may begin with a few non-negotiable constraints and measurements; a brownfield estate begins with the accumulated evidence of its own history, however poorly represented.
Model-first and implementation-first suit different starting conditions. Where consequential intent is already known and costly to reconstruct, representing it before implementation narrows the search toward useful candidates. Where the right shape of the system is still uncertain, implementation is itself the experiment: build cheaply, watch what the result teaches, and model what proves consequential.
So the practical question is not how much to model in the abstract. It is how much structure the next unit of work needs. A familiar problem with strong tests and a well-matched reasoner may need little added representation; an unfamiliar domain with weak observability may need substantial modeling before autonomous work can be trusted. Add enough representation and evidence for the next unit of work to be delegated and evaluated responsibly; then let realization proceed.
At the low-structure extreme, implementation-first agentic development is heuristic search driven largely by the model's learned prior. In a familiar domain a capable agent can infer adequate abstractions, architecture, and implementation from surprisingly little explicit structure. Repeated failure changes the calculation. When agents keep reconstructing the same concept, cross the same hidden boundary, or re-search the same unproductive region, another attempt is worth less than changing how the problem is represented. Implementation-first becomes churn when the search repeatedly reconstructs structure whose explicit representation would cost less than continued rediscovery.
4.2.1 Start with What You Already Have
Survey the estate before designing the replacement. Architecture documents, API specifications, deployment manifests, ADRs, schemas, ownership files, tests, CI configuration, permissions, and runbooks are all partial engineering structures. Some are models: reduced representations of the system used for a purpose. Some are already governance mechanisms: CI rejects a merge, a schema excludes an invalid state, a permission denies an action. Many are both weakly connected and unevenly trusted.
So the first brownfield move is two inventories in parallel, not one:
- What representations do we already reason through? Which document, schema, graph, test structure, or configuration repeatedly saves someone from reading raw implementation?
- What authority already acts? Which permissions, types, validators, CI checks, reviews, and operational gates currently constrain or admit work — and what semantics can each actually see?
That second inventory matters because inherited artifacts may already carry authority. A CI configuration may already gate merges; a schema may already reject data; a type system may already constrain states; tests may already carry admission authority. A mature estate may already contain substantial local Alignment while relying on human reviewers to bridge gaps in system-level representation. The migration target is to connect useful representations to the territory they describe, make important obligations explicit enough to evaluate, and place authority where the necessary semantics are available. Strengthen both inventories together rather than repairing the map first and installing authority afterward.
A wiki is a useful first path because many organizations already have one and the migration distance is short. Treat it as an informal model: a reduced representation people already use to reason about the system, even though its semantics are weak and nothing mechanical guarantees correspondence with the code. Pages supply nodes of a kind, links supply relationships of a kind, and search supplies navigation. That does not make the wiki equivalent to the structured graph Part 2 built. It makes it raw material.
The first improvement is connection, not replacement. Important claims should point to the code, tests, configuration, or runtime evidence that realizes them; important implementation regions should point back to the pages that explain why they exist.†† The earlier Parts develop the stronger form of this join: every model node can point to the implementation it represents, and that implementation can point back to the model node that explains it. The resulting trace is not yet a proof that the prose is true. It is a disciplined join that lets audits and later mechanisms discover when the map and the territory disagree. Figure 4.2-2 draws the two independent ways that join can be strengthened.
Brownfield representations can be strengthened incrementally from informal descriptions to structured, machine-readable models and, where a consumer justifies the cost, executable models.
4.2.2 Recover Latent Models from the Implementation
Brownfield models need not begin with an engineer inventing the right abstraction in advance. Existing implementation can contain repeated structure that points toward a missing model. In DocAble, a weak primitive-density detector surfaced regions repeatedly passing anonymous strings, integers, tuples, and other low-level values. Most findings deserved no new abstraction. Where similar shapes recurred across related components, however, comparison exposed candidate concepts that the implementation had been expressing repeatedly without naming. Language mutations, job identities, and other concepts became explicit types through this process.
The detector did not discover the model by itself. It located repeated form. Architectural context distinguished signal from legitimate low-level detail, and engineering judgment decided whether the recurring shape represented a consequential concept worth naming. Brownfield modeling can therefore proceed from implementation outward: expose repetition, compare instances, propose an abstraction, and retain it only where the abstraction makes later reasoning cheaper or more reliable. Figure 4.2-3 traces that path from realized code up to a named model, and marks where mechanical discovery ends and engineering judgment begins.
4.2.3 Reconcile the Map, Then Promote Obligations
Begin with audit. Join the representation to the implementation and surface disagreement without blocking work — a legacy map is known to drift, and making every claim mandatory before repairing it would only canonize old errors. Reconcile nearby claims through ordinary feature and maintenance work, requiring each disagreement to be surfaced rather than silently resolved toward prose or code.
Promote selected obligations only after the representation is trustworthy enough to support the consumer. Extend the model where retrieval, analysis, generation, validation, measurement, or gating will use the added structure — do not add model structure without a consumer. The companion From-Drifted-Wiki-to-Trusted-Model operator card holds the drill whole.
4.2.4 Migrate One Bounded Surface at a Time
The immediate goal is a trusted map over the parts that matter now: important claims have known anchors, important implementation regions have explanatory homes, and agents can navigate the join without rediscovering it. Beyond that, build only enough representation and evidence to support the quality case the system must defend — enough architecture, behavior, ownership, measurement, and traceability to answer the properties you mean to stand behind. The right endpoint is the one whose future value justifies its upkeep.
A live estate rarely tolerates a wholesale cutover. Choose a bounded surface — a subsystem, service, policy family, or recurring workflow — and let old and new coexist while the replacement earns trust. A strangler migration and a pilot cell are two versions of the same operational principle: bound the experiment, preserve reversibility, and generalize only what the slice supports. DocAble's serverless re-platforming ran this shape — a stateful job pipeline moved off long-lived polling workers onto instances that scale to zero behind a managed push queue; the push plane grew beside the poll plane behind a default-off toggle, proved green on a staging namespace running identical code, took the traffic, then the old cluster was deleted and preserved on an archive branch.
Within that slice, recover the representation using whichever direction the evidence supports. Top-down work starts from intended architecture or requirements and refines toward implementation. Bottom-up work induces stable abstractions from the code and rises toward a model. Removing accidental complexity — under tests — first can make either direction easier. The directions should meet at the grain where the relevant property becomes answerable.
A disagreement then has the familiar three-way diagnosis — the representation is wrong, the grain is wrong, or the implementation is wrong. Do not decide which in advance; shifting a legacy codebase from "the tests pass" to "these properties hold across all behaviors" surfaces disagreements by design.
SOFTWARE ENGINEERINGInset — When the model and the code disagree
Tighten a model against real code and it will, sooner or later, contradict the code. The disagreement usually points to one of three questions:
- Is the model wrong? It may claim something the code never promised. Refine the model.
- Is the model at the wrong grain? It may be asking a question at a level of abstraction that cannot see the answer. Build a different model at the grain the property lives at.
- Is the implementation wrong? The model may be right, the code disagrees, and the disagreement may expose an implementation defect.
The discipline is to decide which before you act. Fixing code when the model was wrong bakes in a false claim; refining the model when the code had a bug hides the bug behind an adjusted spec.
For consequential substrate changes, represent important dependencies strongly enough to query the migration blast radius before cutover; the control↔substrate dependency model gives the operator pattern.
4.2.5 Audit → Drain → Promote
A valid future invariant is not always a valid present admission rule. A legacy tree may violate the rule everywhere on the day you first decide the rule is worth having. Turning it blocking immediately does not improve the estate; it merely breaks all work already in flight. So introduce a new obligation over an inherited estate in three beats — and the order is the safety.
The move — Audit → Drain → Promote
- Audit. Install the proposed rule as evidence only. It reports every detected violation and blocks no commit, so it never breaks an in-flight agent.
- Drain. A fix wave repairs the existing findings under sufficient tests and other evidence to preserve intended behavior, converting the legacy code to the new convention behavior-for-behavior.
- Promote. Once the estate satisfies the obligation, grant the mechanism blocking authority so detected violations cannot re-enter through the governed boundary.
Cloudflare's public account describes a similar approved → enforced sequence, landing each policy control advisory and flipping it to enforced once the tree is clean, so the promotion never breaks work in flight 33. Timo Reimann, “How Cloudflare Enforces Engineering Standards Using Ai,” Cloudflare, August 4, 2026, https://blog.cloudflare.com/engineering-standards-enforcement/.. Figure 4.2-4 draws the three beats as one lint is worn into a legacy tree.
4.2.6 Finding the Model in the Code
Brownfield modeling has a bootstrapping problem. The system already embodies years of engineering decisions, but many of them exist only as repeated implementation shapes. Asking an engineer to "model the architecture" merely moves the reconstruction problem into a meeting.
DocAble encountered this problem through type discipline. An audit-only lint measured primitive density: roughly, how much of a file's function interface was expressed through bare or untyped values rather than structured types. A high score was deliberately treated as a smell, not a defect. String-heavy protocol adapters can be perfectly sound; so can numerical helpers and one-off tooling. The lint's job was to direct attention, not pronounce judgment.
The first large audit demonstrated why that distinction mattered. Hundreds of files triggered the detector. They did not share one remedy. Some needed ordinary annotations. Some were test fixtures or tooling whose primitive-heavy interfaces were appropriate. Some sat at IPC or external boundaries where wire formats naturally expose primitive values. Others required local engineering judgment. The backlog therefore had to be partitioned before it could be drained.
A smaller subset was much more interesting. In the C# document mutators, the audit exposed recurring parameter shapes across files: language mutations repeatedly carried a language tag and pass name; alt-text mutations repeatedly carried description, attribution, and related state; title and contrast operations showed similar repetition. These were not duplicated bugs. They were domain concepts that existed in the implementation without names. The migration extracted them into explicit records that subsequent code could share.
The lint alone could not reliably tell which primitive-heavy interfaces represented missing domain concepts. DocAble's component model supplied another piece of information: whether code lived at an internal, external, or IPC boundary. Internal application code and external protocol adapters warranted different expectations. Once the boundary kind was explicit, primitive density could be interpreted in architectural context rather than as a universal style rule.
The two views composed. Primitive density asked, what shape may be hiding here? The component model asked, what kind of boundary is this? An agent could then inspect clusters rather than isolated findings and ask whether several sites were independently expressing the same concept. In one C# wave, this exposed six cross-file patterns across document mutators. Subsequent extraction and migration also exposed an attribution defect and an asymmetric API that neither lint alone was designed to detect.
Figure 4.2-5 draws the method. Weak signals identify places worth inspecting; a partition separates legitimate primitive-heavy code and mechanical debt from the repeated shapes that reveal missing concepts; and naming those concepts changes the substrate later migration runs on.
Field record — Primitive-density migration
The primitive-density backlog fell from 698 file-level findings on June 8 to 472 on June 9 and 299 on June 10. The declining count is not the important result. What matters is that the remaining population was repeatedly reclassified as it shrank:
- June 8 — 698 findings, partitioned into cluster-uniform, mechanical, per-site-judgment, boundary, and test-fixture-or-exempt buckets.
- June 9 — 472 findings.
- June 10 — 299 findings, dispositioned as annotate, exempt, extract-a-model, redesign, retire, or defer-for-judgment.
Some findings disappeared through mechanical work, some exposed missing shared types, some warranted exemptions or lint changes, and some required design judgment. The chronology records those observations; it makes no claim that a single planned wave caused each step of the decline.
The detector finds the problem; it does not choose the model
The same method could conclude that no new model was warranted. A later partition found experimental files that might simply be retired, HTTP handlers needing ordinary return annotations, lint implementations whose primitive-heavy signatures were canonical, and fuzzing code whose repeated operation shape did warrant a shared protocol. The detector was useful precisely because its findings were not treated as commands.
This is a useful brownfield pattern: use weak signals to locate reconstruction work, partition before repairing, and extract a model only where repeated structure earns one.
The migration then compounds. Once a concept has a name, later work can refer to it directly. Mechanical codemods can migrate consumers. Type checking can enforce relationships that previously had to be reconstructed. Other lints can become simpler because they no longer need to infer the concept independently. And design practice can move upstream: if a new design depends on JobId, ChunkId, or a mutation specification, the design can name that vocabulary before implementation scatters anonymous copies of it.
The final step is subtle. DocAble did not turn primitive density itself into a universal blocking rule. The smell remained audit-only. After the backlog had been understood and reduced, narrower mechanisms could prevent specific regressions; the design called for a drift guard rather than pretending that every future high-density file was wrong.
Sometimes you promote the obligation, not the instrument that discovered it. That is the mature form of Audit → Drain → Promote: an audit mechanism may reveal the property that deserves authority without itself becoming the final authority mechanism.
Figure 4.2-6 generalizes the three beats. The audit produces findings; a partition sorts them into drain, redesign, and accept; draining and redesign identify the real obligation; and only then does the work choose the final mechanism — promoting the detector to blocking authority in the simple case, or retaining it as an audit signal while a narrower control carries the authority.
Audit → Drain → Promote does not require the discovering mechanism itself to become blocking. The final authority may belong to a narrower constraint, validator, or gate while the original detector remains an audit signal.
4.2.7 Measure What the Map Still Cannot Explain
Models expose what they contain more readily than what they omit. Reverse traceability can surface candidate gaps: exercised implementation with no corresponding model claim, or modeled obligations with no evidence. Treat these as candidates for investigation — some reveal missing representation, others missing trace edges or deliberately unmodeled detail. A companion mechanism runs the join in the other direction: project test coverage onto model nodes so an untested invariant appears as a named gap instead of hiding inside a line-coverage percentage. This is coverage-to-model mapping.
4.2.8 Derive, Don't Copy
Recovering a model is only the first step. The next question is what happens to the facts it owns.
A common failure is to name one representation as the source of truth while continuing to copy its contents elsewhere. A service belongs to one team in the model and to that team again in a config file. An architectural edge exists in the graph and again in a hand-maintained allowlist. A symbol-to-requirement relation exists in the model and again as a string in a test. Parallel copies turn agreement into maintenance work and drift independently.
If a fact already has a queryable source of truth, consume or derive it rather than copying it. This holds for relationships as much as for scalar facts. Prefer a join over two independently maintained lists. Prefer a stable symbol or identifier over a copied path or line number — a snapshotted cross-reference rots silently, while a derived one reddens the moment the code moves. Prefer generation over synchronized hand-editing when one representation can honestly own the result.
Where neither representation should be derived wholly from the other, make their correspondence observable rather than assumed. A parity check earns its keep precisely because neither side is silently trusted: the implementation can expose a stale model, and the model can expose an implementation that has escaped its declared intent. A one-way regenerate-from-code check makes the implementation the source of truth for that relation and cannot detect whether the model should instead constrain the implementation. Where one side is the source of truth, derive or generate accordingly; where neither fully determines the other, maintain explicit traceability and check the correspondences that are mechanically decidable.
Figure 4.2-7 draws the shape: once a fact has a source of truth, downstream consumers query, join, derive, or generate from it, and where neither fully determines the other, a correspondence check makes disagreement visible.
Hidden assumptions deserve the same treatment as copied facts. A control that depends on a particular runtime, directory layout, deployment topology, tool version, or model capability carries that dependency whether or not anyone wrote it down. When changing the substrate could invalidate the control, represent the dependency strongly enough that its blast radius can be queried before the change — the blast-radius move above is exactly this, worked through on a real re-platforming. Do not model every dependency. Model the ones whose rediscovery by breakage is expensive. Make consequential assumptions queryable before they become surprises.
4.2.9 Price the Investment
A recurring gap does not automatically deserve machinery. Price the expected future loss against the cost of building and carrying the response. Frequency and consequence are useful first approximations; detectability, reversibility, legal exposure, and the stability of the obligation can move the decision substantially. Cheap, rare failures often remain judgment. Cheap, frequent failures may justify a lint, script, or reusable procedure, because repeated agent and human attention is itself cost. Consequential failures justify a larger investment and a stronger assurance response: better representation, prevention by construction where possible, stronger sensing and validation, and authority at an appropriate boundary. Catastrophic or legally intolerable failures need not recur even once to earn that investment.
Table 4.2-1 crosses how much a failure costs against how often it happens; it gives a first-pass heuristic, not a decision rule. Do not build the larger mechanism until the expected cost of the failure justifies it.
| Cost × frequency | Rare failure | Frequent failure |
|---|---|---|
| Cheap | Judgment. | A lint, script, or reusable procedure. |
| Costly | Recovery with admission control. | Structural prevention with admission control. |
Price the upkeep as well as the construction. Models drift, validators require calibration, and skills age. Engineering capital depreciates: a control that once retired meaningful cost becomes overhead when the obligation disappears or the upkeep exceeds the return. Build the smallest asset that retires the recurring cost; retire it when the return disappears. Fleet-scale migration amplifies the same economics: Spotify reports a migration workflow in which work that previously involved hundreds of teams over weeks could be scoped by one engineer over days, with tooling targeting and scheduling concurrent agent work 44. Max Charas and Marc Bruggmann, “Honk: Autonomous Code Migration at Spotify,” Spotify Engineering, 2026, https://engineering.atspotify.com/..
Worked Examples
DocAble. A fast-built prototype was matured in place as domain models, typed seams, and controls were introduced incrementally — a roughly 300,000-line estate reduced to roughly 200,000 production lines, lint by lint and model by model. Part 5 (The Evidence) reconstructs the migration.
Takeaway. Start from the estate you own. Recover useful structure, reconcile it to reality, and invest where stronger representation or authority repays its cost.
Works Cited
- Lehman, Meir M., and L. A. Belady. Program Evolution: Processes of Software Change. Academic Press, 1985.
- Red Hat. “Why Developer Portals Matter More in the Age of AI Agents.” Red Hat, 2026. https://www.redhat.com/en/blog/why-developer-portals-matter-more-age-ai-agents.
- Reimann, Timo. “How Cloudflare Enforces Engineering Standards Using Ai.” Cloudflare, August 4, 2026. https://blog.cloudflare.com/engineering-standards-enforcement/.
- Charas, Max, and Marc Bruggmann. “Honk: Autonomous Code Migration at Spotify.” Spotify Engineering, 2026. https://engineering.atspotify.com/.