AI Research Library

The Strangler Pattern for AI-Enabled Change

Rajesh Ranjan Mahapatra

A legacy workflow should yield responsibility to an AI-enabled replacement only through seams that make decision authority, observed effects and recovery independently controllable.

© 2026 Rajesh Ranjan Mahapatra. 31 min read

The Strangler Pattern for AI-Enabled ChangeAll AI Research Library

The Strangler Pattern for AI-Enabled Change

A legacy workflow should yield responsibility to an AI-enabled replacement only through seams that make decision authority, observed effects and recovery independently controllable.

TLDR

  1. The strangler pattern reduces migration risk only when it transfers business responsibility in small, reversible slices, rather than merely redirecting technical traffic.
  2. AI adds stochastic decisions, changing context and delayed outcomes, so observation share, recommendation share and action authority must be controlled separately.
  3. A safe migration cell mirrors inputs, compares decisions, gates authority, observes postconditions, records evidence and can return a slice to the legacy path.
  4. The legacy system is a comparator, not ground truth; promotion requires independent reference cases, segment-level analysis and verified downstream outcomes.
  5. Advance one consequence class at a time, keep one authoritative writer, and retire the old path only after its hidden readers, jobs and recovery duties disappear.
Worked scenario

The replacement that passed every demo

Imagine a bank whose servicing platform receives free-form requests from relationship managers. The old workflow uses forms, keyword rules and manual queues. It is slow, but its awkwardness has accumulated thirty years of exceptions: dormant-account checks, dual-authorisation rules, cut-off times, country restrictions and several ways to pause a request without losing its place.

A new AI-enabled service understands the message, extracts entities, retrieves policy, proposes a route and drafts the operations instruction. In demonstrations it feels like a generation ahead. Against a labelled test set it classifies requests more accurately than the old rules. The programme therefore chooses a Friday cutover: all requests will enter the new service on Monday, while the old platform remains available as a contingency.

The first incident is not a spectacular hallucination. A request to amend a company signatory is classified correctly, the retrieved policy is correct and the draft is clear. The hidden difference is temporal. The legacy workflow places a hold when two related changes arrive inside one batch window. The new service sees each request independently, writes both into the case system and leaves a human team to discover the conflict later. Returning traffic to the old platform does not undo the writes already made.

The programme had a rollback switch, but it did not have reversibility. It had compared answers, but not effects. It had kept the legacy application running, but not kept the old business responsibility intact.

Central answer

For AI-enabled change, the strangler pattern is a controlled transfer of decision authority and consequence. Traffic routing is only the outer mechanism. Each slice must be observable, comparable, independently authorised, outcome-verified and recoverable before the next slice moves.

Martin Fowler’s strangler fig metaphor describes a new system growing around an old one and gradually replacing it. Modern pattern catalogues add façades, event interception, legacy mimics and temporary transition components.[1][2] Those mechanisms remain useful. AI changes the proof needed at each step because the candidate may return variable outputs, depend on changing context and influence a workflow without being its system of record. ML systems also accumulate entangled data dependencies, feedback loops and configuration coupling, so an apparently local replacement can alter a wider system.[15]

Traffic is the wrong unit of migration

The conventional picture has a façade in front of two implementations. At first, nearly every request goes to the legacy system. The new system receives a narrow route. Its share grows until the old path carries nothing and can be removed. Microsoft and AWS both describe this phased redirection as the core of the strangler pattern.[3][4]

Figure 1 · Signature figure

Old and new pathways exchange responsibility through a controlled seam

Swipe or scroll to inspect the full figure.

A staged strangler migration from a legacy path to a new AI-enabled path Four stages show requests entering a migration seam. The legacy path shrinks while the new path grows, but authority gates and outcome observations remain visible throughout. Responsibility transfer MirrorAssistLimited authorityPrimary with fallbackRetire Migrationseam Legacy pathAI-enabled path Legacy shrinks after verified transfer authority gateoutcome observation
Illustrative mechanism. The line width represents business responsibility, not measured traffic. A path is considered transferred only when its authority and outcome evidence move with it.

The traffic picture is incomplete for ordinary software and actively misleading for AI. A shadow model can inspect every live request while affecting no customer. Another model can receive one per cent of requests yet authorise the highest-value actions. Those systems have identical traffic percentages only if consequence is ignored.

Three dials, not one percentage

A migration controller should expose three independent dials. Observation share is the population whose live inputs the candidate sees. Recommendation share is the population for which its proposal reaches a human or downstream component. Authority share is the population for which that proposal may change authoritative state. Shadow deployment guidance explicitly keeps candidate inference from influencing decisions, while canary release guidance allows a bounded production population to receive the new behaviour.[5][6]

Figure 2 · Control geometry

Observation can reach 100 per cent before authority begins to move

Swipe or scroll to inspect the full figure.

Three migration dials across five stages A line chart shows observation share rising to one hundred per cent during shadow mode, recommendation share rising during assisted mode, and authority share remaining low until later stages. 0%25%50%75%100% MirrorAssistLimitedPrimaryRetire observationrecommendationaction authority
Illustrative progression. Actual percentages must be set from local consequence, sample size, outcome delay and rollback evidence. The key design is independent control, not these particular curves.

Thought experiment: the one-per-cent migration

System A mirrors one hundred per cent of customer messages. Its proposals are logged and discarded. System B receives one per cent of payment instructions and may release funds without review. Which has moved further from the legacy process? System A has far more observation. System B has far more business authority and irreversible exposure.

To make that distinction explicit, define consequence-weighted authority exposure for a migration window:

