> For the complete documentation index, see [llms.txt](/llms.txt).
> A full single-fetch corpus is available at [llms-full.txt](/llms-full.txt).
---
title: Tools
description: Give agents typed callable actions for APIs, code execution, search, and provider-hosted capabilities.
last_verified: 2026-10-06
---



Tools give your agent the ability to take actions: call an API, run code, or search the web. The agent decides when to call a tool based on the conversation, runs it, then loops back with the result until it has a final answer.

---

## Custom tools



**Python:**

Define a tool with the `@tool` decorator. The SDK reads your type hints and docstring to build the JSON schema the model uses to call it correctly.

```python
from agnt5 import Agent
from agnt5.context import Context
from agnt5.tool import tool

@tool
async def get_weather(ctx: Context, city: str) -> str:
    """Get the current weather for a city.

    Args:
        city: Name of the city to check.
    """
    # call your weather API here
    return f"Sunny, 22°C in {city}"

agent = Agent(
    name="assistant",
    model="openai/gpt-6-luna",
    instructions="You help users with weather queries.",
    tools=[get_weather],
)

result = await agent.run("What is the weather in Paris?")
print(result.output)
```

**Things to know**

- The first parameter must be `ctx: Context` (injected automatically, not exposed to the model).
- Every other parameter becomes a field the model can fill in. Type hints (`str`, `int`, `bool`, `list`) and docstring `Args:` descriptions are used to build the schema.
- Sync functions are wrapped in a thread pool automatically.
- The tool is registered globally at import time and can be reused across agents.

`@tool` decorator options:

| Parameter | Type | Default | Description |
|---|---|---|---|
| `name` | `str` | function's `__name__` | How the tool appears to the model |
| `description` | `str` | first line of docstring | What the model reads to decide when to call this tool |
| `auto_schema` | `bool` | `True` | Build the schema from type hints and the docstring. Set `False` to supply `input_schema` yourself |
| `input_schema` | `dict` | `None` | A JSON Schema for the tool's input, used when `auto_schema=False` |
| `recovery_policy` | `str` | `"unknown_outcome"` | How interrupted work is settled. Use `"idempotent_retry"` for tools that are safe to run again, or `"durable_steps"` for tools that run durable steps of their own |





**TypeScript:**

Define a tool with `tool(name, options, handler)`. Write the JSON Schema for the model in `inputSchema`.

```typescript
import { Agent, LM, tool } from '@agnt5/sdk';
import type { Context } from '@agnt5/sdk';

export const getWeather = tool(
  'get_weather',
  {
    description: 'Get the current weather for a city.',
    inputSchema: {
      type: 'object',
      properties: {
        city: { type: 'string', description: 'Name of the city to check.' },
      },
      required: ['city'],
    },
  },
  async (ctx: Context, args: { city: string }): Promise<string> => {
    // call your weather API here
    return `Sunny, 22°C in ${args.city}`;
  },
);

const agent = new Agent({
  name: 'assistant',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions: 'You help users with weather queries.',
  tools: [getWeather],
});

const result = await agent.run('What is the weather in Paris?');
console.log(result.output);
```

**Things to know**

- The handler's first parameter is always `ctx: Context` (injected automatically, not exposed to the model).
- `inputSchema` is a plain JSON Schema object — it's what the model sees, so field `description`s matter.
- The tool is registered globally at call time and can be reused across agents.

`tool(name, options, handler)` options:

| Option | Type | Default | Description |
|---|---|---|---|
| `description` | `string` | tool `name` | What the model reads to decide when to call this tool |
| `inputSchema` | `JSONSchema` | `{ type: 'object', properties: {}, required: [] }` | The schema the model fills in when calling the tool |





**Go:**

Define a tool with `agnt5.NewTool(name, handler, opts...)`. Describe its input with a JSON Schema using `WithToolSchema`.

