> ## Documentation Index
> Fetch the complete documentation index at: https://docs.standardagents.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools

> Extend AI capabilities with custom functions, sub-prompts, and agent handoffs

## What Are Tools?

Tools are callable capabilities that extend what AI agents can do beyond generating text. They allow agents to:

* **Execute code**: Run custom business logic
* **Access data**: Query databases and APIs
* **Perform actions**: Create records, send emails, trigger workflows
* **Call other AI**: Invoke sub-prompts or hand off to specialized agents

> [!NOTE]
>
> In Standard Agents Builder, "tools" includes function tools, prompt tools, and agent tools. Callable `dual_ai` agent tools can execute as autonomous subagents.

## Quick Start

Create your first tool in `agents/tools/search_database.ts`:

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';

export default defineTool({
  description: 'Search the customer database for matching records',
  args: z.object({
    query: z.string().describe('Search query'),
    limit: z.number().optional().default(10).describe('Maximum results'),
  }),
  execute: async (state, args) => {
    const results = await searchDatabase(args.query, args.limit);

    return {
      status: 'success',
      result: JSON.stringify(results),
    };
  },
});
```

> [!TIP]
>
> That's it! Your tool is now available to any prompt that includes it in its `tools` array. The framework auto-discovers it from the `agents/tools/` directory.

## Tool Types

Standard Agents Builder supports three types of tools that all appear the same to LLMs:

> [card] Function Tools
>
> Custom TypeScript functions
>
> **Defined in:** `agents/tools/`
>
> **Best for:**
>
> * Database queries
> * API calls
> * File operations
> * Business logic

> [card] Prompt Tools
>
> Other prompts called as sub-prompts
>
> **Defined in:** `agents/prompts/` with `exposeAsTool: true`
>
> **Best for:**
>
> * Specialized analysis
> * Content generation
> * Data summarization
> * Validation tasks

> [card] Agent Tools
>
> Full agents for handoffs
>
> **Defined in:** `agents/agents/` with `exposeAsTool: true`
>
> **Best for:**
>
> * Specialist routing
> * Complex workflows
> * Domain expertise
> * Multi-turn tasks

## Runtime Subagent Lifecycle Tools

When a prompt defines resumable subagent relationships, Standard Agents Builder injects runtime lifecycle tools:

* `subagent_create`
* `subagent_message`

These tools are runtime-provided and scoped to the prompt/thread state. They are not user-defined files in `agents/tools/`.

`subagent_create` requires a non-empty `name` for the spawned child instance.

Non-resumable subagents still behave like regular tool calls.

### How They Work Together

```typescript theme={null}
// Function tool: agents/tools/lookup_customer.ts
export default defineTool({
  description: 'Look up customer by ID',
  args: z.object({ customerId: z.string() }),
  execute: async (state, args) => {
    const customer = await db.getCustomer(args.customerId);
    return { status: 'success', result: JSON.stringify(customer) };
  },
});

// Prompt tool: agents/prompts/summarizer.ts
export default definePrompt({
  name: 'summarizer',
  exposeAsTool: true,
  toolDescription: 'Summarize text into key points',
  prompt: 'You are an expert summarizer...',
  model: 'gpt-5.4',
  requiredSchema: z.object({
    text: z.string().describe('Text to summarize'),
  }),
});

// Agent tool: agents/agents/billing_specialist.ts
export default defineAgent({
  name: 'billing_specialist',
  exposeAsTool: true,
  toolDescription: 'Hand off billing issues to specialist',
  sideA: { prompt: 'billing_expert' },
});

