Skip to content

Write tools

wardrik.tool({
name: "find_order",
description: "The order a Stripe payment intent paid for: items, address, dates.",
input: z.object({ payment_intent: z.string().describe("As pi_123") }),
readOnly: true,
run: ({ payment_intent }) => orders.byPaymentIntent(payment_intent),
});
Key
name 1 to 55 letters, digits, _ and -
description the description sent to the model
title a display name
input a zod object schema for the arguments; .describe() documents a field for the model
readOnly the tool changes nothing, so it never needs approval
idempotent the tool is safe to call twice with the same arguments
run the function; receives the arguments and { agent, run, idempotencyKey, signal, cost }
  • Add workers/* to an agent's tools. The model sees workers__find_order.
  • In a policy, it's workers/find_order.

Return an object or a string. Return only what the model needs, since every token counts.

Throw to fail the call. Write an error message the model can act on:

run: async ({ order_id }) => {
const order = await orders.find(order_id);
if (!order) throw new Error(`No order ${order_id}. Order IDs look like ord_1234.`);
return { id: order.id, status: order.status };
},

agent is the calling agent's ID. Wardrik sets it, not the model. Use it to scope data:

run: (_args, { agent }) => db.orders.where({ agent }),

Call cost with a paid API's cost in US dollars. It's added to the agent's budget:

run: async ({ email }, { cost }) => {
const found = await enrich.lookup(email);
cost(0.01);
return found;
},

idempotencyKey is the same on every attempt of a call. Pass it to the API, and mark the tool idempotent:

wardrik.tool({
name: "refund_order",
description: "Refund an order in full.",
input: z.object({ orderId: z.string() }),
idempotent: true,
run: ({ orderId }, { idempotencyKey }) =>
stripe.refunds.create({ payment_intent: orderId }, { idempotencyKey }),
});
  • Only idempotent or readOnly tools are retried. See Durable runs.
  • For an API without idempotency keys, store each key's result and return it on a repeat.
  • signal aborts when the call is stopped or times out. Pass it to fetch.
  • A call times out after 60 seconds.
  • run in the context is the turn's ID, the same as in its events.
  • Calling cost twice adds both amounts.
  • A tool with an invalid name, or an input that isn't an object schema, is skipped.