Adding a Tool
How to add a new tool to the registry
Tools are typed TypeScript functions with JSON-Schema parameters. They live
at apps/agent/src/tools/<name>.ts and export a factory that returns a
ToolEntry[].
Minimal example
Create apps/agent/src/tools/my-provider.ts:
import { PermissionTier, type ToolEntry } from "../core/tool-registry.js";
import { ok, err, errFromThrow } from "./_shared/result.js";
export function createMyProviderTools(): ToolEntry[] {
const apiKey = process.env.MY_PROVIDER_API_KEY;
if (!apiKey) return []; // feature-gate: registry hides missing tools
return [
{
name: "my_provider_search",
toolSet: "research",
permissionTier: PermissionTier.READ_ONLY,
isAsync: true,
description: "Search My Provider for a query",
schema: {
name: "my_provider_search",
description: "Search My Provider for a query",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "The search query" },
limit: { type: "number", description: "Max results", default: 10 },
},
required: ["query"],
},
},
handler: async ({ query, limit }) => {
try {
const res = await fetch(
`https://api.myprovider.com/search?q=${encodeURIComponent(query)}&limit=${limit ?? 10}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
if (!res.ok) return err(`HTTP ${res.status}`);
return ok(await res.json());
} catch (e) {
return errFromThrow(e);
}
},
},
];
}Register it in apps/agent/src/app.ts (around line ~365, where all tool factories are
plugged in):
import { createMyProviderTools } from "./tools/my-provider.js";
// ...
toolRegistry.register(...createMyProviderTools());Add a tool-set entry in apps/agent/src/core/tool-registry.ts inside
BUILTIN_TOOL_SETS:
research: {
description: "Market and social research tools",
tools: [
// ... existing
"my_provider_search",
],
},Permission tiers
Permission tiers are defined in apps/agent/src/core/tool-registry.ts:
| Tier | Name | When to use |
|---|---|---|
| 1 | READ_ONLY | price / balance / search / read_file |
| 2 | CONFIRM_ONCE | analysis, research, small swaps |
| 3 | ALWAYS_CONFIRM | write_file, patch, fund-moving, document generation |
| 4 | MANUAL_ONLY | withdraws, external-address sends, emergency stop |
When in doubt, match the closest existing tool's tier.
Result envelope
Every handler returns a string via ok({...}) / err("...") from
apps/agent/src/tools/_shared/result.ts. Never throw from handlers. Use
errFromThrow(e) to convert caught errors. The agent loop parses the
envelope and presents structured errors to the LLM.
Sandbox-rooted file tools
If your tool touches the filesystem, resolve every path through
resolveInSandbox() from apps/agent/src/tools/_shared/sandbox.ts. This is
non-negotiable. See Sandbox & Permissions.