Guardrails and refusals
Four of the five agents carry a scope gate: a set of pure functions over the message text, with no I/O and no model call. That design choice is the guardrail. A gate implemented as a system-prompt instruction is a suggestion the model weighs against everything else in the context. A gate implemented as a regular expression over the message is a decision the model never participates in. Two consequences follow:- A refusal is reproducible. The same message always gets the same answer, and “the model decided not to today” is not a possible outcome.
- It is impossible to prompt-inject. There is no instruction the message can carry that changes what the gate computes, because the gate does not read instructions.
What each gate decides
Every gate is a denylist that short-circuits out, with everything else in.
An unrecognised in-lane message gets a scoped answer, not a refusal.
Greetings and “what can you do?” are in scope on the Clerk.
A capability question is a legitimate use of an agent, and refusing it is the kind of unhelpfulness that makes people stop using a tool.
A refusal names who does it instead
- states its lane positively, so the user learns what to ask instead of only what not to;
- says the refusal is by design, so it does not read as a failure or a permissions bug;
- hands the request on, naming the thing that does do it.
Proximity, not adjacency
The sharpest lesson in this part of the system, and it cost two agents a real defect.A scope-gate denylist must match verb and noun by PROXIMITY, not adjacency.“Classify this supplier invoice and file it” names a concrete document type, not the literal word “document”. An adjacency-only pattern let it straight through into a full audit sweep on the Auditor, and a close run on the Accountant. Both gates now match with a bounded gap between the verb and the noun.
One deliberate omission: the Accountant’s classify-verb noun list leaves out “statement”, because financial statements are its own lane.
A denylist that is right in general and wrong for the agent’s own vocabulary refuses the thing the agent exists to do.
Enumerated families never short-circuit
The Auditor’s family router is a union, not a first-match. A question naming three families runs three. And an explicit “full check” wins outright over the enumeration.That last rule is deliberate, and the trade-off is worth stating: for a read-only agent, running one family too many costs a database read, while running one too few answers half the question.
So an explicit full-sweep request can never be narrowed by a keyword the enumeration happened to miss.It keys on a generic assurance noun (check, audit, sweep, scan, review), never a family noun, so a narrow question like “any chain breaks?” still routes to the hash chain alone.
Shipped; see the Auditor.
The output check does not nuke a handoff
The Clerk’s chat path runs a drift guard over its own reply. It uses the off-topic patterns only, not the cross-domain ones. Otherwise a reply that names Bookkeeper terms in order to hand the request off gets suppressed for mentioning them, and the user gets nothing instead of a redirect.An untrusted document gets no filesystem
A turn carrying document text is a turn carrying content from outside the system, and it is treated that way.The human-task gate: ambiguity means no task
Not every turn is work. The human worklist at Audit → Actions is an operator’s to-do list, and a task row must exist for real work only. The top-level Actions surface is different: it records agent runs whether they changed anything or failed before a workspace existed. The rules fire in an order that resolves ambiguity toward not creating a row:
The informational rule fires before the imperative one, deliberately.
A user whose genuine request is read as chat gets an answer and can restate it.
A user whose greeting mints a task loses trust in the tab.
Where it is enforced
The tests that would fail if it broke
Each agent’stest_scope.py, plus test_doc_sandbox.py for the sandbox options and the pre-tool hook.
The gates are pure functions, which is exactly why they are cheap to test exhaustively: a scope test is a table of messages and expected verdicts, with no model and no database.
One honest note.
The Auditor’s gate is known to let one write-request phrasing through instead of declining it.
That is a scope-gate miss, not a control failure - the Auditor has no write channel, so the worst outcome is a read-only sweep in response to a request it should have declined.
It is recorded rather than left for a reader to find.
Related
- The agent org - what each agent’s lane is
- The Auditor - the family router in full
- Where the business logic lives - the other structural boundary
- Two gates, three people - the human guardrails