Backoffice run by agents
Ten operational jobs an agent can hold instead of a person, each mapped to the platform mechanism behind it — plus the four rules that make handing over operational work safe rather than reckless.
An agent that answers questions is a chatbot. An agent that holds a job reads your data through your own code, calls the tools you granted, writes through operations that enforce your rules, and stops for a human on whatever you gated.
This page is the operational half of How software is changing, made concrete for a commerce or marketplace operation.
Ten jobs an agent can hold
| Job | What the agent does | Mechanism |
|---|---|---|
| Order triage | Screens incoming orders for address problems, fraud signals, and mismatches; emails the customer for clarification; releases the clean ones | Queue consumer |
| First-line support | Answers "where is my order", returns policy, and sizing questions from real order data across web chat, email, and Slack | Browser chat + messengers |
| Inventory watch | Compares stock against sales velocity nightly, drafts restock proposals, flags dead stock | Schedule |
| Promotion drafting | Proposes discounts on slow lines within a margin floor enforced in code | Approval-gated action |
| Catalogue QA | Audits new listings for missing images, thin descriptions, wrong category, duplicate SKUs | Queue on listing creation |
| Vendor application review | Checks submitted documents and details for completeness and consistency; escalates anything ambiguous | Queue + approval |
| Dispute and chargeback prep | Gathers order, delivery, and conversation evidence into a case file; drafts the response | Agent tools over your data |
| Payout preparation | Assembles the commission run, reconciles against orders, flags variances | Approval-gated action |
| Morning exception digest | Emails what needed attention overnight and what it handled | Schedule + email |
| Content and SEO | Writes product descriptions and category copy in your voice, from your attributes | Operator-started jobs |
Nothing there is speculative machinery — each maps to a documented platform mechanism, which is the difference between an operations plan and an aspiration.
Worked example: order triage
The highest-volume job, and usually the first one worth handing over.
Four properties make this safe to run unattended:
- The queue is durable. At-least-once delivery with retries and a dead-letter queue, so a downstream failure delays orders rather than losing them. The handler is idempotent, so redelivery can't double-process.
- The agent can't invent facts. Address validation and stock come from tool calls into your code. There's no path by which it guesses a stock level.
- The expensive action is gated. Releasing an order is routine; issuing a refund pauses for a person.
- Everything is recorded. Each outcome emits an event to the app's own feed, including refusals — so "what happened to order 4471" is a query.
Worked example: promotions with a hard floor
The pattern for "let the agent decide, within limits" — and the clearest illustration of why limits belong in code.
// app/src/domain/promotions.ts — one implementation, every caller
export async function upsertPromotion(env: Env, input: PromoInput, actor: string) {
const margin = await marginFor(env, input.sku, input.discount);
if (margin < 0.15) {
await emitAppEvent(env, 'guardrail.refused',
{ rule: 'margin_floor_15pct', sku: input.sku }, { actor });
throw new Error('Below margin floor');
}
// ...write, emit promo.created
}
The agent proposes; the action is approval-gated; a manager confirms. And then the part that matters:
The floor still holds after approval. A manager clicking approve on a 40% discount that breaches the floor gets a refusal, and the refusal is recorded.
Had the floor lived in the agent's instructions, approval would be the exact moment it evaporated — the model was argued into proposing it, and the human rubber-stamped it. In code, it binds the agent, the backoffice form, the API, and the scheduled job identically.
Worked example: support across every channel
One agent, several connectors: the storefront chat, the support inbox, and a Slack channel for staff. Same instructions, same tools, same limits — so the answer a customer gets in chat matches what they'd get by email, because the behaviour lives in one place rather than in three integrations.
Identity is the part to get right. When a signed-in customer chats, the agent receives verified claims, and its tools read the customer from that rather than from anything the conversation says:
// ✅ identity from the platform, not from the model
order_status: tool({
inputSchema: z.object({}), // no customerId argument
execute: () => getOrderStatus(this.env, this.identity?.userId),
}),
A customerId parameter is a parameter a model can be persuaded to fill with someone else's — see Writing an agent.
The four rules
Distilled from the pages this section links to. Every one exists because the alternative was a real incident somewhere.
1. Rules in code, never in prompts. A limit in a domain function binds every caller and survives human approval. A limit in a prompt is guidance a sufficiently persuasive request removes.
2. Least privilege, by default. An agent holds exactly the tools you granted — no ambient database, filesystem, or shell. A triage agent that never received a refund tool cannot issue one, however the conversation goes.
3. Gate the consequential, not everything. Money out, customer commitments, irreversible actions, anything public. Gating everything produces an approval queue nobody reads, which manufactures the appearance of oversight while removing the real thing.
4. Verify from traces, not from prose. Every tool call and model step is recorded. "Did it actually check stock?" is answered by an edge in the trace, not by the agent's summary — see Observability and audit.
How to start
A sequence that avoids the usual failure of handing over too much at once:
- Pick the highest-volume, lowest-consequence job. Triage or first-line support. Read-only tools to begin with.
- Run it alongside the humans for a week. Compare its conclusions to theirs; the disagreements teach you what your rules actually are.
- Move the rules you discovered into domain code, not into the prompt.
- Grant one write, gated on approval. Watch the approval queue — if you're approving everything unread, the gate is in the wrong place.
- Emit an event for every outcome, then build the dashboard from that feed.
- Only then add the next job. Fleets accrete; they shouldn't be launched.
Next
- How agents work — the model underneath.
- Human approvals — gating writes properly.
- From one person to a marketplace — where this fits as you scale.
Frequently asked questions
Which backoffice work should an agent take first?
The highest-volume job with the lowest consequence per decision — usually triage or first-line support. You get the biggest time back with the smallest downside if the agent gets one wrong, and you learn how it behaves before handing it anything expensive.
What should never be fully automated?
Anything that moves money outward, makes a customer commitment, is hard to reverse, or is publicly visible. Those become approval-gated: the agent prepares the decision and a person confirms it.
How do we know the agent didn't invent something?
Read the trace, not the reply. Every tool call is recorded, so if an agent claims it checked stock and there is no corresponding tool edge, it produced plausible prose. That single habit catches the failure mode that matters.
Do agents replace the operations team?
They change what the team does. People move from processing the routine flow to setting policy and handling exceptions, which is why volume stops mapping directly to headcount.
What happens when an agent gets it wrong?
The same things that protect you from human error: rules enforced in code that refuse the write regardless of who asked, an audit event for every outcome including refusals, and 30-day point-in-time recovery if data needs rewinding.