
The three layers side by side, with the module each one lives in. Layer 2 is the source of truth because the USDC sits inside the contract, so the cap is enforced by code rather than by a server that could be bypassed. Layers 1 and 3 are a pre-check and a hosted screen around it.
Three independent enforcement layers
1
Server policy engine (pre-check)
The fast first gate: daily cap, auto-approve ceiling, payee allowlist, freeze, and
the human-approval requirement. Resets at 00:00 UTC. This is a check, not the
source of truth: it is the fallback, not the guarantee.
2
On-chain policy vault (the source of truth)
Each agent can get its own
AgentSpendPolicy contract on Arc that holds the USDC
and enforces the same policy on-chain. An over-limit pay() reverts on Arc
with a typed error you can read on the explorer, not a server “no”. The human
owner can override an approved payment, adjust limits, freeze, or withdraw.3
Circle Agent Wallet (hosted screening)
Optionally, the agent’s USDC lives in a Circle Developer-Controlled Wallet whose
hosted policy engine screens every transfer at the wallet layer (sanctions,
address allow/block, freeze). A screening denial is authoritative.
executeInstruction routes a payment vault, then Circle, then direct, each
additive with a fallback, so one layer hitting an infra error never fabricates a
success, and a real policy rejection always stops the payment.
The money shot: an on-chain revert
The difference between a demo and a product is where the “no” comes from. Here it comes from the chain:Owner vs operator. The vault’s
owner is a human wallet (sets policy, overrides,
freezes, withdraws). The operator is the agent’s signer (may only pay within
policy). They are genuinely separate keys, and the agent can never change its own limits.What an owner should know about the limits
- The cap is a calendar day. It resets at 00:00 UTC, so up to twice the cap can go within seconds around that time. With a cap that does not change, the agent’s payments reach at most 2x the cap between any two moments up to 24 hours apart. Lowering the cap does not undo today’s spending, so to stop an agent now, freeze the vault.
- An owner payment counts toward the agent’s day and can stop the agent until 00:00 UTC, unless the owner raises the cap. A withdrawal does not count.
setPolicywrites both limits and the allowlist setting in one call, so a caps change that sendsfalseturns the allowlist off. The list survives, so turning it on again restores it.- 0 means no limit, not no spending, on the cap, the ceiling and the end date. The vault page and our backend refuse it; the contract accepts it.
- The end date belongs to the vault, not to one key. A new end date after a revoke turns the same key back on, and a new key keeps the end date already there. If a key may have leaked, replace it first: that alone stops it.
What each layer actually enforces
See it on the explorer
Provision a vault from the Permissions screen, then watch an over-limit payment
revert on Arc testnet.
