Governed context for AI agents over enterprise knowledge graphs

Why agents fail on enterprise graphs, and what a context plane has to provide before an agent can be trusted with one.

· 9 min read

Most enterprises already have a world model. What they buy and sell, from whom, under what terms, and what breaks when a supplier fails — all of it is known somewhere. What is usually missing is a single place where that model is written down. It gets reconstructed instead, repeatedly and under time pressure, from an ERP, a procurement system, a supplier master, a contract repository and a dozen team-owned spreadsheets that were never designed to agree with one another.

Every consumer pays that reconstruction cost. Engineers have always absorbed it quietly, by knowing things. An LLM agent cannot, and that is why agents are the consumer that finally makes the missing model visible.

The gap an agent cannot close

An LLM agent is not an exotic consumer. It is an ordinary one that cannot compensate for a model that was never written down. Consider what an experienced analyst actually supplies when they query an operational database: they know which export is stale, which identifier joins to which, which table is authoritative when two disagree, and which query is safe to run against production at four in the afternoon.

None of that is in the schema. It is in the analyst. Hand the same database to an agent and every one of those compensations disappears at once. The agent will join on the wrong key, trust the stale export, and run the query that locks the table — not because it is a weak model, but because the information it needed was never written down anywhere it could read.

This makes agents a fair test of whether a governed metalayer exists at all. A model that can be served to an agent safely is one that has been made explicit, bounded and free of engine-specific dialect — and a model with those properties serves applications, analysts and aggregation layers better too.

Why a schema dump is not context

The instinctive response to “the agent needs to know the schema” is to send it the schema. On a real business this fails immediately, for three separate reasons.

It does not fit, and what fits is arbitrary. A four-hundred-type schema does not fit usefully in a context window alongside the task, the conversation and the tool definitions. Truncating it produces a slice chosen by serialization order rather than by relevance — the model is not told what was cut, so it cannot know that the type it needs was in the part that fell off the end.

It describes storage, not meaning. A dump names tables, columns and types. It does not say which types are entry points that can be looked up by a real-world key, which are hubs that most queries pass through, or which of two similarly named relations is the one that actually carries dependency. The agent is left inferring intent from naming conventions.

It grows without bound. Every new source makes the dump worse. Anything whose cost scales with the size of the business is the wrong shape for a context window, which does not.

Schema dumpordered by storage, not relevancecontext windowcut here — the agent is not toldCard, then budgeted subschemaranked by graph structurecontext windowschema cardhubs · entry points · relation vocabularysubschema · seeds: Supplier, Orderself-contained: no dangling referenceselided: term_count, 4 types (reported)headroom left for the actual task
Figure 1. The same budget, spent two ways. A dump is truncated by serialization order and says nothing about what was cut; a card plus a budgeted subschema fits, stays self-contained, and reports its own elisions.

What an agent needs is not the schema. It is an answer to two questions, in order: what can I ask? and then ask it.

The discovery surface: what can I ask?

The first question is answered by a bounded orientation layer rather than by a dump.

A schema card is a compact map of the model: entity counts, the hub types, the entry points an agent can look up by a real-world key, and the vocabulary of relations available. It is bounded by construction — it stays roughly the same size as the graph grows, because it summarises shape rather than enumerating content. It is the thing an agent should read first, and it is small enough that reading it first is free.

A budgeted subschema answers the follow-up. The agent names the types it cares about — say Supplier and Order — and receives a valid, self-contained schema slice trimmed to a token budget, ranked by graph structure: proximity, connectivity, identity quality. Two properties matter more than the ranking. The slice is valid, so every relation in it points at a type that is also in it — the agent never sees a dangling reference. And the elisions are reported, so the agent knows something was cut and can ask for more, rather than silently reasoning over a partial model it believes is complete.

That second property is the one most often skipped, and it is the difference between a model that says “I don't have enough here” and one that confidently gives a wrong answer.

The read surface: asking without the dialect

