Approve Every Order? Modeling Rules-Based Agent Autonomy as Order Holds
A shopping agent buying something is not really one feature. It is three, depending on how much the human has delegated, and each one puts a different state machine in your OMS.
The numbers
Riskified’s Agentic Commerce Pulse, released Oct 27, surveyed 5,100 consumers in the US, UK, Japan, Mexico and Brazil:
- 80% “have used AI for some parts of the shopping process.”
- “just 11% of consumers have completed a purchase inside an AI tool.”
- “just 7% of people surveyed said they were comfortable letting AI buy something without their approval.”
- “More than four-in-10 shoppers (43%)… still want to approve every purchase before it happens, while 29% would allow an AI agent to purchase automatically within rules they set, either requiring approval for exceptions or receiving notifications after purchases are made.”
Riskified calls this “conditional autonomy”. One line from its CMO matters for the back office: “‘The agent did it’ is about to become a new refund-abuse vector.” The survey also found 51% think the AI platform should issue refunds when the AI makes a mistake.
Retailers are already shipping this split. Walmart’s Oct 21 announcement of Agent Profiles says customers “can require approval for every purchase or provide permission for the agent to make purchases within a spending limit they set – for example, $50, $100 or $200.” Its agent terms add that “Customers must approve agent purchases by default, and Walmart may require additional approvals even for purchases within an agent’s spending limits.”
That last clause is the interesting one. Even inside a rule, the merchant keeps the right to stop an order and ask. That is an order hold.
Three modes, one question: who said yes?
| Mode | Survey bucket | Who authorizes | When the order is releasable |
|---|---|---|---|
| Approve every order | 43% | The human, per order | After explicit approval |
| Auto within rules | part of 29% | A rule the human set | Immediately if a rule matches; otherwise held for approval |
| Notify after | part of 29% | A rule, plus a cancel window | Immediately, but cancellable until fulfillment starts |
The 7% fully-unsupervised group is mode 3 with no notification. In data terms it’s the same model, just with an empty undo window.
The engineering question isn’t “is this an agent order”. It’s which authority released this order, and can I prove it later.
Where approval happens: before or after the order exists
There are two places approval can live, and they produce very different OMS work.
Before the order exists (platform-side). The agent platform asks the human, then completes checkout. UCP’s checkout spec takes this view: “The checkout has to be finalized manually by the user through a trusted UI unless the AP2 Mandates extension is supported.” When buyer input is needed, the session goes to requires_escalation and the platform hands off via continue_url. With the AP2 Mandates extension, the platform “MUST provide cryptographically signed proofs (Mandates) during the complete operation, proving the user explicitly authorized the specific checkout state and funds transfer,” and a trusted platform provider “MUST ensure that mandates are not created without explicit user consent from trusted, deterministic channels.”
In this case your OMS receives a normal order. Your job is to store the evidence (the mandate reference) on the order.
After the order exists (merchant-side). The merchant holds the approval relationship, as Walmart’s Agent Profiles do. The order is placed, payment is authorized, and the order waits for the human in the merchant’s own app. Now the OMS owns a real hold state.
I checked UCP’s order capability on current main. It has no approval or hold status, so a merchant-side hold is internal state you model yourself. Nothing in the spec tells an agent “this order is awaiting the buyer’s approval”. (Mercury’s write tools use the same propose-then-approve-with-expiry pattern for money movement. I covered that in The Agent Can Ask, the Human Signs.)
Most merchants will see both: platform-approved orders arriving with evidence, and their own exception holds on top. Model them with the same fields.
The state machine
Here is how I’d lay out merchant-side states. These are internal OMS states, not UCP fields.
placed (payment authorized, stock allocated, deadline set)
│
├─ mode = approve_every, or no rule matched ──► pending_approval
│ ├─ approve ─────────► released
│ ├─ decline ─────────► canceled
│ └─ timeout ─────────► canceled
│
├─ rule matched ──────────────────────────────────────────────────────► released
│
└─ mode = notify_after ──────────────────────────► released_cancellable
├─ buyer cancels ───► canceled
└─ window closes ───► released
released ──► 3PL release, ERP posting, capture on ship
canceled ──► void auth, free allocation, notify buyer and agent
merchant risk check may move any branch to pending_approval
Three rules make this behave:
releasedis the only state that talks to the warehouse. Nothing inpending_approvalorreleased_cancellablegoes to the 3PL. If you have to start picking before the cancel window closes (same-day delivery), close the window at release-to-3PL, not at a fixed clock time.- Every hold has a deadline.
pending_approvalwithout anapproval_expires_atis how orders rot. - Timeout is a decision, not an absence. A timeout is recorded with
decided_by: "timeout"and drives a cancel, just like a decline does.
Payment and inventory during a hold
A held order is a promise you haven’t kept yet, so be explicit about what it’s holding.
- Payment: authorize at placement, capture at release or ship. Never capture during
pending_approval; a declined approval should end in a void, not a refund. A refund is slower, costs more, and looks like a return in your books. - Auth lifetime: card authorizations don’t last forever, and how long they last depends on the network and method. Set
approval_expires_atwell inside the auth’s validity, so a late approval never runs into an expired auth. - Inventory: allocate at placement, with the same deadline as the approval. The hold occupies real stock, so it needs the same discipline as any reservation: a fixed expiry that is never extended in place, a cap on concurrent pending holds per buyer and per agent, and an explicit release on timeout. For scarce SKUs, a long approval window is a long out-of-stock for everyone else.
- Price: the human approves a specific total. If anything changes during the hold (tax, shipping, a substitution), invalidate the approval and ask again. In UCP terms, AP2’s checkout mandate binds “the full checkout response”, so an edited order no longer matches what was signed.
My inference on timeouts: approval windows for agent orders should be short (minutes to a few hours), with an auto-cancel and a clear notification. A long window feels kind, but it ties up stock and auth headroom. My guess, which I haven’t measured, is that most approvals that ever arrive will arrive early.
Rules, and recording which one fired
“Auto within rules” only works if a rule is data, not code paths.
type Mode = "approve_every" | "auto_within_rules" | "notify_after";
interface AutonomyPolicy {
policy_id: string;
version: number; // immutable; edits create a new version
buyer_id: string;
agent_id: string; // one policy per agent profile
mode: Mode;
rules: Rule[]; // evaluated in order, first match wins
approval_ttl_minutes: number; // e.g. 60
cancel_window_minutes?: number;// notify_after only
}
interface Rule {
rule_id: string;
max_order_total?: Money; // per-order spend cap
max_spend_per_period?: { amount: Money; period: "day" | "week" | "month" };
allowed_categories?: string[];
blocked_categories?: string[]; // e.g. gift cards, alcohol
allowed_merchants?: string[]; // for marketplace / multi-seller
reorder_only?: boolean; // only items bought before
}
interface AuthorizationDecision { // one per order, append-only
decision_id: string; // idempotency key for release/cancel
order_id: string;
outcome: "released" | "held" | "canceled";
decided_by: "rule" | "human" | "timeout" | "merchant_risk";
policy_id?: string; policy_version?: number; rule_id?: string;
evidence_ref?: string; // mandate id, approval event id
inputs: { total: Money; categories: string[]; period_spend_before: Money };
decided_at: string;
}
The fields that matter later:
policy_version+rule_id. When a buyer disputes an order three weeks later, “ruler-groceriesin policy v4, total $43.10 against a $100 cap” is an answer. “The agent was allowed” isn’t. Never edit a policy in place.inputs. Snapshot what the rule saw, especially period spend. Recomputing it later gives a different number once refunds land.decided_by: "merchant_risk". This is Walmart’s “additional approvals even for purchases within an agent’s spending limits.” A rule can match and your fraud stack can still hold the order. Record both.
Period spend caps need care. Count authorized-but-uncaptured holds against the period, or two orders placed a second apart will both fit under the same remaining budget. Make the check-and-increment one atomic operation per buyer and agent.
Idempotent release and cancel
Approval arrives over unreliable channels: a push notification tap, a webhook from the agent platform, a retry from your own job runner. Everything must be safe to repeat.
- Transitions are conditional writes:
UPDATE orders SET state='released' WHERE id=? AND state='pending_approval'. Zero rows updated means someone else already decided. Return the existing decision, don’t raise an error. - Release and cancel race. Whichever commits first wins, and the loser gets the winner’s
AuthorizationDecisionback. A late “approve” after a timeout-cancel must not resurrect the order. The agent should place a new one, through the rules again. - Downstream side effects (void auth, free allocation, send to 3PL) are keyed by
decision_id, so a replayed event can’t double-void or double-ship.
Downstream effects
- 3PL: only
releasedorders are sent. A cancel duringreleased_cancellableshould never need a warehouse intercept. If it does, your window outlasts your fulfillment SLA. - ERP: don’t post a sales order or invoice for held orders. Post at release, and carry
authorized_by(rule/human/ platform mandate) as a dimension. Finance will want to see agent-auto revenue separately, and so will your auditors. - Refunds and returns: a “my agent shouldn’t have bought this” return should be classified by the decision record. If a human approved it or a rule they set matched, it’s an ordinary return under your policy. Walmart says its “returns process applies – including if an agent doesn’t act as intended.” If no valid authority exists, that’s your bug, and you refund without arguing.
- Disputes: the
AuthorizationDecision, the policy version and any mandate are your chargeback evidence. Riskified’s point is that the platform’s terms make the user liable while consumers expect the platform to refund, and “in practice, that dispute lands on the merchant.” The audit field is what lets you win the dispute.
Checklist
- Every agent order has exactly one
AuthorizationDecision, append-only -
decided_by,policy_id,policy_version,rule_idand an input snapshot are recorded - Policies are versioned; edits never mutate history
-
pending_approvalalways hasapproval_expires_at, set inside auth validity - Timeout auto-cancels: void the auth, free the allocation, notify
- No capture and no 3PL release before
released - Any price or line change during a hold invalidates the approval
- Period spend caps count pending holds, checked atomically
- Release and cancel are conditional transitions; side effects keyed by
decision_id - Merchant risk holds can override a matched rule, and are recorded separately
- Platform mandates (e.g. UCP AP2) are stored as
evidence_ref - ERP and dispute tooling can filter by authorization source
What I could not settle
- No standard hold signal. UCP’s order capability has no “awaiting buyer approval” state, so an agent can’t learn that its order is held except through merchant-specific channels.
- Rule portability. Walmart’s limits live in Walmart’s Agent Profiles; a platform’s rules live in the platform. Which one wins when both exist is a policy choice nobody has written down publicly.
- Survey vs behaviour. 43% say they want to approve everything. I don’t know how many will keep approving once the prompts get tedious, or how fast approval timeouts will climb.