// Use all three in a prompt
export default definePrompt({
  name: 'support_router',
  tools: [
    'lookup_customer',      // Function tool
    'summarizer',           // Prompt tool
    'billing_specialist',   // Agent tool
  ],
  // ...
});
```

## ThreadState Context

Every tool receives a `ThreadState` object providing execution context:

```typescript theme={null}
defineTool({
  description: 'My tool',
  args: z.object({ input: z.string() }),
  execute: async (state, args) => {
    // Access execution context
    console.log('Thread ID:', state.threadId);
    console.log('Turn:', state.turnCount);
    console.log('Agent:', state.agentConfig.name);

    // Access storage
    const result = await state.storage.sql.exec('SELECT * FROM data WHERE id = ?', args.input);

    // Access environment
    const apiKey = await state.env('MY_API_KEY');

    return { status: 'success', result: 'Done' };
  },
});
```

> [!NOTE]
>
> ThreadState gives tools access to thread storage, message operations, file system, and execution state. See the [ThreadState documentation](https://docs.standardagents.ai/core-concepts/flowstate.md) for details.

### Sandboxed Code Execution

Use `state.runCode()` when a tool needs to evaluate model- or user-authored JavaScript/TypeScript. The code runs in a Dynamic Worker sandbox with no implicit host capabilities and is loaded by stable content ID when possible. Bridge only the functions and data you want the code to access. Use `modules` when the entry source imports local relative modules, and `execute` when you want to run a named export or pass arguments. Sandbox code can call and await a bridged function at the top level of a module as well as inside an exported function.

```typescript theme={null}
const run = state.runCode(code, {
  execute: { fn: 'main', args: ['notes.txt'] },
  imports: {
    fs: {
      readFile: async (path: string) => {
        const data = await state.readFile(path);
        return data ? new TextDecoder().decode(data) : null;
      },
    },
  },
  report: (value) => console.log('sandbox report', value),
});

const result = await run;
```

The handle is awaitable, exposes live `reports`, and can be stopped with `run.terminate(reason)` from a caller-owned timeout.

## Input Validation with Zod

Tools use Zod schemas to validate arguments:

```typescript theme={null}
const argsSchema = z.object({
  // Required string
  query: z.string().describe('Search query'),

  // Optional with default
  limit: z.number().optional().default(10).describe('Max results'),

  // Enum
  format: z.enum(['json', 'text', 'markdown']).describe('Output format'),

  // Nested object
  filters: z.object({
    category: z.string().optional(),
    minPrice: z.number().optional(),
  }).optional().describe('Filter options'),

  // Array
  tags: z.array(z.string()).optional().describe('Filter by tags'),
});
```

> [!WARNING]
>
> Always use `.describe()` on parameters! These descriptions help LLMs understand how to use your tools correctly.

## Tool Results

Tools return a `ToolResult` object:

```typescript theme={null}
interface ToolResult {
  status: 'success' | 'error';
  result?: string;
  error?: string;
  stack?: string;
  attachments?: Array<ToolAttachment | AttachmentRef>;
}

