Schema Drift Reaches the Tool Definition
Context layer series Everyone who sells a context layer talks about freshness. Fresh rows, streaming…
Ask “how many active customers do we have in EMEA” in Claude Code, in Cursor, and in the ops team’s LangGraph app. You get three numbers. None of the agents is broken. Each one carries its own copy of what “active customer” means, learned from whatever repo, prompt, or memory it was pointed at. The fix is not a fourth agent. It is one governed context that all four call, and a gateway that knows who is calling: the person, and the agent acting for them.
Hover over each card to see the definition the harness was carrying. The fourth card is what the same question returns when every harness reads the definition from one governed Nexset instead of from its own memory.
Data engineer, working in the warehouse repo
4,812
Local definition: a row in subscriptions with status = 'ACTIVE', region from the account’s sales territory. Learned from a dbt model in the repo.
Product engineer, working in the app repo
6,140
Local definition: any login in the last 30 days, from the product analytics events table. Region from the user’s locale setting. Learned from a SQL snippet in a README.
RevOps analyst, using the internal ops assistant
5,377
Local definition: a paid invoice in the last 90 days, from the finance warehouse. Region from billing country. Written into the app’s system prompt in March and not updated since.
Any harness, same tool, same policy
5,102
Governed definition: customers_active_v3: paid subscription, no open churn notice, EMEA by billing country, refreshed hourly. Owner: RevOps. The same answer from every harness.
Counts are illustrative. The point is the spread, not the numbers.
Three of those definitions are reasonable. Each was right for the person who wrote it down. The problem is that nothing in the architecture makes the agents agree. A harness holds what it was told. It has no way to know that RevOps changed the churn rule in April.
This is the “many harnesses, one context” argument, and Atlan made it well in Claude Code and Codex in the Enterprise: The Context Gap (Aug 5, 2026): publish one portable context repo, serve it over MCP, and every agent reads the same definitions. We agree with that framing and will not repeat it. This post is about what has to be true of the context layer for it to work when the harnesses are not all yours, the users are not all the same, and the agents do not all have the same rights. That comes down to three things: identity, consistency, and handoffs.
Every harness has memory. Claude Code has its project files and session state. Cursor has rules and indexed code. A LangGraph app has a checkpointer. Copilot Studio has topics and variables. This is correct. Memory is where an agent keeps what it has concluded so far, and it is shaped by the loop that agent runs.
The memory vendors saw the three-answers problem and built sharing into memory. Mem0’s Multica shares a memory store across agents. Letta’s Conversations let several agents read and write one thread. Honcho markets a “shared brain.” Each of these is useful when the problem is “agent B should know what agent A learned about this user.” None of them touches the problem in the cards above, because the disagreement was never about what the agents remembered. It was about what “active customer” means, how fresh the number is, and who is allowed to see the EMEA rows.
Replicating memory is not sharing meaning. If Cursor’s definition of active customer lands in a shared memory store, the LangGraph app now has two definitions instead of one, and no way to tell which is authoritative. Worse, it has a wrong definition faster. The thing that has to be shared is not a conclusion. It is the governed object the conclusion should be drawn from: the Nexset, its schema, its refresh time, its lineage, its policy, and the tool that exposes it.
| Shared memory | Shared context | |
|---|---|---|
| What is shared | What agents concluded: facts about users, summaries, prior turns | What is true about the data: definitions, schemas, freshness, lineage, policy, tools |
| Who owns it | Whichever agent wrote last | The data owner (RevOps owns customers_active_v3) |
| When it changes | Every conversation | When the source, the definition, or the policy changes |
| Conflict handling | Merge, dedupe, decay | One version, one owner, versioned tool |
| Permissions | Per memory namespace, usually per app | Per user (rows, columns, residency) and per agent (tools, rates, writes) on every call |
| Freshness | Whenever an agent last updated the memory | Carried on the object: snapshot id, refresh time, stale flag |
| Failure mode | Stale or wrong conclusions spread to every agent | Stale context is visible as stale, and every agent sees the same version |
| Right tool for | Continuity inside one agent or one user relationship | Agreement across agents, teams, and harnesses |
Keep your memory stores. Stop asking them to do the context layer’s job. Memory is per-harness because it should be. Context is shared because it has to be.
In our June post on MCP security we wrote that the agent is invisible to the source system and the user is the identity: Salesforce sees Sarah, not the agent. That is still right for the credential. The source system gets Sarah’s rights, pushed down through the gateway, and never a shared service account. This post extends it. Once you have four harnesses, the gateway also has to know which agent is calling, because two agents acting for the same Sarah should not get the same tool set. So the model is credential pushdown plus agent scope. Two identities, two kinds of checks, one call.
Answers: whose data is this?
arr_usd, can they see email)Comes from your IdP. The harness relays it. The harness does not mint it.
Answers: what is this agent allowed to do?
Comes from agent registration at the gateway. A credential the harness holds, tied to one registered agent.
The key design point is that the gateway does not trust the harness to tell it either identity in plain text. The harness presents two credentials it cannot forge. The user credential is a token from your IdP that the gateway validates against the IdP’s keys, scoped to the user’s session (OIDC works fine here). The agent credential is issued when you register the agent: a client credential, a signed JWT with a key you rotate, or mTLS. The harness holds both. It can drop one. It cannot invent one. This is on-behalf-of delegation: the token says who the subject is and who is acting for them. Scalekit’s write-up (sub is the agent, act.sub is the user) and Salesforce’s identity propagation piece cover that pattern for generic APIs. Neither covers what the gateway does with both identities once it also owns the context. Here is the shape in plain terms.
{
"tool": "customers_active_v3.count",
"tool_version": "2026-08-11.2",
"arguments": { "region": "EMEA" },
"user": {
"token": "eyJ... (OIDC id token from your IdP, validated by the gateway)",
"sub": "sarah.k@acme.com",
"groups": ["revops", "emea-readers"],
"residency": "eu"
},
"agent": {
"token": "eyJ... (agent credential issued at registration)",
"id": "agent://acme/revops-assistant",
"harness": "langgraph",
"harness_version": "0.4.2",
"scopes": ["nexset:read", "nexset:customers_active_v3"]
},
"request_id": "req_01J8...",
"gateway_checks": [
"user token signature and expiry valid against IdP keys",
"agent token valid, agent registered, not revoked",
"tool is on this agent's allowlist",
"tool_version is current, else return the current version in the error",
"user has row rights for region=EMEA and column rights for the returned columns",
"residency=eu, route through the EU path",
"rate limit for this agent and this user not exceeded",
"write tools only: approval present if the policy requires it",
"log request_id, both identities, decision, snapshot id"
]
}
Each check is cheap. Together they turn “the harness said it was Sarah” into “the IdP says it is Sarah, the registration says it is the RevOps assistant running in LangGraph, and this combination is allowed to count EMEA customers but not to export their emails.”
Agent registration is the part most teams skip, so be concrete about it. Your IdP is the source of truth for people. The gateway’s agent registry is the source of truth for agents. For each agent, record an id, the owning team and an on-call contact, the harness and version it runs in, the tool allowlist, the rate limit and token budget, whether it holds write scope, and the credential with its rotation date. Treat a new harness pointing at the gateway the way you treat a new service asking for a database account: it gets registered, scoped, and reviewed, or it gets the unregistered defaults below. The registry is also what lets you revoke one agent without touching any user, which you cannot do when the only identity on the call is the person.
The interesting cases are the incomplete ones. Use the toggle to see what a gateway should allow and deny in each state. The rules are yours to set. The defaults below are the ones we recommend.
A valid user token, no registered agent credential.
Typical cause: someone pointed a new harness, a notebook, or a raw HTTP client at the gateway with their own login. The human is real. The agent is unknown.
agent = unregistered and the client’s user agent stringA valid agent credential, no user token.
Typical cause: a scheduled job, a service that runs without a person in the loop, or a harness that lost the user session and kept calling. Sometimes legitimate, often a bug.
user = noneBoth present and both valid.
The normal case. The gateway evaluates the user’s data rights and the agent’s tool rights, and the call proceeds only where both say yes.
Notice what the third state does. An admin user in a read-only agent cannot write. A write-capable agent acting for a read-only user cannot write. That is the whole point of carrying two identities. Either one alone is a single key that opens too much.
Once several agents share one context, you inherit the problems any shared system has. Three of them show up in the first week.
Nexset refresh starts. customers_active_v3 begins its hourly rebuild. Snapshot s-1187 is current. s-1188 is in progress.
Cursor agent reads. The gateway pins the read to s-1187 and returns the snapshot id with the result.
LangGraph agent reads the same Nexset. Pinned to s-1187 too. Same number, same snapshot, even though a rebuild is running.
Refresh completes. s-1188 becomes current. Both agents still hold s-1187 results, and both results say so.
Claude Code agent writes a churn notice on an EMEA account through a write tool. The gateway records the write and marks cached context for customers_active_v3 as invalidated for every harness.
Next read from any harness gets a stale flag on s-1187 and is served from the post-write state. Execution history from all three calls lands in one Context Engine.
If the context layer serves “whatever the table holds right now,” two agents reading ten seconds apart during a rebuild get different numbers and neither knows why. The fix is to pin every read to a snapshot and return snapshot_id and refreshed_at with the result. Two agents that read during the same window get the same snapshot, and an agent can see whether it read the same version as another before it argues about the number. This is the same discipline we argued for in the real-time context post, applied across harnesses instead of within one.
Harnesses cache. Cursor caches the tool list. A LangGraph checkpoint holds a tool result from three steps ago. If the Claude Code agent writes a churn notice, the LangGraph agent’s cached count is now wrong, and nothing in LangGraph knows it. The gateway is the only component that sees both the write and the later read, so invalidation lives there. Every write records which Nexsets it touched. Every later read of those Nexsets from any agent carries a flag that says the cached snapshot is behind, and the tool description tells the agent what to do about it (re-read, or proceed and say so). You cannot make the harness cache smart. You can make the context it caches self-describing.
Here is the upside of convergence. When four harnesses call one gateway, the gateway sees every call: which tools were used together, which arguments failed validation, which tool versions agents still request after a change. In Nexla that history is one of the inputs Helix uses to ground the next tool call, alongside schemas, samples, lineage, policies and business docs. We wrote about the compounding effect for one connector at a time. With many harnesses on one gateway, the compounding is cross-harness: what the ops assistant learns about customers_active_v3 improves the tool description the Cursor agent reads next week. Four memory stores cannot do that. They do not see each other.
None of this is expensive. Snapshot pinning is a version column and an id in the response. Invalidation is a small table keyed by Nexset id that the gateway consults on reads. The history feed is the audit log you already write, read back by the Context Engine. The cost of not doing it is four agents that quietly disagree, and a person in a meeting deciding which one to believe.
Agents hand work to each other. The ops assistant drafts a list of at-risk EMEA accounts and passes it to a Claude Code agent to open tickets. The default way to do this is to paste the text of the result into the next agent’s prompt. That breaks every property you just built. The text carries no snapshot id, so the receiving agent cannot tell it is stale. It carries no policy, so a user who sees the list in one harness can leak it to a user who cannot in another. It carries no tool version, so the receiving agent re-runs the query with a different definition and gets a different list.
Hand off references instead. A handoff envelope names the governed objects the next agent needs and says nothing about their contents. The receiving agent resolves each reference through the gateway, under its own agent identity and the same user identity, so policy is evaluated again at the receiving end.
{
"handoff_id": "ho_01J8...",
"from_agent": "agent://acme/revops-assistant",
"to_agent": "agent://acme/ticket-writer",
"on_behalf_of": "sarah.k@acme.com",
"expires_at": "2026-08-19T18:00:00Z",
"context_refs": [
{
"kind": "nexset",
"id": "customers_active_v3",
"snapshot_id": "s-1188",
"filter": { "region": "EMEA", "risk_score_gte": 0.7 }
},
{
"kind": "tool",
"name": "support.create_ticket",
"version": "2026-07-30.1"
},
{
"kind": "policy_scope",
"id": "emea-readers",
"note": "receiving agent inherits no rights from the sender, gateway re-evaluates"
}
],
"task": "Open one P2 ticket per account in the referenced set. Dry run first.",
"do_not_include": ["row contents", "credentials", "prior conversation text"]
}
Look at what the envelope does not contain. No rows. No tokens. No prior chat. The ticket-writer agent calls the gateway with the Nexset id and snapshot id. If Sarah cannot see a row, the ticket-writer does not see it either, regardless of what the RevOps assistant saw. If support.create_ticket has moved to a newer version, the gateway says so and the receiving agent refreshes. If the snapshot is behind because someone wrote to the Nexset in between, the stale flag comes back with the read. The handoff is small, auditable, and cannot carry more privilege than the gateway grants at the receiving end.
This matters most when the two agents live in different harnesses owned by different teams. Ops runs the LangGraph app, engineering runs the Claude Code skill, and neither reads the other’s code. They agree on the gateway, and the gateway enforces the same policy on both sides.
Here is the shape we recommend, and the one Nexla runs. N harnesses on top. One MCP gateway that validates both identities, enforces policy, pins snapshots, and writes the audit log. One Helix behind it, grounding every tool call with schemas, samples, lineage, policies, business docs and execution history. Many sources underneath, reached with the user’s pushed-down credential. The animation shows one call travelling from a harness through the gateway to a source and back, and the context and execution history loop between the gateway and Helix.
Three placement decisions matter more than the boxes.
Audit lives at the gateway, not in the harness and not in the source. The harness logs what it thinks it did. The source logs what Sarah did, because that is the identity it saw. Only the gateway saw both identities, the tool version, the snapshot id, the policy decision, and the diff. One log, one schema, queryable across harnesses. When someone asks “which agent changed this account and who was it acting for,” you answer from one place.
Policy lives in the context layer, not in the harness. If you put a row filter in the Cursor rules file, the LangGraph app does not have it. If the Nexset carries the policy, every harness gets it on every call, including the plain HTTP client someone writes next quarter. This is also why MCP Studio enforces read-only at the connector layer and keeps credentials in Token Vault rather than in any harness’s config: the harness cannot loosen what it never held.
Semantics live in the tool, generated from the governed object. The tool description the Cursor agent reads and the one the Copilot Studio agent reads are the same bytes, generated from the same Nexset, at the same version. When RevOps changes the churn rule, the tool changes, and every harness picks it up on the next call. That is the argument from the second post in this series, and the multi-harness case is where it pays off most. You update one thing and four agents agree.
The test is simple to state. Take one user, one question, and four clients: Claude Desktop, Cursor, a LangGraph node, and curl. The answer and the policy outcome should be identical. If they differ, the harness is carrying context it should not own. Work through these.
Run this as a script, not as a one-time review. One user token, one agent credential per client, one tool call, and a diff of rows, columns, snapshot id and policy decision across the four clients. Put it in CI so it runs whenever a Nexset, a tool, or a policy changes. A context layer that passes today and fails after the next churn rule change was never harness-agnostic. It was lucky.
Item eleven is the one teams fail. It is also the one that tells you whether you have a context layer or a very well organized set of prompts.
You will not standardize on one harness. Engineers will use what ships in their editor, ops will build what fits their workflow, and business users will use what their suite gives them. That is fine. What you standardize on is the context: one governed definition per business object, one policy evaluated against two identities on every call, one version of the truth with a snapshot id on it, and one audit log that sees all of it. The memory stays in the harness. The meaning leaves it.
MCP Studio generates task-specific servers from governed Nexsets, enforces read-only at the connector, keeps credentials in Token Vault, and logs every call with the identities that made it. Bring your Claude, your Cursor, your LangGraph app and your curl. The policy outcome should be the same. If it is not, we want to see the call.
Read next: MCP security: identity, credentials and policies and The context compounding effect.
Context layer series Everyone who sells a context layer talks about freshness. Fresh rows, streaming…
Context layer series MCP tool schema design is the practice of writing a tool’s name,…
Context layer series Two MCP servers sit in front of the same warehouse. You ask…