Published on

Agentic Systems: Bounded Tools, Not Free-Form Access

Authors

This is Part 1 of Agentic Systems: Bounds and Evidence, the second series in a pillar on building agentic systems.

  • Part 1: Bounded Tools, Not Free-Form Access: constraining what an agent can express, not just checking what it produced.
  • Part 2: The Ledger Is the Product: what gets recorded at decision time, and why reconstruction after the fact does not work.
  • Part 3: One Interface, Many Models: the model layer underneath, and what swapping it does and does not give you.

The first series was about how a decision gets made, routed, and scored. This series is about the machinery around that decision: what the system is permitted to do with it, what it writes down, and what it all runs on. This post takes the first of those, and it is the one where instinct from ordinary application security transfers most directly - and where teams building agentic systems most often fail to apply it.

Table of Contents


The Capability You Actually Granted

Return to the alert-triage system. The routing decision is made: page the owning team. Now the system needs to act, which means it needs a tool.

The convenient tool is an HTTP client with credentials for the paging provider. It is one integration, it covers every paging operation the system might ever need, and it will work on the first try. It also grants, precisely, the ability to make any call that provider's API supports with those credentials: page anyone, escalate anything, resolve an unrelated open incident, delete a schedule, mass-notify every on-call rotation in the organisation at 3am.

Nobody intends that. The intent is "page the owning team." But intent is not a security boundary, and the gap between what was intended and what was granted is not closed by the prompt. It is not closed by the system message either, or by a careful description of the tool's purpose, because all of those are instructions to a component whose defining characteristic is that it decides what to do. A tool description tells the agent what you want. The tool's signature determines what is possible.

The same instinct already exists in most engineers for ordinary code. A function that takes a raw SQL string and a function that takes typed query parameters offer the same capability in the happy path, and the entire history of injection vulnerabilities is the difference between them under adversarial or merely unusual conditions. Agentic tools have reproduced that mistake at a higher level, with an actor that is not adversarial but is reliably unpredictable.

Expressiveness Is the Risk Surface

The useful reframe: the risk of a tool is not what the agent is likely to do with it. It is the size of the space of things the tool can express.

That space is worth actually enumerating when designing a tool, because the exercise is short and clarifying. For the paging client above, the expressible space is the provider's entire API surface. For a tool that accepts { team_id, incident_id, urgency }, the expressible space is the cross product of three enumerable fields - and crucially, "delete a schedule" is not a point in that space. It cannot be reached, not because the agent has been told not to, but because there is no way to say it.

This is the difference between a guardrail and a bound. A guardrail is a check that runs after the agent has decided, and it can only catch what its author anticipated; the unanticipated case passes through because nothing matched it. A bound is a property of the interface, and it excludes by construction - including cases nobody thought about, which are the ones that matter, because the anticipated failures were already handled.

Both are worth having. But they fail in opposite directions, and only one of them fails safe.

Intent Parameters, Not Commands

The practical shape this takes: a tool should accept a small set of typed parameters describing an intent, and the system should translate that intent into the underlying operation. The agent never constructs the operation itself.

Concretely, the difference is between an agent that emits

POST /v1/incidents  { ...arbitrary body... }

and an agent that emits

{ "action": "page_team", "team_id": "checkout", "urgency": "high", "incident_ref": "alert-88213" }

The second is checkable in ways the first is not. Every field is enumerable or references an existing entity, so validation is a matter of membership rather than interpretation. The set of possible actions is a closed list that can be reviewed by a person in a minute. And when this system is wrong, it is wrong within the space of paging somebody, which is a recoverable class of error, rather than within the space of everything the credentials permit.

A signal-driven trading system applies this at the point where a decision becomes money. The orchestrator does not emit an order to the broker; it emits a routed decision describing an instrument, a direction, and sizing within risk bounds. A pre-trade validator and a set of exception checks sit between that intent and any order actually reaching the market. The agent layer cannot express "liquidate the account," because that sentence has no representation in the structure it emits - the containment is in the shape of the interface, not in a rule that noticed and blocked it.

Bounded Form Does Not Mean Bounded Effect

Here is the mistake that survives the first round of tightening, and it is worth stating plainly because a structured schema creates a strong and sometimes false sense of safety.

A tool that accepts { "action": "adjust_risk_limit", "delta": <number> } is structurally bounded. It has one action and one typed parameter. It is also capable of setting the risk limit to anything at all, because delta has no ceiling. The form is constrained; the effect is not. Structure alone bought nothing here except the appearance of rigour.

So bounding requires two questions, not one. What can this tool express? and what is the worst state reachable by expressing it? The second is about ranges, rate limits, and cumulative effects, and it is the one that gets skipped, because the schema looks tidy and tidy schemas stop the review early.