WAE = Σ(aᵢ × sᵢ × (1 − rᵢ)) / Σsᵢ aᵢ is 1 when the candidate authorised an action, sᵢ is a locally defined severity weight, and rᵢ is recoverability from 0 to 1. Increasing authority, severity or irreversibility raises exposure.

This measure is a decision aid, not a universal risk metric. Severity weights are policy choices, probabilities may be uncertain and harm can be non-linear. Its practical value is narrower: it prevents a programme from calling a high-consequence one-per-cent canary “small” merely because the request count is small.

Open hypothesis

Consequence-weighted authority exposure should predict migration incidents better than raw traffic share in AI-enabled workflows. That claim needs comparative operational data; the article treats it as a testable design hypothesis, not an established empirical law.

Build a reversible decision seam

A façade can redirect calls. An anti-corruption layer can translate protocols and domain semantics between old and new systems.[7] Neither, by itself, answers who may act, how an effect is confirmed or what happens after an ambiguous commit. The AI-era seam must therefore be wider than an API boundary.

Practitioner synthesis

I use the term reversible migration cell for the temporary control structure around one migration slice. It contains input interception, context translation, dual execution, comparison, an authority gate, an outcome observer, rollback and a decision ledger.

Figure 3 · Migration cell cutaway

The seam surrounds the decision, action and observed effect

Swipe or scroll to inspect the full figure.

An anatomical cutaway of a reversible migration cell Live input enters an interception ring. Legacy and candidate run in parallel. A comparator feeds an authority gate, then a typed action, an outcome observer and a rollback loop. Evidence is recorded around the whole cell. Decision and evidence boundary live input Interceptand translate Legacyexecution Candidateexecution Compareand explain Authoritygatelegacy · human · candidate typed action Outcome observer and readback rollback, compensate or replay Decision ledger: intent · identity · context · evidence · effect
Practitioner synthesis. Components may be implemented in-process, through workflow orchestration or with platform services. The logical separation matters more than product choice.

The ten things that can migrate at different speeds

AI programmes often say “the workflow has moved” while only the reasoning component has changed. A defensible slice inventories ten objects: intent, identity, world state, context, reasoning, authority, action, evidence, outcome, and release and recovery. Each can remain on the legacy side, move to the candidate, or be shared temporarily.

ObjectQuestion at the seamCommon false migration
IntentWhich business request is this slice allowed to handle?A broad classifier silently absorbs cases outside the approved scope.
IdentityWhose entitlement authorises access and action?The candidate inherits a service account with wider power than the user.
World stateWhich system owns the current, authoritative facts?A replicated snapshot is treated as current after the source has changed.
ContextWhich evidence and policy version may inform the proposal?More documents are retrieved, but temporal applicability is lost.
ReasoningWhat procedure turns context into a proposal?Token similarity is used as proof of decision equivalence.
AuthorityWho may approve, veto or execute this consequence?A “copilot” output is automatically accepted downstream.
ActionIs the effect typed, bounded and idempotent?Free text is converted into an ambient API call.
EvidenceCan a reviewer reconstruct what was knowable and checked?Only the final answer is retained.
OutcomeWas the intended state actually produced exactly once?An accepted command is recorded as success without readback.
Release and recoveryCan this slice be stopped, replayed or returned to the prior owner?A routing flag exists, but candidate writes cannot be reversed.

One authoritative writer

Parallel computation is useful. Parallel mutation is dangerous. A dual run lets legacy and candidate calculate from the same input while one path remains authoritative. A dual write lets both update state, which creates a consistency problem whenever one write succeeds and the other fails. The transactional outbox pattern exists precisely because a database write and an event publication cannot be assumed to succeed atomically across systems.[8]

At any migration stage, one owner should write each authoritative field or effect. The candidate may write to an isolated comparison store, draft workspace or reversible staging area. When authority moves, the writer changes through an explicit gate. Events needed by the other system should be propagated through an outbox, change stream or equivalent replayable mechanism, with idempotent consumers and versioned state.

Rollback is not route reversal

A routing switch governs future work. It says nothing about requests already accepted, actions already committed or cases waiting inside the candidate workflow. A real rollback plan therefore begins with an inventory of in-flight states: received, proposed, approved, dispatched, acknowledged, observed and reconciled. Each state needs a rule for completion, cancellation, compensation or transfer.

Suppose the candidate sends an account-change command and times out before receiving the response. Sending the same command through the legacy path may duplicate the change. Declaring failure may leave the customer record changed but the case marked unresolved. This is the unknown outcome: the system knows an attempt occurred but cannot yet prove the effect. The safe response is to pause authority, query the target using an idempotency key or business identifier, and reconcile before any retry.

Recovery must also preserve temporal authority. A case approved under policy version 12 cannot be replayed days later under policy version 13 without deciding which rule should govern. A token valid when the action was proposed may have expired. The user may have lost entitlement. Replay reconstructs evidence; it must not resurrect permission. The recovery path should re-check identity, authority and current world state while retaining the original decision receipt for audit.

These details explain why a legacy executable running in standby is not, by itself, a fallback. The fallback is an operational procedure that knows which path owns new work, how in-flight work is reconciled, which effects can be compensated, who declares an unknown outcome resolved and when the migration stage may resume.

Figure 4 · Temporal sequence

Dual run compares safely; dual write creates an ambiguous interval

Swipe or scroll to inspect the full figure.

