Skip to main content
Tools are functions that agents invoke to take actions and gather information. In each iteration of an agent’s loop, the model returns the next steps as tool calls. The agent invokes these tools, gathers their responses, and continues its loop.

Python Functions as Tools

Wrap Python functions to create tools. Any function becomes a tool that your agent invokes.
images/main/main.py
from autonomy import Agent, Model, Node, Tool
from datetime import datetime, UTC


def get_weather(city: str) -> str:
  """
  Get the current weather for a city.
  
  Args:
    city: The name of the city
  """
  # In a real app, this would call a weather API
  return f"The weather in {city} is sunny and 72°F"


def current_iso8601_utc_time():
  """
  Returns the current UTC time in ISO 8601 format.
  """
  return datetime.now(UTC).isoformat() + "Z"


async def main(node):
  await Agent.start(
    node=node,
    name="henry",
    instructions="You are Henry, a helpful assistant",
    model=Model("claude-sonnet-4-v1"),
    tools=[
      Tool(get_weather),
      Tool(current_iso8601_utc_time)
    ]
  )


Node.start(main)
images/main/Dockerfile
FROM ghcr.io/build-trust/autonomy-python
COPY . .
ENTRYPOINT ["python", "main.py"]
autonomy.yaml
name: tools-example
pods:
  - name: main-pod
    public: true
    containers:
      - name: main
        image: main
http POST \
"https://CLUSTER-ZONE.cluster.autonomy.computer/agents/henry" \
message="What's the weather in San Francisco and what time is it?"
The Tool() wrapper converts your function into a tool specification. Docstrings become tool descriptions that help the model understand when to use the tool. Type hints define parameter types (converted to JSON schema). Both sync and async functions are supported.

MCP Tools

Model Context Protocol (MCP) provides agents with tools from external servers. MCP servers expose standardized interfaces to services like web search, databases, and APIs.

Configure MCP Server

Define MCP servers in your autonomy.yaml:
autonomy.yaml
name: mcp-example
pods:
  - name: main-pod
    public: true
    containers:
      - name: main
        image: main

      - name: mcp
        image: ghcr.io/build-trust/mcp-proxy
        env:
          - BRAVE_API_KEY: secrets.BRAVE_API_KEY
        args:
          ["--sse-port", "8001", "--pass-environment", "--",
           "npx", "-y", "@modelcontextprotocol/server-brave-search"]
Store your API key in secrets.yaml:
secrets.yaml
BRAVE_API_KEY: "YOUR_BRAVE_API_KEY_HERE"

Use MCP Tools

Reference MCP server tools using McpTool():
images/main/main.py
from autonomy import Agent, Model, Node, McpTool, McpClient, Tool
from datetime import datetime, UTC


def current_iso8601_utc_time():
  """
  Returns the current UTC time in ISO 8601 format.
  """
  return datetime.now(UTC).isoformat() + "Z"


async def main(node):
  await Agent.start(
    node=node,
    name="henry",
    instructions="""
      You are Henry, an expert legal assistant.
      
      When answering questions, decide if a web search would improve your response.
      If so, use the brave_web_search tool to find current information.
    """,
    model=Model("claude-sonnet-4-v1"),
    tools=[
      McpTool("brave_search", "brave_web_search"),
      Tool(current_iso8601_utc_time)
    ]
  )


Node.start(
  main,
  mcp_clients=[
    McpClient(name="brave_search", address="http://localhost:8001/sse")
  ]
)
images/main/Dockerfile
FROM ghcr.io/build-trust/autonomy-python
COPY . .
ENTRYPOINT ["python", "main.py"]
http POST \
"https://CLUSTER-ZONE.cluster.autonomy.computer/agents/henry" \
message="Find recent Supreme Court decisions on contract law"
The agent will use the brave_web_search tool to find current information and incorporate it into its response.

Built-in Tools

Agents have access to several built-in tools:

Time Tools

Always available without configuration:
  • get_current_time_utc - Get current time in UTC timezone
  • get_current_time - Get current time in a specific timezone (e.g., “America/New_York”)
await Agent.start(
  node=node,
  name="assistant",
  instructions="You are a helpful assistant",
  model=Model("claude-sonnet-4-v1")
)
# Agent can automatically use get_current_time_utc and get_current_time

Human-in-the-Loop

Agents pause and request user input during interactive conversations. See Human-in-the-loop for complete documentation on the ask_user_for_input tool.

