What Is a Tool?

In the context of AI agents, a tool is a callable function with a typed schema. The agent's LLM decides when and how to invoke a tool; the framework executes the actual code and returns the result back to the model as an Observation.

Examples of built-in OpenClaw tools: web_search, fetch_url, file_reader, file_writer, python_executor, sql_query.

Tool Schema: Name, Description, Parameters

Every tool is described to the LLM via a JSON Schema. This schema tells the model what the tool does and what arguments it expects:

{
  "name": "send_email",
  "description": "Send an email to a recipient. Use only when explicitly asked to send email.",
  "parameters": {
    "type": "object",
    "properties": {
      "to": {
        "type": "string",
        "description": "Recipient email address"
      },
      "subject": { "type": "string" },
      "body":    { "type": "string" }
    },
    "required": ["to", "subject", "body"]
  }
}
Write clear descriptions. The description is the model's only instruction for when to use the tool. Vague descriptions cause the model to over-use or under-use a tool.

Function Calling Flow

  1. The LLM receives the user goal + tool schemas in its context.
  2. It decides a tool is needed and emits a structured tool call (JSON with function name + args).
  3. OpenClaw validates the args against the schema, then calls the Python function.
  4. The result is formatted as an Observation and appended to the context.
  5. The LLM continues reasoning with the new information.

Defining a Custom Tool

from openclaw import tool, Agent

@tool(description="Get the current price of a cryptocurrency in USD")
def get_crypto_price(symbol: str) -> float:
    """symbol: uppercase ticker, e.g. BTC, ETH"""
    import requests
    resp = requests.get(
        f"https://api.coingecko.com/api/v3/simple/price",
        params={"ids": symbol.lower(), "vs_currencies": "usd"},
        timeout=10,
    )
    resp.raise_for_status()
    data = resp.json()
    # Return price or raise if symbol unknown
    if symbol.lower() not in data:
        raise ValueError(f"Unknown ticker: {symbol}")
    return data[symbol.lower()]["usd"]

agent = Agent(
    model="gpt-4o-mini",
    tools=[get_crypto_price],
)
print(agent.run("What is the current price of Bitcoin in USD?"))

Tool Chaining

The output of one tool automatically becomes the input context for the next decision. The model chains tools naturally:

Thought: I need to search for data, then save it.
Action: web_search("Python 3.13 new features")
Observation: Python 3.13 adds JIT compiler, improved REPL, ...

Thought: Got the data. Now save it to a file.
Action: file_writer("py313-features.txt", "JIT compiler, improved REPL, ...")
Observation: Written 1,240 bytes to py313-features.txt.

Tool Safety: Sandboxing and Permissions

tools:
  file_writer:
    allowed_dirs: ["./output/"]   # restrict write paths
    max_file_size_kb: 512
  python_executor:
    sandbox: true                 # run in isolated subprocess
    timeout_seconds: 30
    network_access: false         # no outbound calls from code
  web_search:
    max_results: 10
    safe_search: true
Key takeaways:
  • Tools extend an agent's capabilities beyond pure text generation.
  • Each tool has a name, description, and JSON Schema parameter definition.
  • The LLM decides when to call a tool; OpenClaw handles execution and returns results.
  • Custom tools are Python functions decorated with @tool.
  • Always restrict tool permissions to the minimum necessary scope.

Tool Security and Sandboxing

Tools that execute code, read files, or make network requests require careful scoping to prevent unintended access. Follow the principle of least privilege when registering tools:

from openclaw import tool
from pathlib import Path

ALLOWED_DIR = Path("/data/reports")

@tool(description="Read a report file. Only files in /data/reports are accessible.")
def read_report(filename: str) -> str:
    # Prevent path traversal
    safe_path = (ALLOWED_DIR / filename).resolve()
    if not str(safe_path).startswith(str(ALLOWED_DIR)):
        raise PermissionError(f"Access denied: {filename}")
    return safe_path.read_text()

Asynchronous and Long-Running Tools

Some tools (API calls, database queries, file processing) can take seconds to complete. OpenClaw supports async tools to prevent blocking the event loop:

import asyncio, httpx
from openclaw import tool

@tool(description="Fetch data from external API asynchronously.")
async def fetch_api_data(url: str, params: dict = {}) -> dict:
    async with httpx.AsyncClient(timeout=30) as client:
        response = await client.get(url, params=params)
        response.raise_for_status()
        return response.json()

For tools that take more than 60 seconds, use OpenClaw’s long-running tool pattern with status polling — see the Tool Creation guide.