Read Context Is Solved. Write Context Is Not.

Read Context Is Solved. Write Context Is Not.
Context layer series

Write context is the information an AI agent needs before it changes data: which values a field accepts, which fields are safe to write, what the change triggers downstream, whether a person has to approve it, and whether a retry is safe. Read context tells an agent what data means. Write context tells it what a write will do, and it has to be enforced at the tool, not requested in the prompt. Most context layers, ours included, only carry the first kind. This post defines the second kind and shows how to build it.

Here is the failure in one exchange. Ask an agent “what is Acme’s ARR” and a decent context layer gets you the right number. It knows that arr_usd lives on the account object, that it is in dollars, and that it comes from the billing feed. Now ask the same agent to “fix Acme’s ARR, it should be 240K.” Watch what happens.

Read

“What is Acme’s ARR?”

The agent finds arr_usd, knows the unit and the source, returns the right number. Read context did its job.

Write

“Fix Acme’s ARR, it should be 240K.”

The agent writes 240000. The call succeeds. Nothing told it the field is derived nightly from invoices, is read-only downstream, and feeds three dashboards and a commission run. By morning the billing job overwrites it and nobody knows an agent touched it.

That is a write context failure. The agent had everything it needed to read the field. It had nothing it needed to change it.

Every context layer on the market today, ours included, was built for the first question. This post is about the second one.

What is write context, and how is it different from read context?

When an agent reads, context answers “what does this mean.” When an agent writes, context has to answer “what will this do.” Take the same field, arr_usd, and look at what the agent needs in each case.

Read context

Answers: what does this mean?

  • Definition of the field
  • Unit and currency
  • Where the value comes from
  • How fresh it is

Schemas, glossaries and lineage give you this. Every vendor stops here.

Write context

Answers: what will this do?

  • Does the field accept direct writes at all
  • Which values are valid, which are deprecated
  • What else has to change with it
  • What depends on it downstream
  • Whether a person has to sign off
  • Whether a retry is safe, and how to undo it

Almost none of this is in a schema. Some is in app validation code. Most is in someone’s head.

The stakes are also different. A read failure gives you a wrong answer that a person can catch. A write failure changes state. It compounds. It triggers workflows. It shows up in reports a week later with no trace back to the tool call that caused it.

This is where agent pilots die. Teams get an agent answering questions in a few weeks. Then they try to let it update a ticket, change a subscription, or move a deal stage, and the project stalls for months because nobody can say what the agent is allowed to change and under what conditions.

What does an agent need to know before it writes? The six things write context carries

Here is what an agent needs to know before it calls a tool that mutates data. If your context layer cannot supply these, your write tools are running on luck.

1Constraints and allowed values

Not just the type. The actual set of values the target system accepts, the ones your business uses, and the ones that are deprecated. A status field that is a string in the schema is really an enum of six values, and one of them (“Churned”) triggers an offboarding workflow. The agent has to see the enum and the consequence, not “string.”

2Source of truth versus derived

Which fields this system owns and which are copies. Writing to a derived field is at best a no-op and at worst a silent conflict with the upstream job. Lineage already tells you this. Nobody exposes it to the agent at write time.

3Business rules and validation

The rules the app enforces in code and the rules the ops team enforces by habit. A discount over 20 percent needs a manager. A close date cannot move backwards. An account cannot be Active without a billing ID. The agent needs these as part of the tool, not as a paragraph in a system prompt it forgets by turn eight.

4Idempotency and retry semantics

Agents retry. Networks flake. If a write is not idempotent, a retry creates a duplicate order or double posts a comment. The tool has to say whether repeating the call is safe, and if not, how to make it safe: an idempotency key, a precondition on the current value, a check before write.

5Approval and dual control

Some writes should never happen without a person. The context has to encode which ones, and the tool has to route them through an approval step rather than trusting the model to ask. “Please confirm before making changes” in a prompt is not a control. It is a suggestion.

6Consequence metadata

What reacts to this change. Which workflows fire, which downstream systems sync from this field, which reports use it. Read lineage tells you where a value came from. Write context needs the reverse: the blast radius.

Why can’t you put write context in the system prompt?

The obvious move is to write all of this into the system prompt. Teams try it. It fails for three reasons.

Prompts have no enforcement authority
A rule in a prompt is a request. The model follows it most of the time. Most of the time is not a control.
Prompts do not scale
Six items times two hundred writable fields across forty systems is not a prompt. It is a database. And it changes every time someone adds a status value or a validation rule.
Prompts do not know the current state
Is the account already Active? Did the billing job update this field an hour ago? The prompt is static. Write safety is not.

