The Anticorruption Layer Contains, It Does Not Cure

Every team I talk to that has put a language model somewhere near a real decision eventually arrives at the same meeting. Someone has noticed that the thing is now deciding something it was not supposed to decide, or deciding it in a way nobody can quite explain, and the question on the table is what to do about it.

The industry has a ready answer, and it is not a bad one. Wrap the language model. Put a control plane in front of it, a layer of deterministic checks that does not care how the model reasoned and only looks at what it is trying to do. The model wants to approve a six hundred euro purchase; the code checks the amount against a limit and the vendor against a list and either lets it through or does not. The phrase people reach for is that this is engineering, not magic. You stop trusting the model and start trusting the wrapper you wrote.

I have no quarrel with the wrapper. My quarrel is with treating it as the answer, because by the time you are writing validation code you have already skipped the question that actually decides whether your design is sound. Not how do I enforce the boundary. Which decisions are allowed across it in the first place. A guardrail cannot tell you that. It does not know which of your decisions are the ones you would be mad to hand to an outside party, because that is not a fact about your code, it is a fact about your domain. And a fact about your domain is exactly what Context Mapping has spent twenty years giving us a language for.

That is the part I want to spend this post on. The catalog already has the relevant patterns. You can be a Conformist to the model, you can wrap it in an Anticorruption Layer, or you can keep the decision in your own code and refuse the model the job. Three moves, all of them old. The trouble is that every one of them was worked out for an upstream that behaved nothing like a language model, so the old advice about when to use which quietly stops being safe. Working out the new advice is the whole job here, and it is the question I deliberately left open at the end of the last post, where I called the Anticorruption Layer the firewall between the part of a system you let the model decide and the part you must not, and then said nothing about where that firewall goes or when. This is where I say it.

I should warn you where this lands, because it cuts against most of what has been written lately. The choice between those three moves is a strategic question before it is a technical one. And the Anticorruption Layer you reach for, the one everyone is now describing as the way to handle a model, is not doing the job people think it is doing. Against this kind of upstream it does not get you out of the coupling. At best it holds the coupling still long enough for you to live with it.

The three things you can do about an upstream

Strip away the novelty and you are in a situation domain-driven design has always had words for: you are downstream of something you do not control, and you have to decide how close you are willing to stand to it. There are three honest answers, and the intro already named them. The things that look like more options, a human in the loop, a shadow deployment, a second model checking the first, evidence extraction wired to deterministic code, are not a fourth answer. They are these three combined, or placed at different points along the boundary. Here is what each one commits you to on its own.

Conform, and you take what the model gives you and build on it as if it were yours. Its categories quietly become your categories. This used to be a considered decision, the kind you made when an upstream's model was good enough that translating away from it felt like wasted effort. Agentic coding has turned it into something else, something that happens to you whether you decided it or not, which is most of why it gets its own section below.

Wrap it, and you put an Anticorruption Layer between the model and your domain, and at that boundary you decide what you take, what you rewrite, and what you hand back. This is the move Evans made in his own integration, and it is the one nearly everyone writing on the subject has landed on. I think they are right to reach for it and wrong about what it does, which is the argument of the whole post.

Or refuse, and keep the decision in code you wrote, narrowing the model down to fetching evidence instead of passing judgment, or leaving it out of this particular decision altogether.

None of this is new. Conformist, Anticorruption Layer, and the choice not to integrate at all are in the book, and most readers will have used all three. What is new is the thing on the other end. Every one of these patterns assumed an upstream that was a team or a system: something with its own settled way of describing the world, that broke loudly when it changed, and that you could in principle phone up and argue with. The upstream here has none of those properties. It brings no language of its own, it drifts instead of breaking, and there is nobody to call. So the patterns survive and the rules of thumb attached to them do not, and the rest of the post is me trying to write better ones.

The Conformist you did not know you signed