interface ToolAttachment {
  name: string;           // Filename (e.g., "generated.png")
  mimeType: string;       // MIME type
  data: string;           // Base64-encoded file data
  width?: number;         // Image width in pixels
  height?: number;        // Image height in pixels
}
```

When tool-driven subagent communication crosses threads, attachment paths are copied and rewritten to destination-local paths before delivery.

### Common Patterns

**Text Result**

```typescript theme={null}
return {
  status: 'success',
  result: 'Operation completed successfully',
};
```

**JSON Data**

```typescript theme={null}
return {
  status: 'success',
  result: JSON.stringify({ id: '123', name: 'John' }),
};
```

**Error Handling**

```typescript theme={null}
return {
  status: 'error',
  error: 'Failed to find user: User not found',
};
```

## Thread Environment Variables

Tools can declare required thread environment variables using the `variables` option:

```typescript theme={null}
export default defineTool({
  description: 'Search through uploaded files using vector embeddings',
  args: z.object({
    query: z.string().describe('Search query'),
  }),
  execute: async (state, args) => {
    const vectorStoreId = await state.env('VECTOR_STORE_ID');
    const results = await searchVectorStore(vectorStoreId, args.query);
    return { status: 'success', result: JSON.stringify(results) };
  },
  variables: [
    {
      name: 'VECTOR_STORE_ID',
      type: 'secret',
      required: true,
      description: 'OpenAI Vector Store ID',
    },
  ],
});
```

Variables are validated at runtime. If required variables are missing, the tool execution fails with a clear error message. Thread env values marked `secret` should be redacted from tool output and errors; values marked `text` may be shown. Tools can write thread env values with `state.setEnv(name, value, { type: 'text' | 'secret' })`.

When a tool reads `state.env(name)`, Standard Agents Builder resolves values in this order:
thread, user account, Standard Agents Builder instance, agent definition, then prompt definition.
User-account and instance-level variables can also persist their own `text` /
`secret` display type, so safe defaults may preload in the UI without forcing
secrets to be revealed.

## Provider-Executed Tools

Some tools are executed by the LLM provider rather than locally. Provider
packages usually expose these names through `getTools()`, and models opt in with
`providerTools`:

```typescript theme={null}
export default defineModel({
  name: 'gpt-5.4-with-tools',
  provider: openai,
  model: 'gpt-5.4',
  providerTools: ['web_search', 'code_interpreter'],
});
```

Prompts select enabled provider tools by name in `tools`, just like local tools.
Standard Agents Builder marks those definitions with `executionMode: 'provider'` and
passes them to the provider. The local tool executor does not run provider tools;
the provider package owns native request translation, execution, and reporting
completed calls through the generic provider-tool log path. Legacy provider
events such as web search are adapted into the same log format.
Generated prompt files may store selected provider tools as
`provider:<toolName>` so they remain distinct from local tools with the same
name.

## Common Use Cases

### Database Query Tool

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';

export default defineTool({
  description: 'Look up customer information by ID or email',
  args: z.object({
    customerId: z.string().optional().describe('Customer ID'),
    email: z.string().email().optional().describe('Customer email'),
  }),
  execute: async (state, args) => {
    const databaseUrl = await state.env('DATABASE_URL');

    let customer;
    if (args.customerId) {
      customer = await lookupCustomer(databaseUrl, { customerId: args.customerId });
    } else {
      customer = await lookupCustomer(databaseUrl, { email: args.email });
    }

    if (!customer) {
      return {
        status: 'error',
        error: 'Customer not found',
      };
    }

    return {
      status: 'success',
      result: JSON.stringify(customer),
    };
  },
});
```

### API Integration Tool

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';

export default defineTool({
  description: 'Fetch current weather for a city',
  args: z.object({
    city: z.string().describe('City name'),
    units: z.enum(['metric', 'imperial']).default('metric'),
  }),
  execute: async (state, args) => {
    const apiKey = await state.env('WEATHER_API_KEY');

    try {
      const response = await fetch(
        `https://api.weather.com/v1/current?city=${args.city}&units=${args.units}&key=${apiKey}`
      );

      if (!response.ok) {
        return {
          status: 'error',
          error: `Weather API error: ${response.status}`,
        };
      }

      const data = await response.json();

      return {
        status: 'success',
        result: `Weather in ${args.city}: ${data.temperature}°, ${data.conditions}`,
      };
    } catch (error) {
      return {
        status: 'error',
        error: `Failed to fetch weather: ${error.message}`,
      };
    }
  },
});
```

### Tool Chaining

Use `queueTool` to chain multiple tools:

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { queueTool } from '@standardagents/builder';
import { z } from 'zod';

export default defineTool({
  description: 'Process order and queue follow-up tasks',
  args: z.object({
    orderId: z.string().describe('Order ID'),
  }),
  execute: async (state, args) => {
    const order = await validateOrder(args.orderId);

    if (!order.valid) {
      return {
        status: 'error',
        error: 'Order validation failed',
      };
    }

    // Queue additional tools to execute after this one
    queueTool(state.rootState, 'charge_payment', {
      orderId: args.orderId,
      amount: order.total,
    });

    queueTool(state.rootState, 'send_confirmation_email', {
      userId: order.userId,
      orderId: args.orderId,
    });

    return {
      status: 'success',
      result: `Order ${args.orderId} queued for processing`,
    };
  },
});
```

