Skip to content

Tools

Tools extend the agent’s capabilities with actions like running commands, searching the web, and reading files.

Overview

Tools are the primary way Ash interacts with the world. Each tool:

  • Has a unique name and description
  • Defines input parameters via JSON Schema
  • Executes asynchronously
  • Returns structured results

Tool Interface

Location: src/ash/tools/base.py

from abc import ABC, abstractmethod
from typing import Any
class Tool(ABC):
@property
@abstractmethod
def name(self) -> str:
"""Unique identifier for this tool."""
@property
@abstractmethod
def description(self) -> str:
"""Description for the LLM."""
@property
@abstractmethod
def input_schema(self) -> dict[str, Any]:
"""JSON Schema for input parameters."""
@abstractmethod
async def execute(
self,
input_data: dict[str, Any],
context: ToolContext,
) -> ToolResult:
"""Execute the tool and return result."""

ToolContext

Context provided to tool execution:

@dataclass
class ToolContext:
session_id: str | None = None
user_id: str | None = None
chat_id: str | None = None
provider: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
env: dict[str, str] = field(default_factory=dict)

ToolResult

Tool execution result:

@dataclass
class ToolResult:
content: str
is_error: bool = False
metadata: dict[str, Any] = field(default_factory=dict)
@classmethod
def success(cls, content: str, **metadata) -> "ToolResult":
"""Create a successful result."""
@classmethod
def error(cls, message: str, **metadata) -> "ToolResult":
"""Create an error result."""

Built-in Tools

Bash Tool

Location: src/ash/tools/builtin/bash.py

Executes commands in the Docker sandbox:

class BashTool(Tool):
name = "bash"
description = "Execute bash commands in a sandboxed environment"
input_schema = {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The bash command to execute.",
},
},
"required": ["command"],
}

All commands run in isolated Docker containers. See Sandbox for security details.

Web Search Tool

Location: src/ash/tools/builtin/web_search.py

Searches the web using Brave Search:

class WebSearchTool(Tool):
name = "web_search"
description = "Search the web for information"
input_schema = {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query.",
},
"max_results": {
"type": "integer",
"description": "Maximum results to return (default: 10).",
},
},
"required": ["query"],
}

Requires Brave Search API configuration:

[brave_search]
api_key = "BSA..."

Web Fetch Tool

Location: src/ash/tools/builtin/web_fetch.py

Fetches and extracts content from URLs:

class WebFetchTool(Tool):
name = "web_fetch"
description = "Fetch and read the content of a web page"
input_schema = {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to fetch (http or https).",
},
"extract_mode": {
"type": "string",
"enum": ["text", "markdown"],
"description": "Content format: 'markdown' preserves structure.",
},
},
"required": ["url"],
}

Features:

  • Extracts readable content from HTML pages
  • Converts to markdown-like format (links, headings, lists)
  • Handles redirects (up to 5 hops)
  • Supports text/HTML/JSON content types

Read File Tool

Location: src/ash/tools/builtin/files.py

Reads file contents from the workspace:

class ReadFileTool(Tool):
name = "read_file"
description = "Read file contents from the workspace"
input_schema = {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Path to the file (relative to workspace).",
},
"offset": {
"type": "integer",
"description": "Line number to start from (1-indexed).",
},
"limit": {
"type": "integer",
"description": "Maximum lines to read.",
},
},
"required": ["file_path"],
}

Features:

  • Returns content with line numbers
  • Pagination via offset/limit
  • Automatic truncation for large files

Write File Tool

Location: src/ash/tools/builtin/files.py

Writes content to files in the workspace:

class WriteFileTool(Tool):
name = "write_file"
description = "Write content to a file in the workspace"
input_schema = {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Path to write to (relative to workspace).",
},
"content": {
"type": "string",
"description": "Content to write to the file.",
},
},
"required": ["file_path", "content"],
}

Features:

  • Creates files if they don’t exist
  • Creates parent directories automatically
  • Size limits enforced

Use Agent Tool

Location: src/ash/tools/builtin/agents.py

Invokes built-in agents for complex tasks:

class UseAgentTool(Tool):
name = "use_agent"
description = "Run a specialized agent for complex tasks"
input_schema = {
"type": "object",
"properties": {
"agent": {
"type": "string",
"description": "Name of the agent to run.",
},
"message": {
"type": "string",
"description": "Message/task for the agent.",
},
"input": {
"type": "object",
"description": "Additional input data (optional).",
},
},
"required": ["agent", "message"],
}

Built-in agents include:

  • research - Multi-step research tasks
  • skill-writer - Creates new skills autonomously

See Agents for details.

Tool Registry

Location: src/ash/tools/registry.py

Tools are discovered and registered:

registry = ToolRegistry()
registry.register(BashTool(executor))
registry.register(WebSearchTool(cache=cache))
tools = registry.all()
tool = registry.get("bash")

Tool Execution

Location: src/ash/tools/executor.py

The executor handles tool calls from the LLM:

class ToolExecutor:
async def execute(
self,
tool_call: ToolCall,
context: ToolContext,
) -> ToolResult:
tool = self.registry.get(tool_call.name)
return await tool.execute(tool_call.input, context)

Configuration

Enable web search capabilities:

[brave_search]
api_key = "BSA..."

Get an API key from Brave Search API.

Workspace

Configure the workspace directory for file operations:

workspace = "~/.ash/workspace"

See Configuration Reference for all options.

Creating Custom Tools

  1. Create a class implementing Tool
  2. Define input_schema as a JSON Schema dict
  3. Implement async execute() method
  4. Register with the tool registry

Example:

from typing import Any
from ash.tools.base import Tool, ToolContext, ToolResult
class WeatherTool(Tool):
@property
def name(self) -> str:
return "weather"
@property
def description(self) -> str:
return "Get current weather for a location"
@property
def input_schema(self) -> dict[str, Any]:
return {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or coordinates.",
},
},
"required": ["location"],
}
async def execute(
self,
input_data: dict[str, Any],
context: ToolContext,
) -> ToolResult:
location = input_data.get("location")
# Implementation here
return ToolResult.success(f"Weather for {location}: 72°F, sunny")

Register the tool:

from ash.tools.registry import ToolRegistry
registry = ToolRegistry()
registry.register(WeatherTool())