```go
import "github.com/agnt5dev/sdk-go/agnt5"

getWeather, err := agnt5.NewTool("get_weather", func(_ context.Context, args map[string]any) (any, error) {
    city, _ := args["city"].(string)
    // call your weather API here
    return "Sunny, 22°C in " + city, nil
}, agnt5.WithToolDescription("Get the current weather for a city."),
    agnt5.WithToolSchema(map[string]any{
        "type": "object",
        "properties": map[string]any{
            "city": map[string]any{"type": "string", "description": "Name of the city to check."},
        },
        "required": []string{"city"},
    }),
)

agent, err := agnt5.NewAgent("assistant",
    agnt5.WithAgentModel(model),
    agnt5.WithAgentInstructions("You help users with weather queries."),
    agnt5.WithAgentTools(getWeather),
    agnt5.WithAgentMaxTurns(3),
)

result, err := agent.Run(ctx, agnt5.AgentInput{Message: "What is the weather in Paris?"})
fmt.Println(result.Response)
```

`NewAgent` defaults `MaxTurns` to `10`, matching `max_iterations`/`maxIterations` in the other SDKs, so `WithAgentMaxTurns` is optional — the sample above caps the loop at `3`. Each turn is one model call plus the tool calls it requests, so an agent needs at least two turns to call a tool and then answer; lower the limit to cap cost on simple agents, raise it for long tool-heavy loops. When the limit is hit, `Run` returns `agnt5.ErrAgentMaxTurnsExceeded`.

**Things to know**

- `ToolHandler` is `func(context.Context, map[string]any) (any, error)` — untyped input, read fields with a type assertion.
- `WithToolSchema` is a plain JSON Schema as `map[string]any` — it's what the model sees, so field descriptions matter.
- `RegisterTool(worker, tool, opts...)` registers a tool onto a `Worker`'s global registry so it can be reused across agents.

`NewTool(name, handler, opts...)` options:

| Option | Type | Description |
|---|---|---|
| `WithToolDescription` | `string` | What the model reads to decide when to call this tool |
| `WithToolSchema` | `map[string]any` | The JSON Schema the model fills in when calling the tool |
| `WithToolMetadata` | `map[string]any` | Arbitrary metadata attached to the tool |



---

## Inspecting tool calls



**Python:**

After `agent.run()`, `result.tool_calls` lists every tool the agent invoked during its loop.

```python
result = await agent.run("What is the weather in Paris?")

print(result.output)      # final answer
print(result.tool_calls)  # [{"name": "get_weather", "arguments": '{"city": "Paris"}', "iteration": 1}]
```





**TypeScript:**

After `agent.run()`, `result.toolCalls` lists every tool the agent invoked during its loop.

```typescript
const result = await agent.run('What is the weather in Paris?');

console.log(result.output);    // final answer
console.log(result.toolCalls); // [{ name: 'get_weather', arguments: '{"city":"Paris"}', iteration: 1 }]
```





**Go:**

After `agent.Run()`, `result.ToolCalls` is the number of tool calls made; `result.ToolCallDetails` lists each one.

```go
result, err := agent.Run(ctx, agnt5.AgentInput{Message: "What is the weather in Paris?"})

fmt.Println(result.Response)         // final answer
fmt.Println(result.ToolCalls)        // 1
fmt.Println(result.ToolCallDetails)  // []AgentToolCall{{Name: "get_weather", Arguments: map[string]any{"city": "Paris"}, Iteration: 1, Result: ...}}
```



---

## Agents as tools



**Python:**

Pass another `Agent` directly in the `tools` list. The SDK wraps it automatically. The calling agent invokes it like any other tool and gets the specialist's response back before continuing its own reasoning.

```python
from agnt5 import Agent

lookup_agent = Agent(
    name="lookup",
    model="openai/gpt-6-luna",
    instructions="You look up factual information and return concise answers.",
)

orchestrator = Agent(
    name="orchestrator",
    model="openai/gpt-6-luna",
    instructions="Use lookup to find information, then summarize it for the user.",
    tools=[lookup_agent],   # auto-wrapped as ask_lookup
)

result = await orchestrator.run("What is the capital of Japan?")
print(result.output)
```





**TypeScript:**

Pass another `Agent` directly in the `tools` list. The SDK wraps it automatically. The calling agent invokes it like any other tool and gets the specialist's response back before continuing its own reasoning.