Additional Built-in Tools

  • Filesystem tools - Read, write, and search files. See Filesystem Tools.
  • Subagent tools - Delegate work to specialized sub-agents. See Subagents.

Tool Factories (Advanced)

Tool Factories create scope-aware tools that are instantiated per-user, per-conversation, or per-agent. This is essential for multi-tenant applications where tools need isolated resources.

When to Use Tool Factories

Use tool factories when:
  • Tools need per-user or per-conversation state
  • Tools access user-specific resources (databases, files, APIs)
  • Tools require tenant isolation for security
  • Static tool instances can’t be shared safely

Implement ToolFactory Protocol

Create a class that implements the create_tools() method:
from autonomy import Agent, Model, Node, Tool, ToolFactory
from typing import Optional, List


class DatabaseToolFactory(ToolFactory):
  """
  Creates tenant-isolated database tools.
  
  Each tenant gets their own database connection with isolated data access.
  """
  
  def __init__(self, connection_string: str):
    self.connection_string = connection_string
  
  def create_tools(
    self,
    scope: Optional[str],
    conversation: Optional[str],
    agent_name: Optional[str] = None
  ) -> List:
    """
    Create database tools for a specific tenant.
    
    Args:
      scope: Tenant/user identifier (e.g., "tenant-123", "user-alice")
      conversation: Conversation identifier
      agent_name: Agent name
    
    Returns:
      List of Tool instances with tenant-specific database connections
    """
    # Create tenant-specific database connection
    tenant_id = scope or "default"
    db = Database(self.connection_string, tenant=tenant_id)
    
    def query_records(table: str, filter: str = ""):
      """
      Query records from a database table.
      
      Args:
        table: Table name to query
        filter: Optional SQL WHERE clause filter
      """
      return db.query(f"SELECT * FROM {table} WHERE {filter}" if filter else f"SELECT * FROM {table}")
    
    def insert_record(table: str, data: dict):
      """
      Insert a record into a database table.
      
      Args:
        table: Table name
        data: Record data as key-value pairs
      """
      return db.insert(table, data)
    
    return [
      Tool(query_records),
      Tool(insert_record)
    ]


async def main(node):
  # Create factory instance
  db_factory = DatabaseToolFactory("postgresql://localhost/mydb")
  
  await Agent.start(
    node=node,
    name="assistant",
    instructions="You are a helpful assistant with database access",
    model=Model("claude-sonnet-4-v1"),
    tools=[db_factory]  # Pass factory, not individual tools
  )


Node.start(main)
The agent framework detects the factory implements ToolFactory. For each conversation, the framework calls create_tools(scope, conversation, agent_name). You create tools with proper isolation based on the provided context. Each user/conversation gets their own tool instances with isolated resources.

Factory vs Static Tools

Static tools (using Tool(func)):
  • Same instance shared across all users and conversations
  • Suitable for stateless operations (time, calculations, read-only APIs)
  • Simple and efficient
Factory tools (using ToolFactory):
  • New instances created per-user/per-conversation
  • Suitable for stateful operations (databases, filesystems, user-specific APIs)
  • Provides tenant isolation and security

How Tools Work

Tool Specification

Tools are described to models using JSON schema format:
{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get the current weather for a city",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {
          "type": "string",
          "description": "The name of the city"
        }
      },
      "required": ["city"]
    }
  }
}
The Tool() wrapper automatically generates this specification from:
  • Function name → tool name
  • Docstring → description
  • Type hints → parameter types
  • Required vs optional parameters

Type Coercion

The framework coerces JSON values to match Python type hints:
def calculate(amount: int, rate: float, active: bool):
  """Calculate something with typed parameters"""
  return amount * rate if active else 0

# JSON: {"amount": "100", "rate": "1.5", "active": "true"}
# Coerced to: {"amount": 100, "rate": 1.5, "active": True}
Supported coercions:
  • strint, float, bool
  • String booleans: “true”/“false”, “yes”/“no”, “1”/“0”
  • Numbers to strings
  • Empty/null handling

Error Handling

Tool errors are captured and returned as strings to the model:
def divide(a: int, b: int) -> float:
  """Divide two numbers"""
  return a / b

# If b=0: "Tool execution failed: ZeroDivisionError: division by zero"
The agent sees the error and decides how to respond—retry with different parameters, ask for clarification, or explain the problem to the user.

Size Limits

  • JSON arguments: 1MB maximum
  • Tool responses: No hard limit, but keep responses concise for model context efficiency