# Tools

URL: https://cogitator.app/docs/core/tools

> Create type-safe tools that give agents the ability to interact with external systems.

## Overview

Tools enable agents to take actions -- call APIs, read files, execute code, query databases. Cogitator tools are defined with Zod schemas for full type safety and automatic JSON Schema generation for LLM function calling.

```typescript
import { tool } from '@cogitator-ai/core';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get the current weather for a city',
  parameters: z.object({
    city: z.string().describe('City name'),
    units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
  }),
  execute: async ({ city, units }, context) => {
    const response = await fetch(`https://api.weather.com/${city}?units=${units}`, {
      signal: context.signal,
    });
    return response.json();
  },
});
```

## The `tool()` Factory

```typescript
function tool<TParams, TResult>(config: ToolConfig<TParams, TResult>): Tool<TParams, TResult>;
```

### ToolConfig

| Field              | Type                                    | Description                                                                                                                     |
| ------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | `string`                                | Unique tool name (used in LLM function calling)                                                                                 |
| `description`      | `string`                                | What the tool does (sent to the LLM)                                                                                            |
| `parameters`       | `ZodType`                               | Zod schema defining accepted parameters                                                                                         |
| `execute`          | `(params, context) => Promise<TResult>` | Implementation function                                                                                                         |
| `category`         | `ToolCategory`                          | `'math'`, `'text'`, `'file'`, `'network'`, `'system'`, `'utility'`, `'web'`, `'database'`, `'communication'` or `'development'` |
| `tags`             | `string[]`                              | Optional tags for filtering                                                                                                     |
| `sideEffects`      | `SideEffectType[]`                      | `'filesystem'`, `'network'`, `'database'`, `'process'`, `'external'`; shown to approvers                                        |
| `requiresApproval` | `boolean \| (params) => boolean`        | Require human approval before execution ([Approvals](/docs/tools/approvals))                                                    |
| `timeout`          | `number`                                | Per-call timeout in milliseconds                                                                                                |
| `sandbox`          | `SandboxConfig`                         | Run in an isolated environment (see below)                                                                                      |

### Tool Context

`execute` receives the validated parameters and a `ToolContext`:

| Field                      | Description                                                                  |
| -------------------------- | ---------------------------------------------------------------------------- |
| `agentId`, `runId`         | The agent and run that called the tool                                       |
| `signal`                   | Aborted when the run is cancelled, times out, or the tool's `timeout` passes |
| `threadId`                 | The conversation                                                             |
| `userId`                   | The user the run acts for (`RunOptions.userId`)                              |
| `channelType`, `channelId` | Where the message came from, for channel gateways                            |

When `timeout` is set, the runtime aborts `context.signal` once the limit is reached and reports `Tool "<name>" timed out after <ms>ms` to the model as the tool error. Long-running tools should pass `context.signal` to `fetch` and other cancellable APIs so the work actually stops.

## Parameter Schemas

The `parameters` field accepts any Zod schema. Cogitator converts it to JSON Schema (OpenAPI 3.0 format) before sending it to the LLM.

```typescript
const createIssue = tool({
  name: 'create_github_issue',
  description: 'Create a new issue in a GitHub repository',
  parameters: z.object({
    repo: z.string().describe('Repository in owner/repo format'),
    title: z.string().max(256).describe('Issue title'),
    body: z.string().describe('Issue description in markdown'),
    labels: z.array(z.string()).optional().describe('Labels to apply'),
    priority: z.enum(['low', 'medium', 'high']).default('medium'),
  }),
  execute: async ({ repo, title, body, labels, priority }) => {
    const issue = await github.createIssue(repo, { title, body, labels, priority });
    return { issueNumber: issue.number, url: issue.html_url };
  },
});
```

Use `.describe()` on each field -- these descriptions are sent to the LLM and significantly improve tool call accuracy.

Arguments the model sends are parsed with the schema before `execute` runs, so defaults and transforms apply. Arguments that fail validation never reach `execute`: the model gets `Invalid arguments: ...` as the tool result and can try again.

## Tool Results

The `execute` function can return any serializable value. Cogitator converts the result to JSON and feeds it back to the LLM as a tool response:

```typescript
const calculator = tool({
  name: 'calculator',
  description: 'Evaluate a mathematical expression',
  parameters: z.object({
    expression: z.string().describe('Math expression to evaluate'),
  }),
  execute: async ({ expression }) => {
    const result = eval(expression);
    return { result, expression };
  },
});
```

If the execute function throws, the error message is sent back to the LLM (as `{ "error": "..." }`) so it can retry or adjust its approach. A call to a tool the agent does not have gets `Tool not found: <name>`.

### Images in Tool Results

A result object with a base64 image in `image` or `imageBase64` (PNG, JPEG, GIF or WebP, plain or as a `data:image/...;base64,` URL) reaches the model as an image, so a vision model sees a screenshot or chart instead of its base64 text. The rest of the object is still sent as JSON, with the image field replaced by `"(image attached)"`:

```typescript
const renderChart = tool({
  name: 'render_chart',
  description: 'Render a line chart of the given values as a PNG',
  parameters: z.object({ values: z.array(z.number()) }),
  execute: async ({ values }) => {
    const png = await drawChart(values);
    return { points: values.length, image: png.toString('base64') };
  },
});
```

Each backend places the image where its API allows: inside the tool result for Anthropic, Bedrock and the OpenAI Responses API, after the function responses of the turn for Google, in the tool message's `images` for Ollama, and in a user message right after the turn's tool messages for Chat Completions (`api: 'chat-completions'`, OpenAI-compatible `baseUrl`, Azure), whose tool messages take only text. Results of the image-generation tool (`imageBase64`) and the browser screenshot tools (`image`) take this path.

## Typed Tool Sets

A function that returns several tools as `Tool[]` loses each tool's own parameter types. `toolset()` keeps them as a tuple, which is still an array an agent accepts:

```typescript
import { toolset } from '@cogitator-ai/core';

