← All postsGoverned Agent Writes · Part 1

When an AI Agent Writes to Your System of Record

Synergy of AI and Knowledge GraphsAI AgentsMCPGovernanceSHACLLLMs

An agent that can only read is a demo. An agent that can write, update a record, assert a relationship, trigger a downstream decision, is a liability, unless the write path is governed. Most teams try to close that gap with prompting: longer system messages, more caveats, sterner instructions. Prompts are suggestions. Production systems need mechanisms.

This two-part series builds one such mechanism end to end, against a single running case study. This part sets up the problem: what the system looks like before the agent touches it, what we are asking the agent to do, why an LLM is genuinely the right tool for the job, and the four distinct ways it fails at that job. Only then do the technologies show up, each one introduced as the answer to a failure you have already watched happen. Part 2 implements the whole thing in code.

The principle underneath it fits in one sentence:

Automate the drafting. Gate the asserting.

Let the LLM do what it is uniquely good at, reading messy input and drafting structured claims. Never let those claims become facts without passing a gate the model cannot talk its way through.

The moment that matters

Start at the end, with the person the whole system exists to serve.

A procurement manager at a mid-sized manufacturer has to release the Q4 production build. One of the sign-offs on that release is supplier clearance: every supplier feeding the build must be current on certifications, free of unresolved audit findings, and within the risk tolerance the compliance policy sets. They open the internal Supplier Risk Console and type the question they actually have:

“Is Acme Speciality Chemicals cleared for the Q4 build?”

The manager’s job here is not to be curious. It is to sign off, in writing, in a system that will be read back to them if something goes wrong eighteen months from now. That distinction drives every architectural decision in this series, so it is worth stating plainly:

A system that answers questions needs to be useful. A system that supports a signature needs to be accountable. Those are different engineering problems, and only one of them is solved by a better model.

Hold that scene. We will come back to it twice: once when the naive system answers it, and once when the governed system does.

The job we are trying to automate

Behind that question sits a quarterly grind.

Every quarter, suppliers send in disclosures: self-assessment questionnaires, ESG and conflict-minerals declarations, third-party audit summaries, certification renewals, sub-tier sourcing statements. For our manufacturer that is roughly 8,000 documents a quarter against six analysts. No two suppliers use the same template. Perhaps a third of the content is free prose. Several languages are in play. The documents arrive as PDFs, email bodies, and spreadsheet attachments with merged cells.

An analyst reads a disclosure, works out what actually changed, and updates the supplier’s record. The work is not glamorous, but it is genuinely cognitive: deciding that “the May labour-practices audit raised significant concerns” is material and “we continue to monitor our environmental footprint” is not, is a judgement call, not a regex.

The result is the situation every compliance function knows: tier-1 suppliers get assessed properly, tier-2 gets assessed late, and tier-3, where a surprising share of real risk lives, does not get assessed at all. Not because anyone decided it was unimportant. Because there were six analysts.

💡
Why this is genuinely an LLM problem Heterogeneous formats, free-text judgement, long tail of phrasings, no stable schema to parse against, and a volume-to-headcount ratio that no amount of hiring fixes. This is not a task where rules were working fine and someone wanted to use AI. Deterministic extraction has been tried here for twenty years; it handles the structured 40% and falls over on the rest. Reading messy prose and drafting structured claims is exactly what language models are unusually good at.

That is worth saying clearly, because the rest of this series is about constraining the model, and constraint arguments are easy to mistake for skepticism. The LLM is not the problem. The LLM is the only reason this job is automatable at all. The problem is what happens between the model’s output and the system of record.

The system before the agent touches it

Four artifacts define the starting state. A reader who holds these four in their head can follow everything that follows.

1. The graph is already there

This is not a greenfield project. The knowledge graph is the system of record for supplier risk, and it was populated long before anyone proposed an agent: supplier master data, the plant registry, materials, contracts, and certifications, loaded from the ERP and reconciled quarterly.

That matters more than it sounds. The agent is not building a graph from nothing, where any plausible output is an improvement. It is writing into a store that other systems already trust and query. The bar is not “better than empty.” The bar is “does not corrupt what is already relied upon.”

