Programmatic Tool Calling
Enable LLMs to write and execute code that calls tools programmatically, reducing token usage by 85-98%
Programmatic Tool Calling
Programmatic Tool Calling (PTC) enables LLMs to generate TypeScript code that runs in an isolated Deno Worker to orchestrate and execute tool calls. The code processes data internally, and only the final result is shared with the LLM. This approach can significantly reduce token usage for complex tasks that involve many tool calls or large datasets.
Traditional: Programmatic Tool Calling (PTC):
┌─────────┐ ┌─────────┐
│ LLM │ │ LLM │
└────┬────┘ └────┬────┘
│ 20 tool calls │ execute_code call
│ 110K tokens returned │ 2K tokens returned
▼ ▼
┌─────────────────┐ ┌─────────────────────────────┐
│ Tool Results │ │ Deno Worker (isolated) │
│ (in context) │ │ ┌─────────────────────────┐ │
│ - Employee 1... │ │ │ TypeScript code │ │
│ - Employee 2... │ │ │ calls tools │ │
│ - ... │ │ └─────────────────────────┘ │
│ - Employee 20 │ │ Returns:summary(2K token) │
└─────────────────┘ └─────────────────────────────┘
The programmatic() Function
In Zypher, programmatic tool calling is implemented with the programmatic function. You can use it to wrap tools or MCP servers so they are callable only via code execution:
import { programmatic } from '@corespeed/zypher/tools';Using Programmatic Tool Calling with Tools
import { createTool, programmatic } from '@corespeed/zypher/tools';
import { z } from 'zod';
const getWeather = createTool({
name: "get_weather",
description: "Get the current weather for a city",
schema: z.object({
city: z.string().describe("City name"),
}),
execute: ({ city }) => {
// Return weather data for the city
},
});
const agent = await createZypherAgent({
modelProvider: new AnthropicModelProvider({ apiKey }),
tools: [...programmatic(getWeather)],
});Note: Wraped tools can only be called via the execute_code tool—the LLM cannot call them directly.
Using Programmatic Tool Calling with MCP Servers
const agent = await createZypherAgent({
modelProvider: new AnthropicModelProvider({ apiKey }),
mcpServers: [
programmatic({
id: "deepwiki",
displayName: "DeepWiki",
type: "remote",
remote: { url: "https://mcp.deepwiki.com/mcp" },
}),
],
});How It Works
- LLM calls
execute_codewith TypeScript code - Code runs in an isolated Deno Worker (all permissions disabled)
- Worker calls wrapped tools via RPC
- Tools return results directly to the worker (not LLM context)
- Worker processes data and returns only the final summary
Code Execution Example
When asked "What's the average temperature across 5 cities?", the LLM writes:
const cities = ["Paris", "London", "Berlin", "Madrid", "Rome"];
const results = [];
for (const city of cities) {
const weather = await tools.get_weather({ city });
results.push({ city, temp: weather.temperature });
}
const avgTemp = results.reduce((sum, r) => sum + r.temp, 0) / results.length;
const warmest = results.sort((a, b) => b.temp - a.temp)[0];
return {
averageTemperature: avgTemp.toFixed(1),
warmestCity: warmest.city,
};The code runs in a Deno Worker, and only the final summary is returned to the LLM — not the raw data for each city.
Security
Full isolation:
- Runs in a separate Deno Worker with all permissions disabled
- No file system, network, or environment access
- Cannot spawn subprocesses
Controlled tool access:
- Tools called via RPC through
postMessage - Main thread validates and executes tool calls
Timeout protection:
- Configurable execution timeout (default: 10 minutes)
worker.terminate()forcefully kills runaway code
When to Use
Ideal for:
- Data aggregation across many records
- Batch operations with loops
- Multi-tool workflows with large intermediate results
- Search and filter operations
Avoid for:
- Simple single tool calls
- Small data returns (< 1K tokens)
- When LLM needs all raw data
For creating custom tools, see Built-in Tools.