> ## Documentation Index
> Fetch the complete documentation index at: https://docs.standardagentbuilder.com/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 AgentBuilder, "tools" includes function tools, prompt tools, and agent tools. Callable `dual_ai` agent tools can execute as autonomous subagents.
</Note>

## 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),
    };
  },
});
```

<Check>
  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.
</Check>

## Tool Types

AgentBuilder supports three types of tools that all appear the same to LLMs:

<CardGroup cols={3}>
  <Card title="Function Tools" icon="code">
    Custom TypeScript functions

    **Defined in:** `agents/tools/`

    **Best for:**

    * Database queries
    * API calls
    * File operations
    * Business logic
  </Card>

  <Card title="Prompt Tools" icon="wand-magic-sparkles">
    Other prompts called as sub-prompts

    **Defined in:** `agents/prompts/` with `exposeAsTool: true`

    **Best for:**

    * Specialized analysis
    * Content generation
    * Data summarization
    * Validation tasks
  </Card>

  <Card title="Agent Tools" icon="robot">
    Full agents for handoffs

    **Defined in:** `agents/agents/` with `exposeAsTool: true`

    **Best for:**

    * Specialist routing
    * Complex workflows
    * Domain expertise
    * Multi-turn tasks
  </Card>
</CardGroup>

## Runtime Subagent Lifecycle Tools

When a prompt defines resumable subagent relationships, AgentBuilder 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' };
  },
});
```

<Info>
  ThreadState gives tools access to thread storage, message operations, file system, and execution state. See the [ThreadState documentation](/core-concepts/flowstate) for details.
</Info>

### 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.

```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.
</Warning>

## 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)`, AgentBuilder resolves values in this order:
thread, user account, AgentBuilder 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.
AgentBuilder 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

AgentBuilder provides utility functions for tools:

<AccordionGroup>
  <Accordion title="queueTool" icon="list">
    Queue another tool to execute after the current one

    ```typescript theme={null}
    queueTool(state.rootState, 'other_tool', { param: 'value' });
    ```
  </Accordion>

  <Accordion title="injectMessage" icon="message">
    Add a message to conversation without triggering execution

    ```typescript theme={null}
    await injectMessage(state, {
      role: 'system',
      content: 'Important context update',
    });
    ```
  </Accordion>

  <Accordion title="getMessages" icon="messages">
    Retrieve message history

    ```typescript theme={null}
    const messages = await getMessages(state, 20);
    ```
  </Accordion>

  <Accordion title="emitThreadEvent" icon="broadcast-tower">
    Send custom events to frontend

    ```typescript theme={null}
    emitThreadEvent(state, 'progress', {
      step: 3,
      total: 10,
    });
    ```
  </Accordion>
</AccordionGroup>

See the [ThreadState documentation](/core-concepts/flowstate) for complete details.

## Best Practices

<AccordionGroup>
  <Accordion title="Write Clear Descriptions" icon="file-alt">
    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>

  <Accordion title="Describe All Parameters" icon="list">
    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>

  <Accordion title="Handle Errors Gracefully" icon="shield">
    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>

  <Accordion title="Use snake_case Names" icon="keyboard">
    File names should use snake\_case:

    **Good:** `search_database.ts`, `create_ticket.ts`

    **Avoid:** `SearchDatabase.ts`, `createTicket.ts`
  </Accordion>

  <Accordion title="Keep Tools Focused" icon="bullseye">
    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>

  <Accordion title="Use rootState for Queueing" icon="sitemap">
    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);
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="code" href="/api-reference/define/tool">
    View complete tool API specification
  </Card>

  <Card title="ThreadState" icon="circle-nodes" href="/core-concepts/flowstate">
    Learn about execution context and utilities
  </Card>

  <Card title="Prompts" icon="file-lines" href="/core-concepts/prompts">
    Configure prompts that use tools
  </Card>

  <Card title="Examples" icon="lightbulb" href="/examples/tools">
    Explore real-world tool patterns
  </Card>
</CardGroup>
