The ModelPatch Pattern: Structured, Reviewable AI Changes
Ask an AI assistant to "add an API gateway and route the checkout service through it," and you've handed it two very different kinds of power depending on how the tool is built. In one design, the assistant edits a file or a database row directly, and you find out afterward whether it did what you meant. In another, the assistant produces a proposal: a structured, typed description of exactly what it wants to change, which a program can inspect, validate, and show you in plain language before anything happens. The second design is usually called a ModelPatch. It's not specific to any one product, and it's worth understanding as a general pattern, because it's the difference between AI assistance you can actually govern and AI assistance you can only hope went well.
The problem with letting AI edit anything directly
There are basically two ways to let a language model change something structured — a model, a config, a database, a document. The first is to let it edit the underlying representation directly: hand it the file, let it produce a new version, save it. The second is to let it call functions with arbitrary arguments: give it a toolbelt of operations and let it decide which to invoke and with what parameters. Both are common. Both look, at first glance, like reasonable ways to give an AI assistant real capability. Both fall apart at the exact moment you need to answer the question that actually matters: can I trust this change before it happens?
Direct text or file editing fails first, because it collapses "what changed" into "diff the before and after." A diff tells you which lines differ. It does not tell you whether an element was renamed or deleted and re-created as something new — those look identical in a text diff, and they are catastrophically different in meaning if the element in question has a dozen relationships pointing at it. It does not tell you whether the edit is even structurally valid until you try to parse the result, by which point the change has already happened to the one copy of the file you had open. And it gives a reviewer nothing to check the change against except their own reading of a wall of text, which is precisely the kind of review that gets rubber-stamped after the third time nothing bad happens.
Arbitrary function calling looks more disciplined, because at least there's a defined set of tools. But if those tools are thin wrappers around "run this SQL" or "execute this script" or "call this API with these parameters," you've mostly relocated the problem rather than solved it. The function signature might be typed, but the effect of calling it usually isn't something you can evaluate without actually running it. You can't cheaply ask "if I called this, would it violate a governance rule" — you'd have to call it, look at the result, and decide whether to undo it. That's not a preview. That's a live trial with a rollback plan, and rollback plans are exactly the kind of safety net that turns out to have holes precisely when you need them.
What both approaches are missing is the same thing: a representation of the proposed change that exists before the change happens, is small enough to validate exhaustively, and is closed enough that a program — not just a human eyeballing it — can reason about what it will do. That representation is the patch.
What makes a closed operation set different
The core idea behind the ModelPatch pattern is deceptively simple: instead of giving an AI assistant the ability to edit anything in any way, you give it a small, fixed vocabulary of operations, each with a precise, narrow meaning. Not "edit the model" — add, update, rename, delete, connect, disconnect, and nothing else. The assistant doesn't get a text editor and it doesn't get a general-purpose function-calling interface into your system. It gets a small set of verbs, each of which does exactly one well-defined thing, and it has to describe every change it wants to make as a sequence of those verbs.
This sounds restrictive, and it is — deliberately. The restriction is what makes everything downstream possible. A closed set of typed operations can be validated exhaustively, because there are only a handful of shapes to check instead of an unbounded space of possible edits. It can be previewed in human language, because each operation has a fixed, known meaning that translates cleanly into a sentence — "add a new element," "connect these two elements," — rather than requiring a reviewer to infer intent from a diff. It can be checked against rules mechanically, because a rule engine only has to understand six verbs, not an entire scripting language. And it can be rejected or partially rejected without ambiguity, because an operation either satisfies its own contract or it doesn't; there's no partial edit to reason about.
None of this is available to a system that lets the model produce arbitrary output and apply it. You cannot exhaustively validate "anything a language model might write." You can exhaustively validate six operation types with known fields. That's the entire trade, stated plainly: give up open-ended expressiveness, get back the ability to actually govern what happens.
Anatomy of a patch
A ModelPatch is not the model. It's a proposal about the model — a small, self-contained object with two parts: a human-readable description of what the change accomplishes and why, and a list of typed operations that would produce that change if applied. Nothing in a patch is ambiguous about intent, because the description states the intent in words, and nothing in a patch is ambiguous about mechanism, because each operation is a known, narrow verb with a fixed set of fields.
Here is what that looks like for a genuinely common architectural change: introducing an API gateway in front of a service that currently talks directly to its consumers, and rerouting traffic through it.
{
"description": "Add an API gateway in front of the Checkout Service and route the Web Storefront's calls through it instead of connecting directly.",
"operations": [
{
"op": "add",
"type": "ApplicationComponent",
"id": "api-gateway-checkout",
"name": "Checkout API Gateway",
"description": "Edge gateway for Checkout Service traffic: auth, rate limiting, request logging.",
"properties": {
"tier": "platform",
"owner": "platform-team"
},
"tags": ["gateway", "edge"]
},
{
"op": "connect",
"id": "rel-storefront-gateway",
"relationshipType": "Serving",
"sourceId": "api-gateway-checkout",
"targetId": "web-storefront",
"properties": {
"protocol": "HTTPS"
}
},
{
"op": "connect",
"id": "rel-gateway-checkout",
"relationshipType": "Serving",
"sourceId": "checkout-service",
"targetId": "api-gateway-checkout",
"properties": {
"protocol": "HTTPS"
}
},
{
"op": "disconnect",
"id": "rel-storefront-checkout-direct"
}
]
}
Read it the way a reviewer would, not the way a parser would. The description says what's happening in one sentence. The first operation adds one new element — a gateway — with a name, a description, and a couple of properties that make it queryable later (which team owns it, what tier it belongs to). The next two operations wire that gateway into the existing traffic path: the gateway now serves the storefront, and the checkout service now serves the gateway rather than the storefront directly. The last operation removes the relationship that used to connect the storefront straight to the checkout service, because that path is being replaced, not duplicated.
Nothing here is a script. There's no loop, no conditional, no arbitrary code the assistant wrote and you now have to audit line by line. There are four operations, each naming exactly one thing it does, each referencing existing elements by stable id rather than by name (so a rename elsewhere in the model can never cause this patch to silently attach to the wrong thing). A reviewer — human or automated — can look at this and know, completely, what will happen if it's approved. That completeness is the entire point.