The Conformist usually appears in the catalog as a choice. You look at an upstream's model, decide it is good enough, and adopt it rather than pay for translation. The example in my book is one of my own. Years ago I ran a music magazine and we wanted concert listings without keying them in by hand, so we pulled them from a large music platform whose model of concerts and tours I happened to think was excellent. I let it run straight through us, database to interface, no translation anywhere. It was a Conformist by conviction and it worked beautifully, right up to the afternoon the platform retired the part we depended on, and the breakage surfaced in every place their concepts had reached, which was every place. The thing to notice is that I knew I had done it. I could have told you on any day of those years that we were conforming.

That is the part that does not carry over. You cannot conform to a raw language model the way I conformed to that platform, because there is nothing settled to conform to. It does not hand you a model. It hands you a sentence, phrased a little differently each time, structured by no one. There is no object graph to adopt.

Which is exactly why the conforming still happens, just somewhere you are not watching. You never conform to the model. You conform to the capability you built around it, the bundle from the last post: the model, plus the schema you wrote, the prompt, the tiers you imposed, the confidence number it hands back. That bundle does present a model, and you slide into it without a meeting or a decision anywhere. You start describing applicants the way the rationale field describes them. You let the confidence score do your weighing for you. You name a concept the way the model named it, because it got there first and the name was fine. Evans caught himself doing a version of this and was honest about it: his code was writing prompts in the model's preferred style and taking its confidence scores at face value, conforming to the model's concepts even while he was trying to hold a line somewhere else.

None of those small adoptions is written down anywhere, and that is half the trap. The other half arrives when the upstream moves. When the music platform changed, it changed loudly. Endpoints went dark, calls failed, I knew within the hour. A Conformist to an ordinary upstream at least gets a clean break to react to. A Conformist to a model gets nothing. The provider rolls the version, or a colleague edits the prompt, and every call still returns, the schema still validates, the ratings still land inside your enum. What has actually changed is that the words mean a little less than they did last month, and because you conformed to the words rather than holding a boundary against them, your own language has moved along with them. The Published Language you could have imposed was the one fixed thing that would have shown you the drift. Conforming is the decision to do without it.

So conform on purpose only where what you are adopting is harmless presentation language: the wording of a summary, the phrasing of something a person is going to read and judge anyway. The moment what you are adopting carries a decision you are accountable for, conforming is how you lose it without noticing, while every dashboard stays green.

The Anticorruption Layer contains, it does not cure

The second move is the one most of the recent writing reaches for, and the reaching is right. What I want to question is the reason given for it.

Here is where the conversation sits today. Chris Hughes, in a piece on building MCP servers with domain-driven design that a lot of people have read, puts an Anticorruption Layer inside the server, where in his words it "protects your domain from the LLM's interface requirements," translating between the loose strings a model emits and the structured objects your code wants to work with. Dennis Traub, writing for AWS, gets to the same place from the security side: one server, one model, the layer as the thing that stops the model's interface from bleeding into your domain. They have put the layer in the right place and described the translation job exactly. Parse the response, check the categories, turn the upstream's shapes into yours. As an account of the work it holds, for as long as the problem is the shape of the data.

I do not think the shape is the problem. And if it is not, then the translation everyone is describing is the scaffolding rather than the wall, and mixing those two up is the expensive mistake this whole series keeps circling.

Go back to what the book actually says the Anticorruption Layer is for. Legacy models tend to be unpleasant, you would rather not have them running through your application, so you build an isolating layer that translates the outside model into your own. The thing it buys you is decoupling. Your dependence on the upstream model stops at the layer instead of spreading through the context, so when the upstream changes you fix the layer and nothing behind it has to notice. Against a legacy system that is the whole story, and the translation is doing the heavy lifting, because the upstream's model sits still and the only difficulty is its shape.

