Tools

Beta
Last updated  Oct 3, 2026

Tools are your app’s own actions, like looking up a balance or creating a draft invoice, that the assistant can call when a user asks. You write them in one file, in your frontend, as functions that call your own API. No server code and no auth setup.

Available on every plan, not Pro-only. Tools work on every key from 0.1.0-beta.5. You don’t need Usher Pro for them. See Usher vs Usher Pro.

The file

src/usher.tools.ts
// src/usher.tools.tsimport { defineTools } from "@voqal/usher-react";export default defineTools({  get_customer_balance: {    description:      "Read what a customer owes on open and overdue invoices. Use when the user asks how much a customer owes. Read-only.",    input: { customer_id: { type: "string", description: "The customer's id from find_customer, e.g. cust_acme" } },    run: ({ customer_id }) => fetch(`/api/customers/${customer_id}/balance`).then((r) => r.json()),  },  create_invoice: {    description:      "Create a draft invoice for a customer. Use when the user asks to bill or invoice a customer for an amount. Nothing is sent to the customer.",    input: { customer_id: "string", amount: "number", due_date: "date?" },    confirm: "Create a draft invoice",    run: (args) => fetch("/api/invoices", { method: "POST", body: JSON.stringify(args) }).then((r) => r.json()),  },});
App.tsx
import tools from "./usher.tools";<Usher voqalKey={key} router={adapter} destinations={map} tools={tools} />
  • The name is snake_case, verb first (create_invoice, get_order_status). It can’t be one of Usher’s own tools: navigate, offer_navigation, clarify, decline_navigation, operate.
  • Keep them in their own file, as above, or write them inline or inside a component. A re-render never restarts the assistant. Only a change to a tool’s name, description, input, or confirm does, and the newest run is always the one called.
  • Up to 32 tools. A handful of well-described ones work better than many vague ones.

Who the tool runs as

run executes in the user’s browser, inside your app, as the logged-in user. It calls your API the same way the rest of your frontend does: the same fetch wrapper, cookie, or token. Voqal never sees, stores, or forwards a token, and there is nothing to configure. Your API’s own permission checks decide what the user may do, just as they do for a click.

Never put a secret or server-only key in this file. It ships to the browser like the rest of your frontend code.

Describing the input

Write input in whichever of three formats suits your code:

input formats
// 1. The short mapinput: {  customer_id: { type: "string", description: "The customer's id, e.g. cust_acme" },  amount: "number",  due_date: "date?",              // ? = optional; date = YYYY-MM-DD  status: "enum:draft|sent",  tags: "string[]?",}// 2. JSON Schemainput: {  type: "object",  properties: { customer_id: { type: "string" }, amount: { type: "number" } },  required: ["customer_id", "amount"],}// 3. zod (the schema your app already has; zod is not a dependency of Usher)input: z.object({ customer_id: z.string(), amount: z.number().positive(), due_date: z.iso.date().optional() })
Short typeMeans
stringText
numberAny number
integerA whole number
booleantrue or false
dateA date, YYYY-MM-DD
datetimeAn ISO 8601 date and time
enum:a|b|cOne of the listed values
string[]A list (of any type above)
type?Optional: the model may leave it out
{ type, description }Any of the above, with a description the model reads

Leave input out for a tool that takes no arguments.

Writing descriptions

The model chooses a tool, and fills in its arguments, from your descriptions alone. Say what the tool does, when to use it (“Use when the user asks …”), and whether it’s read-only or what it changes. Describe every id and amount: what it is, where it comes from, its unit, and an example. If a tool needs an id the user won’t know, add a look-up tool that returns it, like find_customer.

WeakStrong
Gets balance.Read what a customer owes on open and overdue invoices. Use when the user asks how much a customer owes. Read-only.
Invoice toolCreate a draft invoice for a customer. Use when the user asks to bill or invoice someone. Nothing is sent to the customer.
id: "string"customer_id: { type: "string", description: "The customer's id from find_customer, e.g. cust_acme" }

In development builds, the console warns about a description that’s too short, never says when to use the tool, or leaves parameters undescribed. Production builds stay quiet. A tool with an invalid name or no description is left out, with a console error. It never breaks your page.

Asking before it runs

Add confirm: true (the card names the tool), or a short line like confirm: "Create a draft invoice". Usher then shows its confirmation card, and run is called only when the user taps Do it. Use it for anything that creates, changes, sends, or charges. Look-ups don’t need it.

Results and errors

  • Return what the user needs, as plain JSON (usually your API’s response). Results are cut at about 4,000 characters. A fetch Response returned unread reports only its status, so call .json().
  • Throw when something fails. The model gets a short, fixed message, never your error’s text, and tells the user it didn’t work.
  • run has 15 seconds by default. Its second argument carries an abort signal you can pass to fetch. After a timeout, the assistant says it can’t tell whether the action went through.
  • The assistant reports the result once, in a sentence or two, in voice and in chat.

What Usher checks for you

  • Arguments are checked against input before run is called: types, required fields, enum values, dates, and no extra fields. If any are wrong, the model is told what to fix, and your code never sees them.
  • Your descriptions can’t change the rules. They sit below Voqal’s instructions, in a fenced section. Navigation stays limited to your listed pages, and confirmation can’t be skipped.
  • Results are data, not instructions. The model is told never to follow instructions inside a result. Text that tries to break out of that framing is removed.
  • Not logged. The conversation log records a tool’s name and how the call ended, never its arguments or result. Diagnostics record tool:run with the status and duration only. Arguments and results do go to the AI model provider during the session, like the rest of the conversation. See What leaves the browser.
  • Limits: each message can trigger at most a few tool calls, so the model can’t loop.

Tools and Usher Pro

Tools work on every key. When a user asks for something, the assistant tries them in this order:

  • If one of your app’s tools fits the request, the assistant uses it. This is the fastest and most reliable path.
  • With Usher Pro, when no tool fits, it does the task on the page, the way the user would.
  • Without Usher Pro, it takes the user to the right page and tells them how to finish.

So tools are worth writing whether or not you have Usher Pro. They cover your most common actions, and Pro Pro covers the long tail. Compare the two in Usher vs Usher Pro.

© 2026 VoqalVoqal SDK & engine documentation