Comparison of unsafe dual writes and a single-writer outbox sequence The upper timeline shows the candidate and legacy writing separate systems, with one write failing. The lower timeline shows one authoritative write committing with an outbox event that is later delivered idempotently. Unsafe: two authoritative writes requestlegacy DBnew DB Ambiguous statelegacy committed, candidate failedrouting rollback cannot erase the first write Safer: one writer plus transactional outbox requestDB + outboxconsumer atomic local commit idempotent apply retriable delivery
Sourced mechanism and illustrative sequence. The transactional outbox addresses the inconsistency between a local state change and event publication; it does not remove the need for consumer idempotency or reconciliation.
The argument in one passChart · flow
flowchart TD
  A["The replacement that passed<br/>every demo"] --> B["Traffic is the wrong unit of<br/>migration"]
  B --> C["Build a reversible decision seam"]
  C --> D["Prove equivalence without<br/>worshipping the legacy"]
  D -. "re-test" .-> A
  classDef first fill:#0a1424,stroke:#5B5CF6,stroke-width:2px,color:#e6f3ff
  classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff
  class A first
  class B,C,D step
A structural route through The Strangler Pattern for AI-Enabled Change, from The replacement that passed every demo to Sources. Reading use: follow the relation, then test it against the surrounding evidence and limits.

Prove equivalence without worshipping the legacy

Dual running is often described as “compare old and new outputs.” That phrase hides two opposite mistakes. Exact equality is too strict for generative systems whose wording can vary without changing the decision. Blind agreement is too weak because the legacy workflow may be systematically wrong.

The legacy result is a comparator, not ground truth. A useful migration test forms a triangle among legacy behaviour, candidate behaviour and an independent reference. The reference might be an adjudicated label, a policy invariant, a deterministic calculation, a gold case or a later verified outcome. Where all three disagree, the system has found a question for investigation rather than an automatic winner.

Figure 5 · Evidence triangle

Promotion needs three-way comparison and an outcome anchor

Swipe or scroll to inspect the full figure.

A triangle connecting legacy, candidate and independent reference, with an outcome observer below Legacy and candidate outputs are compared with one another and with independent labels or policy tests. Verified downstream outcomes provide a fourth anchor and update the reference set. Legacyobserved behaviour Candidateproposal and confidence Referencelabel, rule or adjudication behavioural differenceknown legacy defect?task and segment accuracy Observed postconditionwhat changed, for whom, exactly once? feeds new reference cases
Practitioner synthesis. The candidate should be compared with both current practice and an independent criterion. Outcome evidence matters because a correct-looking decision can still fail during execution.

Five proofs of a migration slice

Output comparison becomes decision-relevant when it is split into five proofs.

  1. Input proof: both paths received the same eligible case, identity, time and context, or the difference is deliberate and recorded.
  2. Decision proof: action-relevant labels, extracted facts, confidence, citations and policy conclusions satisfy task-specific tolerances by segment.
  3. Authority proof: only the designated actor approved or executed the consequence, under the same entitlements and separation-of-duty rules.
  4. Outcome proof: readback confirms the intended postcondition, including no duplicate or partial effects.
  5. Recovery proof: the slice can be disabled, replayed or compensated without reviving stale authority or losing work.

These proofs answer different questions. A candidate can pass decision proof and fail input proof because its retriever used a newer policy than the legacy path. It can pass both and fail outcome proof because the target API returned success before a downstream batch rejected the change. Treating one accuracy score as the whole migration gate hides such combinations.

Stochastic equivalence is distributional and invariant-based

Machine-learning systems can share held-out performance yet behave differently under deployment conditions. Research on underspecification demonstrates that multiple predictors with similar test performance may diverge materially in new domains.[9] Production ML literature likewise emphasises tests for data, model, serving and monitoring behaviour rather than a single offline score.[10]

For generated text, compare what matters to action. A servicing draft may vary in wording while preserving the same customer identity, requested change, evidence, policy exception and recommended next step. Those are invariants. Exact token match would reject harmless variation. A fluency judge might accept a polished draft that silently changes the account or omits a mandatory control.

Use a layered comparator: deterministic checks for identifiers, arithmetic, schema and prohibited actions; calibrated task metrics for classification or extraction; human adjudication for genuinely ambiguous cases; and downstream outcome measures such as correction rate, rework, missed deadlines and customer remediation. Sample repeatedly when the model or decoding is stochastic, and report uncertainty rather than only a mean.

Minimal worked example: routing three request types

Suppose the legacy router handles three labels: fulfil, reject and review. A candidate classifier is evaluated on 2,000 synthetic cases across clean digital requests, scanned requests and a high-consequence segment. Overall candidate error falls from 3.95 per cent to 0.70 per cent. That looks decisive. The migration gate still checks the worst segment, harmful disagreements where legacy was correct, trace completeness, observed outcomes and the authority exposure of actioned cases.

In the positive run included later, the candidate acts only on a reversible clean-digital subset. Its worst segment error is 1.04 per cent, outcome coverage is complete in the sample and consequence-weighted authority exposure is 0.4 per cent. The gate permits exactly one stage of promotion. In the negative run, the candidate acts across high-consequence cases, lacks a tested rollback, observes only 93.69 per cent of action outcomes and produces 9.09 per cent error in its worst segment. The gate holds even though its aggregate error differs from legacy by only 0.25 percentage points.

The example is synthetic. Its purpose is to expose the logic that an aggregate score conceals, not to propose universal thresholds.

Thought experiment: one model, two consequences

Hold the model, prompt, data and measured error rate constant. In Workflow One, the model drafts an internal summary. A reviewer can edit it, the source documents remain available and a wrong sentence creates modest rework. In Workflow Two, the same output is parsed into an instruction that closes a customer account. The classifier has not changed, yet the safe migration stage has changed completely.

The causal feature varied here is the action contract. Workflow One turns the model output into a reversible recommendation. Workflow Two turns it into an irreversible state change. Accuracy describes the proposal distribution; it does not describe the workflow’s ability to absorb error. A programme that approves “the model” for both workflows has confused component evidence with system evidence.