```typescript
import { Agent, LM } from '@agnt5/sdk';

const lookupAgent = new Agent({
  name: 'lookup',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions: 'You look up factual information and return concise answers.',
});

const orchestrator = new Agent({
  name: 'orchestrator',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions: 'Use lookup to find information, then summarize it for the user.',
  tools: [lookupAgent], // exposed to the model as a tool named `lookup`
});

const result = await orchestrator.run('What is the capital of Japan?');
console.log(result.output);
```





**Go:**

Wrap the specialist's `Run` call in `agnt5.NewTool`, describe its input with a schema so the model sends a `question`, and pass the tool to `WithAgentTools`.

```go
lookupAgent, err := agnt5.NewAgent("lookup",
    agnt5.WithAgentModel(model),
    agnt5.WithAgentInstructions("You look up factual information and return concise answers."),
)

askLookup, err := agnt5.NewTool("ask_lookup", func(_ context.Context, args map[string]any) (any, error) {
    question, _ := args["question"].(string)
    result, err := lookupAgent.Run(ctx, agnt5.AgentInput{Message: question})
    return result.Response, err
}, agnt5.WithToolDescription("Ask the lookup specialist a factual question."),
    agnt5.WithToolSchema(map[string]any{
        "type":       "object",
        "properties": map[string]any{"question": map[string]any{"type": "string"}},
        "required":   []string{"question"},
    }),
)

orchestrator, err := agnt5.NewAgent("orchestrator",
    agnt5.WithAgentModel(model),
    agnt5.WithAgentInstructions("Use lookup to find information, then summarize it for the user."),
    agnt5.WithAgentTools(askLookup),
    agnt5.WithAgentMaxTurns(3),
)

result, err := orchestrator.Run(ctx, agnt5.AgentInput{Message: "What is the capital of Japan?"})
fmt.Println(result.Response)
```



---

## Sandbox workspaces

Pass a **[sandbox](/docs/build/sandboxes.md)** to an agent when it needs an isolated file and code execution workspace. AGNT5 adds the standard sandbox capabilities to the agent, and your custom tools can use the same workspace through `ctx.sandbox`.



**Python:**

```python
from agnt5 import Agent, Sandbox

agent = Agent(
    name="coder",
    model="openai/gpt-6-luna",
    instructions=(
        "Use the sandbox workspace to write files, inspect files, "
        "and run code before returning an answer."
    ),
    sandbox=Sandbox(),
)

result = await agent.run("Calculate the first 10 fibonacci numbers in Python")
print(result.output)
```





**TypeScript:**

```typescript
import { Agent, LM, Sandbox } from '@agnt5/sdk';

const agent = new Agent({
  name: 'coder',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions:
    'Use the sandbox workspace to write files, inspect files, and run code before returning an answer.',
  sandbox: new Sandbox(),
});

const result = await agent.run('Calculate the first 10 fibonacci numbers in Python');
console.log(result.output);
```





**Go:**

`WithAgentSandbox` attaches any `SandboxRunner` and adds the standard tools — `sandbox_execute_code`, `sandbox_write_file`, `sandbox_read_file`, and `sandbox_list_files` — so the agent can use the workspace without custom tools. `Run` also attaches the sandbox to the invocation's `*agnt5.Context`, so custom tools can reach it through `ctx.Sandbox()`.

```go
agent, err := agnt5.NewAgent("coder",
    agnt5.WithAgentModel(model),
    agnt5.WithAgentInstructions("Use the sandbox workspace to write files, inspect files, and run code before returning an answer."),
    agnt5.WithAgentSandbox(agnt5.NewInMemorySandbox()),
)

result, err := agent.Run(ctx, agnt5.AgentInput{Message: "Calculate the first 10 fibonacci numbers in Python"})
fmt.Println(result.Response)
```

`NewInMemorySandbox()` is a deterministic in-process stand-in for tests; use `agnt5.NewHTTPSandbox(endpoint, apiKey)` to point at a real sandbox service. See [Sandboxes](/docs/build/sandboxes.md) for provider selection and lifecycle.



The agent can use the sandbox to:

- write files to the workspace
- read files from the workspace
- list files and directories
- execute `python`, `javascript`, or `bash` code

See [Use sandboxes with agents](/docs/build/sandboxes.md) for provider selection, lifecycle, and custom tools that read from `ctx.sandbox`.

---

## Built-in tools

