A support agent per customer
Your app creates one agent per customer. It replies in your app and looks up orders with tools in your code. Refunds up to $50 run. Larger ones need approval in Slack. No agent can access another customer's orders. Idle agents stop, so thousands of them cost almost nothing.
The files
Section titled “The files”org: shop
http: trusted_workers: true
orgs: shop: {policy: support}
channels: slack: type: slack token: ${SLACK_BOT_TOKEN} signing_secret: ${SLACK_SIGNING_SECRET} members: {U024BE7LH: user:shop/@maria, U0G9QF9C6: user:shop/@sam}
notify: targets: support: {channel: slack, room: "#support"}
roles: app: rules: [{verbs: [create, define, ask, read, events, serve], kinds: [agent]}] support: rules: [{verbs: [read, events, decide], kinds: [agent]}]bindings: - {org: shop, role: app, subjects: [user:shop/@app]} - {org: shop, role: support, subjects: [user:shop/@maria, user:shop/@sam]}trusted_workers: truemarks your app's results as trusted, so a lookup doesn't make every refund need approval.- The
supportpolicy applies to every agent in the org, regardless of its definition.
allow: - workers/list_orders - workers/refund_order(amount <= 50)ask: workers/refund_order(amount > 50): A refund over $50.import { Wardrik } from "@wardrik/sdk";import { z } from "zod";import { db, payments } from "./app.ts"; // your app's own
export const wardrik = new Wardrik({ name: "support" });
// Wardrik passes the calling agent's ID. Your app maps it to a customer.const customerOf = (agent?: string) => db.customers.findBy({ agentId: agent });
wardrik.tool({ name: "list_orders", description: "List the customer's orders.", input: z.object({}), readOnly: true, run: async (_args, { agent }) => db.orders.where({ customer: (await customerOf(agent)).id }),});
wardrik.tool({ name: "refund_order", description: "Refund part or all of one of the customer's orders, in US dollars.", input: z.object({ order_id: z.string(), amount: z.number().positive() }), idempotent: true, run: async ({ order_id, amount }, { agent, idempotencyKey }) => { const customer = (await customerOf(agent)).id; return payments.refund({ order_id, customer, amount }, { idempotencyKey }); },});
void wardrik.work();import { wardrik } from "./support.ts";import { db } from "./app.ts";
// The daemon creates the agent and returns its ID. Store the ID on the customer.async function agentFor(customer: { id: string; agentId?: string }): Promise<string> { if (customer.agentId) return customer.agentId;
const response = await fetch(`${process.env.WARDRIK_URL}/v1/owners/shop/agents`, { method: "POST", headers: { authorization: `Bearer ${process.env.WARDRIK_TOKEN}`, "content-type": "application/json" }, body: JSON.stringify({ name: customer.id, instructions: "You help this customer with their orders. Be brief and kind.", tools: ["workers/*"], notify: { to: ["support"], on: ["parked"] }, }), }); const { id } = await response.json(); await db.customers.update(customer.id, { agentId: id }); return id;}
export async function answer(customer: { id: string; agentId?: string }, text: string) { const agent = wardrik.agent(await agentFor(customer)); for await (const event of agent.stream(text)) { if (event.type === "wardrik.message.delta") process.stdout.write(event.data.delta); }}Try it
Section titled “Try it”systemctl --user restart wardrikwardrik token create shop-app --subject user:shop/@app # your app's WARDRIK_TOKENRun your app and ask a customer's agent for a $20 refund. It runs. Ask for $200, and the call is
posted to #support with Approve and Deny.
What it shows
Section titled “What it shows”- One agent per customer, each with its own history. Idle agents stop until the next message.
- Tools receive the calling agent's ID from Wardrik, not from the model's arguments. A prompt can't make an agent read another customer's orders.
- Refund limits are in the org policy. No agent definition can bypass them.
- See Tools in your app.