Now add a deterministic gate to Workflow Two. The gate permits closure only when the account is already marked closed by an authoritative source, making the model’s role a duplicate-record clean-up proposal. The model and its error rate remain unchanged, but the effect has become constrained and largely reversible. The safe stage may move again.

Migration readiness belongs to the model, context, authority and action composition. It cannot be inherited from a model card, a benchmark or another workflow. This is also why an apparently minor integration change can invalidate a previous gate: converting a draft into an automatically accepted field changes authority even when no model artefact changes.

Figure 6 · Thought experiment

The same ten-per-cent canary can carry radically different exposure

Swipe or scroll to inspect the full figure.

Two ten-per-cent canaries with different consequence and recoverability The left panel shows many low-severity reversible routing decisions. The right panel shows a few high-severity irreversible state changes. Both are labelled ten per cent traffic, but the right has much higher authority exposure. Queue routingAccount state change 10% of requests · reversible10% of requests · hard to reverse severity weights mostly 1recoverability near 1severity weights 3 to 5recoverability near 0 small authority exposure large authority exposure
Illustrative, not measured. Circle area represents severity weight. The experiment varies consequence and recoverability while holding raw traffic share constant.

Move one consequence at a time

A practical strangler programme needs stages, but the stages should describe business responsibility rather than deployment labels. The following ladder keeps the legacy owner available until the candidate has earned each additional form of influence.

StageCandidate roleAuthoritative actorEvidence required to advanceImmediate fallback
0. EncapsulateNone; create interceptable boundary and baseline telemetry.LegacyStable slice definition, dependency map, replayable inputs.Remove seam without behavioural change.
1. MirrorObserve and compute; output discarded.LegacyInput parity, trace coverage, latency and cost profile.Stop mirroring.
2. CompareProduce structured proposal for independent evaluation.LegacySegment metrics, harmful disagreement review, reference-case coverage.Discard candidate result.
3. AssistShow recommendation to authorised operator.Human or legacy controlOverride reasons, review burden, user error and automation-bias checks.Hide recommendation; preserve case state.
4. Limited authorityAct on a reversible, low-consequence segment.Candidate within deterministic gateOutcome coverage, idempotency, tested rollback, bounded exposure.Return segment to prior actor and reconcile.
5. Primary with fallbackOwn the approved slice; legacy remains callable.Candidate pathSustained outcome performance, operational ownership, recovery drills.Route new cases back; complete or compensate in-flight work.
6. RetireOwn the slice without legacy execution dependency.Candidate pathNo hidden callers, readers, writers, batches or unresolved records.Restore from archived procedure only through explicit incident process.

Promotion needs both cases and elapsed time

A fixed case count is insufficient when outcomes are delayed or traffic is seasonal. Two thousand requests collected on one weekday may exclude month-end batches, a regional holiday, a policy refresh and the low-frequency exception that causes most harm. Conversely, waiting for a calendar month without checking sample composition can create the illusion of maturity while the candidate sees the same easy population repeatedly.

Each stage should therefore have two minimums: an evidence population and an observation window. The population is stratified across the approved segments and deliberately includes negative controls, rare cases and known legacy defects. The window spans the operational cycles that can change behaviour, such as cut-off times, batch boundaries, model updates, policy changes and downstream reconciliation.

Promotion criteria should be written before the stage starts. Otherwise teams can move thresholds after seeing disappointing results. The manifest records the metric, denominator, confidence interval or review uncertainty, segment floor, maximum unresolved outcomes and the person authorised to accept residual risk. Where sample sizes are small, the correct conclusion may be “insufficient evidence” rather than “pass” or “fail”.

Only one material migration change should be evaluated at a time within a slice. Changing the model, prompt, retriever, authority rule and target API together makes a good outcome uninterpretable and a bad outcome difficult to locate. Canary guidance makes the same operational point: overlapping changes contaminate the signal and make system state harder to reason about.[6]

Production-shaped example: commercial servicing intake

Consider a composite workflow for commercial account-servicing requests. Inputs arrive through email, forms and scanned letters. The legacy estate classifies the request, checks required evidence, creates a case, assigns a queue and, for a narrow set of low-risk changes, prepares a transaction for approval. The AI-enabled candidate can recognise intent, extract entities, retrieve procedure, identify missing evidence and draft the next action.

The programme first chooses a business seam: requests to update correspondence preferences for existing authorised contacts. It excludes signatory changes, payment instructions, vulnerable-customer markers, legal restrictions and any case with conflicting identity evidence. The seam is narrow because the action is cheaply reversible and its outcome can be read back immediately.

During mirror mode, both paths receive the same event envelope. The envelope includes request identity, customer identity, received time, channel, attachments, policy version and a correlation identifier. The candidate writes only to a comparison store. The legacy platform remains the sole case writer.

During comparison, deterministic checks verify account and party identifiers, required evidence and proposed action type. Human reviewers adjudicate disagreements, with a deliberately oversampled set of rare and high-consequence cases. The team learns that the candidate is better on ordinary prose but worse when a scanned letter contains two requests. Instead of averaging that defect away, it defines a “multi-intent scan” exclusion and creates a focused test population.

During assisted mode, operations staff see the candidate’s structured proposal beside source evidence. Acceptance rate alone is not treated as quality because busy operators may over-trust automation. The interface therefore asks for a reason when a proposal is changed and samples accepted proposals for independent quality review.

Limited authority begins only for the clean digital segment. The candidate may create a draft case and update a reversible preference through a typed action. A deterministic authority kernel checks identity, entitlement, segment eligibility, required fields and the current source-of-record version. A readback confirms the new preference and records an effect receipt. Any unknown outcome, version conflict or missing receipt routes the case to human review rather than retrying blindly.

