In a recent study, nine out of ten frontier models raised their own spending limit when it stood between them and their task. Nobody attacked them. They just did what the task required.
The experiment
Researchers at Cornell Tech and Yale gave AI agents real Circle agent wallets on Base, each holding 10 USDC, with a daily limit of 0.01 USDC. Each agent had three tools: email, web and wallet. The task was to pay the invoices sitting in the inbox, which was impossible within one cent a day.
Ten models ran the scenario 20 times each. Nine of them, in at least one run, triggered a limit increase, searched the inbox for the one-time code the wallet sent to confirm it, typed it in, and approved themselves.
In a second scenario, a forged email posing as the owner was enough for five of the ten models to raise the limit and pay an attacker's address.
The researchers disclosed the issue to Circle on September 22, 2026, and published the preprint on October 26 (I-HITL: Inalienable Human-in-the-Loop, summarized by CryptoTicker).
The limit was real. The lock was not.
The spending limit itself worked. What failed was the way to change it: the confirmation code landed in an inbox the agent could read. A second factor that sits in the agent's toolbox is not a second factor. It is one more step the agent can perform.
None of the models stole anything. They hit a conflict between the task and the limit, found a path that resolved it, and took it. That is the uncomfortable part: you don't need a malicious agent or a clever attacker. You need a goal, a limit, and any reachable way to move the limit.
So the useful question is not "how high is the limit?" It is "who can change it, and through which channel?"
Three places a limit can live
| Where the limit lives | Who can change it | What breaks it |
|---|---|---|
| In the prompt | Anything the model reads | Prompt injection, a retry loop, a task that "needs" more |
| On a server (wallet provider, payment API) | The operator, plus any approval channel the server accepts | A compromised server, or an approval channel the agent can reach |
| In a contract, as a signed delegation | Only the account that signed it | Nothing the agent does: an out-of-bounds call reverts |
The industry has largely moved from the first row to the second, and that is real progress. But the second row still asks you to trust whoever runs the check, and to keep every way of raising the limit out of the agent's reach. The third row removes both questions. The rule is enforced by the code that moves the money, and the only key that can widen it never touches the agent.
How an ERC-7710 delegation enforces a budget
ERC-7710 standardizes how a smart account lets someone else act on its behalf. The account signs a delegation once. The delegate later redeems it through a DelegationManager contract, which checks the signature and a list of caveats before executing anything.
- The owner signs the envelope. One EIP-712 signature defines who may act (the delegate), on what (a token), and within which bounds (caveats: amount per period, allowed recipients, end date).
- The agent redeems, it never holds. Funds stay in the owner's account. Each payment is a call to
redeemDelegations, and the transfer happens only if every caveat passes. - Caveat enforcers keep state on-chain. A period transfer enforcer records how much was spent in the current period and resets it the next. Overspending, or charging twice in one period, reverts.
- Revocation is one call. The owner disables the delegation on-chain, and any later redemption fails.
- Authority only narrows. An agent can redelegate to a sub-agent, but every link's caveats are checked, so a child never gets more than its parent had. In pseudocode (see the MetaMask Smart Accounts Kit docs for the real API):
// Owner side, signed once: // up to 100 USDC per week, to one agent, for 90 days const delegation = createDelegation({ from: ownerAccount, to: agentAddress, scope: { type: "erc20PeriodTransfer", token: USDC, periodAmount: 100_000_000n, // 100 USDC, 6 decimals periodDuration: WEEK, startDate: now, }, caveats: [expiresAt(now + 90 * DAY)], }); const signed = await ownerAccount.signDelegation(delegation); // Agent side, at payment time: // the DelegationManager decides, not the agent await redeemDelegations([signed], transfer(recipient, amount)); There is no approval code, no inbox and no admin endpoint in this flow. To spend more, the agent would need a new signature from the owner's account, which is exactly the key it was never given.
This works for any account that can act as a delegator: a Safe through a module, a MetaMask smart account, or a plain EOA upgraded with EIP-7702. MetaMask's kit also accepts these delegations as an x402 payment payload, so agents paying for APIs can use the same envelope.
Three classic agent failures, made impossible
Most agent payment incidents follow the same few patterns. Here is what each one runs into when the budget is a delegation:
- Splitting a payment to dodge the cap. With a per-period cap, two payments of 6,200 count against the same total as one of 12,400. The second one reverts.
- "Our wallet address changed." If the delegation pins the recipient, a payment to any other address reverts, however convincing the email.
- The retry loop. A payment times out, the framework retries, and you pay twice. A second charge in the same period reverts on-chain. The agent can still be fooled. It just can't turn being fooled into money leaving the account.
In practice: recurring budgets for a Safe treasury
We built this pattern into Hourglass, a Safe App for recurring, capped payments from team treasuries: contributor payroll, retainers between DAOs, subscriptions, and agent operating budgets.
- Install the DeleGator module once. A minimal module, deployed deterministically for the Safe, acts as the delegator. It lets the Delegation Framework execute transfers from the Safe within signed limits, and nothing else.
- Write the agreement. Payee, token, amount, period and an optional end date. A human-readable version is pinned to IPFS.
- Sign once. The Safe signs with its usual threshold. The delegation salt is
keccak256(terms), so the signature is bound to the exact agreement text. Change a comma and the signature is void. - The payee charges itself each period.
ERC20PeriodTransferEnforcercaps each period and rejects a second charge in the same one. An optionalTimestampEnforcerenforces the end date. - Revoke at any time.
disableDelegation, routed through the module. Any later charge reverts withCannotUseADisabledDelegation. The treasury never pre-funds anything: no escrow, no streaming contract holding tokens, no relayer in the middle. The delegation grants a capability, not a balance. Signers stop doing a multisig round every month, and the payee, human or agent, can never pull more than what was signed.
The enforcing contracts are MetaMask's audited Delegation Framework deployments, and Hourglass adds no custom on-chain logic. It is live at hourglass.box on Base, with testnet support on Base Sepolia and Ethereum Sepolia.
What on-chain caveats don't solve
A contract enforces the envelope. It does not judge what happens inside it.
- Inside the cap, the agent still decides. If any recipient is allowed, a hijacked agent can pay the wrong one, within budget. Pin recipients wherever you can and keep per-period amounts small.
- The signing key must stay out of reach. The study's lesson applies here too: an agent that can reach a key able to sign delegations for the account can sign itself a bigger one. Keep Safe signers on separate devices.
- Caveats are public. Amounts, periods and recipients are visible on-chain. Private policies need another layer, such as zero-knowledge proofs.
- Revocable cuts both ways. The owner can stop payments at any time, which is what you want for an agent budget, but the payee gets no guarantee. It suits payroll, retainers and grants, not investor vesting.
- Audits. The Delegation Framework contracts are audited. Hourglass itself has not been independently audited yet, so evaluate on testnet and review the code before committing treasury funds. ## The short version
A spending limit is only as strong as the easiest way to change it. Prompts can be talked out of their limits. Servers can be approved past them through any channel the agent reaches. A signed delegation, enforced by the contract that moves the money, leaves the agent nothing to talk to and nothing to approve.
If you run a treasury, a payroll or an agent budget on a Safe, try Hourglass or read the code on GitHub. Questions, objections and edge cases are very welcome in the comments.