Put a probabilistic upstream behind the same layer and the decoupling never arrives. You can translate every response perfectly and still be coupled to the thing that mattered, which was never the shape. It was the judgment and the meaning, and those cross your boundary however cleanly you parse the strings. The layer turns risk_rating into a member of your enum and feels like isolation, while the upstream that chose which rating to send is still choosing, still drifting, and still reporting full conformance on its way past. Evans felt this in his own example and went so far as to ask whether the thing he had built deserved the name Anticorruption Layer at all. His code was conforming to the model's concepts on one side and rejecting anything outside its taxonomy on the other, both at once, and rather than pretend it cleanly removed the coupling he noted the mess and picked the aspect worth drawing. He was right not to pretend. Against this upstream, the coupling does not come off.

This is what the title is about. The parsing and the translation are the visible work, the part everyone describes. They are not the part that holds. An Anticorruption Layer in front of a model is doing three other jobs at the same time, and those are the ones that earn it.

  • It holds a yardstick. The Published Language you imposed gives the drift something fixed to be measured against, so the meaning underneath cannot shift without something registering it. The Conformist gave that up. The layer is where you keep it.

  • It keeps judgment from crossing. Letting evidence through while sending a finished tier back is a decision about what the boundary will accept, and you have to make it on purpose. No amount of parsing makes it for you.

  • It walls off the rest of the system. It sits between the context where you have agreed the model may decide and the Core domain next door where you have agreed it may not, so the ghost upstream you welcomed in one place does not seep into the place you cannot afford it.

The translation an agent can regenerate against a new schema before lunch. These three it cannot, and they did not get cheaper just because the code around them did. If anything they matter more, because the upstream they hold off is the least governable one most of us have ever integrated with.

So place an Anticorruption Layer wherever the model's vocabulary or its judgment would otherwise reach a domain you have decided to defend, and place it with no illusions about what it does. It contains the coupling. It gives the drift a yardstick and gives you one place to absorb a change. It does not make you independent of the upstream, because you cannot translate your way out of being downstream of something that decides. Anyone who tells you the layer solves the problem is describing the upstream they wish they had.

When the answer is to keep the decision

The third move barely registers in the guardrails literature, because it is not a way of handling the model. It is deciding the model does not get near the thing.

For anyone who skipped the last post, a quick reminder of one distinction, because the criterion here rides on it. Domain-driven design sorts subdomains into three kinds. Core is where you differentiate, the reason a customer picks you over the next option. Supporting has to work but is not where you win. Generic is the commodity you were never going to build for yourself. The last post argued that how much ghost ownership a context can stand tracks that classification: fine in Generic, livable in Supporting, dangerous in Core. Here that tolerance turns into an instruction. Where it is lowest, you refuse.

Refusing does not mean keeping the model out of the building. It means putting your weightiest decisions on the evidence side of the line the last post drew. It is worth saying plainly for the catalog-literate reader, because the question is coming: this is not always Separate Ways from the model. Sometimes it is, when the right call is to build the thing yourself and never reach for the model at all. More often it is Separate Ways for the decision while you still consume the model's evidence through a protected boundary. You refuse the judgment without refusing the help. The credit-risk capability can read an applicant's documents and come back with missed payments in March and April, a debt-to-income ratio above what the documents disclose, a gap in the employment history. That is evidence, and your own code takes it and sets the tier. Or the same capability can come back with ELEVATED and quietly do the part you answer for. Same schema, same applicant, and all the difference in the world, because in the second version the decision now lives inside something that keeps no language, drifts without a word, and will be no help at all when a regulator asks how it was reached.

The rule, then, is the tolerance heuristic turned around. Keep the decision in code you wrote when it is regulated, when a lot of money rides on it, or when it is Core, and let the model feed evidence into that code rather than make the call. The strongest version of this argument is not even mine. People working on compliance have started saying that where an obligation is a hard constraint, one that must never be violated rather than usually satisfied, a probabilistic model in the path of the decision is itself the exposure, since a filter that catches the wrong trade ninety-nine times in a hundred has, in the part that counts, still not caught it, and "almost always compliant" is not a phrase that survives an audit. Their answer is to write those hard constraints in deterministic logic the model cannot talk its way around. That is this same refuse move, arrived at from the regulatory side: some decisions do not belong to a probabilistic upstream at any confidence level, and the design question is not how to fence them in but how to keep them out of reach.