Once an agent knows what to ask, it should not have to learn a query language to ask it. Writing Cypher, GSQL or SPARQL is a generation task with a large surface for subtle error, and the errors are not obvious: a traversal with the wrong direction returns results, just the wrong ones.

The operations that actually matter for an agent are a short list — objects of a class with filters, the neighbors of an entity, bounded multi-hop traversal, and aggregations. Served as typed operations rather than as generated query text, they are checkable before execution, and they behave identically on every backend. The engine underneath stops being something the agent needs an opinion about.

This is also what makes the model portable across consumers. The same typed read that serves an agent serves a dashboard and an aggregation layer, because none of them were coupled to the dialect.

Enforcement, not documentation

An agent respects a limit only where the limit is enforced. A number in a README is a suggestion; a cap applied before the query reaches the database is a fact. The distinction is the entire security posture.

A defensible read policy has four properties:

  • Read-only by default, with writes a deliberate, separately granted capability rather than the natural consequence of having a connection.
  • Hard caps on traversal depth, result size, element count, query timeout and request rate — enforced ahead of the database, so a runaway traversal is refused rather than survived.
  • Policies that narrow and never widen. Where a local policy and the platform ceiling disagree, the stricter value wins. A policy can only ever tighten.
  • A safe unconfigured state. A connection with no policy attached should be read-only under the platform's caps. If forgetting to configure something opens it up, it will eventually be open.

In practice that is a small, declarative object attached to a connection — not code, and not a convention:

a read policy, with the platform defaults shown
read_only: true          # writes are a separate, granted capability
max_hops: 3              # traversal depth
max_rows: 1000           # rows returned
max_elements: 5000       # vertices + edges touched
timeout_s: 30.0
max_edge_types: 20
max_seeds: 10
rate_limit_per_min: 120
projection_allow_list:   # omit to allow every projection
  - procurement_read

Every one of those is checked before the query is compiled, so a request that would exceed a cap never reaches the engine — the distinction between refusing a traversal and surviving one.

One detail matters more than it sounds: when an agent exceeds a limit, it should receive an actionable message rather than a generic error. An agent told “result set exceeded the cap, narrow your filter or request a page” will self-correct. An agent told “query failed” will retry the same query.

Governance also has to hold at the level of which tools exist. A surface that merely hides tools from a listing is not gated — a client that already knows a tool's name can still call it. Gating means a tool outside the caller's surface is rejected when invoked by name, not just omitted from the menu. In GraFlo the consuming surface is ten read-side tools; the seventeen authoring tools that build and write models are a different audience, and a tool that declares no audience is denied rather than shared, so a new capability cannot leak into the consuming surface by omission.

MCP is one surface, not the only one

MCP is the right protocol for agents, and a context plane should speak it natively so any MCP-capable client can consume the model without custom glue.

But most consumers in an enterprise are not agents, and a context plane that only speaks MCP quietly forces every other consumer back onto raw database access — which is exactly the ungoverned path it was built to replace. The same model, the same policy gate and the same audit record should be reachable over an ordinary documented HTTP API, published as an OpenAPI specification. An application team generates a typed client from that specification and gets identical governed answers.

Serving both from one core is what keeps the governance claim true. Two planes with two policy implementations is two postures, and the weaker one is the real one.

How to evaluate a context plane

Questions worth asking of any candidate, including ours:

  • When the schema does not fit the budget, does the response say what was left out?
  • Is a returned subschema self-contained, or can it reference types it did not include?
  • Are limits enforced before the query reaches the database, or applied to the result afterwards?
  • Is a tool outside the caller's surface rejected when called by name, or merely hidden?
  • What happens to a connection with no policy attached — is the unconfigured state the safe state?
  • Can a non-agent consumer reach the same governed model, or does it have to go around it?
  • If you change graph engine, how much of this has to be rebuilt?

That last question is its own subject, and we take it up in database independence for knowledge graphs. The related problem of keeping the model correct as the business changes is the subject of version control for world models.

GraFlo's context plane is the model layer described here, served under a token budget and a policy gate — see GraFlo for the engine itself, or the glossary for the vocabulary used throughout this paper.