The Four Checkpoints Between an AI Agent and Your Money
I built an AI agent that can actually spend money — wallet-agent. You tell it "buy this if it's under $1," it goes and looks, and then it stops and asks you before it spends anything. That last part sounds simple. "The agent asks permission" is one sentence. But once I actually wired the payment through — Amazon Bedrock AgentCore, a crypto wallet, a real signature — I found out that sentence hides four completely different questions, and each one needed its own answer. This is a walkthrough of all four, in the order the money actually travels through them, including the one question I never answered and probably should have.
Why "just ask permission" isn't enough
Picture handing your credit card to a new employee and saying "check with me before you buy anything." That one instruction actually depends on a pile of assumptions you're not even thinking about. Is this actually the employee you hired, or someone pretending to be them? How much are they allowed to spend, and for how long? When they hand the cashier a card, whose card is it really? And when they come back and say "you said it was okay" — did you actually say that, or is someone lying about it?
An AI agent that can spend money has to answer the exact same four questions, except none of the answers can rely on "I recognize this person's face." Everything has to be proven with something a computer can check. Here's how wallet-agent answers each one — and where it currently doesn't.
Checkpoint 1: Is this actually my agent talking?
The first question has nothing to do with money yet. It's simpler: when something calls Amazon's payment service and says "let me use this wallet," how does Amazon know that request is really coming from my agent, and not from some random script somebody wrote?
The answer is a kind of ID badge, called an IAM role in AWS. I created one specifically for this agent, and I told AWS two things: only my agent's runtime is allowed to wear this badge, and even while wearing it, it can only ever ask about this specific payment setup — not anyone else's.
{
"Effect": "Allow",
"Principal": { "Service": "bedrock-agentcore.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "761018866498" },
"ArnLike": {
"aws:SourceArn": "arn:aws:bedrock-agentcore:us-east-1:761018866498:payment-manager/walletagentpm*"
}
}
}
Translated out of AWS-speak: "Only the AgentCore service itself can hand out this badge, only to requests from my account, and only for payment setups whose name starts with walletagentpm." It's the equivalent of a badge that not only has your photo on it, but also only opens the one door it's supposed to open. Without this, anything with valid AWS credentials in the wrong hands could try to poke at the payment system pretending to be my agent.
This checkpoint answers "who is asking?" — nothing more. It doesn't yet say how much they can spend.
Checkpoint 2: How much, and until when?
Passing checkpoint 1 gets the agent through the door. It does not hand it a blank check. The second question is: even if this really is my agent, what's it actually allowed to do right now?
This is where a payment session comes in — a temporary, disposable permission slip that exists only for the current conversation. Every time I start a new chat with the agent and give it a spending limit, it creates a fresh session with that limit baked in, plus an expiry time:
sess_resp = dp.create_payment_session(
userId=user_id,
paymentManagerArn=manager_arn,
expiryTimeInMinutes=60,
limits={
"maxSpendAmount": {"value": "1.00", "currency": "USD"}
},
)
Think of it like a parent giving a kid an allowance card loaded with exactly five dollars, valid only for today. The card works — but the store can't charge it for six dollars, and it stops working tomorrow whether or not the kid spent it. The agent never gets a wallet with no limit; it gets a fresh, capped, time-boxed one every single time. If it tries to go over the limit, the payment system itself rejects it before any money moves. The agent's own good behavior isn't what's stopping it — the ceiling is enforced somewhere the agent can't touch.
This checkpoint answers "how far can they go?"
Checkpoint 3: who actually holds the key?
Here's where it gets interesting, and where I got stuck for a few days. Even with a valid badge and a capped allowance, somebody still has to physically sign the transaction — the digital equivalent of signing your name on a check. That signature is what actually moves money on the blockchain. So who signs it?
Not my AWS account. On purpose. The wallet's private key lives inside a separate service (I used a company called Privy), and it never touches my code at all. Instead, I generated a special signing key and registered it with Privy as an approved "co-signer" on the wallet:
body = {"additional_signers": [{"signer_id": signer_id}]}
# PATCH https://api.privy.io/v1/wallets/{wallet_id}
It's like a bank vault that isn't in my building. I don't get to walk in and grab the cash myself — I registered one specific key with the bank, and only requests signed with that exact key get honored, by the bank's own systems, not mine. When AWS's payment service needs a transaction signed, it doesn't ask me for the wallet's actual key — it asks Privy to sign using the co-signer key it already trusts. The actual money-moving secret is never in a place I could leak, misplace, or accidentally commit to GitHub.
This is also the part that broke first. I registered the key, but Privy didn't automatically treat it as trusted for the wallet it had created — I had to explicitly attach it as a signer in a separate step before anything would work. My first few attempts failed with a plain "access denied," and it took a while to realize the badge (checkpoint 1) and the allowance (checkpoint 2) were both fine — the actual signature was the missing piece.
This checkpoint answers "who's allowed to actually sign?"
Checkpoint 4: did a human really say yes?
Three checkpoints down, and here's the one that's supposed to matter most, because it's the only one with an actual person in it. Before the agent spends anything, it has to stop, show a card explaining what it wants to buy and why, and wait:
entry = approvals.request_approval(
resource=resource_id,
amount_usd=amount_usd,
justification=justification,
)
# ...prints the approval card, then blocks until someone decides...
final = approvals.wait_for_decision(entry["approval_id"])
A human sees the card and taps "approve" or "reject." Simple. Except — and this is the part I want to be honest about — nothing in that flow checks who tapped approve. The approval system tracks the decision, not the identity of the person who made it. Anyone who can see or guess the approval link can click yes, and the agent has no way to tell the difference between the actual owner approving a purchase and a stranger who stumbled onto the same page.
Compare that to the other three checkpoints. The AWS badge is cryptographically tied to my account. The spending cap is enforced by AWS's own servers, not by the agent's honesty. The signature can only come from one registered key. Every one of those has a real lock on it. The human approval step — the one meant to be the final safety net — currently has no lock at all. It's a screen door on a bank vault: everything behind it is genuinely secure, but the door itself just needs a push.
This checkpoint is supposed to answer "did the right person actually say yes?" — and right now, it only answers "did someone say yes."
What this actually taught me
Laid end to end, the four checkpoints look like this:
- Identity — is this really my agent, backed by AWS IAM roles that only my agent's runtime can assume
- Scope — how much can it spend and until when, backed by a fresh, capped, expiring payment session
- Custody — who actually holds the signing key, kept outside my code entirely, in a service that only honors one registered signer
- Consent — did a real, verified human say yes — and this is the one that's still just an honor system
The first three are the kind of thing security people usually mean when they say "zero trust" — nothing is assumed, everything is checked by a system, not by good intentions. Building them felt tedious in the moment (that missing-signer bug alone cost me an evening) but each one closes a door that would otherwise be wide open.
The fourth one is the reminder that "add a human in the loop" is not automatically a security feature. A human approval step is only as strong as the thing verifying it's actually that human clicking. Right now, mine isn't verifying anything — it's a good UX pattern wearing a security costume. The fix is exactly what you'd expect: put real user login in front of the approval page, and check that the person clicking "approve" is the same person the task was created for. I haven't built that part yet, and I think that's a more useful thing to admit than to quietly gloss over.
If you're building anything where an AI agent gets to spend money, sign a document, or take any real-world action on someone's behalf, it's worth walking through these same four questions for your own system: who is it, how far can it go, who actually holds the key, and — the one people skip — can you prove who really said yes?
Try it yourself
Full source, including the IAM policies, the payment session code, and the approval flow: github.com/yama3133/wallet-agent

Top comments (0)