The context has to live next to the tool, be generated from the systems that own the rules, and be enforced at the point where the write actually happens.

How do you enforce write context? At the connector, not the model

Here is how we handle this in Nexla, and the pattern is worth copying whether or not you use our platform.

When you build a server in MCP Studio, every tool is scoped at the connector layer, not in the model’s instructions. In the pipeline risk agent tutorial we built a read-only server by blocking every write and DDL at the connector and whitelisting the columns each tool can filter on. The model can be as confused as it likes. The write does not go through.

Write tools use the same mechanism in the other direction. Instead of blocking writes, the connector enforces the write context: allowed values, preconditions, approval routing, and audit. The tool the agent sees carries the semantics. The connector carries the enforcement. The model sits in between and does what it is good at, which is figuring out intent.

Agent

Figures out intent. Reads the tool definition on every call.

Tool (semantics)

Real enums, deprecated valuesWhich identifierDerived vs source of truthConsequence in one sentenceDry run by default

Gateway (enforcement)

Value in enumField is writablePrecondition on current state holdsApproval present if requiredCaller has rights to this row

Source system

The write lands only after every check passes.

Every step, pass or fail, goes to the audit log with agent identity, user identity, and the diff. The moving dot is the call. It stops at the gateway when a check fails.

Three things make this work.

Dry run by default. Every write tool has a mode where it validates and reports what would change without changing it. The agent uses it first, shows the user the diff, then commits. This is cheap to build and catches most mistakes.

Read-only tool sets as a first step. Ship the agent with reads only, watch what it tries to do, then add the write tools it actually needs with the write context those specific tools require. Least privilege per tool, not per server.

Policy checks before the write hits the source. All of the checks in the gateway box above run before the write, and the result goes into the audit log either way.

One tool, built two ways

Here is a write tool for a support platform, first the way most teams ship it, then with write context.

Bare works, and lets the agent do damage
{
  "name": "update_customer_status",
  "description": "Update the status of a customer account.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customer_id": { "type": "string" },
      "status": { "type": "string" }
    },
    "required": ["customer_id", "status"]
  }
}
Grounded generated from the data product and its policies
{
  "name": "update_customer_status",
  "description": "Change the lifecycle status of a customer account. Setting status to CHURNED triggers offboarding and revokes access within 15 minutes. Setting status to ACTIVE requires an existing billing_id on the account. Use dry_run=true first and show the user the returned diff before committing.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customer_id": {
        "type": "string",
        "description": "Canonical customer identifier (cust_id_v2). Not the CRM account ID and not the billing account number."
      },
      "status": {
        "type": "string",
        "enum": ["TRIAL", "ACTIVE", "PAUSED", "CHURNED"],
        "description": "Target status. SUSPENDED is deprecated, use PAUSED."
      },
      "reason_code": {
        "type": "string",
        "enum": ["NON_PAYMENT", "CUSTOMER_REQUEST", "FRAUD", "MIGRATION", "OTHER"],
        "description": "Required when status is PAUSED or CHURNED."
      },
      "expected_current_status": {
        "type": "string",
        "description": "The status you last read. The write fails if the account changed since, so you never overwrite a concurrent update."
      },
      "dry_run": {
        "type": "boolean",
        "default": true,
        "description": "When true, validates and returns the diff and downstream effects without writing."
      }
    },
    "required": ["customer_id", "status", "expected_current_status"]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": true
  },
  "x-nexla": {
    "source_of_truth": "support_platform.accounts.status",
    "downstream": ["billing.subscription_state", "analytics.customer_health", "workflow.offboarding"],
    "requires_approval_when": { "status": "CHURNED" },
    "audit": "always"
  }
}
Identifier disambiguatedEnum is real, deprecated value markedConditional requirement statedPrecondition guards concurrent writesDry run is the defaultConsequence in one sentenceExtension block declares source of truth, blast radius, approval rule

The bare tool works. It also lets the agent set status to “actve”, set a Churned account back to Active without a billing ID, and trigger the offboarding workflow by accident. Every one of those calls returns 200.

Here is the same call through the grounded tool in three states. Click through them. This is the whole life of one write.





Dry run response what the agent shows the user before committing
{
  "mode": "dry_run",
  "would_change": { "status": { "from": "ACTIVE", "to": "CHURNED" } },
  "side_effects": [
    "workflow.offboarding will start",
    "billing.subscription_state will move to CANCELLED at next sync (hourly)",
    "access revoked within 15 minutes"
  ],
  "approval_required": true,
  "approver_group": "cs-managers",
  "blocking_errors": []
}

Nothing changed. The agent shows this diff to the user and asks one question: do you want to proceed. That question is backed by a real list of consequences, not a guess.