It is worth saying out loud that the economics push the other way, which is why the move needs defending. Letting the capability return the tier is cheaper, faster, and demos beautifully. Keeping the decision in code you have to maintain is the slower, costlier path, and against the collapsing price of everything else it can look like nostalgia. The heuristic is what tells you when the cost is the point. In a Generic subdomain, refusing is waste; you reached for the commodity precisely so you would not have to own it. In a Core one, refusing is the thing you compete on surviving contact with a technology that would happily blend it into everyone else's. Same cost either way. The only question is whether you can afford the alternative.

The supplier who will not take your call

You might read all this as a free choice between three moves. It is not, and the reason is the upstream itself. Conform, wrap, and refuse are all ways of being downstream, and which one is wise depends on what kind of upstream you are downstream of. This one has a name in the catalog, and it is the least pleasant entry in it.

In the book I take the Customer/Supplier relationship and put names to the ways it curdles when you meet it in the wild. There is the Over-Cautious Supplier, who ships rarely because it is frightened of breaking you. The Reluctant Supplier, who puts up enough process that its customers give up trying to reach it. The Vetoing Customer, who blocks the upstream from below. These are descriptions of behaviour, the kind of thing you write on a map when you are being honest about how the teams actually treat each other. Hold the Reluctant Supplier up against a frontier model provider and it stops being an analogy. It is just a description, taken to its limit.

A Reluctant Supplier is hard to reach. A model provider cannot be reached at all. There is no ticket you can file that changes its mind, no way to get your requirement onto its roadmap, because there is no conversation in which your bank's definition of creditworthiness becomes an input to its next release. You cannot ask it to keep a concept you depend on. The influence that a working Customer/Supplier relationship runs on, where the downstream's priorities get budgeted into the upstream's plan, is not faint here. It is absent. And it does the one thing even a Reluctant Supplier usually spares you: it replaces the product under you on a schedule that is none of your business. Evans built his example against Claude Sonnet 3.5, and named the context that exactly, on purpose, because the thing on the other side of his boundary was that specific model and not the general idea of one. That model is already retired. The deprecation calendars read like sunset notices, a short window from the email to the forced migration, and the teams who have their house in order keep a register of every place they call a model and run their evaluation suites against the next version before the cutoff, so the migration is a known quantity rather than a fire on the day. The teams who do not find out when the behaviour shifts and nothing in the logs explains why.

This is what turns the three moves from a menu into something closer to a forced hand. You are not picking how to partner with someone. You are picking how to defend yourself against a supplier you cannot negotiate with, cannot schedule, and cannot stop from swapping the thing you rely on. Against a supplier like that, the consumer-side habits stop being good practice and become the price of staying downstream without getting hurt. Pin the version, so you know which context you are really talking to and a silent swap becomes an event you can see. Run the evals against the next version before the provider puts you on it whether you like it or not. Watch your own language for drift against the yardstick the layer is holding. And stay honest, at all times, about which of the three moves you are actually making, because the worst place to be is conforming to this upstream while you believe you have wrapped it.

What makes the firewall hold

A boundary is only as good as whoever is watching it, and two things decide whether the layer you placed is a wall or a photograph of one. Neither is a piece of technology, which is roughly why the recent writing walks past them.

The first is decision provenance. For every decision the system makes, you want to be able to say afterward where it came from: an observation the model produced, a deterministic rule you wrote, or a person who overrode both. And you want the surrounding conditions logged with it, the prompt, the retrieval, the model version, the eval suite that was live at the time, since those are exactly the things that can move while the schema keeps validating. Most of this machinery already exists under the name of the audit trail, capturing model and prompt versions, the inputs as they were seen, which rule or guardrail fired and whether the action passed or was stopped or went to a human. I am not going to walk through how to build it. What I care about is what it is for here. Provenance is what makes the line between evidence and judgment checkable after the fact, instead of a thing you asserted at design time and then lost track of. A context map is a claim about who owns which decisions. Provenance is how you find out whether the running system actually behaved the way the map says it does. The last post offered a smell for ghost ownership: can the team state the decision rule without pointing at the model? Provenance is how you answer that with a record rather than a shrug. A decision that traces back to a rule is one you own. A decision that traces back only to a model observation, with nothing of yours between the observation and the outcome, has moved upstream, and the provenance trail is the place that movement stops being invisible. The logging is not the point. The point is that without it, ghost ownership leaves no mark, and you find out about the drift around the same time a regulator does.

