The code, the tests and the full evidence for this blog are in the repo HERE, and you can run the whole lab with three commands
1. Nobody decided how much power travels
Say a customer called Sam asks a shopping assistant to buy something. The assistant is an AI agent, which just means software that takes actions for you. It hands the job to an ordering agent, and the ordering agent hands the payment to a third agent that deals with the money.
Three agents, one purchase. What I wanted to know was how much authority travels along with the request at each handover. In a lot of setups, the honest answer is that nobody decided. The agents share one login that can do everything, or each one quietly inherits whatever the one before it had. That shared login is what people call a service account. It works fine for a single server. It’s the wrong picture for agents that pass work to each other.
Think about a hotel instead. Reception gives you a key card for your floor. The lift attendant doesn’t copy that card for the next person. They give out a card that opens one room.
The thing doing this job in software is called a token. A token is a digital key card: it says who you are, what it opens, and when it stops working. I’ll use that word from here on.
My argument is simple. Every agent should have its own identity, and the token it carries should get smaller each time work is handed on.
2. The two rules
I built a small lab to test this, and it comes down to two rules.
The first is that the token shrinks at every handover. Sam’s token lets the shopping assistant do three things: place an order, charge a payment, and ask for a refund (which only files a request for a human to approve). It lasts five minutes. The token it passes to the ordering agent allows two of those, lasts two minutes, and works for one order only. The token the ordering agent passes to the payment agent allows exactly one thing: charge that order. No handover can produce a token bigger than the one it came from.
The second is that the payment agent can never refund, no matter who asks. No token anywhere in the system carries the right to pay out a refund, so there’s nothing to misuse. A refund request goes to a human for approval, and even a perfectly valid payment token can’t be stretched into one.
3. How the lab is built
The shopping agent, the ordering agent and the payment agent, each agent has its own name and its own secret key. When an agent asks for a token, it proves who it is by signing the request with that key, which stops one agent from pretending to be another. In the lab this is simulated. A real company would use a proper identity service for it.
A small issuer hands out the tokens. Here is the whole idea in one picture:
And here are the three tokens behind one purchase, written out:
There’s one more small rule: a token can never outlive the one it came from. If the parent has 90 seconds left, the child gets 90 seconds at most.
This is what the lab prints when Sam buys order 1042, asks for a refund on it, and then buys order 1043. Two charges go through. The refund request is parked for a human, and no money moves:
4. Governance as code
The part I’m most pleased with is the agent register. It’s a plain table with one row per agent, and it answers the questions a product manager would ask on day one: who owns this agent, what is it for, what is it allowed to do, who can it pass work to, who approved it, and when is it next due for review.
Look at the last row. The payment agent’s “may hand work to” column says nobody. It’s the end of the line, and it only holds one permission: charge a payment.
The issuer reads this table before it hands out any token. If a permission isn’t in the register, the token doesn’t get issued. That sounds almost too simple, so I tested it. I had the shopping agent ask for a token addressed straight to the payment agent, skipping the ordering agent. It was refused, and nothing in the code knew that rule on its own. The register simply doesn’t list the payment agent as someone the shopping agent may hand work to.

This is what I mean by governance as code. A change to the chain is a change to the register, and a change to the register needs the owner’s approval. There’s also an access review script that compares what has actually been issued against the approved register, and flags anything that has crept in.
In the lab I’m the owner, the approver and the reviewer of every agent, because I’m the only person here. In a real company those would be different people, and that’s the reason the table has separate columns for them.
5. The attacks that failed
The attack I cared about most was getting the system to refund money through the payment agent. I tried it at three points.
First, I asked the issuer to give the shopping agent a token that allows a refund. It said no, because that would be bigger than the token the shopping agent already held. Second, I tried the same with the ordering agent, and got the same answer. Third, I took a perfectly valid payment token and tried to use it to refund. The payment agent checks every token itself, and it refused, because no token carries that permission.
Test output showing the three refund relay tests passing. The three tests behind that picture, passing.

Any one of those would have been enough on its own. That’s the point of having layers: one mistake somewhere doesn’t turn into a breach.
I also tried a few simpler things. Using the same token twice was refused. Buying the same order twice was refused. And a charge above a hard limit was refused by the payment service itself, which matters because it means tricking an agent into asking for a huge amount still doesn’t work.
6. When something goes wrong
Prevention is half of governance. The other half is what you can do once you suspect an agent is misbehaving, and I wanted two answers to be quick: how fast can I switch an agent off, and how fast can I find out who did what?
For the first, I ran a drill. I switched off the ordering agent, then tried to make a purchase. The purchase was blocked, and so was any token the ordering agent was already holding. A switched-off agent can’t get new tokens either. In my lab the gap between “switched off” and “first blocked request” was 3 milliseconds. That figure only shows the lab does what it should. Everything runs on one laptop, so it says nothing about how fast this would work across real services.

For the second, I wrote a report for any single order. Ask it about order 1042 and it tells you the customer, the three handovers in order, what each token allowed and for how long, and who owns and approved each agent. It’s one screen. Without something like it, an incident review means digging through logs for days, and “who authorised this?” turns into a long argument.
I also wrote a one-page incident playbook. It names three roles: the agent’s owner, who decides to switch it off; the person who runs the response; and a reviewer who approves bringing it back. The steps are short. Switch it off, save the record, answer “who did this?”, tell the owners of every agent involved, then fix the cause and get the register approved again before anything is restored. In the lab all three roles are me. The point of writing them separately is that they won’t be in a real organisation.
7. What the numbers say, and what they don’t
After the demo scenario (two purchases, one refund request, three attacks and the switch-off drill) the lab produced five numbers:
I'd rather request you read the right-hand column than the middle one. These are numbers from a lab with made-up data. They show that the controls work as designed, and they don't prove anything about how often attacks happen or how the controls would hold up in production. For the record, this is the raw output they come from:
The demo also caught two mistakes that my tests had missed, and I think they’re the most useful part of the exercise. The first was cosmetic. In the summary of why requests were refused, one reason was filed under the label “spiffe”, which is a fragment of an agent’s technical name and means nothing to a reader. The second mattered more. The “who did this?” report for order 1042 listed eight handovers instead of three, because one login had also driven another purchase and a refund request, and my report grouped everything by login. A report like that would be wrong in an incident, and it would look convincing while being wrong. I fixed it by tracing back from the actual charge, one token to the one it came from, so the report follows that order’s chain and nothing else.
I found both only by reading the output of a complete run, and neither one was a failing test.
8. What production would need
It would be easy to read this lab and conclude the problem is solved. It isn’t. The lab shows the shape of the controls, and a real system needs more.
A real identity system. Here each agent signs with a key stored on disk, which is fine for made-up data. A company would use a proper workload identity service, which also proves the agent is the software it claims to be and rotates its keys.
A record nobody can quietly edit. My audit log is a file. A real one has to resist tampering.
Different people in each role. The owner, the approver and the reviewer should not be the same person.
Decisions the lab can’t make for you. How long a token should live, which limits to set, and when customers have to be told about an incident are choices for each organisation.
The takeaway I’d leave you with is this: give every agent its own identity, make each handover smaller than the last, and write down who owns each agent before something goes wrong.