### Returning File Attachments

```typescript theme={null}
export default defineTool({
  description: 'Generate an image based on a text prompt',
  args: z.object({
    prompt: z.string().describe('Text description of the image'),
  }),
  execute: async (state, args) => {
    const image = await generateImage(args.prompt);

    return {
      status: 'success',
      result: `Generated image for: "${args.prompt}"`,
      attachments: [{
        name: `generated-${Date.now()}.png`,
        mimeType: 'image/png',
        data: image.base64,
        width: 1024,
        height: 1024,
      }],
    };
  },
});
```

## ThreadState Utilities

Standard Agents Builder provides utility functions for tools:

> [accordion] queueTool
>
> Queue another tool to execute after the current one
>
> ```typescript theme={null}
> queueTool(state.rootState, 'other_tool', { param: 'value' });
> ```

> [accordion] injectMessage
>
> Add a message to conversation without triggering execution
>
> ```typescript theme={null}
> await injectMessage(state, {
>   role: 'system',
>   content: 'Important context update',
> });
> ```

> [accordion] getMessages
>
> Retrieve message history
>
> ```typescript theme={null}
> const messages = await getMessages(state, 20);
> ```

> [accordion] emitThreadEvent
>
> Send custom events to frontend
>
> ```typescript theme={null}
> emitThreadEvent(state, 'progress', {
>   step: 3,
>   total: 10,
> });
> ```

See the [ThreadState documentation](https://docs.standardagents.ai/core-concepts/flowstate.md) for complete details.

## Best Practices

> [accordion] Write Clear Descriptions
>
> Tool descriptions help LLMs understand when to use each tool:
>
> **Good:**
>
> ```typescript theme={null}
> defineTool({
>   description: 'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles sorted by relevance.',
>   // ...
> });
> ```
>
> **Avoid:**
>
> ```typescript theme={null}
> defineTool({
>   description: 'Search stuff',
>   // ...
> });
> ```

> [accordion] Describe All Parameters
>
> Use `.describe()` on every parameter:
>
> ```typescript theme={null}
> args: z.object({
>   query: z.string().describe('Search query - supports partial matches'),
>   category: z.string().optional().describe('Product category filter'),
>   minPrice: z.number().optional().describe('Minimum price in USD'),
> })
> ```

> [accordion] Handle Errors Gracefully
>
> Return errors as tool results, don't throw:
>
> ```typescript theme={null}
> try {
>   const result = await riskyOperation(args);
>   return {
>     status: 'success',
>     result: JSON.stringify(result),
>   };
> } catch (error) {
>   return {
>     status: 'error',
>     error: `Operation failed: ${error.message}`,
>   };
> }
> ```

> [accordion] Use snake\_case Names
>
> File names should use snake\_case:
>
> **Good:** `search_database.ts`, `create_ticket.ts`
>
> **Avoid:** `SearchDatabase.ts`, `createTicket.ts`

> [accordion] Keep Tools Focused
>
> Each tool should do one thing well:
>
> **Good:**
>
> * `lookup_customer.ts` - Just lookup
> * `update_customer.ts` - Just update
> * `delete_customer.ts` - Just delete
>
> **Avoid:**
>
> * `customer_manager.ts` - Does everything

> [accordion] Use rootState for Queueing
>
> When queueing tools from sub-prompts, use `state.rootState`:
>
> ```typescript theme={null}
> // Correct
> queueTool(state.rootState, 'next_tool', args);
>
> // Incorrect (may not work in sub-prompts)
> queueTool(state, 'next_tool', args);
> ```

## Next Steps

> [card] [API Reference](https://docs.standardagents.ai/api-reference/define/tool.md)
>
> View complete tool API specification

> [card] [ThreadState](https://docs.standardagents.ai/core-concepts/flowstate.md)
>
> Learn about execution context and utilities

> [card] [Prompts](https://docs.standardagents.ai/core-concepts/prompts.md)
>
> Configure prompts that use tools


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.