Tools
Tools give agents the ability to interact with the outside world — reading files, making HTTP requests, connecting to MCP servers, calling APIs, or running custom Python functions. They are configured in the spec.tools list, keyed on the type field.
Tool Types
| Type | Description |
|---|---|
filesystem | Read/write files within a sandboxed root directory |
http | Make HTTP requests to a base URL |
mcp | Connect to MCP servers (stdio, SSE, streamable-http) |
custom | Load Python functions from a module |
delegate | Invoke other agents as tool calls |
api | Declarative REST API endpoints defined in YAML |
web_reader | Fetch web pages and convert to markdown |
python | Execute Python code in a subprocess |
datetime | Get current time and parse dates |
sql | Query SQLite databases (read-only) |
git | Run git operations in a subprocess |
shell | Execute shell commands with allowlists |
web_scraper | Scrape web pages and extract structured data |
slack | Send messages via Slack webhooks |
| (plugin) | Any other type resolved via the plugin registry |
Quick Example
spec:
tools:
- type: filesystem
root_path: ./src
read_only: true
allowed_extensions: [".py", ".md"]
- type: http
base_url: https://api.example.com
allowed_methods: ["GET", "POST"]
headers:
Authorization: Bearer ${API_TOKEN}
- type: mcp
transport: stdio
command: npx
args: ["-y", "@anthropic/mcp-server-filesystem"]
- type: custom
module: my_tools
config:
db_url: "postgres://..."
- type: api
name: weather
base_url: https://api.weather.com
endpoints:
- name: get_weather
path: "/current/{city}"
parameters:
- name: city
type: string
required: trueFilesystem
Sandboxed file operations within a root directory. Paths cannot escape the root (path traversal is blocked).
tools:
- type: filesystem
root_path: ./src
read_only: true
allowed_extensions: [".py", ".md", ".txt"]| Field | Type | Default | Description |
|---|---|---|---|
root_path | str | "." | Root directory for file operations |
allowed_extensions | list[str] | [] | File extensions to allow (empty = all) |
read_only | bool | true | Only allow read operations |
Registered functions: read_file(path), list_directory(path), and write_file(path, content) (when read_only: false).
HTTP
Makes HTTP requests to a configured base URL.
tools:
- type: http
base_url: https://api.example.com
allowed_methods: ["GET"]
headers:
Authorization: Bearer ${API_TOKEN}| Field | Type | Default | Description |
|---|---|---|---|
base_url | str | (required) | Base URL for requests |
allowed_methods | list[str] | ["GET"] | Allowed HTTP methods |
headers | dict | {} | Headers sent with every request |
Registered function: http_request(method, path, body).
MCP
Connects to MCP (Model Context Protocol) servers, exposing their tools to the agent.
tools:
# Stdio transport (local process)
- type: mcp
transport: stdio
command: npx
args: ["-y", "@anthropic/mcp-server-filesystem"]
# SSE transport (remote server)
- type: mcp
transport: sse
url: http://localhost:3001/sse
# Streamable HTTP transport
- type: mcp
transport: streamable-http
url: http://localhost:3001/mcp
tool_filter: [search, get_document]| Field | Type | Default | Description |
|---|---|---|---|
transport | str | "stdio" | "stdio", "sse", or "streamable-http" |
command | str | null | null | Command for stdio transport |
args | list[str] | [] | Arguments for the stdio command |
url | str | null | null | URL for SSE or streamable-http transport |
tool_filter | list[str] | [] | Only expose these tools (empty = all) |
Custom
Load Python functions from a module and register them as agent tools.
tools:
# Auto-discover all public functions
- type: custom
module: my_tools
# Load a single function
- type: custom
module: my_tools
function: search_db
# With config injection
- type: custom
module: my_tools
config:
api_key: ${MY_API_KEY}| Field | Type | Default | Description |
|---|---|---|---|
module | str | (required) | Python module path (must be importable) |
function | str | null | null | Specific function to load (null = auto-discover all) |
config | dict | {} | Config injected into functions with a tool_config parameter |
Functions that declare a tool_config parameter receive the config dict automatically — the parameter is hidden from the LLM.
Scaffold a tool module:
initrunner init --template tool --name my_toolsComplete Custom Tool Walkthrough
Here's a full example with the Python module and the role YAML that uses it.
my_tools.py — every public function becomes an agent tool:
"""Custom tools module for InitRunner.
All public functions are auto-discovered as agent tools. Type annotations and
docstrings are used as tool schemas and descriptions. Functions accepting a
``tool_config`` parameter receive the config dict from role.yaml (hidden from
the LLM).
"""
import hashlib
import json
import uuid
def convert_units(value: float, from_unit: str, to_unit: str) -> str:
"""Convert a numeric value between common measurement units.
Supported conversions: km/mi, kg/lb, c/f, l/gal, m/ft, cm/in.
"""
conversions: dict[tuple[str, str], float | None] = {
("km", "mi"): 0.621371,
("mi", "km"): 1.60934,
("kg", "lb"): 2.20462,
("lb", "kg"): 0.453592,
("c", "f"): None,
("f", "c"): None,
("l", "gal"): 0.264172,
("gal", "l"): 3.78541,
("m", "ft"): 3.28084,
("ft", "m"): 0.3048,
("cm", "in"): 0.393701,
("in", "cm"): 2.54,
}
key = (from_unit.lower(), to_unit.lower())
if key == ("c", "f"):
result = value * 9 / 5 + 32
elif key == ("f", "c"):
result = (value - 32) * 5 / 9
elif key in conversions:
result = value * conversions[key]
else:
return f"Unsupported conversion: {from_unit} -> {to_unit}"
return f"{value} {from_unit} = {result:.4f} {to_unit}"
def generate_uuid() -> str:
"""Generate a random UUID v4 identifier."""
return str(uuid.uuid4())
def format_json(text: str) -> str:
"""Pretty-print a JSON string with 2-space indentation."""
try:
parsed = json.loads(text)
return json.dumps(parsed, indent=2, ensure_ascii=False)
except json.JSONDecodeError as e:
return f"Invalid JSON: {e}"
def word_count(text: str) -> str:
"""Count words, characters, and lines in a text string."""
words = len(text.split())
chars = len(text)
lines = text.count("\n") + 1 if text else 0
return f"Words: {words}, Characters: {chars}, Lines: {lines}"
def hash_text(text: str, algorithm: str = "sha256") -> str:
"""Hash text using the specified algorithm (md5, sha1, sha256, sha512)."""
algo = algorithm.lower()
if algo not in ("md5", "sha1", "sha256", "sha512"):
return f"Unsupported algorithm: {algorithm}. Use md5, sha1, sha256, or sha512."
h = hashlib.new(algo)
h.update(text.encode())
return f"{algo}:{h.hexdigest()}"
def lookup_with_config(query: str, tool_config: dict) -> str:
"""Look up a query using the configured prefix and source.
The tool_config parameter is injected by InitRunner from the role YAML
and is hidden from the LLM.
"""
prefix = tool_config.get("prefix", "DEFAULT")
source = tool_config.get("source", "unknown")
return f"[{prefix}] Result for '{query}' from source '{source}'"custom-tools-demo.yaml — the role that loads it:
apiVersion: initrunner/v1
kind: Agent
metadata:
name: custom-tools-demo
description: Demonstrates custom tool type with auto-discovered Python functions
spec:
role: |
You are a utility assistant with access to custom tools defined in a Python
module. Use these tools to help the user with practical tasks.
Available custom tools:
- convert_units: Convert between common measurement units
- generate_uuid: Generate a random UUID v4 identifier
- format_json: Pretty-print a JSON string
- word_count: Count words, characters, and lines in text
- hash_text: Hash text with md5, sha1, sha256, or sha512
- lookup_with_config: Look up a query using the configured prefix and source
Always use the appropriate tool rather than trying to compute results yourself.
model:
provider: openai
name: gpt-4o-mini
temperature: 0.1
tools:
- type: custom
module: my_tools
config:
prefix: "DEMO"
source: "custom-tools-demo"
- type: datetime
guardrails:
max_tokens_per_run: 20000
max_tool_calls: 15
timeout_seconds: 60Run from the directory containing both files:
cd examples/roles/custom-tools-demo
initrunner run custom-tools-demo.yaml -iExample prompts:
> Convert 72 degrees Fahrenheit to Celsius
> Generate a UUID for me
> Hash "hello world" with sha256
> Look up "test query"Key patterns: Docstrings become tool descriptions. Type annotations become parameter schemas. The
tool_configparameter is injected from the YAMLconfigblock and hidden from the LLM — the agent never seesprefixorsourceas callable parameters. Omittingfunctionin the YAML auto-discovers all public functions in the module.
API
Declarative REST API endpoints defined entirely in YAML — no Python required.
tools:
- type: api
name: github
description: GitHub REST API
base_url: https://api.github.com
headers:
Accept: application/vnd.github.v3+json
auth:
Authorization: "Bearer ${GITHUB_TOKEN}"
endpoints:
- name: get_repo
method: GET
path: "/repos/{owner}/{repo}"
description: Get repository information
parameters:
- name: owner
type: string
required: true
- name: repo
type: string
required: true
response_extract: "$.full_name"
- name: create_issue
method: POST
path: "/repos/{owner}/{repo}/issues"
description: Create a new issue
parameters:
- name: owner
type: string
required: true
- name: repo
type: string
required: true
- name: title
type: string
required: true
- name: body
type: string
required: false
default: ""
body_template:
title: "{title}"
body: "{body}"
response_extract: "$.html_url"| Field | Type | Default | Description |
|---|---|---|---|
name | str | (required) | API group name |
base_url | str | (required) | Base URL for all endpoints |
headers | dict | {} | Headers sent with every request (supports ${VAR}) |
auth | dict | {} | Auth headers merged into headers |
endpoints | list | (required) | Endpoint definitions |
Each endpoint supports name, method, path, description, parameters, headers, body_template, query_params, response_extract, and timeout.
Scaffold an API tool agent:
initrunner init --template api --name weather-agentDelegate
Invoke other agents as tool calls. Each agent reference generates a delegate_to_{name} tool.
tools:
- type: delegate
agents:
- name: summarizer
role_file: ./roles/summarizer.yaml
description: "Summarizes long text"
- name: researcher
role_file: ./roles/researcher.yaml
description: "Researches topics"
mode: inline
max_depth: 3
timeout_seconds: 120| Field | Type | Default | Description |
|---|---|---|---|
agents | list | (required) | Agent references (name + role_file or url) |
mode | str | "inline" | "inline" (in-process) or "mcp" (HTTP) |
max_depth | int | 3 | Maximum delegation recursion depth |
timeout_seconds | int | 120 | Timeout per delegation call |
Git
Subprocess-based git operations with read-only default.
tools:
- type: git
repo_path: .
read_only: true
timeout_seconds: 30| Field | Type | Default | Description |
|---|---|---|---|
repo_path | str | "." | Path to the git repository |
read_only | bool | true | Only allow read operations |
timeout_seconds | int | 30 | Timeout for each git command |
Read tools: git_status, git_log, git_diff, git_show, git_blame, git_changed_files, git_list_files. Write tools (when read_only: false): git_checkout, git_commit, git_tag.
Shell
Execute shell commands with an allowlist.
tools:
- type: shell
allowed_commands: [kubectl, docker, curl]
require_confirmation: false
timeout_seconds: 30
working_dir: .Auto-Registered Tools
Document Search (from ingest)
When spec.ingest is configured, a search_documents(query, top_k) tool is auto-registered. See Ingestion.
Memory Tools (from memory)
When spec.memory is configured, three tools are auto-registered: remember(content, category), recall(query, top_k), and list_memories(category, limit). See Memory.
Plugin Tools
Any type value not matching a built-in is resolved via the plugin registry:
tools:
- type: slack
channel: "#alerts"
token: ${SLACK_TOKEN}List installed plugins: initrunner plugins.
Resource Limits
| Tool | Limit | Behavior |
|---|---|---|
read_file | 1 MB | Truncated with [truncated] note |
http_request | 100 KB | Truncated with [truncated] note |
git_* | 100 KB | Truncated with recovery hint |