The second is the one the whole post has been walking toward, and it is the thing the layer cannot be on its own. The domain experts have to keep authority over the concepts and the decisions. The layer holds the upstream off. It does not decide what the upstream is allowed to decide, and it cannot, because that is a judgment about your domain, and only the people who own the domain are in a position to make it. Which ratings are permitted, what has to be true before ELEVATED is allowed, when a model's answer is simply wrong about an applicant a loan officer would have read differently: none of that lives in the schema or the evals or the layer. It lives with the experts. The trap AI sets is the quiet suggestion that because a schema exists and validates, the language is settled and the experts can ease off. They cannot. A schema fixes the shape of an agreement; the experts are the party to it, the ones who set the tiers in the first place and the only ones who will notice when the meaning under a tier has slipped. Take them out of the loop and you have the most precise possible record of decisions nobody owns.

So let me put the limit plainly, since it is the title. An Anticorruption Layer is necessary and not sufficient. So are the evals. So is the provenance trail. Each one holds the problem a little better and none of them dissolves it, because the thing they are holding off is an upstream that decides, and the only real answer to a decision you cannot afford to lose is a human who owns the domain and stays accountable for it. The layer buys that person time and sight. It does not stand in for them, and a design that reads as if it does has not got rid of the risk. It has only hidden who carries it.

Trade-off navigators, not rules

If you want something to carry out of all this, here are the rules of thumb I actually reach for, offered the way the last post offered its smells: as things that tell you where to look, not what you will find. Each one has a force pulling against it, and that pull is the part you have to weigh. They navigate the trade-off. They do not settle it.

Conform on language you could afford to lose, and use one question as the test of whether you can afford it: if this wording drifts, will a human downstream still catch the thing that matters? Where the answer is yes, conforming is cheap and fine. Where a decision rides on the wording with no person between it and the outcome, you could not afford to lose it, and what you were looking at was never really presentation language.

Wrap where the model's vocabulary or its judgment would otherwise reach a domain you have decided to defend, and keep the whole point of this post in view while you do it: the wrap contains the coupling, it does not remove it. It buys you a yardstick and one place to absorb change. It does not buy you independence, so do not budget as though it had.

Refuse the judgment, not necessarily the model, where the decision is regulated, expensive, or Core. The thing that pulls against the strategic version of this is operational risk. A capability can be Generic by every test of differentiation, the place you were never going to compete, and still be one you cannot let an unowned upstream decide, because fraud screening or an identity check or a safety classification carries a cost of being wrong that has nothing to do with where your differentiation lives. When that happens, let the operational risk win and refuse anyway.

And watch the boundary you cannot draw. If two contexts you have marked Free both reach for the same capability, the three navigators above each protected one context at a time and said nothing about the drift the two will now share. That is the apparent freedom from the last post, the case where a rule of thumb built for one boundary is blind to the coupling running underneath both.

Conform, wrap, refuse

The architects who opened this series had already sorted the catalog by instinct, into the patterns they thought AI had made pointless and the ones they thought it had made essential, without any vocabulary to justify the split. In a room I describe in the first post, they argued that the Anticorruption Layer had become a waste of effort, since you could always point an agent at the new contract and regenerate the translation by lunch. Three posts later, this is the corner of their split that turns into something you can act on.

