DEV Community

Yuuki Yamashita
Yuuki Yamashita

Posted on

Autonomy for $20, a Human Above It: A Pattern for AI Agents That Spend Money

At some point every "agentic" product roadmap runs into the same uncomfortable question: what happens when the agent needs to actually spend money, not just recommend an action to a human who then clicks a button? Recommending is easy to sandbox. Spending isn't. And the usual answers — "just require approval for everything" or "just trust the agent" — both fail in an obvious way. Approve-everything means the agent isn't really autonomous, it's a slow suggestion box. Trust-the-agent means the first bad prompt injection or hallucinated tool call has a direct line to your bank balance.

I wanted to see what a middle position actually looks like in code, not in a slide. So I built a small agent, sera-gate-agent, that pays multi-currency invoices for you and enforces a rule most people would agree with intuitively: small payments go through on their own, larger ones stop and wait for a person.

The trigger: a protocol that treats a keypair as a login

The settlement side runs on Sera, an on-chain FX protocol with an MCP server (sera-mcp) released under MIT specifically so agents can call it. What made me want to build on it wasn't the currency coverage, it was the auth model: there's no signup form. You generate a keypair, sign an EIP-712 message with it, and that signature is your API key request. A human finds this mildly inconvenient. An agent finds it exactly as convenient as an auth flow can be — no page to navigate, no CAPTCHA, no session cookie, just a key it already has.

That detail is what made "give the agent a wallet" feel like a natural next step rather than a stunt. If the whole point of an agent is that it acts without a human driving each click, an auth system built around clicking a human through steps is already fighting the premise.

The design: one number, one gated function

The rule I wanted was simple enough to say in one sentence: under $20, the agent executes on its own; over $20, it issues an approval card and a human has to click "approve" in a browser before anything moves. The part that took actual thought wasn't the rule, it was making sure the rule couldn't be talked around.

The threshold check itself is almost embarrassingly small:

def decide_auto_or_approval(amount_usd):
    threshold = Decimal(os.environ.get("SERA_GATE_AUTO_THRESHOLD_USD", "20"))
    return "AUTO" if amount_usd <= threshold else "REQUIRE_APPROVAL"
Enter fullscreen mode Exit fullscreen mode

What matters more is where it's called from. The underlying protocol exposes tools that actually move funds — execute a swap, send a transfer, pay an invoice directly. None of those are given to the language model. The only thing the model can call is a wrapper function, and that wrapper is the only code path that's allowed to reach the real execution tools. The threshold check lives inside that wrapper, not in a prompt instruction telling the model to "please ask before spending more than $20."

That distinction is the whole point, and it's easy to gloss over. A system prompt is a request. A model that's very good at following instructions will follow it correctly almost all the time — but "almost all the time" is a bad security property for something that moves money, whether the failure mode is prompt injection, a weird edge case in tool output, or the model just deciding the instruction doesn't apply this time. Removing the tool from the model's reachable set removes the failure mode instead of making it rarer.

I also didn't want the agent-side threshold to be the only backstop. Sera has its own server-side policy preset that caps what any single API key can move per transaction and per day, independent of anything my code does. I keep that ceiling well above my $20 agent-side threshold, so it's a true second layer rather than a duplicate of the first — if my code has a bug, or the signing key leaks, there's still a hard ceiling that doesn't route through my logic at all.

What this generalizes to

None of this is specific to on-chain payments. The same shape applies to an agent that can issue refunds, send emails to customers, delete records, or push a deploy: pick a dimension the org already has intuitions about (dollar amount, blast radius, reversibility), pick a threshold, and make the boundary a property of which functions are reachable rather than a property of what the model has been told. The dollar threshold is just the easiest one to make legible in a demo, because everyone already has an intuition for what $20 versus $5,000 means.

The uncomfortable part, and I don't think there's a clean answer to it, is that the threshold is still a judgment call, and it's a judgment call about how much you trust a system that doesn't get tired, doesn't get talked into things the way a person does, but also doesn't have the context a person has for "this specific invoice looks off." Setting it too low turns the agent back into a suggestion box. Setting it too high means the day it's wrong, it's wrong at a scale a human never got the chance to catch. I don't think that number should be static — the next version of this probably ties it to something like payee history or a running risk score instead of a flat constant — but getting the enforcement boundary right, structurally, felt like the part worth building first.

sera-gate-agent runs on Sepolia testnet today, built on AWS Bedrock AgentCore Runtime and Strands, with a Next.js front end for the approval cards a human actually clicks through.

Top comments (0)