Three things worth checking on every tool:

  • Magnitude. Every numeric parameter needs a range, not just a type. Unbounded numbers in a bounded schema are the most common version of this failure.
  • Rate. A single call may be harmless and four hundred calls may not be. This is the same aggregation problem the rate axis in Part 2 of the last series raised for autonomy, and it applies to tools independently of who approved the decision.
  • Composition. Two individually safe tools can compose into an unsafe sequence. Read-then-write is the obvious one; the general case is that bounding each tool separately does not bound the trajectory.

Three Tiers Worth Separating

Not every tool needs the same treatment, and treating them uniformly means either over-constraining reads or under-constraining writes. Three tiers, in increasing order of care:

Read-only tools can usually be broad. Querying logs, fetching a service map, reading a document - the failure mode is wasted tokens and irrelevant context, not damage. The exception is when reading is itself the risk: reading personal data, credentials, or anything whose exposure is the harm. Then a read tool needs bounding as tightly as a write.

Reversible writes deserve intent parameters and validation, and can generally proceed without human approval once the decision type has earned it. Posting a comment, updating a draft, adjusting a non-critical setting.

Irreversible actions - money moving, messages sent to people, data deleted, anything externally visible - need intent parameters, validation, and a gate that does not depend on the agent's judgement being correct. This is where the veto window from the previous series earns its cost.

The tier is a property of the action and its target together, not of the verb. Writing a file is tier two for a scratch directory and tier three for a production config.

Bounding an Agent Whose Output Is Prose

The hardest case is an agent whose entire purpose is to produce free text, because the whole apparatus above assumes an enumerable output space and prose does not have one.

A tender-response drafting stage is exactly this: it writes documents, and no schema constrains them to a closed set. The bound is applied differently. Every drafted response is pinned to the specific source clause it answers, and to the model and prompt version that produced it. The prose is unconstrained; the claim structure around it is not, and a draft that cannot be traced to a clause is not a valid output regardless of how well it reads.

That generalises into a useful rule. When the payload cannot be bounded, bound the provenance and the disposition instead:

  • Provenance: what source does this output claim to be derived from, and does that source exist and say that?
  • Disposition: what is the system allowed to do with this text? In the tender case the answer is "present it to a human for approval, and nothing else" - which means an unbounded payload has a strictly bounded effect.

An agent that writes anything but can only ever file a draft for review is far better contained than an agent with a tidy schema that submits automatically.

The Honest Bill

You gainYou pay
Disallowed actions become inexpressible, not just blockedA tool layer to design, build, and maintain per capability
Validation by membership rather than interpretationLess flexibility when requirements change: new intent, new tool
Review that fits in a person's head: a closed list of actionsMore code between the agent and the thing it is acting on
Failures contained to a recoverable classTemptation to add a generic escape-hatch tool, which undoes all of it
Tool calls that are meaningful units in the ledgerUpfront design work before the first end-to-end demo

The last item on the right is the reason this gets skipped. A generic HTTP tool gets a system working end to end in an afternoon; an intent-parameter layer takes a week and looks like bureaucracy while the demo is the thing being judged. The cost of retrofitting is that by then, the prompt and the specialists have been built around an expressive tool, and narrowing it means rewriting both.

The escape-hatch line deserves its own warning. A system with twelve carefully bounded tools and one run_arbitrary_query for the cases nobody anticipated has the security properties of run_arbitrary_query. Escape hatches are load-bearing exactly as often as they are used, and the reasons they get added are always good ones.

When NOT to Bound Tightly

  • Genuinely exploratory work. A research or investigation agent whose value is open-ended search cannot be given an enumerable action space without destroying the point. Bound the effect instead: read-only credentials, a sandboxed environment, no write path at all.
  • Prototyping, explicitly and temporarily. A broad tool is the right way to find out what the system needs before designing the narrow version. The failure is not using one; it is shipping it, because the prototype's tool shape quietly becomes the architecture.
  • The action space is genuinely unbounded. Code generation, document writing, open-ended analysis. Bound provenance and disposition as above rather than pretending a schema fits.
  • Low-stakes, fully reversible, low-rate actions. Not everything needs this machinery. A tool that updates a scratch note does not need an intent layer, and building one is cost without benefit.

Conclusion

A tool's risk is the size of what it can express, not what it is expected to do, and that space is closed by interface design rather than by instruction. Prefer typed intent parameters over constructed commands, check magnitude and rate rather than assuming a structured schema is a bounded one, tier tools by reversibility rather than by verb, and when the payload genuinely cannot be bounded, bound its provenance and what the system is permitted to do with it.

Bounding determines what an agent can do. Part 2 turns to what gets written down when it does - and why a record captured at decision time is a categorically different thing from an explanation generated afterwards.