Skip to main content
Tools are Python functions or class methods that agents call to interact with external systems, access files, execute code, and perform actions during execution. Why use tools:
  • Extend Capabilities - Give agents access to filesystems, APIs, databases, and custom functionality beyond LLM reasoning
  • Stateless Functions - Use the @tool decorator for simple, standalone operations without shared state
  • Stateful Toolsets - Group related tools with shared configuration, connections, or state using Toolset classes
When to use tools: Use @tool for simple standalone functions. Use Toolset when tools need shared configuration (credentials, paths) or state (connections, caches). Use built-in toolsets like Filesystem for common operations. This guide covers the @tool decorator, toolsets, built-in tools, tool configuration, variants for access control, and best practices.

Overview

Tools in the Dreadnode SDK provide agent-specific features like variants for access control, state management, and built-in toolsets for common operations.
import dreadnode as dn

@dn.tool
def get_stock_price(symbol: str) -> float:
    """Gets the current price of a stock symbol."""
    # ... logic to call a financial API ...
    return 150.75
Use the @tool decorator for simple, self-contained functions. Use a Toolset when you need to group related tools (like db.query, db.insert) or manage shared state (like a database connection).

Creating Tools

Basic Tool Definition

Define tools using the @dn.tool decorator with type hints and a docstring:
from typing import Annotated
import dreadnode as dn

@dn.tool
def search_documentation(
    query: Annotated[str, "The search query"],
    max_results: Annotated[int, "Maximum number of results to return"] = 10
) -> list[dict]:
    """
    Search the documentation for relevant articles.

    Returns a list of matching documents with titles and URLs.
    """
    # Implementation here
    return [{"title": "...", "url": "..."}]
Key elements:
  • Type hints: Required for generating the tool schema
  • Annotated: Provides parameter descriptions for the agent
  • Docstring: Used as the tool’s description
  • Default values: Make parameters optional

Decorator Options

The @dn.tool decorator accepts parameters to customize behavior:
@dn.tool(
    name="custom_name",           # Override function name
    description="Custom desc",    # Override docstring
    catch=True,                   # Catch all exceptions
    truncate=1000                 # Limit output to 1000 chars
)
def my_tool(input: str) -> str:
    pass
The catch parameter controls exception handling:
  • False: Don’t catch exceptions (they propagate)
  • True: Catch all exceptions and return as error messages
  • set[type[Exception]]: Catch only specific exception types
  • None (default): Catches json.JSONDecodeError and ValidationError

Error Handling

Use the catch parameter to control how exceptions are handled. For graceful task termination, use stop conditions or hooks rather than raising from tools.

Async Tools

Tools can be either synchronous or asynchronous:
@dn.tool
async def fetch_async(url: str) -> dict:
    """Fetches data asynchronously."""
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.json()
Use async def for tools that perform I/O operations to enable efficient concurrent execution.

Toolsets

Toolsets are classes that group related tools together and manage shared state.

Basic Toolset

import dreadnode as dn
from dreadnode.agent.tools import Toolset

class Calculator(Toolset):
    """A calculator with memory."""

    precision: int = 2  # Configuration field
    _last_result: float = 0.0  # Private state

    @dn.tool_method
    def add(self, a: float, b: float) -> float:
        """Add two numbers."""
        result = round(a + b, self.precision)
        self._last_result = result
        return result

    @dn.tool_method
    def get_last_result(self) -> float:
        """Get the last calculated result."""
        return self._last_result

# Usage
calc = Calculator(precision=3)
tools = calc.get_tools()  # Returns list of all tool methods

Variants for Access Control

Toolsets support variants to conditionally expose tools. This is useful for permission models:
from dreadnode.agent.tools import Toolset
import dreadnode as dn

class FileManager(Toolset):
    """Manages file operations with different permission levels."""

    variant: str = "read"  # Can be "read" or "write"

    @dn.tool_method(variants=["read", "write"])
    def read_file(self, path: str) -> str:
        """Read a file's contents."""
        with open(path) as f:
            return f.read()

    @dn.tool_method(variants=["write"])
    def write_file(self, path: str, content: str) -> str:
        """Write content to a file."""
        with open(path, 'w') as f:
            f.write(content)
        return f"Wrote {len(content)} bytes to {path}"

# Read-only access
read_only = FileManager(variant="read")
read_only.get_tools()  # Returns only read_file

# Full access
full_access = FileManager(variant="write")
full_access.get_tools()  # Returns both methods

Context Managers

Toolsets can act as context managers for automatic resource setup and cleanup:
from dreadnode.agent.tools import Toolset
import dreadnode as dn

class SSHConnection(Toolset):
    """Tools for executing commands over SSH."""

    host: str
    username: str
    _connection: Any = None

    async def __aenter__(self):
        self._connection = await connect_ssh(self.host, self.username)
        return self

    async def __aexit__(self, *args):
        if self._connection:
            await self._connection.close()

    @dn.tool_method
    async def run_command(self, command: str) -> str:
        """Execute a command on the remote host."""
        return await self._connection.execute(command)
Toolsets with __aenter__/__aexit__ are automatically made re-entrant by the SDK. Multiple context manager entries only call the actual setup/teardown once.

Built-in Tools

The SDK provides toolsets for common operations. Use them directly or as patterns for your own tools.

Filesystem

File operations with variant-based permissions:
from dreadnode.agent.tools.fs import Filesystem

# Read-only filesystem access
fs = Filesystem(path="/project", variant="read")

# Read-write access
fs = Filesystem(path="/project", variant="write")

# With multi-modal support for images
fs = Filesystem(path="/project", variant="read", multi_modal=True)
Read variant provides: read_file, read_lines, ls, glob, grep Write variant adds: write_file, write_lines, mkdir, mv, cp, delete

