Programmable Spending Controls Should Gate AI Agent Purchases Before Signing
An AI agent should not be allowed to pay merely because a service returned a valid payment request. The agent needs a decision layer that checks who is receiving funds, what the task permits, how much has already been spent and whether the transaction is safe to sign. Programmable spending controls turn an open-ended wallet into a bounded purchasing capability.
The control point belongs before wallet authorization. A merchant can reject unsupported assets or routes, but that does not create a buyer's budget. A per-purchase maximum does not limit the sum of hundreds of purchases. A confirmation prompt can reduce risk, but it is not a durable policy. The strongest design combines policy evaluation, transaction validation, wallet permissions and recoverable execution.
GOAT AgentKit is relevant because its public README documents a PolicyEngine with controls such as allowedNetworks, maxRiskWithoutConfirm and writeEnabled, alongside validation, idempotency and retry handling in the execution pipeline. Those are concrete runtime controls. They should not be expanded into a claim that every deployment includes a daily budget ledger, merchant allowlist or atomic multi-agent reservation system.
Separate intent, policy and signing authority
An agent's request to buy a service is an intent. It might say, “obtain current market data for this task, up to a defined amount.” The policy engine translates that intent into conditions. The wallet or signer executes only after those conditions pass.
These responsibilities should remain distinct:
Intent says what the task is trying to accomplish.
Policy says what the agent is allowed to do.
Validation checks what the current transaction actually does.
The signer authorizes the resulting transaction.
The merchant verifies payment and delivers the resource.
Keeping signing authority outside model-generated text matters. A prompt can ask an agent to spend a large amount, but it should not be able to rewrite the wallet's network, recipient or spending rules. Policy should be loaded from a trusted configuration or service, evaluated deterministically and recorded with the payment intent.
The policy result should be more informative than “allowed” or “denied.” It can state the matched rule, remaining budget, required confirmation level, expiry and fields that were checked. That record helps an operator explain why a transaction was authorized and helps a retry reuse the same decision when the underlying payment state is still valid.
Use more than one spending limit
A single maximum is easy to understand but incomplete. An agent may need limits at several levels:
Limit | Example | What it prevents |
|---|---|---|
Per transaction | No payment above $0.25 | One unexpectedly expensive call |
Per service | $2 per data provider | Concentration in one provider |
Per task | $5 for one research job | Workflow overspend |
Per time window | $20 per day | Repeated unattended spending |
Global treasury | Keep a wallet reserve | Exhausting available funds |
The last two limits require shared state if several workers or processes can spend from the same wallet. Checking a balance and then authorizing independently is not an atomic reservation. Two concurrent workers can each observe the same remaining budget and together exceed it.
The public AgentKit evidence supports policy gates and runtime controls, but it does not confirm a built-in cumulative daily budget or distributed reservation ledger. A team that needs those properties should add a shared policy service or ledger and test it under concurrency. This is an important product boundary, not a reason to omit budget controls.
Budgets also need a currency rule. A limit denominated in USDC is not automatically equivalent to a limit denominated in a volatile token. A cross-network budget needs a quote policy, a freshness window and a rule for fees. If the system cannot value the payment reliably, it should require a stronger approval level or stop.
Use allowlists for networks, assets and recipients
Risk controls work best when they constrain concrete transaction fields. An allowlist can cover the network, token contract, merchant address, service identifier and payment route. The agent should compare the current requirement with the list immediately before signing.
The network check answers where the transaction will execute. The asset check answers what token moves. The recipient check answers who receives it. The service or route check answers what the payment is supposed to unlock. Passing one does not imply that the others pass.
GOAT AgentKit's documented allowedNetworks is an example of a network-level policy control. It does not by itself prove an allowlist of every merchant or token, nor does a merchant-side supported pair create a buyer-side approval. Those controls live at different boundaries and should be represented separately in an audit record.
Recipient allowlists need a maintenance policy. A merchant can rotate wallets, use different addresses per network or rely on a configured receiving address. A hard-coded address may block a legitimate update; an unrestricted address may allow a malicious redirect. The safer approach is to bind an approved merchant identity to an expected route and require review when the destination changes.
Do not let the agent infer a recipient from an image, description or arbitrary URL. The payment requirement should be the source for transaction terms, while the policy determines whether those terms are acceptable. A valid signature proves authorization of a transaction; it does not prove that the transaction was wise.
Make risk levels change the required approval
Not every purchase deserves the same friction. A small payment to an established service may pass automatically. A new recipient, unsupported route or high-value transaction may require explicit confirmation or a separate operator approval.
A practical risk policy can combine:
payment amount relative to the remaining task budget;
merchant and route familiarity;
network and asset allowlist status;
transaction type and write scope;
service delivery and refund terms;
whether the request is a first purchase or a retry.
GOAT AgentKit's README documents maxRiskWithoutConfirm and writeEnabled as PolicyEngine controls. These settings show how a runtime can distinguish permitted actions from actions that need confirmation or write access. They are not evidence of a universal risk-scoring model. Each deployment still needs to define what risk means and which facts feed it.
Confirmation should be bound to the exact intent and transaction. A generic “I approve purchases” flag is too broad. Show the merchant, asset, network, amount, recipient, service and expiry. If any of those fields change, invalidate the approval and evaluate again.
Time limits are equally important. A policy approval for a price that expires in five minutes should not remain valid tomorrow. Store the policy version and decision time with the payment intent. If a long-running task needs a new payment, evaluate the new request rather than carrying forward a stale approval.
Validate the transaction after the agent chooses it
Policy evaluation is not the final check. The transaction builder or payment middleware should validate the actual values that will be signed. This catches mismatches introduced by a client library, token decimals or a changed server challenge.
At minimum, validate:
chain ID and network;
token contract and decimals;
exact amount and allowed fee;
recipient and payment route;
resource, order or task identifier;
expiry and nonce where supported;
authorization scope and write method.
The validation result should be deterministic and reproducible. A model should not be able to argue that an amount “looks close enough.” If the amount cannot be represented exactly, the asset is not allowed or the recipient is not recognized, stop before signing.
This two-step pattern is valuable: policy asks whether the agent may perform an action, while validation asks whether the constructed transaction is that action. The distinction limits bugs and prompt-driven parameter substitution. It also gives developers a precise place to add tests.
Treat retries as state recovery, not fresh permission
Autonomous workflows often retry. An RPC call can time out after broadcast, a facilitator can return a temporary error or a merchant can delay verification. A naive retry may sign and submit a second payment even though the first one is pending.
Idempotency connects the business request to its existing payment state. Keep a task ID, payment intent ID, order ID and transaction hash. If a retry finds a broadcast transaction, resume verification. If verification succeeded but the result was lost, retrieve the existing result. If no payment was submitted, request current terms and evaluate a new authorization.
The public AgentKit execution pipeline documents idempotency and retry handling. That supports using those concepts as first-class runtime controls, but it does not establish that one configuration automatically knows the merchant's refund policy or every downstream business state. The application must define what operation is idempotent and what evidence closes it.
Retry budgets should also be explicit. A workflow can have one retry for a transient verification error and zero retries for a changed price. A service execution retry may be free if it retrieves an existing result; a new paid request may need another spending decision. Do not let a generic network retry bypass the policy engine.
Distinguish merchant limits from buyer limits
The merchant and the buyer both have controls, but they solve different problems. A merchant may set a maximum order value, restrict routes or configure a supported token. The buyer may limit how much its agent can spend or which merchants it trusts. One is not a substitute for the other.
GOAT merchant documentation describes operational and route-side limits that govern the seller's payment surface. AgentKit's PolicyEngine sits on the agent side. A merchant limit can reject a payment that exceeds the offer's conditions; it cannot stop an agent from spending its budget elsewhere. A buyer policy can refuse a merchant; it cannot make an unsupported merchant route accept a transfer.
This separation is especially important for multi-agent systems. A supervisor agent may delegate a $1 task budget to a research agent, which delegates smaller budgets to search and verification services. Each delegation should have an identity, expiry, allowed purpose and remaining amount. A parent budget is not safely enforced if child agents can spend directly from an unrestricted shared wallet.
If delegated authorization is not implemented, keep the architecture simple: one policy owner, one signer boundary and one auditable payment-intent record. Adding more agents without adding budget reservations and delegation evidence increases the number of ways a task can overspend.
Log the decision, not only the transaction
A transaction hash answers which network event occurred. It does not answer why the agent authorized it. Preserve the policy version, intent, matched rules, risk level, confirmation state, transaction fields, payment result and delivery outcome.
An operational record might connect:
The record should avoid unnecessary sensitive prompts or personal data. It needs enough evidence to investigate an unexpected purchase without becoming a copy of the agent's entire context. Retention and access rules belong to the operator.
Test spending controls under concurrency and failure
Before allowing unattended purchases, test the policy with a matrix of amounts, merchants, assets and network states. Check that a transaction just below the per-call limit passes, one just above it stops, and a second concurrent request cannot consume the same reserved task budget.
Then test a changed recipient, changed asset, expired challenge, missing gas, pending transaction, duplicated webhook and failed service execution. Confirm that the system asks for a fresh decision or resumes existing state rather than silently authorizing a new payment.
Run the same tests with a policy update between intent creation and signing. The runtime should either pin the approved policy version for that intent or re-evaluate under the new version. It should never leave the choice ambiguous.
Programmable spending controls are the boundary between an agent that can call a wallet and an agent that can operate responsibly in a paid workflow. They should be treated as part of the service's execution contract, not as a cosmetic setting in an orchestration prompt. Every permitted payment needs a reason, a bounded amount, an approved destination and a recoverable state. GOAT AgentKit provides documented PolicyEngine and execution-pipeline concepts that are useful building blocks for this layer. Developers still need to add the budget ledger, identity rules, recipient governance, merchant policy and delivery recovery that their risk model requires. The goal is bounded authorization, not unlimited automatic spending.



