Agents Need Read/Write Separation
An agent needs context to be useful. It needs to read tickets, files, logs, dashboards, policies, and previous decisions. But the permissions that make reading useful are not the same permissions that make writing dangerous. Combining them is one of the fastest ways to turn a helpful assistant into an unclear production actor.
The thesis
Read and write capabilities should be separated by identity, tool surface, state transition, and approval. Observation is not mutation.
This sounds basic, but many agent systems skip it. They give one runtime a broad token and a pile of tools, then ask the model to be careful. The model can search the repo, edit files, post comments, run commands, update tickets, and call deployment tools from the same planning loop. The result is convenient until a stale assumption or prompt injection turns into a write.
Read/write separation is not about slowing the agent down. It is about making the moment of mutation explicit.
The production pattern
A support agent is allowed to inspect account state and draft fixes. It reads a customer note, searches internal docs, looks up current feature flags, and identifies that the account is in the wrong segment. The next useful step is a write: change the segment or open an operations request.
If the same tool token can read and write account state, the boundary depends on model behavior. Maybe the system prompt says, "Do not modify customer accounts without approval." But if the write function is available and the model believes the user already approved it, the system has no hard separation.
The same pattern appears in engineering agents. Reading a pull request, review thread, and CI log is low-risk compared with pushing a branch or resolving a review comment. Searching deployment logs is different from restarting a service. Listing feature flags is different from flipping one.
The work often needs both phases. The mistake is making them indistinguishable.
The model
I design agent runs around a read phase, proposal phase, write phase, and verification phase.
The read phase has broad enough access to build context. It can search, fetch, list, inspect, summarize, and compare. Reads still need limits when they touch private data, but they usually do not need per-action human approval. The read phase should not be able to mutate shared state except for its own audit log.
The proposal phase turns observations into explicit intended writes. A proposal includes target resource, exact change, reason, supporting context, preconditions, expected effect, and verification plan. It is the bridge between analysis and mutation.
The write phase uses a narrower identity or capability grant. It can perform only the approved action, against the approved resource, within the approved time window. If the proposal changes, the write grant is no longer valid.
The verification phase reads after the write. It should not rely on the write tool's success string. It fetches the resource, checks the version, confirms the job, or records the message id. After verification, the write grant can be discarded.
This model is close to how careful humans work. We read broadly, propose specifically, change narrowly, and verify afterward. Agents need the same shape because their planner is less reliable than a careful human, not more.
Where this goes wrong
The most common failure is a single all-purpose tool identity. A GitHub token can read issues, push branches, update comments, and merge pull requests. A cloud credential can list logs and restart services. An internal admin token can inspect accounts and update them. If the agent has that identity throughout the run, policy becomes advisory unless every tool enforces scope internally.
Another failure is hidden writes during reads. A "read" tool that marks notifications as seen, refreshes caches in shared storage, creates audit comments, or triggers lazy jobs is not purely read-only. Those effects may be harmless, but they need to be known. In agent systems, even small hidden writes can confuse replay and verification.
A third failure is allowing write tools to fetch their own context. For example, fix_customer_account(customer_id) both reads account data and applies changes. That makes it hard to review the decision. The proposal should be formed from explicit reads before the write tool is called.
The counterpoint is that strict separation can feel heavy for low-risk tasks. An agent editing a local scratch document does not need a formal write grant for every save. The line should be drawn around shared state, external effects, sensitive data, and operations that trigger other systems.
What I do now
I give read and write tools different names, credentials, and logs. A read token cannot accidentally write because the underlying system denies it. A write token cannot wander because it is scoped to a specific action.
I make the transition visible in the user experience. Before a write, the agent presents the proposed change, not a vague plan. For code, that means a diff. For messages, it means the exact recipient and body. For tickets, it means the exact fields. For operations, it means the command, target, and expected effect.
I also make writes preconditioned on the reads that justified them. If the agent read file hash abc123, the write should fail if the file is now def456. If it read a ticket while assigned to one owner, the update should stop if ownership changed.
Finally, I test that read-only mode is actually read-only. The harness should run a realistic task with write tools absent and confirm no shared state changed. Then it should grant a narrow write and confirm only the proposed resource changed.
Closing takeaway
Agents become safer when observation is cheap and mutation is deliberate. Broad reads can inform the plan, but writes need a separate gate.