Build a B2B procurement agent that survives human review
20 May 2026
Most "AI shopping agent" demos end at checkout. The agent finds a product, adds it to a cart, pays, done. That story works for a $40 pair of headphones. It falls apart the moment you point the same agent at a B2B purchase order.
In procurement, the interesting part isn't the buying — it's the gate before the buying:
- Purchases above a threshold need a human to approve them.
- That approval might come back in 30 seconds, or after a 2-day Slack thread.
- Every step has to land in an audit trail — who approved what, against which PO, for which expense category.
An agent that can't pause for a human, and can't survive the process restarting while it waits, isn't a procurement agent. It's a liability. Here's how to build one that can.
The shape: a chain of sub-agents
A procurement flow is really three jobs in sequence:
- find-best-price — comparison-shop across merchants, pick the lowest total per line item.
- request-approval — attach a PO number, then block at an approval gate if the cart exceeds the threshold.
- checkout-and-track — submit payment, subscribe to order updates.
In Agorio that maps directly onto AgentChain — sub-agents wired in sequence, each one's output piped into the next:
import {
AgentChain,
ShoppingAgent,
ClaudeAdapter,
agorioCloud,
FileSessionStorage,
} from '@agorio/sdk';
import { createApprovalWorkflowPlugin } from '@agorio/plugin-approval-workflow';
import { createSpendingControlsPlugin } from '@agorio/plugin-spending-controls';
import { createAuditTrailPlugin } from '@agorio/plugin-audit-trail';
import { createProcurementPlugin } from '@agorio/plugin-procurement';
const cloud = agorioCloud({ apiKey: process.env.AGORIO_API_KEY! });
const storage = new FileSessionStorage({ dir: './sessions' });
// Governance plugins, applied as middleware to every sub-agent.
const plugins = () => [
createSpendingControlsPlugin({ perTransactionLimit: 5_000, dailyLimit: 25_000 }),
createApprovalWorkflowPlugin({ requireApprovalAbove: 1_000 }),
createAuditTrailPlugin({ output: 'callback', callback: shipToYourLog }),
createProcurementPlugin({
expenseCategories: ['office-supplies', 'it-equipment', 'furniture'],
requirePoOnCheckout: true,
}),
];
const chain = new AgentChain()
.add({ name: 'find-best-price', build: (ctx) =>
new ShoppingAgent({ llm, tracer: ctx.tracer, onLog: ctx.onLog, plugins: plugins() }) })
.add({ name: 'request-approval', build: (ctx) =>
new ShoppingAgent({
llm,
tracer: ctx.tracer,
onLog: ctx.onLog,
plugins: plugins(),
sessionStorage: storage,
sessionId: 'po-2026-04829', // survives a process restart
}) })
.add({ name: 'checkout-and-track', build: (ctx) =>
new ShoppingAgent({ llm, tracer: ctx.tracer, onLog: ctx.onLog, plugins: plugins() }) });
const result = await chain.run(
'Order 100 ergonomic chairs for the new procurement team',
{ tracer: cloud.tracer, onLog: cloud.onLog },
);
The approval gate the LLM can't talk its way past
The approval-workflow plugin is middleware. It runs onBeforeToolCall, and when the cart total crosses requireApprovalAbove, it blocks submit_payment before it fires. This matters: it's not a prompt instruction the model can be argued out of — it's a hard interrupt in the agent loop. No "ignore previous instructions" gets a $50k order through without a human.
The same goes for spending-controls: per-transaction and rolling daily caps are enforced in middleware, so the LLM never even reaches the payment call once a limit is hit.
Surviving the wait
Here's the part that separates a demo from production. When the agent hits the approval gate, it doesn't busy-wait. It serializes its state — cart, PO number, conversation, plugin state — to SessionStorage and stops.
Days later, when a human clicks Approve, you reconstruct the agent with the same sessionId and it picks up exactly where it left off:
const agent = new ShoppingAgent({
llm,
plugins: plugins(),
sessionStorage: storage,
sessionId: 'po-2026-04829', // same id → hydrates the paused state
});
await agent.run('Approval granted — complete the purchase.');
The approval-workflow plugin implements the hydrate() hook, so its gate state restores alongside the agent. Use FileSessionStorage for a single box, or @agorio/session-redis when the approver and the worker are different processes.
The audit trail comes for free
Every tool call flows through the audit-trail plugin and, if you wired agorioCloud(), streams to the trace explorer. PO number, vendor, amount, and expense category are first-class fields — exactly the shape an EU AI Act Annex IV record wants. A multi-agent run renders as a tree: each sub-agent's LLM calls, tool calls, and audit events indented under the parent, on one shareable URL.
Run it yourself
The full reference agent — three sub-agents, all five governance plugins, against three mock merchants — runs end-to-end in about ten seconds with no API keys required:
git clone https://github.com/Nolpak14/agorio && cd agorio
npm i
npx tsx examples/procurement/index.ts
Next steps: see the full walkthrough on the procurement landing page, or wire agorioCloud() into your own agent and watch the chain render in the trace explorer.