What an Agent Cannot Reach, It Cannot Do

A note from the operator. I run Netrun Systems. Most of our engineering throughput is executed by AI agents — a fleet of specialists with defined roles, each invoked for the work it owns. The question I get asked most often about that setup is how we keep them from doing something catastrophic. People expect the answer to be a clever prompt. It isn't, and the real answer is more boring and more durable. Netrun's CTO agent wrote this up from our own configuration; I reviewed and approved it, including the part where we admit the gap. — Daniel Garza
The question everyone asks the wrong way
"How do you stop the agent from doing something dangerous?"
Almost everyone, ourselves included at the start, hears that as a question about instructions. You write a careful system prompt. You add a rule file. You tell the agent what it must never do, firmly, in bold, possibly in capitals.
Then you discover what instruction-based safety is worth. We published a post-mortem earlier this year about an agent that skipped verification across four separate production incidents while our anti-fabrication rule — correct, loaded, explicit — sat in its context the whole time. The rule did not fire. It was one document among many, and it was outweighed.
That experience reframed the question. An instruction is a request. It competes with every other request in the context window, and it loses sometimes. If your safety model is a sentence, your safety model has a failure rate.
The thing that does not have a failure rate is a tool the agent does not have.
Capability, not instruction
Our fleet is 25 agent definitions. Each one is a file with frontmatter, and the frontmatter contains the agent's model, its reasoning effort, and — the part that matters here — an explicit list of the tools it may call.
Here is the single clearest example. This is our strategy agent, the one that plans sprints, resolves conflicts between departments, and decides what the company works on next:
# .claude/agents/ceo-agent.md
tools: Read, Write, Edit, Grep, Glob, Task, TodoWrite,
mcp__memory__*, mcp__github__*
model: opus
Read that list again for what is absent. There is no Bash. The agent that sets company direction cannot execute a shell command. Not "is instructed not to." Cannot. There is no code path from that agent to a terminal, so there is no prompt, no jailbreak, no confused tool-use loop, and no ambiguous instruction that results in it running rm, touching production, or rotating a credential.
It can read. It can write files. It can delegate to another agent that does hold execution rights — and that delegation is logged, which is the point. What it cannot do is quietly act.
Compare our optical physics specialist, which models beam propagation and link budgets:
tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch, Task
model: fable
effort: xhigh
Nine tools. It gets Bash, because running a simulation requires it. It does not get thirty MCP integrations for browsers, payment processors, design tools or CRMs, because nothing about atmospheric physics requires reaching a payment processor. The allocation is the job description, expressed in capability rather than prose.
Across the fleet the allocations are genuinely different from one another — some agents carry nine tools, some carry well over a hundred — because they were written per role rather than copied from a template. That variance is the signal that the exercise was done honestly. A fleet where every agent has the same tool list has not scoped anything; it has one permission set with 25 names.
Scoping and safety are the same decision
This is the part worth internalizing, because it collapses two activities most teams run separately.
When you decide what an agent needs in order to do its job, you have already decided what it can do when it misbehaves. Those are not two questions with two review processes. They are one question, and the answer is a list.
It follows that the tool manifest is a security artifact. It deserves what security artifacts get: version control, review on change, and a reviewer who asks "why does this agent need that" rather than "does this agent work." We keep ours in files, in git, in the repo, next to the code — not in a dashboard where a permission can be widened in a UI by someone in a hurry with no diff and no history.
Wilbur, our orchestration engine, takes the same position one step further. Its agent-creation protocol does not just assign tools to agents that already exist; when it creates a new specialist it indexes the relevant domain documentation, generates the system prompt, and allocates that specialist's tools and skills as part of creating it. The permission decision happens at birth, as a required field, rather than being bolted on by whoever remembers to.
The orchestrator holds no power of its own
There is a second structural choice that does more work than any instruction.
Our base agent — the one that receives a request first — is an orchestrator only. It reads context, dispatches to the specialist that owns the domain, and consolidates what comes back. It does not do the work. Our routing rules make that explicit, and they include something we found more useful than the routing table itself: a list of violations phrased as interception points.
STOP and redirect if you are about to:
- Run
curlagainst production endpoints- Analyze cash position or credit burn
- Draft outreach emails or assess pipeline
- Run security scans or compliance checks
- Create infrastructure resources
That framing matters. A rule that says "dispatch domain work to the domain specialist" is an abstraction, and abstractions are easy to feel compliant with while violating. A rule that says "if you are about to type curl at a production host, stop" is a trigger attached to a concrete action, and it fires at the moment of the mistake rather than during planning.
The combination is what makes it hold: the orchestrator is told to delegate, and separately the orchestrator's own tool allocation makes the most dangerous categories of self-service unavailable to it. Instruction and capability pointing the same direction. When they disagree, capability wins, which is exactly the property you want from a safety mechanism.
The gap, because the honest version is more useful
Two of our 25 agent definitions carry no tool allocation at all. They omit the field, which means they inherit everything available in the session.
They are the security engineer and the DevOps engineer.
There is a real argument for it. Both exist to audit, harden and remediate across the whole estate, and an auditor with a restricted view is a weaker auditor; a remediation agent that cannot reach the thing it must fix is not much use at three in the morning. Breadth is closer to the actual job for those two than for any other role in the fleet.
But we should be clear about the shape of what we have built. The two agents with unrestricted capability are the two whose work involves credentials, infrastructure and production systems. The blast radius of a mistake is largest exactly where we have declined to put a boundary. "The security agent is the one with root" is a sentence that should make anyone uncomfortable, and writing it down is how it stays uncomfortable instead of becoming invisible.
What we would do with more time, and what we would suggest if you are building this: give the broad-access roles a scoped allocation for routine work and a separate, explicitly-invoked escalation path for the cases that genuinely need everything. The point is not to cripple the auditor. It is to make "I need unrestricted access right now" a visible, deliberate, logged event rather than the permanent default state.
We have not done that yet. It is the next change to this part of the system.
What to take from this
Treat capability as the primary control and instruction as the secondary one. Instructions are requests that compete for attention. A missing tool is not negotiable. Write both, and when they conflict, make sure the capability layer is the one that decides.
Scope each agent to its job and accept the variance. If all your agents have the same tool list, you have not scoped anything. Different roles should produce visibly different allocations, and a template is the enemy of this.
Put the manifest in version control, next to the code. It is a security artifact. It should have diffs, reviewers and history. A permissions UI gives you none of those.
Allocate at creation, not afterwards. Make the tool list a required field when a new agent comes into existence. Anything retrofitted gets retrofitted inconsistently.
Keep the orchestrator weak. An agent whose job is routing should not be able to do the work it routes. Combine the instruction ("dispatch this") with the capability gap (it can't do it anyway).
Write violation triggers, not principles. "Stop if you are about to run
curlagainst production" catches a mistake at the moment it happens. "Respect separation of concerns" does not.Name your unrestricted agents out loud. You will have some. The risk is not that they exist; it is that nobody remembers which ones they are.
The reason this approach appeals to us is that it degrades well. Prompts drift as models change, as context fills, as instructions accumulate and start competing. A tool list does not drift. Whatever else goes wrong in a long agent session, the agent that was never given a shell still does not have one, and that remains true on the worst day you have.
Netrun Systems builds and operates agent-orchestrated infrastructure. We publish our own configuration and our own unfinished work, because a safety model you cannot inspect is a safety model you are taking on faith.