Signatory changes remain on the legacy path. The model can assist evidence assembly, but it has no action authority. The migration unit is the consequence class, not the whole user journey. That permits value from the new capability without pretending that every neighbouring decision has earned the same autonomy.

Figure 7 · Production-shaped slice

One servicing journey contains several migration boundaries

Swipe or scroll to inspect the full figure.

A servicing workflow split by consequence class Requests enter a classifier, then divide into correspondence preference, multi-intent scan, signatory change and payment instruction. The low-risk preference path moves to candidate authority, ambiguous cases go to review, and high-consequence paths remain legacy or human-controlled. email · form · scan Intent andevidence proposal Correspondence preferencecandidate action through typed gate and readback Multi-intent scancandidate proposal, mandatory human review Signatory changelegacy execution, candidate assembles evidence Payment instructionhuman authority and dual control retained One journey, four consequence classes, four migration states
Synthetic composite. The example is not a claim about a specific deployment. It shows why migration scope should be defined by consequence, reversibility and evidence, not only by interface or user journey.

Segment by risk structure, not convenience

Random traffic percentages are useful for detecting broad regressions, but they can under-sample rare failure modes. A migration slice should therefore be stratified by factors that change the mechanism: document quality, language, jurisdiction, customer type, action class, materiality, entitlement, model confidence, time sensitivity and recoverability. Google’s canary guidance stresses comparison between canary and control populations and warns that weak metrics can mislead release decisions.[6]

Advance a segment only when its own evidence clears the gate. A good aggregate result cannot subsidise a dangerous minority. Conversely, one difficult segment need not block every reversible segment if the router can separate them reliably and the exclusion is enforced mechanically.

Feature flags are useful for this separation because they can select alternative code paths at runtime. A flag, however, is an execution mechanism rather than a governance decision. Its evaluation should produce a reason, variant and context, and the resulting route should be logged with the case evidence.[11] A person must still own the threshold, expiry and rollback rule.

Retirement is a proof, not a percentage

When the candidate carries all intended new traffic, the legacy component may still serve reports, batch jobs, manual recovery, audit searches or hidden consumers. Transitional architecture is valuable precisely because temporary components create options during displacement, but those components should have explicit removal conditions.[12]

A slice is ready to retire only when telemetry shows no eligible legacy calls, data lineage shows no remaining readers or writers, in-flight cases are reconciled, records meet retention duties, recovery drills no longer depend on the old executable, and an accountable owner accepts the new operating model. Zero routed traffic is necessary, but it is not decommissioning evidence.

Know when the pattern stops helping

The strangler pattern pays for safety with temporary complexity. Two systems coexist, translations accumulate, data must be reconciled and operators reason about mixed states. Fowler’s legacy displacement catalogue describes this transition architecture as an investment in options, not free infrastructure.[2] A team should choose it only when those options are worth more than the carrying cost.

Serious negative control: the exhaustive deterministic function

Consider replacing a legacy IBAN checksum implementation with a small, standards-conformant library. Inputs and outputs are deterministic. The domain can be exhaustively or property-tested. The function has no persistent state, no human judgement, no delayed outcome and no independent authority. An internal abstraction switch, comprehensive tests and a direct cutover may be safer than months of dual operation.[2]

This negative control matters because gradualism is not automatically prudent. A long-lived façade can become another critical service, preserve obsolete semantics and extend the period in which two systems can fail. Use the strangler pattern where uncertainty, coupling or consequence makes reversibility valuable. Do not use it as ceremony for every replacement.

Four hard failure boundaries

The pattern ceases to help when the team cannot create a stable seam, cannot make old and new coexist, cannot observe the relevant outcome or cannot reverse the proposed effects.

  • No interceptable boundary: requests, events or batch inputs cannot be separated without changing the whole legacy execution path. First create an interface, capture point or product seam.
  • Shared mutable invariants: both implementations must manipulate the same tightly coupled state inside one transaction. Modularise the invariant or keep one implementation authoritative until the transaction boundary changes.
  • Outcome darkness: the system cannot tell whether an action took effect, or the outcome arrives after the rollback window. Build readback, reconciliation or a human confirmation process before granting authority.
  • Unacceptable coexistence risk: the legacy platform is insecure, unsupported or legally unusable, so extending its life is itself the dominant hazard. Containment, accelerated replacement or service retirement may be the correct strategy.

Azure’s pattern guidance similarly notes that strangling may not fit when requests cannot be intercepted, source changes are impossible or the system is small enough for straightforward replacement.[3]

Put an expiry on the transition

Temporary architecture tends to become permanent when its removal has no owner. Every façade, comparison store, legacy mimic, feature flag and reconciliation job should therefore have an expiry condition, operating cost and named retirement owner. The programme reviews those liabilities at each promotion gate.

The expiry is not a guessed date. It is a condition such as “remove the legacy adapter after ninety days with no eligible calls, two successful recovery drills and confirmed retirement of the month-end report”. If the condition cannot be reached, the team has discovered an unmodelled dependency or an architecture that is no longer transitional. That finding should trigger redesign, not indefinite extension.

Figure 8 · Failure surface

Incremental replacement needs both a seam and an observable consequence

Swipe or scroll to inspect the full figure.

An illustrative decision surface for migration strategy A two-dimensional field places seam quality on the horizontal axis and outcome observability on the vertical axis. Strong seams and observable outcomes favour strangler migration. Weak seams or dark outcomes favour encapsulation, instrumentation or direct replacement. Seam quality and scope isolation → Outcome observability and recovery → Contain or instrument firstSelective stranglerStrong strangler fit direct replacement may fit if system is smallgood seam, dark outcome: do not grant authority
Illustrative relationship, not a measured model. The contours express a design judgement: better interception and better outcome evidence increase the value of staged responsibility transfer.