Commit response after a CS manager approved in the approval queue
{
  "mode": "commit",
  "status": "applied",
  "changed": { "status": { "from": "ACTIVE", "to": "CHURNED" } },
  "approval": { "id": "apr_7f3k", "approved_by": "m.okafor", "at": "2026-08-18T09:41:12Z" },
  "precondition_checked": { "expected_current_status": "ACTIVE", "actual": "ACTIVE" },
  "audit_id": "aud_01J5X9",
  "actor": { "user": "s.park", "agent": "support-copilot@v12", "harness": "claude-desktop" }
}

The write landed because every check passed: the value was in the enum, the field was writable, the precondition held, the approval existed, and the caller had rights to the row. The audit record names both the person and the agent.

Rejected the billing job changed the row between read and write
{
  "mode": "commit",
  "status": "rejected",
  "reason": "PRECONDITION_FAILED",
  "detail": "expected_current_status=ACTIVE but account is PAUSED (changed 00:02:14 ago by billing.sync)",
  "hint": "Re-read the account and present the new state to the user before retrying.",
  "audit_id": "aud_01J5XA",
  "actor": { "user": "s.park", "agent": "support-copilot@v12", "harness": "claude-desktop" }
}

This is the case that quietly corrupts data when the tool is bare. Here the gateway refuses, tells the agent why, and logs the refusal. The agent re-reads and asks again. Nobody has to notice a problem a week later.

The agent shows the dry run to the user. The user says yes. The approval step routes to a CS manager. Only then does the commit call run, and the audit log records the whole chain. If the row moved underneath the agent, the gateway says no and says why.

None of the semantics in that tool were hand written. They came from the schema (types), the observed data (enum values), lineage (source of truth and downstream), the platform’s own validation rules (the billing_id precondition), and the policy layer (approval). That is what a context layer for writes produces: not a knowledge graph diagram, but a tool the agent cannot easily misuse.

MCP Studio Edit tool panel showing the Guard-rails section with a Read-only toggle that blocks write, DDL, and DML at the connector layer and a Requires approval toggle set to on
Guard-rails on a single tool in MCP Studio. Read-only blocks write, DDL, and DML at the connector layer. Requires approval pauses the call until a human approves it. Both live on the tool definition, not in the prompt.
Activity tab on an MCP server showing a recent activity log with a Created event and a Metadata Updated event, each attributed to a user with the exact change recorded
Every change is an event with an actor and a record of what changed. The metadata update shows the old description next to the new one, which is what an audit trail for context looks like.

Where does write context come from?

The grounded tool above is the output. The input is six sources, the same six that produce good read tools, read in a different direction. Hover each card to see what it contributes to a write.

Schema and samples

Types and the real set of values. Sampling 10,000 rows of the accounts table shows four live status values and one that has not appeared since last year. That is where the enum and the deprecated flag come from.

Lineage

Which field is owned here and which is a copy. The CRM’s arr_usd is a nightly copy from billing, so the write tool does not expose it. The same graph gives the downstream list: what reacts when status changes.

Application validation

The rules the app already enforces in code. “ACTIVE requires billing_id” exists in the app’s validation layer. The connector reads the error contract and turns it into a precondition the agent sees before calling.

Business docs

Policies written for humans. “Churn reversals need a CS manager” lives in a runbook. Helix ties that sentence to the status field and it becomes an approval rule on the tool.

Execution history

What past calls did. If 40 percent of calls that set PAUSED omitted reason_code and failed, the tool makes reason_code conditionally required and the description says so.

Access policy

Who is allowed to write which rows. Row and column scoping apply to writes the same way they apply to reads, and the agent identity adds a second layer: this agent is allowed to call this tool at all.

None of these is new. Every one of them already exists in your stack. The work is pulling them together at the point where the tool is generated, and keeping them attached when the source changes. That second part is the subject of the third post in this series.

Three objections, answered

“We already have approvals in the app.”

You do, for humans using the UI. The agent calls the API. Most application APIs skip the UI’s approval flow because the API was built for integrations that were trusted by default. The agent is not trusted by default. Either the API grows an approval step, or the gateway adds one before the call reaches the API. The gateway is faster to ship and it works across all forty systems at once.

“Isn’t this just RBAC?”

RBAC answers who can write. Write context answers what a correct write looks like and what it causes. A user with full rights to the accounts table can still set status to a value the workflow engine does not understand, or overwrite a row that changed two seconds ago. RBAC does not stop either. Enums, preconditions, and consequence metadata do.

“Just sandbox the agent and replay the writes.”