The graph uses the procurement ontology developed in Ontology Engineering That Ships, under the proc: namespace.

New to RDF? The sixty-second version

A knowledge graph built on RDF stores everything as triples: subject → predicate → object. "Acme has risk rating Medium" is one triple. Millions of them form a graph, because the object of one triple is the subject of another.

The snippets below are written in Turtle, the readable RDF syntax. Four things to know and you can read all of them:

  • proc: is a namespace prefix, shorthand for a long URL (here https://w3id.org/proc/). Every name is globally unique, which is what lets graphs from different systems merge without collisions.
  • a means "is of type". proc:Supplier_Acme a proc:Supplier reads as "Acme is a Supplier".
  • ; means "same subject, next predicate". . ends the statement.
  • "2026-12-31"^^xsd:date is a typed literal: the value plus what kind of value it is.

That is genuinely all you need for this series.

A fragment of the relevant state:

@prefix proc: <https://w3id.org/proc/> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .

proc:Supplier_Acme  a               proc:Supplier ;
                    proc:legalName  "Acme Speciality Chemicals BV" ;
                    proc:country    proc:NL ;
                    proc:riskRating proc:Medium .          # as of Q2 2026

proc:Plant_Munich   a               proc:Plant ;           # a plant WE operate
                    proc:country    proc:DE .

proc:CERT-8871      a                proc:Certification ;
                    proc:scheme      proc:ISO14001 ;
                    proc:heldBy      proc:Supplier_Acme ;
                    proc:validUntil  "2026-12-31"^^xsd:date .

Two details in there will matter shortly. Acme currently carries a proc:Medium risk rating. And proc:Plant means a plant we operate, not any industrial facility in the world.

2. The ontology defines what the words mean

An ontology is the machine-readable answer to “what is a Supplier, and what is it allowed to be related to?” A database schema constrains storage; an ontology defines meaning, and it travels between systems.

Our model reuses one pattern worth noticing, because it shapes the failure modes later. A supply relationship is not a direct edge from supplier to plant. It is reified as its own class, because the relationship carries attributes of its own:

proc:SR-4417  a                  proc:SupplyRelationship ;
              proc:supplier      proc:Supplier_Acme ;
              proc:commodity     proc:Surfactant_SC40 ;
              proc:suppliesPlant proc:Plant_Munich ;
              proc:tier          proc:Tier1 .

Acme supplies the SC-40 surfactant to our Munich plant, at tier 1.

The ontology says what the world is like. It does not say what a valid record looks like, and it deliberately cannot: OWL operates under the open world assumption, where a missing value means “not yet known,” not “invalid.”

For operational validation you need SHACL, the Shapes Constraint Language, which is closed-world and answers a different question: does this data conform? Here is the policy that governs supplier records, and the important thing about it is that it is policy you can read, review, and version, not code buried in an application:

@prefix sh: <http://www.w3.org/ns/shacl#> .

proc:SupplierShape a sh:NodeShape ;
  sh:targetClass proc:Supplier ;

  sh:property [
    sh:path     proc:riskRating ;
    sh:in       ( proc:Low proc:Medium proc:High ) ;   # no invented risk levels
    sh:maxCount 1 ;                                    # and exactly one of them
  ] .

proc:SupplyRelationshipShape a sh:NodeShape ;
  sh:targetClass proc:SupplyRelationship ;

  sh:property [
    sh:path     proc:suppliesPlant ;
    sh:class    proc:Plant ;      # must land on a plant WE operate
    sh:minCount 1 ;
  ] .

If you want the full discipline around writing, layering, and CI-gating shapes, that is its own subject and I have written it up separately in Continuous Semantic Validation. For this series, two shapes are enough.

4. The document that starts the trouble

Acme’s Q3 disclosure arrives. Trimmed to the parts that matter:

FROM:    compliance@acme-speciality.example
SUBJECT: Acme Speciality Chemicals - Q3 2026 Supplier Disclosure (ASC-2026-Q3)

3.2  Manufacturing footprint
     Production of the SC-40 surfactant line transferred to our Rotterdam
     facility in July, following the capacity expansion there.

4.1  Certifications
     Our ISO 14001 certificate (reg. 14001-NL-8871) lapsed on 30 June 2026.
     The recertification audit is scheduled for Q4.

7.1  Audit findings
     The May labour-practices audit at our Johor site raised significant
     concerns regarding contractor working hours. Remediation is underway.

Three short paragraphs. A competent analyst would extract one urgent fact, one location change, and one open finding, and would know which of the three blocks a production release. Let us see what the agent does with it.

The naive path

The obvious architecture is the one most teams build first, and it is not stupid. It is one hop:

Document
Acme Q3 disclosure PDF
LLM extraction
prompt: "extract supplier risk facts as triples"
Write to store
whatever came back, committed
no gate

The one-hop architecture. It works in the demo because the demo document is clean and the reviewer is the person who wrote the prompt.

The model reads the disclosure and drafts five claims. Here is the actual output, and it is worth reading closely, because it is not obviously wrong. It is fluent, well-formed, correctly typed RDF, and it would sail through a JSON-schema validator:

proc:CERT-8871  proc:validUntil  "2026-06-30"^^xsd:date .          # claim 1

proc:Supplier_Acme  proc:riskRating           proc:Catastrophic ;  # claim 2
                    proc:conflictMineralFree  true .               # claim 5

proc:SR-4417  proc:suppliesPlant  proc:Plant_Rotterdam .           # claim 4

# Claim 3 is what is NOT here: nothing retires the existing
# `proc:riskRating proc:Medium`, so Acme ends up carrying both.

Five claims. One of them is good. These five follow us through both posts, so they are worth a moment each:

1 · Correct The ISO 14001 certificate lapsed on 2026-06-30. This is the fact that matters. It is stated explicitly in the document, it is material to release clearance, and the model got it exactly right. Any design that blocks this fact from landing has failed. 2 · Invented value proc:Catastrophic is not a risk rating. The document said "significant concerns." The model, reasonably enough in English, escalated. But the compliance policy defines exactly three ratings, and downstream systems switch on them. A fourth value is not a severe rating; it is an unhandled enum case that will surface as a blank cell in a dashboard. 3 · Contradiction The old proc:Medium rating is still there. The model added a rating without retiring the previous one, because nothing told it a supplier may only have one. Acme now carries two contradictory risk ratings at once, and every query that reads proc:riskRating gets whichever the store happens to return first. 4 · Conflated entity "Our Rotterdam facility" is Acme's site, not our plant. The model read a possessive pronoun in a supplier's document and resolved it against our plant registry. proc:Plant_Rotterdam does not exist. The supply relationship now points into the void. 5 · Unfounded claim Nothing in the document mentions conflict minerals. The model inferred compliance from silence, which is the single most dangerous move an extraction system can make. Note that this assertion is structurally perfect: right subject, right predicate, right datatype.

The part that should worry you

Now re-read that list and ask a different question: how would anyone find out?

Nothing errored. No exception was raised, no log line printed red, no alert fired. The extraction “succeeded.” The document was processed. The queue moved. From the operator’s point of view, the pipeline is healthy and 8,000 documents a quarter are being handled by a system that used to need six people.

⚠️
Silent corruption is the actual failure mode An agent that crashes is an inconvenience. An agent that writes plausible-looking wrong facts into a trusted store, with no signal that anything happened, is a data-quality incident with a delay fuse. You discover it when someone asks a question the corrupted data answers confidently and incorrectly, which may be months later, and by then you cannot tell which facts to distrust.

Meanwhile the dangling proc:Plant_Rotterdam node quietly changes the answer to “which suppliers feed the Munich plant?”, and no one queries it in a way that reveals the break.

Why the obvious fixes do not hold

Everyone reaches for the same three fixes first. Each one helps. None of them closes the gap, and understanding why is the whole justification for the architecture in Part 2.

"Write a better prompt"

Adding "only use risk ratings Low, Medium, or High" to the system message does reduce claim 2. It does not eliminate it, because a prompt is a probability adjustment, not a constraint. More importantly it does not scale as policy: when compliance adds a fourth rating, you now have to find and update every prompt in every agent deployment, and you have no way to prove you got them all.

"Use structured outputs"

Constrained decoding and JSON Schema genuinely solve a class of problems, and you should use them. But look at the failure list again: every single one of those four errors is schema-valid. A schema can enforce that riskRating is a string from an enum. It cannot know that proc:Plant_Rotterdam is not a plant we operate, because that is a fact about the state of the graph, not the shape of the payload.

That second point is the crux, so let me put it directly: the constraints that matter here are relational, not syntactic. Whether an assertion is legal depends on what else is in the graph. proc:suppliesPlant proc:Plant_Rotterdam is a perfectly good triple in isolation. It is invalid only because our plant registry contains no such plant. No output-format mechanism can see that, because the information is not in the output; it is in the store.

The third fix is “have a human review everything,” which does work. It also returns you to six analysts and 8,000 documents, which is where we started. Human review is a scarce resource, and the design question is not whether to use it but how to spend it only on the cases that need it.

The mechanisms, and what each one is for

Now the technologies can show up, and each one has a job you have already seen it needs to do.

The graph is the substrate, because constraints need context

We covered this implicitly, but state it: the reason a knowledge graph is the right store here is not that graphs are fashionable. It is that claim 4 can only be caught by something that knows what else exists. A graph makes the surrounding context queryable at validation time, which is what turns “is this triple well-formed?” into “is this assertion legal given everything we already know?”

SHACL is executable policy, and it catches claims 2, 3 and 4

Point the shapes from earlier at the drafted output and three of the five problems fail immediately, with machine-readable reasons:

Drafted assertion Shape constraint Result
2 riskRating proc:Catastrophic sh:in ( Low Medium High ) Violation · value not permitted
3 second riskRating on the node sh:maxCount 1 Violation · cardinality exceeded
4 suppliesPlant proc:Plant_Rotterdam sh:class proc:Plant Violation · not a proc:Plant
1 validUntil "2026-06-30" xsd:date, single-valued Conforms · the true fact lands

No matter how fluent the argument for it was, a hallucinated edge to a non-existent plant cannot land. And when policy changes, you edit one shape file, versioned in git, rather than re-litigating a prompt across five agent deployments.

Provenance is what makes mistakes survivable, and it is why claim 1 is trustworthy

SHACL says an assertion is legal. It says nothing about whether it is true. Claim 1 passed validation, but so would a fabricated expiry date of 2027-01-01.

The answer is to make every committed fact carry its own receipt, modelled with PROV-O: what evidence it came from, which model version drafted it, when, at what confidence, and who if anyone reviewed it. Provenance is stored as ordinary graph content, which means it is queryable with the same SPARQL as everything else.

The payoff arrives on the bad day. A model release starts asserting nonsense on a Tuesday. With provenance, “find and revoke everything model X asserted since Tuesday” is a query you run before lunch. Without it, it is an archaeology project across your entire graph, and the honest answer to “which facts can we still trust?” is “we don’t know.”

Evidence and confidence handle claim 5, which no shape can catch

Claim 5, the unfounded conflict-minerals assertion, is the interesting one, because it is structurally valid. It will pass every shape you write. There is no constraint expressible in SHACL that distinguishes “the model read this in the document” from “the model inferred this from silence.”

So it needs a different mechanism entirely: a mandatory evidence field, a confidence score, and a router that sends the grey zone to a human.

Two gates, not one SHACL answers "is this assertion structurally legal?" Confidence routing answers "is this assertion sufficiently supported?" They are independent questions and they need independent mechanisms. Systems that build only the first one catch hallucinated entities and let unfounded claims straight through, which is the more dangerous of the two failure classes because it looks like clean data.

Where you set the thresholds is not a model parameter, it is a product decision about how much curation capacity you have and what a wrong fact costs. In compliance, a false assertion is expensive; thresholds sit high and the queue earns its salary.

MCP is the boundary, because capabilities should be enumerable

The last piece is where all of this gets enforced. The Model Context Protocol standardises how an agent discovers and calls tools, and it has an under-appreciated consequence:

The agent's effective capabilities are exactly the tools you expose. Nothing more.

That makes the MCP server the natural choke point. You are no longer hoping the model behaves; you are deciding what behaviours exist. The design rule is reads generous, writes narrow:

  • query_graph · read-only SPARQL against the live graph. Let the agent explore freely; exploration is where agents earn their keep.
  • propose_fact · the only write path. It accepts a typed candidate assertion plus evidence and a confidence score. It does not write. It proposes.

Because MCP separates the tool contract from the model, the same governed surface serves every agent framework you run: one gate to maintain, audit, and harden, instead of one per integration. And because the boundary is a single point, it is also where your metrics live.

The architecture

Here is the whole system. Each named component is implemented, with code, in Part 2, so this diagram doubles as that post’s table of contents.

A · INTAKE & EVIDENCE B · BOUNDARY & GATE C · COMMIT & SERVE Disclosures PDF · email · forms ~8,000 / quarter Evidence layer chunk + embed · entity link hybrid search over corpus Drafting agent LLM · reads freely drafts candidate assertions Policy repository ontology (OWL) + SHACL shapes versioned in git · reviewed MCP SERVER · THE BOUNDARY query_graph read-only SPARQL over the live graph · ACL-scoped propose_fact candidate triple + evidence + confidence SHACL gate validate against graph + candidate IS IT LEGAL? Confidence router threshold policy IS IT SUPPORTED? tool calls shapes loaded as policy legal violation report → retry Curation queue grey zone → human decides decisions become training data Provenance ledger PROV-O stamp: evidence, model version, time, reviewer Knowledge graph system of record Supplier Risk Console "Is Acme cleared for Q4?" every claim cited & re-runnable the signature happens here confident → auto-commit grey zone → human review approved
The governed write path. Agents draft (band A), shapes and thresholds decide (band B), provenance remembers and the graph serves (band C). The only route from model output to system of record runs through the boundary, the gate, the router, and the ledger.

The shape of the argument is visible in the picture: there is exactly one arrow from the agent into the graph, and it passes through two independent checks and a provenance stamp on the way.

Where this leaves the procurement manager

Return to the sign-off. In the governed system, the console answers:

Acme Speciality Chemicals · NOT CLEARED for Q4 build Blocking: ISO 14001 certification lapsed 2026-06-30. Source: ASC-2026-Q3 §4.1 · asserted 2026-07-02 by kg-writer-agent v1.4 · confidence 0.94 · shapes proc-shapes v3.2 · reviewed by A. Patel

One blocking fact, sourced to a section of a named document, stamped with the model and shape versions that produced it, and reproducible: run the query tomorrow and get the same answer. The manager can click through to §4.1 and read the sentence themselves.

The three bad assertions never landed. The unfounded conflict-minerals claim is sitting in a queue with a human’s name on it. And the fact that mattered, the one a human analyst would have flagged, is the one driving the decision.

The end user is not trusting the model. They are trusting the gate. Trust is not a property the interface projects at read time. It is a property the write path enforced months earlier.

What Part 2 builds

This post argued the case. Part 2 implements it, component by component, against this same Acme document:

The evidence layer Where retrieval actually fits. RAG is not the governance mechanism here; it is how the agent gathers the evidence it will be held to, and why every proposal must quote its source verbatim. The join between chunks and graph entities is the subject of Giving RAG a sense of time. The boundary The MCP server in full: tool schemas, why propose_fact takes the arguments it takes, and how the read/write asymmetry is enforced at the network layer rather than requested in a prompt. The gate pySHACL validating a candidate against the live graph without committing it first, which is harder than it sounds. Turning a validation report into feedback an agent can act on, and the retry loop that resolves most rejections in one pass. The ledger & the queue PROV-O provenance in named graphs, the revocation query for the bad Tuesday, threshold policy, and the curation queue as a training-data flywheel. The outcomes Both pipelines run on the Acme document, side by side, with the actual outputs and the actual console answers, so the difference is visible rather than asserted.

This is the neuro-symbolic case made concrete: neural fluency is not reliability, so you don't fix it with a better prompt, you give the model a symbolic backbone it can't argue past. Neural drafts, shapes decide, provenance remembers. The retrieval half of the same pattern is GraphRAG with temporal knowledge graphs; the construction half is the entity-linking flywheel; the ontology and shapes underneath it come from the Agile Ontology Engineering series. A minimal open wireframe of this architecture lives at SHACL-Gated Agent Writes.