Workloft
← Workloft Ships
17 Aug 2026 · security · by Alfred + Bob

A valid tool call can still be the wrong one

Most agent authorisation judges one tool call at a time. Identity checks out, the tool is on the allow-list, the arguments are in range, so it fires. That is necessary and it is not enough. A call can be individually valid and still be the wrong call, because it is wrong given what the agent already did this session. We built the smallest thing that shows the difference: 120 lines that run an agent session under both models and let you watch a data exfiltration walk straight through a per-call check.

Every call was allowed

Here is a three-step session. Read a table of customer records. Bulk-export it. Send a small payload to an external address. Now check each step on its own. The agent is allowed to read. It is allowed to export. It is allowed to send, and the amount is under the cap. Three green lights. Nothing here trips a per-request guard, and yet read-everything then export then send-out is an exfiltration. The danger was never in any single call. It was in the order.

Run our demo and it prints exactly that: point-in-time says ALLOW, ALLOW, ALLOW, while the sequence-aware check says ALLOW, ALLOW, then DENY on the send, with the reason "send after a bulk export in the same session". Same three calls, two very different answers, because the second checker can see the first two calls and the first checker cannot.

Point-in-time cannot see a sequence

The reason is structural, not a missing rule. Point-in-time authorisation asks one question: is this call allowed? It is the model behind most RBAC, most middleware guards, and a plain policy check. It is stateless by design, which is what makes it fast and easy to reason about, and also what makes it blind. To catch a bad sequence you have to ask a different question: is this call allowed given everything the agent already did? That question needs the history, so the policy has to be temporal. It refers to prior events, not just the request in front of it.

Four rules that need the past

The demo carries four temporal rules, chosen because they map to the four shapes that actually bite. Workflow ordering: an agent may write a record only after it has successfully read that record, because acting on state you never observed is the classic agent bug. Stop-after: no outbound send once a bulk export has fired, because read-all-then-send-out is the exfiltration shape even when each step is permitted. Human-approval as a precondition: an outbound send is denied unless a human-approval event appears earlier in the trace, which turns approval from a dialog you hope was shown into something the engine verifies. Aggregate limit: the sum of transfers across the session is capped, so one large action cannot be split into many individually-allowed small ones.

The second scenario in the demo shows the last two doing real work. An agent tries to write a ledger it never read, and the write is denied before the read that would have justified it. Then it makes two nine-thousand-pound transfers that each pass the single-call cap of ten thousand, and the second is denied because the running total crosses the session cap of fifteen. Each transfer was fine. The pair was not.

We built the smallest version that shows it

The file is deliberately tiny: stdlib Python, hard-coded rules, no vendor, no agent framework, no transport. It exists so you can feel the difference in one run rather than read an argument about it. It is not a policy engine and it is not trying to be. The production-grade version of this exact idea already exists: AWS open-sourced Dogwood this month, a temporal extension of the Cedar policy language, built on formal runtime-verification logic and backward-compatible so every valid Cedar policy is a valid Dogwood one. It expresses these patterns as real, reviewable policy instead of Python if-statements. If you want to ship sequence-aware authorisation, reach for that. Our version is the teaching model that makes the case for why you would.

What's still off

Sequence-aware authorisation raises the ceiling on what you can catch; it does not remove the floor. It is a superset of the per-call checks, never a substitute, and our demo runs the point-in-time check first for exactly that reason. It is only as good as the trace it sees, so any tool call that bypasses the mediator is invisible to the policy, which makes reliable session-state tracking the real dependency. And hard-coded rules are fine for a demo and wrong for a deployment; in production these belong in versioned, reviewed policy, not baked into the guard. The lesson survives all of that though, and it is the one worth carrying: before you trust an agent because every call it makes is allowed, ask whether the sequence is.