Sandboxes are good for testing and useless for production writes, because the point of the write is that it lands. A dry run against the real system with real policy checks gives you the safety of a sandbox with the truth of production. That is why dry run is the default mode, not a separate environment.

How to roll this out in four weeks

You do not need to do every system at once. Here is the order that worked for us and for the teams we have watched do it.

Week 1

Ship reads only

Build the server with read tools, turn on the audit log, and let the agent run. Collect every attempted write the agent wanted to make. That list is your requirements document.

Week 2

Pick one write, ground it

Take the most common attempted write. Pull the enum from samples, the downstream list from lineage, the rule from app validation, the approval from the runbook. Generate the tool. Run it in dry run mode only.

Week 3

Turn on commit behind approval

Enable commit for that one tool with approval required on every call. Watch the rejections. Each rejection is either a bug in the grounding or a real save. Fix the first kind, celebrate the second.

Week 4

Relax approval where the data says you can

Look at four weeks of audit. Writes that were approved 100 percent of the time with no downstream incident move to auto-approve with a precondition. Writes that got rejected keep the human. Then pick the next write and repeat.

The pattern compounds. Each write tool you ground produces enum, lineage, and policy facts that the next tool reuses. By the fifth tool you are mostly clicking approve on generated definitions.

Write-readiness checklist

Before you expose any tool that mutates data to an agent, check these.

  1. Every parameter with a finite set of valid values is an enum, and deprecated values are marked.
  2. Every identifier parameter says which identifier it is.
  3. Derived and read-only fields are not writable through the tool at all.
  4. Conditional requirements (field B is required when field A is X) are stated in the schema, not implied.
  5. The tool has a dry run mode and the description tells the agent to use it first.
  6. Writes carry a precondition on the current value or an idempotency key.
  7. Approval rules are declared on the tool and enforced at the gateway, not requested in the prompt.
  8. The description states the consequence of the write in plain language.
  9. Every call, including dry runs and rejected calls, is logged with agent identity, user identity, and diff.
  10. The tool was generated from the data product and its policies, so it updates when they do.

If you can tick all ten, your agent is ready to write. If you can only tick the first four, keep it read-only and fix the rest.

Frequently asked questions

What is write context for AI agents?

Write context is the information an agent needs before it mutates data: allowed values, derived versus source-of-truth fields, business rules, idempotency and retry semantics, approval requirements, and the downstream blast radius of the change. It lives on the tool definition and is enforced at the gateway.

Can AI agents safely write to enterprise systems?

Yes, when the write tool carries real enums, preconditions, and consequence metadata, and the gateway enforces policy and approval before the write reaches the source. A bare write tool with two string parameters is not safe, because every bad call still returns 200.

What is a dry run in an agent write tool?

A dry run validates the write and returns the diff, the side effects, and any blocking errors without changing anything. The agent shows that diff to the user first, then commits. Make dry run the default mode on every write tool.

How do you stop an agent from overwriting a concurrent change?

Require a precondition parameter such as expected_current_status. The gateway rejects the write if the record changed between the agent’s read and its write, tells the agent why, and logs the refusal. The agent re-reads and asks again.

Is write context just RBAC?

No. RBAC answers who is allowed to write. Write context answers what a correct write looks like and what it causes. A fully authorized user can still set an invalid status value or trigger an offboarding workflow by accident. Enums, preconditions, and consequence metadata stop that, RBAC does not.

Do agent writes need human approval?

Some do. Declare the approval rule on the tool and enforce it at the gateway, then relax it where the audit log shows a write was approved every time with no downstream incident. A prompt that says “please confirm before making changes” is a suggestion, not a control.

What comes next

Read context got solved because it was the easy half. The schemas, glossaries and lineage were already there. Write context is harder because the rules live in application code, in ops habits, and in the heads of the people who run the systems. Pulling that into the context layer and enforcing it at the tool is the work.

We are building this into MCP Studio now

Write tools get the same treatment read tools already get: generated from the governed data product, scoped at the connector, logged end to end. If you are trying to move an agent from answering questions to taking actions and hitting the wall described here, get in touch. We want to see the write that scared you.

Try out MCP Studio

Read next in this series: The Tool Description Is the Context, Schema Drift Reaches the Tool Definition, How to Evaluate a Context Layer, and Whose Identity Is Calling? Background: Task-specific vs system-specific MCP servers, The C in MCP, and the Helix Context Layer.


You May Also Like

A Guide to AI Readiness
Intercompany Integration Overview

Join Our Newsletter

Share

Related Blogs

The Data Layer Your AI Is Missing

Connect, contextualize, and govern enterprise
data across 1000+ systems in real time.