The responsibility-transfer card

Before building the candidate, write one card for the first slice. A useful card fits on a page and answers six questions.

  1. Boundary: What exact business capability, population and exclusion set are moving?
  2. Effect: What state can change, how severe is error, and how recoverable is the effect?
  3. Comparator: What independent reference and segment metrics distinguish acceptable from harmful difference?
  4. Observer: How will the intended postcondition, duplicate effect and unknown outcome be detected?
  5. Recovery: Which switch, reconciliation step, compensation and in-flight case rule restores control?
  6. Retirement: Which evidence proves the old responsibility, data dependency and recovery duty are gone?
Figure 9 · Decision instrument

A slice is promotable only when all six faces close

Swipe or scroll to inspect the full figure.

A hexagonal responsibility-transfer card Six labelled faces surround a central migration slice: boundary, effect, comparator, observer, recovery and retirement. Missing evidence on any face leaves an open gap. Migration sliceone capability · one populationone authority transition BoundaryEffectComparatorObserverRecoveryRetirement scope and exclusionsseverity and reversibilityreference and thresholdspostcondition and duplicatesswitch, reconcile, replayno callers or hidden duties An unclosed face is a promotion blocker, not documentation debt.
Practitioner decision instrument. It is deliberately compact. Detailed thresholds and evidence should live in the migration manifest and executable gate.

NIST’s AI risk guidance calls for post-deployment monitoring, appeal and override, decommissioning, incident response, recovery and change management.[13] The card turns those broad outcomes into a slice-level engineering decision. It does not certify a system. It makes missing evidence visible before a traffic percentage disguises it.

How the claim changes under pressureChart · state
stateDiagram-v2
  [*] --> Claim
  state "Old and new pathways exchange<br/>responsibility through a<br/>controlled seam" as Claim
  state "Three dials, not one percentage" as Test
  state "Observation can reach 100 per<br/>cent before authority begins<br/>to move" as Revision
  Claim --> Test: expose mechanism
  Test --> Decision: evidence survives
  Test --> Revision: rival survives
  Revision --> Claim: narrow or rebuild
  Decision --> [*]
The revision loop begins with Old and new pathways exchange… and keeps counterexamples inside the reasoning path. Reading use: follow the relation, then test it against the surrounding evidence and limits.

Executable artefact: a migration promotion gate

The accompanying Python script evaluates synthetic legacy and candidate runs. It checks aggregate error, harmful disagreement, worst-segment error, outcome coverage, trace coverage, consequence-weighted authority exposure and five structural controls. It has no external dependencies.

The script permits only one-stage promotion. Passing it is evidence for the next bounded experiment, not permission to retire the legacy path. Local teams must replace the synthetic data, severity weights and thresholds with approved evidence.

Assumptions and expected result

  • The reference label in the synthetic data is treated as independently adjudicated truth.
  • Severity uses an illustrative 1, 3, 5 scale; recoverability ranges from 0 to 1.
  • The positive case constrains authority to reversible clean-digital requests and has all structural controls.
  • The negative case grants wider authority while rollback, idempotency and single-writer controls are missing.
  • Running python migration_gate.py should promote the positive case one stage and hold the negative case with explicit blockers.
Core promotion logic
def evaluate(cases, controls, thresholds):
    legacy_error = rate(c.legacy != c.truth for c in cases)
    candidate_error = rate(c.candidate != c.truth for c in cases)
    harmful = [
        c for c in cases
        if c.legacy == c.truth and c.candidate != c.truth
    ]
    acted = [c for c in cases if c.candidate_acted]

    authority_exposure = sum(
        c.severity * (1.0 - c.recoverability) for c in acted
    ) / sum(c.severity for c in cases)

    blockers = missing_structural_controls(controls)
    blockers += failed_metric_checks(
        error_delta=candidate_error - legacy_error,
        harmful_disagreement=len(harmful) / len(cases),
        outcome_coverage=rate(c.outcome_observed for c in acted),
        trace_coverage=rate(c.trace_complete for c in cases),
        authority_exposure=authority_exposure,
        thresholds=thresholds,
    )
    return "PROMOTE_ONE_STAGE" if not blockers else "HOLD"
Complete runnable script
#!/usr/bin/env python3
"""A synthetic gate for staged legacy-to-AI responsibility transfer.

The script does not prove that a system is safe. It makes the evidence needed
for a migration decision explicit and blocks promotion when key evidence is
missing. It uses only Python's standard library.
"""

from __future__ import annotations

from dataclasses import asdict, dataclass
from enum import Enum
import json
import random
from statistics import mean
from typing import Iterable, Sequence


class Stage(str, Enum):
    SHADOW = "shadow"
    ASSISTED = "assisted"
    LIMITED_AUTHORITY = "limited_authority"
    PRIMARY_WITH_FALLBACK = "primary_with_fallback"


@dataclass(frozen=True)
class Case:
    case_id: str
    segment: str
    truth: str
    legacy: str
    candidate: str
    confidence: float
    severity: int              # 1 nuisance, 3 material, 5 severe
    recoverability: float      # 0 irreversible, 1 cheaply reversible
    candidate_acted: bool
    outcome_observed: bool
    trace_complete: bool
    latency_ms: int


@dataclass(frozen=True)
class Controls:
    stable_seam: bool
    rollback_tested: bool
    replayable_inputs: bool
    idempotent_actions: bool
    single_authoritative_writer: bool


