Tools
import { defineTool } from "@hellohelen-ai/goliath";import { z } from "zod";
const completeTask = defineTool({ name: "completeTask", description: "Mark a task done using its ID from listTasks.", parameters: z.object({ id: z.number().int().positive() }), writes: true, requires: ["listTasks"], execute: ({ id }) => tasks.complete(id), toModelOutput: (result) => `done: ${result.title}`,});Return IDs alongside titles in listTasks output so the model can select an exact task.
The example app also uses resolveInput to check that the selected ID appeared in this turn’s
lookup before approval or execution; requires only checks that the prerequisite tool ran.
defineTool is an identity function that pins the types. Everything it accepts is on
the reference page. This page is about what makes a tool work
on a small model.
Five or fewer
Section titled “Five or fewer”Each tool definition costs the conductor about 70 tokens on every step, and past about five definitions a 3B model picks wrong or invents arguments. If your app has more, create more than one agent, each with the tools one screen needs.
Flat schemas
Section titled “Flat schemas”Primitives and enums. No nested objects, no unions, no arrays of objects. That is what a 3B model fills in reliably and what Apple’s guided generation accepts.
// Goodz.object({ title: z.string(), due: z.enum(["today", "tomorrow", "next week"]) });
// Will fail on devicez.object({ task: z.object({ title: z.string() }), tags: z.array(z.object({ name: z.string() })) });One-sentence descriptions
Section titled “One-sentence descriptions”The description is read on every step. Say what the tool returns or does, in one sentence, and stop.
Say what a tool needs
Section titled “Say what a tool needs”requires lists tools that must run earlier in the turn. The conductor reads it as a sentence:
“Use listTasks before completeTask.” This one line was TinyAgent’s biggest plan-shape lever.
For values the model should always have, such as today’s date or the user’s name, do not write a tool. Pass them as facts instead.
Shape what the model sees
Section titled “Shape what the model sees”The app keeps the full return value. The model gets toModelOutput(result), or by default a set of
key: value lines capped at 600 characters. Use toModelOutput when the default keeps the wrong
fields.
For literal excerpts, outputMode: "content" uses token budgets instead of the character cap.
The filesystem module supplies read/search tools that paginate
within this allowance, plus opt-in writing tools with revision checks.
Errors are results
Section titled “Errors are results”A tool that throws does not end the turn. The message becomes the step’s result and the conductor plans around it. Two errors in a row escalate.
Missing values
Section titled “Missing values”A worker that cannot find a value in the brief names it instead of inventing one. The conductor then decides whether to look it up or ask. “Wrong but valid” arguments are almost always invented values, so this rule exists to stop them.