Custom Tools
Tools are capabilities that Alice can use during conversations. Adding a custom tool makes it available to all AI providers automatically.
Tool Definition
Tools are defined using the Vercel AI SDK's tool() function with a Zod schema:
import { tool } from 'ai'
import { z } from 'zod'
const myTools = {
myCustomTool: tool({
description: 'A clear description of what this tool does and when to use it.',
inputSchema: z.object({
query: z.string().describe('What to search for'),
limit: z.number().int().positive().optional().describe('Max results'),
}),
execute: async ({ query, limit }) => {
// Your business logic here
return { results: [], count: 0 }
},
}),
}
Key points:
- description — Written for the AI. Be specific about when to use the tool and what it returns.
- inputSchema — Zod schema. Add
.describe()to every field — the AI uses these to understand parameters. - execute — Async function that receives validated input and returns JSON-serializable data.
Registration
Register tools with ToolCenter during bootstrap in main.ts:
toolCenter.register(myTools, 'my-category')
The second argument is a group name for organizational purposes (shown in the tool inventory API).
Once registered, tools are automatically:
- Available to the Vercel AI SDK provider (via
getVercelTools()) - Available to the Agent SDK provider (via the in-process MCP server)
- Listed in the tool inventory API
- Controllable via the disable list
The Bridge Pattern
OpenAlice follows a pattern where tool/ files are thin shells that delegate to domain/ modules:
src/tool/trading.ts → delegates to → src/domain/trading/
src/tool/equity.ts → delegates to → src/domain/market-data/
src/tool/brain.ts → delegates to → src/domain/brain/
This separation keeps business logic testable and independent from the AI tool interface. For simple tools, you can put everything in the tool definition. For complex tools, create a domain module.
Disabling Tools
Globally
Add tool names to data/config/tools.json:
{
"disabled": ["myCustomTool", "browser"]
}
Per Channel
Add to a sub-channel's disabledTools in data/config/web-subchannels.json:
{
"id": "research",
"label": "Research Only",
"disabledTools": ["placeOrder", "tradingCommit", "tradingPush"]
}
The disable list is read on each request, so changes take effect immediately.
Tips
- Keep tool descriptions concise but specific — the AI reads them to decide when to use each tool
- Return structured JSON from
execute— the AI parses the output to form its response - Use
.describe()on every Zod field — this is how the AI understands parameters - Throw errors for exceptional cases — the framework catches and reports them to the AI
- Keep
executefunctions thin — delegate to domain modules for testability