Skip to main content
Language models are powerful reasoners but they live in a text box. They cannot roll a die, look up your database, or call an API — unless you give them tools. A tool is a function you register with an agent. The model receives a description of the tool and its parameters. During a run, whenever the model decides it needs information or wants to take an action, it emits a structured tool call. Vibes validates the arguments, executes your function, and appends the result to the message history so the model can continue reasoning. This is more powerful than stuffing everything into the system prompt. Tools are called on demand, only when needed, with typed and validated arguments. The model can call them zero times or ten times in a single run — it decides.

A motivating example: a dice game

Let’s build something concrete. Imagine a game where the agent rolls dice and asks for the player’s name. Two tools, two different styles.
  1. plainTool is the simplest factory. execute receives only the validated args — no context object. Use it for pure functions.
  2. tool<Deps> is the full-featured factory. execute receives a RunContext<Deps> as its first argument, then the args second. The ctx gives you access to injected dependencies, current token usage, the run ID, and more.
  3. Tools are passed as a plain array. Vibes sends the full list to the model on every turn unless you use prepare to conditionally exclude a tool (see below).

What the message trace looks like

Under the hood, each turn is a structured conversation. After the run above, result.messages would contain something like this:
The model called both tools in a single turn (it can do that), received both results, then produced the final text. Vibes handles all the serialization and routing. You only write the execute functions.

tool() — with dependencies

Use tool<TDeps>() whenever your tool needs to call a database, an API client, or any other injected service. The TDeps type parameter tells TypeScript what shape ctx.deps will have.
The deps are injected at call time via agent.run("...", { deps: { db: myDb } }). This keeps your tools testable — pass a mock in tests, a real connection in production. ctx also exposes:
  • ctx.usage — token counts accumulated so far in this run
  • ctx.runId — a unique ID for this run (useful for logging)
  • ctx.toolName — the name of the currently executing tool
  • ctx.retryCount — how many times this result has been retried
  • ctx.metadata — per-run metadata supplied by the caller
  • ctx.attachMetadata(toolCallId, meta) — attach structured metadata for a tool call that callers can inspect after the run

plainTool() — no context

plainTool() is a convenience wrapper for tools that are pure functions. The execute receives only the validated arguments — there is no RunContext parameter.
plainTool also supports maxRetries and argsValidator. It does not support prepare, requiresApproval, or sequential — those require access to the run context. Use tool() if you need those features.

outputTool() — terminal tools

Sometimes you want the model to fill in a structured result and stop. outputTool() creates a tool that ends the run. When the model calls it, the return value becomes the agent’s final output and no further turns occur.
  1. The output schema is defined inline in the tool’s parameters.
  2. execute typically returns args directly — the model has already structured the data.
  3. outputMode: "tool" tells Vibes to treat the tool call as the output mechanism.

fromSchema() — raw JSON Schema

When you already have a JSON Schema — from an OpenAPI spec, a schema registry, or a third-party library — use fromSchema() to avoid rewriting it in Zod. There is no TypeScript inference for args, so you’ll need to cast.

Tool return types

A tool’s execute function can return: For image-returning tools, return a BinaryContent object:
The image is passed back to the model as a vision content part. Vision-capable models can then reason about the image content.

Conditional availability with prepare

Every tool is sent to the model on every turn by default. The prepare function lets you change that. It is called once per turn before the tool list is sent. Return null or undefined to exclude the tool from that turn; return the tool definition (or a modified version of it) to include it.
  1. prepare receives the RunContext, giving it access to deps and all run metadata.
  2. Returning null hides the tool. The model won’t know it exists for this turn.
  3. Returning undefined (or the tool definition itself) includes it normally.
You can also return a modified tool definition from prepare to dynamically update the description or parameters based on runtime state:

Argument validation with argsValidator

Zod validates the shape and types of each argument. But sometimes you need cross-field validation — for example, ensuring a start date is before an end date. That’s what argsValidator is for.
  1. Throwing inside argsValidator rejects the call. The error message is sent back to the model without consuming a retry — it’s treated as a validation failure, not an execution failure.

Retries with maxRetries

Tool execution errors are surfaced back to the model by default. If you want Vibes to automatically retry before giving up, set maxRetries:
  1. maxRetries: 2 means up to 3 total attempts (1 initial + 2 retries).
  2. Any thrown error triggers a retry. After all attempts are exhausted, the final error is propagated.
maxRetries on a tool retries the execution. It is independent of result validation retries (configured on the agent via maxRetries).

Full options reference

All options accepted by tool(): plainTool() supports name, description, parameters, execute, maxRetries, and argsValidator only. outputTool() supports name, description, parameters, and execute only. fromSchema() supports name, description, jsonSchema, execute, and maxRetries only.

Toolsets

Group, filter, and compose tools into reusable collections

Dependencies

Inject runtime context via RunContext deps