Zypher Agent
Core ConceptsTools & MCP

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

  1. LLM calls execute_code with TypeScript code
  2. Code runs in an isolated Deno Worker (all permissions disabled)
  3. Worker calls wrapped tools via RPC
  4. Tools return results directly to the worker (not LLM context)
  5. 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.