Built-in tools run entirely on the provider's infrastructure. You enable them and the provider handles execution. Results are already included in the model's response; no local code runs.



**Python:**

```python
from agnt5 import Agent
from agnt5.lm import BuiltInTool

agent = Agent(
    name="researcher",
    model="openai/gpt-6-luna",
    instructions=(
        "You are a research assistant. Always use web_search to find current "
        "information, never answer from training knowledge alone."
    ),
    built_in_tools=[BuiltInTool.WEB_SEARCH],
)

result = await agent.run("Who won the most recent Formula 1 championship?")
print(result.output)
```

Available built-in tools:

| Tool | What it does | Provider |
|---|---|---|
| `BuiltInTool.WEB_SEARCH` | Live web search with cited results | OpenAI, Anthropic |
| `BuiltInTool.CODE_INTERPRETER` | Run code in a provider-hosted sandbox | OpenAI only |
| `BuiltInTool.FILE_SEARCH` | Search over files uploaded to the provider | OpenAI only |
| `BuiltInTool.WEB_FETCH` | Fetch the content of a specific URL | Anthropic only |

You can mix built-in tools with sandbox workspaces and custom tools on the same agent:

```python
from agnt5 import Agent, Sandbox
from agnt5.lm import BuiltInTool

agent = Agent(
    name="assistant",
    model="openai/gpt-6-luna",
    instructions="Search the web for information and run code when needed.",
    built_in_tools=[BuiltInTool.WEB_SEARCH],
    sandbox=Sandbox(),
)
```





**TypeScript:**

```typescript
import { Agent, LM } from '@agnt5/sdk';

const agent = new Agent({
  name: 'researcher',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions:
    'You are a research assistant. Always use web_search to find current information, never answer from training knowledge alone.',
  builtInTools: ['web_search'],
});

const result = await agent.run('Who won the most recent Formula 1 championship?');
console.log(result.output);
```

Available built-in tools (`BuiltInTool` is a string union):

| Tool | What it does | Provider |
|---|---|---|
| `'web_search'` | Live web search with cited results | OpenAI, Anthropic |
| `'code_interpreter'` | Run code in a provider-hosted sandbox | OpenAI only |
| `'file_search'` | Search over files uploaded to the provider | OpenAI only |
| `'web_fetch'` | Fetch the content of a specific URL | Anthropic only |

You can mix built-in tools with sandbox workspaces and custom tools on the same agent:

```typescript
import { Agent, LM, Sandbox } from '@agnt5/sdk';

const agent = new Agent({
  name: 'assistant',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions: 'Search the web for information and run code when needed.',
  builtInTools: ['web_search'],
  sandbox: new Sandbox(),
});
```





**Go:**