You are downstream of an upstream that brings no language, drifts instead of breaking, and will not take your call. You have three honest answers, the same three the catalog always offered, now read against an actor none of them were written for. Which one is right is not a question about the model. It is a question about which of your decisions you can stand to let an unowned upstream shape, and that is about the oldest question strategic design knows how to ask.

The recent writing has the mechanism and grabs it first: wrap the model, guard the output, validate the response. The mechanism is fine. It just comes after the decision that matters, and that decision is a thing you can only really see on a map: where the firewall stands, which decisions cross it as judgment and which cross only as evidence, who stays accountable for the ones you keep. The same lesson has turned up in each of these posts wearing a different boundary. AI did not make the wall less necessary. It made the wall harder to see and more tempting to skip, and it stood the least governable upstream most of us have met on the far side of it.

Which leaves the question the series has been walking toward the whole time. If the firewall belongs on the map, and the ghost upstream belongs on the map, and the line between evidence and judgment belongs on the map, then we need a map that can actually carry all of it, and the notation we have cannot. That is the next post: how to draw a Context Map that admits there is a model in the room. I would rather work it out in the open than hand you something finished, so if you have tried to draw any of this and found the shapes you had were not enough, that is the conversation I am hoping to start.

References

Related writing

Eric Evans. Context Mapping with an AI-based Component. Domain Language, January 2026. https://www.domainlanguage.com/articles/context-mapping-an-ai-based-component/ The worked example this post leans on for the part-Conformist, part-Anticorruption-Layer reading, the honest-map principle, and the precise naming of the upstream as a specific model version rather than the abstract category.

Vaughn Vernon. Implementing Domain-Driven Design. Addison-Wesley, 2013. Source of the canonical Conformist and Anticorruption Layer phrasings the rereads here build on.

Michael Plöd. Hands-on Domain-driven Design by Example. The Conformist by conviction, the Anticorruption Layer as isolation rather than translation, the Customer/Supplier downstream anti-pattern family (Over-Cautious Supplier, Reluctant Supplier, Vetoing Customer), and the Big Pug Loans case study are all developed there.

Michael Plöd. Context Maps in the Age of AI. The opening post in this series. The three flows, the load-bearing and scaffolding distinction, and the energy-sector debate this post closes back to.

Michael Plöd. MCP as Open-Host Service, and the Published Language the LLM Cannot Give You. The first deep dive. The language model as an upstream Big Ball of Mud, the Published Language imposed at the boundary as a yardstick against silent drift, and the Big Pug Loans credit-risk contract.

Michael Plöd. Ghost Ownership: You Own the Capability, Not the Decision. The second deep dive. The composed capability as the bounded context, the evidence-versus-judgment hinge this post operationalizes, the Anticorruption Layer named as the firewall, and the Core, Supporting, Generic tolerance heuristic the refuse criterion inverts.

Chris Hughes. Building Scalable MCP Servers with Domain-Driven Design. https://medium.com/@chris.p.hughes10/building-scalable-mcp-servers-with-domain-driven-design-fb9454d4c726 The Anticorruption Layer placed inside the MCP server as a translation layer between the language model's interface and the domain objects the code works with. The closest existing treatment to this post's subject, and the one whose translation-first framing I argue is necessary but not the load-bearing part.

Dennis Traub. Rediscovering Domain-Driven Design, one MCP server at a time, and the follow-up Your agent keeps using that word. AWS, May 2026. https://dev.to/aws/rediscovering-domain-driven-design-one-mcp-server-at-a-time-1i79 MCP servers as bounded contexts with Anticorruption Layers at their boundaries, argued from the security and blast-radius side, and the ubiquitous-language point that a tool's name shapes how an agent reasons about it.

Michael Plöd

Michael works as an independent tech consultant with 20+ years of experience specialized in Domain Driven Design, Team Topologies, Software Architecture and Collaborative Modeling. He is a regular speaker at international conferences and an author. Michael is also an INNOQ Fellow and Team Topologies Advocate.

https://www.michael-ploed.com
Next
Next

Ghost Ownership: You Own the Capability, Not the Decision