What Should Botie Be Unable to Do?
A helpful agent can still propose the wrong operation. Start with the effects we need to prevent, then put a control where each effect would happen.
14 sections · 6 questions · 1 coding challenge
The arrow keys move between sections too.
Software Needed Boundaries Before It Could Choose Its Own Steps
BOTIE
An agent adds a controller whose proposals depend on text and changing context. We can still describe a forbidden consequence precisely: no record from another tenant may be returned, and no public post may be created without its required authorisation. Those statements give us something more useful to test than whether the agent sounded careful.
Sources for this history
The Answer Looks Fine. The Tool Call Does Not.
Botie answers a maintenance question correctly. While finding the answer, he also copies a customer record into a public issue. If we only grade the final sentence, we miss the more serious mistake. What would have stopped that copy before it happened?
Let’s draw the route from a request to its consequences. We will give each boundary a job, then see what happens when a useful-looking action reaches one without the evidence it needs.
These are invented records and deterministic policy checks. The browser runs no agent, shell command, MCP server or network request. Passing these fixtures establishes only the stated policy behaviour.
Step 1 of 2
Follow the proposed public post
The document influences the proposal. Application-owned state decides whether it can proceed.
- 1
Retrieved page
Untrusted evidence - 2
Model proposal
Publish a note - 3
Application policy
Approval and output checks - 4
Public service
Write happens here
Step 2 of 2
Put the rule beside its effect
These are separate requirements for our example.
| Effect | Required control | Owner |
|---|---|---|
| Read a record | Object access | Record service |
| Run generated tests | Execution isolation | Runner |
| Publish a note | Bound approval and output checks | Publishing service |
Read a record
- Required control
- Object access
- Owner
- Record service
Run generated tests
- Required control
- Execution isolation
- Owner
- Runner
Publish a note
- Required control
- Bound approval and output checks
- Owner
- Publishing service
An effect needs a boundary that can stop it
Each action keeps its own missing requirements. The read can proceed while publishing waits.
Find the missing control before the action
Give each proposed effect its required checks, then compare them with the controls currently installed.
BOTIE
Action routes
[
{
"id": "read-7",
"route": "ready"
},
{
"id": "publish-7",
"route": "hold"
}
]Follow the operations
1. Proposed effects
[ { "id": "read-7", "effect": "read" }, { "id": "publish-7", "effect": "publish" } ]2. Application-owned control policy
{ "read": [ "object-access" ], "publish": [ "approval", "output-check" ] }3. Find the required controls
[ { "id": "read-7", "required": [ "object-access" ] }, { "id": "publish-7", "required": [ "approval", "output-check" ] } ]4. Installed control names
- 0object-access
5. Find missing controls
[ { "id": "read-7", "missing": [] }, { "id": "publish-7", "missing": [ "approval", "output-check" ] } ]6. Hold actions with gaps
[ { "id": "read-7", "route": "ready" }, { "id": "publish-7", "route": "hold" } ]7. Result
[ { "id": "read-7", "route": "ready" }, { "id": "publish-7", "route": "hold" } ]
These are invented records and deterministic policy checks. The browser runs no agent, shell command, MCP server or network request. Passing these fixtures establishes only the stated policy behaviour.
1. Name the useful job before its hazards
Our technician needs a supported inspection answer. Reading the relevant manual helps. Exporting every customer’s records does not follow from that need. Write the allowed job in terms of information and effects before choosing a framework.
A guardrail is a check or restriction intended to keep behaviour within a stated boundary. Some checks inspect content and can make classification errors. Others enforce a rule, such as rejecting a write without a current grant. We need to know which kind we are relying on.
Start with a consequence you can observe. “Be safe” has no clear passing result.
2. Mark who can change each input
A user controls their request. An external author controls a retrieved page. A package maintainer controls downloaded code. Your application owns its permission store, but only if ordinary tools cannot edit that store. Draw those ownership boundaries explicitly.
For a coding agent, include issue comments, repository documentation, test output and dependency installation logs. For an application agent, include messages and customer uploads. An MCP response joins this list even when the connection itself is authenticated.
A trusted route can deliver untrusted content. Keep origin and authority separate.
3. Turn a concern into an invariant
An invariant is a statement that should remain true through the run. In our application, “every returned record belongs to the authenticated tenant” is one. “Every public write has approval for its exact payload” is another. We can observe the record owner and approval binding to check them.
State the exception policy too. A read might continue during an optional classifier outage, while a write requiring a missing approval must wait. An unknown operation should not acquire permission because no rule happened to mention it.
The fallback belongs in the policy, not in an optimistic interpretation of a timeout.
Check yourself
Questions on Parts 1 to 3
Question 1 of 2
Which state determines permission?
Use the information below.
A public write requires approval. The model says it has approval, but the server store has none.4. Keep the policy outside the model’s proposal
Use structured proposals with validated fields, then consult independently supplied policy. A proposal may contain a reason. That reason can help review, but it cannot supply its own trusted grants.
The workshop reduces controls to names so we can inspect coverage. In a service, those names must correspond to enforced checks on every applicable execution path. An installed-control list is an inventory, not proof that the implementation works.
A documented boundary still needs a test that reaches it.
Find the missing publishing check
Set difference preserves the requirement that has not been installed.
required = {"approval", "output-check"}
installed = {"approval"}
print(sorted(required - installed))['output-check']Approval alone leaves the output requirement unsatisfied.
5. Separate guidance, detection and enforcement
A prompt tells Botie what we want. A classifier estimates whether content or an action looks suspicious. An execution policy decides whether this operation is permitted. The same system may use all of them, but their failure modes differ.
Test a deliberately incorrect classifier result while retaining the execution rule. If a passing screen can grant access to another tenant, the protection depends on the screen never being wrong. Repair that dependence at the service boundary.
Useful redundancy comes from controls that address different failures. Repeating the same prompt is not an independent boundary.
6. Give every stop a usable next step
A held action should explain what is missing without revealing protected data. The technician may supply a record revision, an authorised reviewer may inspect a draft, or the system may finish a narrower read-only task. Keep these routes explicit.
Do not ask for confirmation on every harmless step. Define the cases that require review using impact, reversibility and current authority. Broad approval requests can make it harder for someone to notice the action that actually matters.
Keep permitted work usable while making unresolved requirements visible.
7. Test the place where the effect occurs
Unit tests check the policy function. Integration tests call the real tool handler with a wrong tenant or missing approval. Adversarial runs test whether manipulated context can reach an unintended path. Inspect effects independently of the final assistant response.
Preserve the policy version and relevant case when a check fails. Later lessons will carry this through coding, MCP connections and a release review. Our first construction checks only whether the stated control inventory has a gap.
The next question is whether a coding agent can change the very test that is meant to catch its mistake.
Check yourself
Questions on Parts 1 to 7
Question 1 of 4
The denial proves the detector worked.
Consider this example.
A detector misses a malicious instruction, but the service denies the forbidden export.Try it with Botie
Build with Botie
Write and run Python