In Go, add search, fetch, and similar capabilities as a [custom tool](#custom-tools) that calls the API you want, or use an [MCP tool](#mcp-tools) that exposes them.



---

## MCP tools

Use [Model Context Protocol](/docs/build/mcp.md) when a tool already exists in an external MCP server. `MCPClient` connects to the server, discovers tools, and returns AGNT5 `Tool` objects that you pass to an agent.



**Python:**

```python
from agnt5 import Agent
from agnt5.mcp import MCPClient

mcp = MCPClient(id="repo-tools")
mcp.add_streamable_http_server("deepwiki", "https://mcp.deepwiki.com/mcp")
await mcp.connect()

agent = Agent(
    name="repository_researcher",
    model="openai/gpt-6-luna",
    instructions="Use available tools to answer questions about code repositories.",
    tools=mcp.get_tools(),
)
```





**TypeScript:**

```typescript
import { Agent, LM, MCPClient } from '@agnt5/sdk';

const mcp = new MCPClient('repo-tools');
mcp.addStreamableHttpServer('deepwiki', 'https://mcp.deepwiki.com/mcp');
await mcp.connect();

const agent = new Agent({
  name: 'repository_researcher',
  model: LM.openai(),
  modelName: 'openai/gpt-6-luna',
  instructions: 'Use available tools to answer questions about code repositories.',
  tools: mcp.getTools(),
});
```





**Go:**

`agnt5.NewMCPClient(transport)` connects to a server and lists its tools. Wrap each tool you want to expose with `agnt5.NewTool`, forwarding calls through `client.CallTool`:

```go
import "github.com/agnt5dev/sdk-go/agnt5"

transport := agnt5.NewSSEMCPTransport("https://mcp.deepwiki.com/mcp", nil)
mcp, err := agnt5.NewMCPClient(transport)

mcpTools, err := mcp.ListTools(ctx)
tools := make([]agnt5.Tool, 0, len(mcpTools))
for _, t := range mcpTools {
    t := t
    tool, err := agnt5.NewTool(t.Name, func(c context.Context, args map[string]any) (any, error) {
        return mcp.CallTool(c, t.Name, args)
    }, agnt5.WithToolDescription(t.Description), agnt5.WithToolSchema(t.InputSchema))
    if err != nil {
        return err
    }
    tools = append(tools, tool)
}

agent, err := agnt5.NewAgent("repository_researcher",
    agnt5.WithAgentModel(model),
    agnt5.WithAgentInstructions("Use available tools to answer questions about code repositories."),
    agnt5.WithAgentTools(tools...),
    agnt5.WithAgentMaxTurns(5),
)
```



See [Model Context Protocol](/docs/build/mcp.md) for transports, direct tool calls, and exposing AGNT5 primitives as an MCP server.

---

## Human-in-the-loop tools

In Python and TypeScript, two built-in tool classes let an agent pause and wait for a human response before continuing. Both need a workflow context because execution durably suspends while waiting.



**Python:**

```python
from agnt5 import Agent, workflow, WorkflowContext
from agnt5.tool import AskUserTool, RequestApprovalTool

@workflow
async def agent_with_hitl(ctx: WorkflowContext, task: str) -> dict:
    agent = Agent(
        name="assistant",
        model="openai/gpt-6-luna",
        instructions=(
            "Ask the user for clarification when the request is ambiguous. "
            "Always request approval before making any changes."
        ),
        tools=[AskUserTool(ctx), RequestApprovalTool(ctx)],
    )
    result = await agent.run(task, context=ctx)
    return {"response": result.output}
```

| Tool | Behaviour |
|---|---|
| `AskUserTool(ctx)` | Agent calls `ask_user` with a question. Workflow pauses until the user types a response |
| `RequestApprovalTool(ctx)` | Agent calls `request_approval` with an action description. Workflow pauses and shows Approve / Reject to the user |





**TypeScript:**

```typescript
import { Agent, LM, workflow, AskUserTool, RequestApprovalTool } from '@agnt5/sdk';
import type { Context, ContextImpl } from '@agnt5/sdk';

export const agentWithHitl = workflow(
  'agent_with_hitl',
  async (ctx: Context, input: { task: string }) => {
    const agent = new Agent({
      name: 'assistant',
      model: LM.openai(),
      modelName: 'openai/gpt-6-luna',
      instructions:
        'Ask the user for clarification when the request is ambiguous. Always request approval before making any changes.',
      // AskUserTool/RequestApprovalTool require the concrete ContextImpl the runtime passes in.
      tools: [new AskUserTool(ctx as ContextImpl), new RequestApprovalTool(ctx as ContextImpl)],
    });
    const result = await agent.run(input.task, ctx);
    return { response: result.output };
  },
);
```

| Tool | Behaviour |
|---|---|
| `new AskUserTool(ctx)` | Agent calls `ask_user` with a question. Workflow pauses until the user types a response |
| `new RequestApprovalTool(ctx)` | Agent calls `request_approval` with an action description. Workflow pauses and shows Approve / Reject to the user |





**Go:**

In Go, ask for human input from your workflow handler with `ctx.AskUser` or `ctx.RequestApproval`, before or after calling `agent.Run`. Both durably suspend the workflow while they wait. See [Human-in-the-loop](/docs/build/human-in-the-loop.md) for the full reference.



## Next steps

- [Agents](/docs/build/agents.md): configure the agent loop that calls your tools.
- [Sandboxes](/docs/build/sandboxes.md): give agents an isolated workspace for files and code.
- [Model Context Protocol](/docs/build/mcp.md): import external MCP tools or expose your own.
- [Human-in-the-loop](/docs/build/human-in-the-loop.md): pause a workflow for approvals and input.
