Tools
Tools give agents the ability to call external functions. defineTool wraps a typed execute function with a Zod input schema so the model knows what to pass and what to expect back.
Defining a tool
Section titled “Defining a tool”import { z } from 'zod';import { defineTool } from '@kuralle-agents/core';
const echo = defineTool({ name: 'echo', description: 'Echo back the provided text', input: z.object({ text: z.string() }), execute: async ({ text }) => ({ echoed: text }),});name and description go directly into the model’s tool schema. Keep descriptions accurate — the model uses them to decide when to call the tool.
input is a Zod object schema. Kuralle validates the model’s arguments against it before calling execute.
execute receives the validated input and returns any value. The return value is serialized and passed back to the model as the tool result.
Wiring a tool to an agent
Section titled “Wiring a tool to an agent”There are two wiring steps — both are required:
tools: buildToolSet(...) — makes the tool model-visible. The model sees the schema and can decide to call it.
tools: { ... } — registers the executors on the agent so the runtime can resolve them by name (for example when a flow node calls a tool). Tool calls run through the runtime’s effect log, which gives exactly-once replay on retry.
import { defineAgent, buildToolSet } from '@kuralle-agents/core';
const tools = { echo };
const agent = defineAgent({ id: 'support', instructions: 'Use the echo tool when asked.', model: openai('gpt-4o-mini'), tools: buildToolSet(tools), // model-visible tools: tools, // durable executor});What buildToolSet does
Section titled “What buildToolSet does”defineTool returns a Kuralle effect tool — { name, description, input, execute }, where input is a Standard Schema and execute is your durable handler. The model, however, speaks the Vercel AI SDK tool format. buildToolSet is the bridge between the two:
buildToolSet({ echo, lookup_order })// → an AI SDK ToolSet: each entry has the tool's name, description, and// inputSchema — the shape the model needs to know a tool exists and how to call it.It copies each tool’s name, description, and input schema into an AI SDK ToolSet. It deliberately does not copy execute into the model-facing tool — the AI SDK entry is schema-only. The actual execution runs through the runtime, which routes every model-issued call through the durable effect log for exactly-once replay. (buildToolSet keeps each tool’s executor recoverable, so a model-issued call replays even before you register it in tools.)
That’s why the two fields are complementary rather than redundant:
tools: buildToolSet(tools)— what the model can see and decide to call (name + description + input schema).tools: tools— the executor registry the runtime resolves by name (e.g. from flow nodes), and the fallback for tools not built withbuildToolSet.
You author each tool once with defineTool; buildToolSet derives the model-facing view from it.
Durable execution
Section titled “Durable execution”Every tool call is keyed in an append-only effect log. The runtime checks that key before calling execute — if the call already ran, it returns the logged result without calling execute again.
This means a payment tool won’t charge twice if the turn fails mid-execution, and a booking tool won’t double-book a slot.
Multiple tools
Section titled “Multiple tools”const tools = { lookup_order: lookupOrder, cancel_order: cancelOrder, create_ticket: createTicket,};
const agent = defineAgent({ id: 'support', instructions: '...', model: openai('gpt-4o-mini'), tools: tools,});Example
Section titled “Example”import { openai } from '@ai-sdk/openai';import { z } from 'zod';import { defineAgent, defineTool, createRuntime } from '@kuralle-agents/core';
// Define a tool with a Zod input schema and an async execute functionconst echo = defineTool({ name: 'echo', description: 'Echo back the provided text', input: z.object({ text: z.string() }), execute: async ({ text }) => ({ echoed: text }),});
// Wire durable tools on the agent; flow nodes use buildToolSet for model-visible schema.const agent = defineAgent({ id: 'support', instructions: 'Use the echo tool when asked.', model: openai('gpt-4o-mini'), tools: { echo },});
const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });
const handle = runtime.run({ input: 'Echo "hello world"' });for await (const part of handle.events) { if (part.type === 'text-delta') process.stdout.write(part.delta); if (part.type === 'done') console.log('\nDone.');}await handle;