/CONCEPT · 2026-07-13
Four gates: allowlist, schema, sandbox, confirm
If an agent can call tools, a scolding system prompt is not a control. Name the four gates or admit you have a chat box with side effects.
We keep seeing the same product: a model, a bag of tools, a long policy paragraph. Jailbreaks that only change the tone are a content problem. Jailbreaks that emit a tool call are an incident. The difference is whether a broker sits in front of the tools.
This is the same thesis as prompts → loops. Here are the four gates we look for on every agent audit.
1. Allowlist
The model gets a named set of tools, never raw argv. Give a customer-facing bot “SearchCorpus,” not “Shell.” If a retrieved document or a user message can extend the list at runtime, the list is an installation vector.
2. Schema
Each tool takes a JSON schema the broker validates. Extra fields dropped. Types coerced or rejected. A string that looks like cmd does not become a shell because the model asked. If you parse arguments with “the model usually gets it right,” that is not a schema.
3. Sandbox
The tool runs as a principal you can name: a service account, a container, a network policy. It cannot see other tenants. It cannot reach metadata endpoints unless that is the product (it should not be). Filesystem and egress are default-deny. If the tool is “run this SQL,” the database user is not the migration user.
4. Confirm
High-impact actions wait. Send email, move money, change IAM, delete. A human or a second control has to say yes. “The model was very sure” is not a confirmation. Logging after the fact is not a confirmation.
Vendor refusal screens (Fable downshifts, Daybreak enrollment) are extra. They do not replace a broker.
How we test, without a circus
We do not publish a list of magic strings. We try to make the product do something outside the inventory: extra tool, extra field, extra host, skip confirm. We stop at the broker. If the broker holds, the finding is “prompt is messy, tools are contained.” If the broker does not exist, the finding is “this is not an agent product.”
if (!ALLOWLIST.has(tool)) return deny("not a tool")
const args = schema.parse(raw) // throws → deny
await sandbox.run(tool, args) // no extra env
if (HIGH_IMPACT.has(tool)) await confirm(human)
RAG is a fifth surface, not a fifth gate
Retrieved text must not become a new tool name or a new system block. Isolate, ground, strip, refuse. Do not confuse “the chunk said to call shell” with a successful call. If your schema cannot name shell, the chunk is noise.