function createIssueTools() {
  return toolset(createIssue, getWeather);
}

const [issues, weather] = createIssueTools();
await weather.execute({ city: 'Tokyo', units: 'celsius' }, context); // typed parameters
const agent = new Agent({
  name: 'ops',
  model: 'openai/gpt-6.1-sol',
  instructions: '...',
  tools: createIssueTools(),
});
```

## Built-in Tools

Cogitator ships with ready-to-use tools (`builtinTools` is all of them as one array; see [Built-in Tools](/docs/tools/built-in) for parameters):

```typescript
import {
  calculator,
  datetime,
  uuid,
  fileRead,
  fileWrite,
  fileList,
  fileExists,
  fileDelete,
  httpRequest,
  exec,
  webSearch,
  webScrape,
  sqlQuery,
  vectorSearch,
  sendEmail,
  githubApi,
  jsonParse,
  jsonStringify,
  regexMatch,
  regexReplace,
  hash,
  base64Encode,
  base64Decode,
  sleep,
  randomNumber,
  randomString,
} from '@cogitator-ai/core';
```

Use them directly:

```typescript
const agent = new Agent({
  name: 'file-assistant',
  model: 'openai/gpt-6.1-sol',
  instructions: 'You help users manage files.',
  tools: [fileRead, fileWrite, fileList, fileDelete],
});
```

## Tool Registry

The `ToolRegistry` class manages collections of tools and provides lookup by name:

```typescript
import { ToolRegistry } from '@cogitator-ai/core';

const registry = new ToolRegistry();
registry.register(getWeather);
registry.register(createIssue);
registry.registerMany([calculator, datetime]);

const weatherTool = registry.get('get_weather');
const allNames = registry.getNames();
const schemas = registry.getSchemas();
registry.has('get_weather'); // true
registry.getAll(); // Tool[]
registry.clear();
```

Registering a tool under a name that already exists replaces it.

The Cogitator runtime uses `ToolRegistry` internally to resolve tool calls from the LLM.

## Tool Schema Conversion

Under the hood, `toolToSchema()` converts a Zod-based tool into the JSON Schema format that LLM providers expect:

```typescript
import { toolToSchema } from '@cogitator-ai/core';

const schema = toolToSchema(getWeather);
// {
//   name: 'get_weather',
//   description: 'Get the current weather for a city',
//   parameters: {
//     type: 'object',
//     properties: {
//       city: { type: 'string', description: 'City name' },
//       units: { type: 'string', enum: ['celsius', 'fahrenheit'], default: 'celsius' }
//     },
//     required: ['city']
//   }
// }
```

The conversion uses the schema's input type, so fields with a `.default()` are optional for the model; the default is applied when the arguments are parsed. `tool.toJSON()` returns the same schema.

## Sandboxed Execution

Tools that run untrusted commands can declare a sandbox. A sandboxed tool does not run its `execute` function: the runtime executes the call in the sandbox instead (install `@cogitator-ai/sandbox`):

* `type: 'docker'` runs the `command` argument with `sh -c` in a container of `image`, with optional `cwd` and `env` arguments, and returns `{ stdout, stderr, exitCode, timedOut, duration, command }`.
* `type: 'wasm'` passes the arguments as JSON on stdin to `wasmModule` and returns its stdout, parsed as JSON when possible.

```typescript
const runPython = tool({
  name: 'run_python',
  description: 'Run a shell command in a Python 3.12 container, e.g. python -c "print(2 ** 10)"',
  parameters: z.object({
    command: z.string().describe('Shell command to run'),
  }),
  execute: async ({ command }) => ({ command }), // not called while the sandbox is available
  sandbox: {
    type: 'docker',
    image: 'python:3.12-slim',
    network: { mode: 'none' },
    resources: { memory: '256MB', cpus: 0.5 },
    timeout: 30_000,
  },
});
```

The container is managed by the runtime (configure it with `sandbox` on the `Cogitator`). Two fallbacks log a warning and run without isolation: when `@cogitator-ai/sandbox` cannot be loaded, the runtime calls `execute` natively, so keep `execute` harmless; when Docker is not available, the sandbox manager runs the command on the host shell. Both are allowed by `sandbox.allowNativeFallback` (default `true`); set it to `false` on the `Cogitator` when untrusted commands must never reach the host, and such calls fail instead — see [Sandbox](/docs/deployment/sandbox#fallback).

## Tool Caching

Wrap tools with `withCache` to avoid repeated expensive calls:

```typescript
import { withCache } from '@cogitator-ai/core';

const cachedWeather = withCache(getWeather, {
  strategy: 'exact',
  ttl: '5m',
  maxSize: 1000,
  storage: 'memory',
});
```

See [Tool Caching](/docs/tools/tool-caching) for TTL strategies, semantic matching, and Redis-backed storage.