What each operation type actually does — and catches
The six verbs are worth walking through individually, because the value of a closed vocabulary comes from each verb having a narrow, unambiguous contract, not from the vocabulary being clever.
| Operation | What it does | What it makes checkable |
|---|---|---|
add | Creates a new element: a type, a name, and optionally an id, description, properties, and tags. | Whether the type is a real type in the active profile, whether required properties are present, whether the id (if supplied) collides with something that already exists. |
update | Changes an existing element's properties, referenced by id. | Whether the id resolves to a real element, whether the properties being set are valid for that element's type, whether a locked or governed property is being touched without authorization. |
rename | Changes an element's display name, referenced by id, while preserving its identity. | Whether the id resolves to a real element — and, critically, guarantees the operation cannot be confused with a delete-plus-create, so every relationship and every view referencing that element survives intact. |
delete | Removes an element, referenced by id. | Whether anything still depends on it — orphaned relationships, references from views — and whether a governance rule protects it from deletion outright. |
connect | Creates a relationship of a given type between two element ids. | Whether both ids resolve to real elements, whether the relationship type is legal between elements of those two types, and whether the new relationship would violate a boundary or governance rule. |
disconnect | Removes a relationship, referenced by id. | Whether the relationship exists, and whether removing it breaks something the rule set considers mandatory — a required dependency, for instance. |
Notice what's absent from that list on purpose: there's no run, no exec, no operation that takes a free-form expression and evaluates it against the model. Every operation is a noun-shaped fact about the model's state, not a verb-shaped instruction about how to compute one. That's not an accident of API design — it's the whole reason validation is tractable. A system that has to validate "is this expression safe to run" is solving a much harder problem than a system that has to validate "does this id exist and is this relationship type legal between these two types." The second problem is decidable in milliseconds against the current model state. The first, in the general case, isn't decidable at all.
Four layers, four different kinds of mistake
A patch doesn't go straight from proposal to committed change. It passes through a sequence of checks, and it's worth being precise about what each layer actually catches, because they're not redundant with each other — each one is designed to catch a specific category of mistake that the others structurally cannot.
Schema validation: is this even a well-formed patch?
The first layer doesn't know anything about architecture. It knows about JSON. It checks that the patch parses at all, that every operation has the fields its type requires, that those fields have the right shape — a string where a string is expected, an array where an array is expected, an id where an id is expected rather than, say, a whole embedded object. This is the layer that catches an AI assistant hallucinating a field name that doesn't exist, omitting a required id, or producing a relationship type as a number instead of a string. It's boring, mechanical, and absolutely necessary, because every layer above it assumes the patch is structurally sound before it starts reasoning about meaning. Nothing interesting can be said about whether a change makes sense until you're sure it's at least a change the format can represent.
Metamodel validation: does this make sense for these kinds of things?
The second layer knows the shape of the model — its metamodel, or in a profile-driven tool, the active profile (Generic, ArchiMate, C4, or whatever else is configured). This is where a patch gets checked against what's actually legal to connect to what. An ArchiMate profile knows that a Serving relationship is a reasonable thing to draw between an application component and another application component, and that trying to connect a business actor to a data object with a Realization relationship is nonsense — not because a rule forbids it for governance reasons, but because it doesn't mean anything in the notation. A C4 profile has a different, narrower set of legal relationship shapes at each level of the model, and enforces those instead. This layer catches the AI assistant proposing something that's syntactically fine but semantically incoherent: connecting two elements with a relationship type that isn't valid for their types, referencing a type that doesn't exist in the active profile, or building a relationship where the source and target don't match what that relationship type requires.
Architecture rule checks: is this technically valid but still against policy?
The third layer is where it gets interesting, because this is the layer that catches changes which are completely legal by the metamodel's standards and still wrong. A relationship from a public-facing component straight into a database tagged Restricted is perfectly valid ArchiMate — nothing about the notation forbids it. It might still violate a governance rule that says nothing outside the Trusted boundary may connect directly to anything inside it without passing through an approved gateway. That rule has nothing to do with what the metamodel permits and everything to do with what the organization has decided is acceptable. This is also where rules like "Tier-1 applications must have a named owner," "public services must route through the approved API gateway," or "nothing in the Restricted zone may be deleted without a linked change ticket" get enforced — deterministically, against every patch, not sampled occasionally in a design review. A change can sail through schema validation and metamodel validation cleanly and still get stopped here, and that's the layer doing its actual job, not a false positive.
Human-readable preview: can a person actually see what this does?
The last layer before approval isn't a validator at all — it's a translator. It takes the surviving patch and renders it as plain language and, where useful, a visual diff against the current model: this element gets added, these two relationships get created, this one gets removed, here's what changes in the views that reference the affected elements. This is only possible because the operations are typed and narrow; a system that let the model produce arbitrary edits would have to reverse-engineer a diff back into English after the fact, which is a much weaker guarantee than generating the English directly from a known, closed vocabulary of verbs. The preview is where a human reviewer actually exercises judgment — not on whether the patch parses or whether it's technically legal, both of which have already been checked, but on whether it's the right change to make.
The layering matters as much as the individual checks. A patch that fails schema validation never reaches the metamodel layer, because there's nothing coherent yet to check against the metamodel. A patch that's structurally and semantically valid but violates a boundary rule never reaches a human as something requiring their judgment on whether the JSON was well-formed — it's already been filtered down to a policy question by the time a person sees it. Each layer's job is to catch what the layers before it structurally cannot, and to hand only the questions that actually require its kind of reasoning to the layer above it. By the time a change reaches a human for approval, the only question left is the one only a human can answer: is this the right change, given context the system doesn't have.
The point of the layering isn't to make AI-proposed changes slower. It's to make sure that when a change reaches a human, the only remaining question is the one that actually needs a human — not "is this well-formed," not "is this legal," but "is this the right call."
Why the closed vocabulary is the load-bearing part
It's worth returning to why this whole structure is even possible, because it's easy to read the four-layer pipeline as the interesting part and the operation vocabulary as an implementation detail underneath it. It's the other way around. The pipeline only works because the vocabulary is closed. Every check described above — schema conformance, metamodel legality, rule compliance, human-readable preview — depends on the fact that there are exactly six things a patch can say, each with a fixed, known shape. Take away that constraint, let the AI assistant express "apply this arbitrary transformation," and every one of those checks either becomes vastly harder or stops being possible in principle. You cannot metamodel-check an arbitrary expression. You cannot generate a reliable plain-language preview of a script without running it. You cannot rule-check a change whose effect isn't knowable until it executes.
This is also why a good closed operation set resists the temptation to grow a seventh, more powerful verb every time someone hits a case the six don't cover elegantly. The discipline is in staying narrow. A transform operation that takes a JSONPath and a replacement value would make some edits more convenient and would also reopen exactly the hole the whole design exists to close — because now there's an operation whose effect depends on evaluating an expression against arbitrary current state, rather than an operation whose effect is fully determined by its own fields. Every operation type added to the vocabulary should be judged by whether it can still be validated, previewed, and rule-checked with the same rigor as the other six — not by whether it would be convenient.
Why this generalizes past architecture tools
None of the reasoning above is specific to architecture models, and that's worth being direct about, because the pattern is more useful the more clearly you see it as general. Any time you want an AI assistant to propose changes to something structured and consequential — where a mistake is expensive, hard to undo, or affects other people — the same shape applies: define a small, closed set of operations that fully describe the space of legitimate changes, require every proposal to be expressed in that vocabulary and nothing else, and run it through layered validation before anything is committed.
A database schema migration tool could adopt exactly this shape: addColumn, dropColumn, renameTable, addIndex, rather than letting an AI assistant write raw DDL that gets executed directly against production. An infrastructure-as-code assistant could propose typed changes to a resource graph — add this security group rule, attach this policy, remove this route — validated against the cloud provider's actual constraints and the organization's guardrails before apply ever runs, instead of generating a Terraform diff a human has to read cold and trust. A legal-document assistant could propose typed edits to a contract's defined terms and clauses rather than freely rewriting paragraphs, so that a change to the definition of "Confidential Information" can be checked against every clause that references it before it's accepted. A financial model or a spreadsheet-backed planning tool could expose addLineItem, updateFormula, renameSheet as its only vocabulary, so a proposed change can be checked for circular references and formula validity before it lands, rather than after someone notices the totals stopped adding up.
What all of these share with the ModelPatch pattern is the underlying trade described earlier: giving up some raw expressiveness in exchange for the ability to validate, preview, and govern. That trade is worth making almost anywhere the cost of an unreviewed mistake is higher than the cost of asking the AI to express its intent in a smaller vocabulary. It's a bad trade in a scratch notebook where nothing matters if it's wrong. It's close to mandatory anywhere the change is going to affect a shared, structured, consequential system that other people depend on being correct.
Where the pattern has real limits
It's worth being honest about what this doesn't solve, because overselling a governance pattern is how organizations end up trusting it more than they should. A closed operation set constrains how a change can be expressed; it doesn't guarantee the change is a good idea. An AI assistant can propose a patch that's perfectly valid — well-formed, metamodel-legal, rule-compliant — and still architecturally wrong in a way none of the automated layers can catch, because judging whether something is a good idea requires context about the business, the roadmap, or a decision made in a meeting last week that never made it into the model. That's exactly why human approval stays in the loop as a real gate, not a formality: the automated layers exist to make sure the only question left for a person is the one that actually needs judgment, not to replace the judgment itself.
The pattern also only helps to the extent the operation vocabulary genuinely covers the space of legitimate changes. A vocabulary that's too narrow pushes people toward workarounds — editing the underlying store directly to do something the patch format can't express, which quietly reopens the exact hole the pattern was built to close. Getting the vocabulary right, and keeping it closed under pressure to add convenient escape hatches, is most of the actual design work. It looks like a small, almost boring decision — six verbs instead of an open-ended edit surface — and it's the decision that makes every governance guarantee downstream of it possible.
In Mooodels, this is what happens every time an AI assistant proposes a change: it produces a ModelPatch, not a direct edit, and that patch goes through schema, metamodel, and rule validation before a human ever sees a preview to approve or reject. The AI never touches the model. It only ever gets to propose, in a vocabulary narrow enough that every proposal can be checked before it counts.
See the model this article describes, working in a real editor.
Try the live demo