Skip to content
Pilotoo
v0.1

Tools

The kit ships no business tools. You register yours at startup, and each agent definition selects among them by name. A tool is an ordinary LangChain StructuredTool.

This is how the demo API (apps/api) registers its tools:

src/app.module.ts
import { createAskHumanTool, createWebSearchTool } from '@pilotoo/agent-runtime';
import { PilotooModule } from '@pilotoo/server';
PilotooModule.forRoot({
env: process.env,
tools: (registry) => {
registry.register(createWebSearchTool(process.env));
registry.register(createAskHumanTool(), { optIn: true });
},
});

The callback receives the ToolRegistry (the TOOL_REGISTRY token). A plain StructuredTool[] is also accepted and registers each tool with defaults. A duplicate name throws at registration.

Without PilotooModule, build the registry yourself with createToolRegistry() and pass it to the runner.

src/tools/echo.ts
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
export const echo = tool(async ({ text }) => text, {
name: 'echo',
description: 'Repeats the text back.',
schema: z.object({ text: z.string() }),
});

Register it with registry.register(echo), then let an agent use it:

.pilotoo/agents/assistant.yaml
runtime:
tools: [echo]

Pass { optIn: true } for a tool that is dangerous or only some agents should have, such as one that pauses a run to ask the user.

registry.register(createAskHumanTool(), { optIn: true });
In the agent definition The agent gets
tools: null Every registered tool except opt-in ones.
tools: [a, b] Exactly the named tools, opt-in included.
tools: [] No registered tool.

An unregistered name is warned about and skipped.

Built-in tools (ls, read_file, execute, task and the rest) are separate: an agent gets exactly the ones its builtinTools list names. See Agents.

A tool that reads or changes your app’s data should act with the permissions of the person chatting, not with a service account. createHostFetch(config) returns a fetch that adds the caller’s Authorization header, so your API authorizes each call as that caller.

src/tools/get-order.ts
import { tool } from '@langchain/core/tools';
import { createHostFetch } from '@pilotoo/agent-runtime';
import { z } from 'zod';
export const getOrder = tool(
async ({ id }, config) => {
const hostFetch = createHostFetch(config);
const response = await hostFetch(`https://api.example.com/orders/${id}`);
return response.text();
},
{
name: 'get_order',
description: 'Reads one order.',
schema: z.object({ id: z.string() }),
},
);

callerOf(config) returns the calling identity when the tool needs the user or tenant itself. The caller travels in LangGraph’s per-invocation context. It is never written to state, the checkpoint or the stream.

Tool What it does
createWebSearchTool(env) A web_search tool (Tavily). The key is read only from TAVILY_API_KEY in the env you pass; without one the call fails with a readable error.
createAskHumanTool() An ask_human tool that pauses the run until the user answers. Register it with { optIn: true }.

Both are exported by @pilotoo/agent-runtime.

registry.register(tool, options?):

Option Default Meaning
optIn false Only agents that name the tool get it.