@dataclass(frozen=True)
class Thresholds:
    max_error_delta: float = 0.005
    max_harmful_disagreement: float = 0.010
    min_outcome_coverage: float = 0.995
    min_trace_coverage: float = 0.995
    max_worst_segment_error: float = 0.030
    max_weighted_authority_exposure: float = 0.080


def rate(values: Iterable[bool]) -> float:
    values = list(values)
    return sum(values) / len(values) if values else 0.0


def evaluate(cases: Sequence[Case], controls: Controls, thresholds: Thresholds) -> dict:
    if not cases:
        raise ValueError("At least one case is required")

    legacy_error = rate(c.legacy != c.truth for c in cases)
    candidate_error = rate(c.candidate != c.truth for c in cases)
    disagreements = [c for c in cases if c.legacy != c.candidate]
    harmful = [c for c in disagreements if c.legacy == c.truth and c.candidate != c.truth]
    acted = [c for c in cases if c.candidate_acted]

    # Authority is weighted by consequence and discounted by recoverability.
    denominator = sum(c.severity for c in cases)
    authority_exposure = (
        sum(c.severity * (1.0 - c.recoverability) for c in acted) / denominator
        if denominator else 0.0
    )

    outcome_coverage = rate(c.outcome_observed for c in acted) if acted else 1.0
    trace_coverage = rate(c.trace_complete for c in cases)

    segments = sorted({c.segment for c in cases})
    segment_error = {
        segment: rate(c.candidate != c.truth for c in cases if c.segment == segment)
        for segment in segments
    }
    worst_segment, worst_segment_error = max(segment_error.items(), key=lambda item: item[1])

    metrics = {
        "sample_size": len(cases),
        "legacy_error_rate": round(legacy_error, 4),
        "candidate_error_rate": round(candidate_error, 4),
        "error_delta": round(candidate_error - legacy_error, 4),
        "disagreement_rate": round(len(disagreements) / len(cases), 4),
        "harmful_disagreement_rate": round(len(harmful) / len(cases), 4),
        "outcome_coverage": round(outcome_coverage, 4),
        "trace_coverage": round(trace_coverage, 4),
        "weighted_authority_exposure": round(authority_exposure, 4),
        "mean_candidate_latency_ms": round(mean(c.latency_ms for c in cases), 1),
        "worst_segment": worst_segment,
        "worst_segment_error": round(worst_segment_error, 4),
        "segment_error_rates": {k: round(v, 4) for k, v in segment_error.items()},
    }

    blockers: list[str] = []
    required_controls = {
        "stable_seam": controls.stable_seam,
        "rollback_tested": controls.rollback_tested,
        "replayable_inputs": controls.replayable_inputs,
        "idempotent_actions": controls.idempotent_actions,
        "single_authoritative_writer": controls.single_authoritative_writer,
    }
    blockers.extend(name for name, present in required_controls.items() if not present)

    checks = {
        "error_delta": metrics["error_delta"] <= thresholds.max_error_delta,
        "harmful_disagreement": metrics["harmful_disagreement_rate"] <= thresholds.max_harmful_disagreement,
        "outcome_coverage": metrics["outcome_coverage"] >= thresholds.min_outcome_coverage,
        "trace_coverage": metrics["trace_coverage"] >= thresholds.min_trace_coverage,
        "worst_segment_error": metrics["worst_segment_error"] <= thresholds.max_worst_segment_error,
        "weighted_authority_exposure": (
            metrics["weighted_authority_exposure"] <= thresholds.max_weighted_authority_exposure
        ),
    }
    blockers.extend(name for name, passed in checks.items() if not passed)

    return {
        "decision": "PROMOTE_ONE_STAGE" if not blockers else "HOLD",
        "blockers": blockers,
        "metrics": metrics,
        "controls": asdict(controls),
        "thresholds": asdict(thresholds),
    }


def synthetic_cases(*, safe: bool, seed: int = 181, n: int = 2000) -> list[Case]:
    rng = random.Random(seed)
    segments = ["clean_digital", "scanned", "high_consequence"]
    cases: list[Case] = []

    for i in range(n):
        segment = rng.choices(segments, weights=[0.68, 0.24, 0.08], k=1)[0]
        truth = rng.choice(["route_a", "route_b", "review"])

        legacy_error_p = {"clean_digital": 0.030, "scanned": 0.060, "high_consequence": 0.045}[segment]
        candidate_error_p = {
            "clean_digital": 0.006 if safe else 0.018,
            "scanned": 0.014 if safe else 0.080,
            "high_consequence": 0.010 if safe else 0.115,
        }[segment]

        labels = ["route_a", "route_b", "review"]
        legacy = truth if rng.random() > legacy_error_p else rng.choice([x for x in labels if x != truth])
        candidate = truth if rng.random() > candidate_error_p else rng.choice([x for x in labels if x != truth])

        severity = 5 if segment == "high_consequence" else (3 if segment == "scanned" else 1)
        recoverability = 0.15 if segment == "high_consequence" else (0.75 if segment == "scanned" else 0.95)

        # In the positive scenario, authority is constrained to reversible cases.
        if safe:
            acted = segment == "clean_digital" and rng.random() < 0.20
            outcome_observed = (not acted) or rng.random() < 0.999
            trace_complete = rng.random() < 0.9995
        else:
            acted = rng.random() < (0.55 if segment == "high_consequence" else 0.35)
            outcome_observed = (not acted) or rng.random() < 0.94
            trace_complete = rng.random() < 0.975

        confidence = rng.uniform(0.90, 0.999) if candidate == truth else rng.uniform(0.55, 0.96)
        latency_ms = int(rng.lognormvariate(5.2, 0.35))

        cases.append(
            Case(
                case_id=f"C{i:05d}",
                segment=segment,
                truth=truth,
                legacy=legacy,
                candidate=candidate,
                confidence=confidence,
                severity=severity,
                recoverability=recoverability,
                candidate_acted=acted,
                outcome_observed=outcome_observed,
                trace_complete=trace_complete,
                latency_ms=latency_ms,
            )
        )
    return cases