Memory

In-memory key-value store for maintaining state across tool calls:
from dreadnode.agent.tools.memory import Memory

memory = Memory()
agent = dn.Agent(
    name="assistant",
    model="gpt-4o-mini",
    tools=[memory]
)
Provides: save_memory, retrieve_memory, list_memory_keys, clear_memory

Execute

Shell command and Python code execution:
from dreadnode.agent.tools.execute import command, python

# Execute shell commands
result = await command(cmd=["ls", "-la"], timeout=120)

# Execute Python code
result = await python(code="print('Hello')", timeout=120)

Planning

Task management and reasoning tools:
from dreadnode.agent.tools.planning import think, update_todo
  • think(): Records agent reasoning process
  • update_todo(): Tracks tasks with status (pending, in_progress, completed)

Tasking

Task completion tools (used by TaskAgent):
from dreadnode.agent.tools.tasking import finish_task, give_up_on_task
  • finish_task(): Concludes task with success/failure status and summary
  • give_up_on_task(): Aborts when irrecoverably stuck

Reporting

Surface findings for human review:
from dreadnode.agent.tools.reporting import highlight_for_review

await highlight_for_review(
    title="SQL Injection in Login",
    interest_level="high",  # high, medium, low
    justification="Found unsanitized input in query..."
)
See the SDK reference for complete function signatures and parameters.

Using Tools with Agents

Passing Tools

Tools can be passed to agents in several ways:
# Standalone tools
@dn.tool
def my_tool(input: str) -> str:
    return f"Processed: {input}"

# Toolset instance
fs = Filesystem(path="/project", variant="read")

# Create agent with tools
agent = dn.Agent(
    name="assistant",
    model="gpt-4o-mini",
    tools=[
        my_tool,           # Standalone tool
        fs,                # Toolset (auto-discovers all tool methods)
    ]
)

# Or pass specific toolset methods
agent = dn.Agent(
    name="assistant",
    model="gpt-4o-mini",
    tools=[
        fs.read_file,      # Specific method
        fs.ls,             # Another method
    ]
)

Tool Invocation Modes

The SDK supports multiple modes for how tool calls are communicated between the agent and model:
  • auto (Default): Uses native function calling if available, otherwise falls back to XML
  • api: Uses the provider’s native function calling API
  • xml: Injects tool schemas into the prompt; model outputs XML tool calls
# Force XML mode (works with any model)
agent = dn.Agent(
    name="assistant",
    model="gpt-4o-mini",
    tools=[my_tool],
    tool_mode="xml"
)
Use auto for most cases—it’s smart about provider capabilities.

MCP Integration

The Model Context Protocol (MCP) is an open standard for language models to interact with external tools. The SDK supports both consuming and serving MCP tools.

Using MCP Tools

Connect to MCP servers and use their tools in your agents:
import dreadnode as dn
import rigging as rg

# Via stdio (local process)
async with rg.mcp("stdio", command="my-mcp-server", args=["--port", "stdio"]) as mcp:
    agent = dn.Agent(
        name="mcp-user",
        model="gpt-4o-mini",
        tools=[*mcp.tools]
    )
    result = await agent.run("Use the MCP tools...")

# Via SSE (HTTP endpoint)
async with rg.mcp("sse", url="http://localhost:8001/mcp") as mcp:
    agent = dn.Agent(
        name="mcp-user",
        model="gpt-4o-mini",
        tools=[*mcp.tools]
    )

Serving Tools via MCP

Expose your tools as an MCP server:
import dreadnode as dn
import rigging as rg

@dn.tool
def write_file(filename: str, content: str) -> str:
    """Writes content to a local file."""
    with open(filename, "w") as f:
        f.write(content)
    return f"Wrote {len(content)} bytes to {filename}"

if __name__ == "__main__":
    rg.as_mcp([write_file], name="File Tools").run()
# Add to Claude Code
claude mcp add file_writer -- uv run --with rigging file_writer.py

Return Types

Tools can return various types:
# Basic types (str, dict, list) - returned as-is or JSON-serialized
@dn.tool
def simple() -> str:
    return "Hello"

# Pydantic models - converted to XML in agent context
@dn.tool
def structured() -> AnalysisResult:
    return AnalysisResult(score=0.85, findings=["..."])

# Multi-modal content
import rigging as rg

@dn.tool
def with_image(path: str) -> rg.ContentImageUrl:
    return rg.ContentImageUrl.from_file(path)

# Multiple content parts
@dn.tool
def mixed() -> list[rg.ContentTypes]:
    return [
        rg.ContentText(text="Analysis:"),
        rg.ContentImageUrl.from_file("/path/to/chart.png"),
    ]

Debugging Tools

Viewing Schemas

Inspect what the agent sees for a tool:
@dn.tool
def my_tool(query: str, limit: int = 10) -> dict:
    """Search for items."""
    return {}

print(my_tool.name)              # my_tool
print(my_tool.description)       # Search for items.
print(my_tool.parameters_schema) # JSON schema

Inspecting Tool History

After agent execution, inspect what tools were called:
result = await agent.run("Analyze the codebase")

for message in result.messages:
    if hasattr(message, 'tool_calls') and message.tool_calls:
        for call in message.tool_calls:
            print(f"Tool: {call.function.name}")
            print(f"Args: {call.function.arguments}")

Testing Tools Directly

Tools are just Python functions—test them outside agents:
@dn.tool
async def fetch_data(url: str) -> dict:
    return {"status": "ok"}

# Test directly
result = await fetch_data("http://example.com")
assert result["status"] == "ok"
See the SDK reference for complete API documentation.