In part 5, I wrote about binding an intent to the state it was based on. If the branch moved after the agent read it, the request should stop.

That check works. But it runs late.

By the time a request arrives at the write path, the agent has already decided what to propose. That decision was made earlier, based on whatever the agent happened to read.

And nothing in the system described what it had been allowed to read. It opened files, followed imports, and pulled in whatever looked relevant. The intent was formed from that.

A request can be perfectly bound to state and still be built on the wrong view of the world.

The earlier parts of this series treated reading as a precondition. The agent has to see something before it can propose anything. Reading was never treated as an operation that needs its own control.

I think that was a gap.

A read is not as passive as it looks

Start with the obvious version. Reading does not change the target.

Except that it often does. Opening an email can mark it as read. Fetching a record can bump a counter, write an access log, or cost money. Something changed, just by looking at it.

But that is the small problem.

The bigger one is that every read changes the agent. What it sees becomes what it knows, and what it knows becomes what it proposes next.

Read access is also not one permission.

Reading a repository is not the same as reading deployment secrets. Reading an email subject is not the same as downloading every attachment. Reading one record is not the same as exporting a table.

These are all called "read". The word is too coarse.

Whoever writes the content gets a say in the plan

Prompt injection is usually described as bad instructions tricking a model. It is also a question of what the agent was allowed to read in the first place.

A ticket, an email, or a file is not passive text to a language model. Content can read like an instruction. Task data and control signals arrive through the same channel.

So if an agent freely decides what to pull into its context, anyone who can leave text where the agent will look has a say in what it does next. Whether it is a comment on an issue, a line in a config file or in a support ticket from a stranger.

The email can be fetched correctly, be the current version, and come from a known sender. It can still carry instructions meant to steer the agent.

An allowed read is not a safe read.

It is worth being careful about what any boundary can promise here. It cannot control how a model reads text. There is no reliable way to clean natural language so that manipulation is gone.

What it can control is smaller. Which sources are read. Which fields come back. How much is returned. Whether the content is marked as untrusted.

The goal is not to declare the content safe. It is to know where it came from, and how much of it got in.

The maze makes this easy to see

Imagine an agent in a maze.

Giving it the full map is one way to answer a read request. Giving it only its current position and the cells next to it is another. Both may be enough to decide the next move. They expose very different amounts of information.

The agent can decide where it wants to go. It should not be able to hand itself a bigger map.

That is the distinction I want to look at here.

State view: what the agent was actually shown, and under what authority.
State binding: how that view connects to what it proposes.

This is about state that arrives through tools. Not the whole prompt, not memory, not everything the model happens to have in context.

This is not just read access

A sandbox can limit which files, networks, or processes an agent can reach. That stays necessary.

But reachability is not observation. Knowing a file could be reached does not tell you what the agent actually got, which version it was, or under whose permission.

A state view is narrower. It says which source was read, which fields came back, which version it was, and who allowed it.

A sandbox limits where the agent can go. A state view records what it was actually handed.

A read can still leak

A write is meant to change the external system. A read usually is not. That still does not make it free.

When an agent pulls content into its context, that content travels further than the system it came from. It can end up with a model provider, a runtime, a log, a trace, a tool server, or another sub-agent.

No target state changed. The information boundary changed.

That is a reach question, applied to reading.

What a state view looks like

If a state view is a controlled observation, something has to issue it and be able to check it later. The one thing it cannot be is the agent itself.

It also has to be written after the read came back, not when the read was allowed. Otherwise it only records what the agent could have seen, not what it actually got.

{
  "state_view_id": "sv_01J...",
  "view_version": 3,
  "source": {
    "system": "github",
    "repository": "example/app",
    "path": "config/deployment.yaml"
  },
  "observation": {
    "source_version": "blob:8f31c2",
    "fields": ["content"],
    "observed_at": "2026-07-22T10:15:00Z"
  },
  "authority": {
    "scope": "repository_read",
    "granted_by": "policy:repo-read"
  },
  "issued_by": "boundary",
  "content_trust": "untrusted"
}

Enter fullscreen mode Exit fullscreen mode

The exact fields do not matter. What matters is that the agent did not write this record, and that it says enough to be checked later.

A view also does not have to be built in one read. Agents explore. They read, think, follow a reference, and read again. The view grows.

But the record itself should not quietly change. Each addition needs its own version, or a new record that points back at the previous one. Otherwise a request could point at a view ID that means something different by the time anyone checks it.

This is the same stale-state problem, one level up. A reference that moves under you is not a reference.

So the request points at one exact version.

{
  "operation": "modify_file",
  "target": "config/deployment.yaml",
  "state_view_ref": {
    "id": "sv_01J...",
    "version": 3
  }
}

Enter fullscreen mode Exit fullscreen mode

State binding ties a request to the view it was given. State view control says whether the agent should have had that view at all.

Not every read needs a decision

I do not think every read should trigger an approval or a heavy policy check. That would make agents unusable, and it would be the wrong lesson.

Most reads can be admitted automatically inside a scope that was set beforehand.

The claim is narrower.

The agent may ask for a view. It should not be able to give itself one.

Read and write are not the same boundary

A write needs a decision before it can create impact. A read needs a scope and a record before the agent reasons on it, and again every time the view grows.

There is an uncomfortable side to this. The better an agent gets, the more it explores. It searches, follows links, and calls more tools until it has enough to act. The capability that makes it useful is the same one that widens its view.

That is why gating the write alone is not enough. The request can look perfect when it arrives. Current state, correct format, properly bound. And still be built on something the system never meant to show.

Controlling the outcome is half the path. The other half is controlling the view it came from.

A write changes the external system.
A read changes the world the agent reasons about.

Both belong inside the boundary.


Project: Impact Boundary Labs