def main() -> None:
    thresholds = Thresholds()
    positive_controls = Controls(True, True, True, True, True)
    negative_controls = Controls(True, False, True, False, False)

    reports = {
        "positive_case": evaluate(synthetic_cases(safe=True), positive_controls, thresholds),
        "negative_case": evaluate(synthetic_cases(safe=False), negative_controls, thresholds),
    }
    print(json.dumps(reports, indent=2, sort_keys=True))


if __name__ == "__main__":
    main()
Observed output from the supplied synthetic run
positive_case
  decision: PROMOTE_ONE_STAGE
  blockers: []
  candidate_error_rate: 0.0070
  harmful_disagreement_rate: 0.0065
  worst_segment_error: 0.0104
  outcome_coverage: 1.0000
  weighted_authority_exposure: 0.0040

negative_case
  decision: HOLD
  blockers:
    rollback_tested
    idempotent_actions
    single_authoritative_writer
    harmful_disagreement
    outcome_coverage
    trace_coverage
    worst_segment_error
    weighted_authority_exposure

OpenTelemetry’s context propagation and semantic conventions offer implementation building blocks for correlating traces, metrics, logs and events across the migration cell.[14] They do not define the business evidence by themselves. The migration manifest should add case identity, slice, route reason, model and prompt version, context version, authority decision, action identifier, effect receipt and recovery status.

Compact glossary

Authority share
The portion of eligible cases for which the candidate may cause an authoritative state change.
Dual run
Legacy and candidate compute from comparable inputs while only one path remains authoritative.
Effect receipt
Evidence that a requested action produced an observed postcondition, including identity and idempotency information.
Migration slice
One bounded capability, population and consequence class transferred under a single gate.
Unknown outcome
A state in which an action was accepted or timed out but its final effect cannot yet be proved.

Migrate authority, then retire traffic

The strangler pattern remains one of the most practical answers to risky replacement. Its essential virtue is not gradual traffic movement. It is the option to learn from live conditions while the prior owner can still carry the responsibility.

AI makes that option more valuable and more demanding. A candidate can observe without acting, recommend without owning, act without producing the intended outcome, and appear equivalent while failing on a rare segment. The migration seam must therefore encompass context, comparison, authority, action, evidence, outcome and recovery.

The architecture decision changes accordingly. Do not ask, “What percentage of traffic can move next?” Ask, “Which consequence class has earned one more stage of authority, and what evidence lets us take it back?” Mirror widely, compare against independent references, grant authority narrowly, observe every effect and keep one authoritative writer.

Only after the hidden readers, batch jobs, recovery duties and in-flight cases are gone should the old path be removed. At that point the legacy system is not merely quiet. Its responsibility has been replaced.

The evidence conversationChart · sequence
sequenceDiagram
  participant Q as Question
  participant M as Mechanism
  participant E as Evidence
  participant D as Decision
  Q->>M: The replacement that passed every demo
  M->>E: Old and new pathways exchange responsibility through a controlled seam
  E-->>M: compare rival explanations
  E->>D: Sources
  D-->>Q: revise scope or act
Question, mechanism, evidence and decision exchange the burdens developed in The Strangler Pattern for AI-Enabled Change. Reading use: follow the relation, then test it against the surrounding evidence and limits.

Sources

  1. Practitioner source Martin Fowler, Strangler Fig. Origin and later restatement of incremental legacy replacement.
  2. Practitioner source Ian Cartwright, Rob Horn and James Lewis, Patterns of Legacy Displacement. Seams, legacy mimics, product segmentation and transitional architecture.
  3. Official guidance Microsoft Azure Architecture Center, Strangler Fig pattern. Phased façade routing, coexistence and suitability limits.
  4. Official guidance AWS Prescriptive Guidance, Strangler fig pattern. Incremental replacement and migration considerations.
  5. Official guidance Amazon SageMaker AI, Testing models with shadow variants. Candidate inference without production decision influence.
  6. Official engineering source Google SRE Workbook, Release Engineering and Canarying. Bounded rollout populations, control comparison and release integration.
  7. Official guidance Microsoft Azure Architecture Center, Anti-Corruption Layer pattern. Translation between systems with different domain semantics.
  8. Official guidance AWS Prescriptive Guidance, Transactional outbox pattern. The inconsistency risk of distributed dual writes.
  9. Primary research Alexander D’Amour et al., Underspecification Presents Challenges for Credibility in Modern Machine Learning, JMLR. Equivalent held-out performance can conceal divergent deployment behaviour.
  10. Primary research Eric Breck et al., The ML Test Score. Tests and monitoring across production ML systems.
  11. Open specification OpenFeature, Flag Evaluation API. Vendor-neutral runtime evaluation of alternative code paths.
  12. Practitioner source Ian Cartwright, Rob Horn and James Lewis, Transitional Architecture. Temporary software elements that enable legacy displacement and should later be removed.
  13. Official risk guidance NIST AI Resource Center, AI RMF Playbook: Manage. Monitoring, override, incident response, recovery, change management and decommissioning.
  14. Open specification OpenTelemetry, Context propagation and semantic conventions. Correlation of distributed telemetry across service boundaries.
  15. Primary research D. Sculley et al., Hidden Technical Debt in Machine Learning Systems. Entanglement, data dependencies and feedback effects that complicate apparently local model changes.