Tools
Give agents the ability to call functions
Tools let agents call your TypeScript functions. Each tool has a name, description, Zod parameter schema, and an execute function. Stratus requires Zod 4 (zod@^4.0.0).
Stratus uses Zod 4's built-in toJSONSchema() to convert tool parameter schemas to JSON Schema for the Azure API. This means all Zod types are supported — including recursive objects, template literals, discriminated unions, and everything else Zod 4 can represent.
Stratus handles the full tool loop automatically - parallel execution, error recovery, and 429 retries. No JSON.parse(), no dispatch tables, no manual message management. See the Agentic Tool Use guide for the full picture.
Looking for server-side tools like web search, code interpreter, or MCP? See Built-in Tools.
Want the LLM to write code that chains multiple tools together? See Code Mode.
Defining a Tool
import { tool } from "@usestratus/sdk/core";
import { z } from "zod";
const getWeather = tool({
name: "get_weather",
description: "Get the current weather for a city",
parameters: z.object({
city: z.string().describe("City name"),
unit: z.enum(["celsius", "fahrenheit"]).optional(),
}),
execute: async (_ctx, { city, unit }) => {
const temp = await fetchWeather(city, unit);
return `${temp}° in ${city}`;
},
});Tool Anatomy
| Property | Type | Description |
|---|---|---|
name | string | Unique tool name sent to the model |
description | string | What the tool does (helps the model decide when to use it) |
parameters | z.ZodType | Zod schema for the parameters |
execute | (context, params, options?) => string | Function that runs when the model calls the tool |
timeout | number? | Timeout in milliseconds. Throws ToolTimeoutError if exceeded |
isEnabled | boolean | (ctx) => boolean | When false, the tool is excluded from the model's tool list |
needsApproval | boolean | (params, ctx) => boolean | When truthy, pauses for human approval before execution. See Human-in-the-Loop |
retries | object? | Retry configuration for transient failures. See Retries |
The execute function receives up to three arguments:
context- The context object passed viarun()options or session configparams- Parsed and validated parameters matching the Zod schemaoptions- OptionalToolExecuteOptionswith anAbortSignalfor cancellation
Using Context
Tools can access shared context for things like database connections, API clients, or user info:
interface AppContext {
userId: string;
db: Database;
}
const lookupOrder = tool({
name: "lookup_order",
description: "Look up an order by ID",
parameters: z.object({ orderId: z.string() }),
execute: async (ctx: AppContext, { orderId }) => {
const order = await ctx.db.orders.find(orderId, ctx.userId);
return JSON.stringify(order);
},
});
const agent = new Agent<AppContext>({
name: "support",
model,
tools: [lookupOrder],
});
await run(agent, "Where is my order #123?", {
context: { userId: "user_abc", db: myDb },
});Passing Tools to Agents
const agent = new Agent({
name: "assistant",
model,
tools: [getWeather, lookupOrder, searchDocs],
});Passing Tools to Sessions
const session = createSession({
model,
tools: [getWeather, lookupOrder],
});Tool Call Flow
Model returns a tool call
The model responds with a tool call containing a name and JSON arguments.
Arguments are parsed
Stratus parses the arguments JSON and validates them against the Zod schema.
Execute function runs
The execute function runs with the parsed parameters and context.
Result sent back to model
The result string is sent back to the model as a tool message.
Model generates final response
The model generates a final response (or calls more tools). This loop continues until the model responds without tool calls, or toolUseBehavior causes an early stop.
Abort Signal
When a run is started with an AbortSignal, it's passed to each tool's execute function via the options parameter. Use it to cancel long-running operations:
const searchTool = tool({
name: "search",
description: "Search documents",
parameters: z.object({ query: z.string() }),
execute: async (_ctx, { query }, options) => {
const res = await fetch(`/api/search?q=${query}`, {
signal: options?.signal,
});
return await res.text();
},
});See Streaming - Abort Signal for details on passing a signal.
Timeout
Set a timeout in milliseconds to limit how long a tool can run. If the tool doesn't complete in time, a ToolTimeoutError is thrown internally. The error message is sent back to the model as a tool result so it can recover.
const slowSearch = tool({
name: "search",
description: "Search with a timeout",
parameters: z.object({ query: z.string() }),
timeout: 5000, // 5 second limit
execute: async (_ctx, { query }) => {
return await slowExternalApi(query);
},
});ToolTimeoutError is caught by the run loop and converted to a tool error message. It does not propagate out of run() — the model sees the timeout and can respond accordingly.
Conditional Tools (isEnabled)
Use isEnabled to dynamically include or exclude a tool based on context. When false, the tool is not sent to the model at all.
const adminTool = tool({
name: "delete_user",
description: "Delete a user account",
parameters: z.object({ userId: z.string() }),
isEnabled: (ctx: AppContext) => ctx.isAdmin,
execute: async (ctx, { userId }) => {
await ctx.db.users.delete(userId);
return "User deleted";
},
});isEnabled accepts a boolean or a function that receives the context and returns boolean | Promise<boolean>. The check runs before each model call, so a tool can appear or disappear mid-run based on changing context.
Human-in-the-Loop (needsApproval)
Tools that need human approval before execution can set needsApproval. When the model calls this tool, the run pauses and returns an InterruptedRunResult instead of executing.
const deleteFile = tool({
name: "delete_file",
description: "Delete a file",
parameters: z.object({ path: z.string() }),
needsApproval: true,
execute: async (_ctx, { path }) => {
await fs.rm(path);
return `Deleted ${path}`;
},
});needsApproval accepts a boolean or an async function that receives the parsed parameters and context:
const processPayment = tool({
name: "process_payment",
description: "Process a payment",
parameters: z.object({ amount: z.number(), recipient: z.string() }),
needsApproval: async (params, ctx) => params.amount > 100,
execute: async (_ctx, { amount, recipient }) => {
return await chargeCard(amount, recipient);
},
});When approval is needed, run() returns an InterruptedRunResult:
import { run, resumeRun } from "@usestratus/sdk/core";
import type { InterruptedRunResult } from "@usestratus/sdk/core";
const result = await run(agent, "Delete /tmp/test.txt");
if (result.interrupted) {
// Show pending tool calls to the user
for (const pending of result.pendingToolCalls) {
console.log(`Tool: ${pending.toolName}, Args: ${pending.arguments}`);
}
// Get human decision, then resume
const resumed = await resumeRun(result, [
{ toolCallId: result.pendingToolCalls[0].toolCallId, decision: "approve" },
]);
console.log(resumed.output);
}To deny a tool call:
await resumeRun(result, [
{
toolCallId: result.pendingToolCalls[0].toolCallId,
decision: "deny",
denyMessage: "User declined this action",
},
]);needsApproval works with both run() and stream(). In streaming mode, the stream completes and the result promise resolves to an InterruptedRunResult.
Retries
Configure automatic retries for tools that call unreliable external services:
const flakyApi = tool({
name: "search_api",
description: "Search an external API",
parameters: z.object({ query: z.string() }),
retries: {
limit: 3,
delay: 1000,
backoff: "exponential",
shouldRetry: (error) => !(error instanceof ClientError),
},
execute: async (_ctx, { query }) => {
return await externalSearch(query);
},
});| Option | Type | Default | Description |
|---|---|---|---|
limit | number | — | Required. Maximum retry attempts |
delay | number | 1000 | Base delay in ms between retries |
backoff | "fixed" | "exponential" | "exponential" | Backoff strategy |
shouldRetry | (error) => boolean | Retry all | Predicate to skip retries for specific errors |
ToolTimeoutError is never retried — timeouts are treated as deterministic failures.
Error Handling
If a tool's execute function throws, the error message is sent back to the model as the tool result. This lets the model recover gracefully:
execute: async (_ctx, { query }) => {
const results = await search(query);
if (results.length === 0) {
throw new Error("No results found. Try a different query.");
}
return JSON.stringify(results);
},Schema Conversion
Stratus converts Zod schemas to JSON Schema for the Azure API. The conversion adds additionalProperties: false to all objects for Azure strict mode compatibility.
Supported Zod types include objects, strings, numbers, booleans, arrays, enums, optionals, nullables, defaults, unions, and descriptions.
Last updated on