AI Agent Tool Design Patterns: How Execution Tools Enable Self-Evolving Agents
AI agent execution tools follow a five-step pipeline of discover, read, verify, create, and reuse, enabling agents to extend their own capabilities without human intervention.
The ai-agent-book repository implements a self-evolving AI agent named Alita that autonomously builds, validates, and reuses execution tools through LLM-driven function calling. This article breaks down the core AI agent tool design patterns that power dynamic capability expansion, based on the source code in chapter8/self-evolving-tools/.
The Five-Step Execution Tool Pipeline
The architecture in agent.py defines a strict evolution sequence that every new capability must traverse. Each step maps to a concrete function schema passed to the LLM.
Step 0: Reuse — Query the Tool Library First
Before any external work, the agent invokes search_tools to check its persistent tool library. This minimizes redundant API calls and enables cumulative knowledge building.
The schema is defined in agent.py lines 73-82:
{
"name": "search_tools",
"description": "Search for existing tools in the tool library...",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Keywords to search for"}
},
"required": ["query"]
}
}
Step 1: Discover — Find External Packages
When no existing tool matches, the agent calls web_search (lines 21-34) to locate free, open-source Python libraries. The LLM formulates search queries based on the user's intent.
Step 2: Read — Extract Documentation
The read_webpage tool (lines 39-45) fetches and parses candidate library documentation, giving the LLM context for implementation.
Step 3: Verify — Sandbox Execution with Real Output Validation
Critical to the AI agent execution tool pattern: the code_interpreter (lines 49-64) runs candidate code in a sandboxed subprocess and mandates a real numeric output for verification.
{
"name": "code_interpreter",
"description": "Execute Python code in a sandboxed environment...",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string"},
"packages": {"type": "array", "items": {"type": "string"}}
}
}
}
The system prompt enforces: "You must verify the tool works by seeing real numeric output before creating it." This prevents hallucinated tools from entering the library.
Step 4: Create — Package Verified Logic
Upon successful verification, create_tool (lines 69-88) persists the new capability to tool_manager.py's ToolLibrary class as a JSON-serialized function with schema, code, and test arguments.
Step 5: Invoke — Immediate Reuse
Future queries hit search_tools and retrieve the newly created tool, completing the self-evolution loop.
Core Implementation Files
| Component | File | Key Element |
|---|---|---|
| LLM client builder | agent.py lines 33-70 |
_PROVIDERS dict with OpenAI, Moonshot, ARK, OpenRouter support |
| Base tool schemas | agent.py lines 21-90 |
BASE_TOOL_SCHEMAS — JSON function definitions for LLM |
| Tool persistence | tool_manager.py |
ToolLibrary class with JSON storage and normalization |
| Sandbox runner | base_tools.py |
Concrete code_interpreter implementation |
| Execution orchestration | scripts/sync_chapter2_figures.py |
Annotated message-tool-result loop |
Key Design Patterns in AI Agent Execution Tools
Function-Calling Driven Orchestration
The agent contains no hard-coded branching logic. All decisions flow from the LLM selecting among provided function schemas. This pattern, central to modern AI agent tool design, enables open-ended capability expansion without rewriting agent code.
Verification-Before-Publication
The code_interpreter gate ensures only functionally validated tools enter the library. A tool must:
- Install its dependencies via pip
- Execute without errors
- Produce observable, non-trivial output
This mirrors continuous integration practices in software engineering.
Provider-Agnostic Abstraction
The build_client helper (lines 33-70) normalizes across OpenAI, Moonshot, ARK, and OpenRouter APIs. The same execution pipeline runs regardless of backend, reducing vendor lock-in.
Practical Example: Autonomous Tool Creation
Here's how the AI agent execution tool pipeline handles a stock price query:
# User request
messages = [{"role": "user", "content": "Give me the latest price of AAPL"}]
# Agent execution flow (simplified from agent.py loop):
# 1. search_tools("stock price") → no match
# 2. web_search("open source python library stock price yfinance") → finds yfinance
# 3. read_webpage("https://github.com/ranaroussi/yfinance") → extracts API
# 4. code_interpreter(
# code="import yfinance; print(yfinance.Ticker('AAPL').info['currentPrice'])",
# packages=["yfinance"]
# ) → outputs 187.43 (verified)
# 5. create_tool(
# name="get_stock_price",
# code="def run(symbol): import yfinance; return yfinance.Ticker(symbol).info['currentPrice']",
# test_args={"symbol": "AAPL"}
# )
# 6. Subsequent TSLA query: search_tools → instant reuse, no web search needed
Manual tool injection via ToolLibrary is also supported:
from tool_manager import ToolLibrary
library = ToolLibrary()
library.add_tool(
name="get_weather",
description="Fetch current weather using weather-api package",
parameters={"type": "object", "properties": {"city": {"type": "string"}}},
code="""
def run(**kwargs):
from weather import Weather
return Weather().get_weather(kwargs['city'])
""",
test_args={"city": "Paris"}
)
System Prompt Enforcement Rules
The prompt in agent.py lines 73-82 encodes three invariants:
- No fabrication — All data must originate from tool execution
- Mandatory verification — Every new tool requires observed output
- Mandatory reuse — Always query
search_toolsbefore external discovery
These constraints transform an LLM into a reliable, reproducible execution engine.
Summary
- AI agent tool design in ai-agent-book centers on a five-step evolution pipeline: reuse → discover → read → verify → create → invoke
code_interpreterenforces verified execution through sandboxed subprocesses and mandatory real outputToolLibraryintool_manager.pyprovides persistent, searchable tool storage- Function-calling architecture eliminates hard-coded control flow, enabling open-ended self-evolution
- Provider-agnostic client supports OpenAI, Moonshot, ARK, and OpenRouter with unified interface
Frequently Asked Questions
How does the agent prevent creating broken or hallucinated tools?
The code_interpreter tool requires observable, non-trivial output before create_tool can execute. As implemented in base_tools.py, the sandbox runs candidate code with mandatory package installation and prints results. Only verified outputs trigger tool registration, mirroring test-driven development practices.
Can the agent work with different LLM providers without code changes?
Yes. The build_client function in agent.py (lines 33-70) abstracts provider selection through the _PROVIDERS dictionary. It handles OpenAI, Moonshot, ARK, and OpenRouter with automatic model mapping and API key routing, allowing the same execution pipeline across backends.
What happens when a user asks for something similar to a previous request?
The search_tools function executes first on every query. If semantic similarity matches an existing tool — e.g., "stock price" matching get_stock_price — the agent bypasses all discovery steps and invokes the cached tool directly. This reuse pattern reduces latency and API costs significantly after initial tool creation.
How is tool code safely executed without risking the host system?
The code_interpreter in base_tools.py runs in a subprocess sandbox with isolated package installation. It optionally accepts a packages parameter for pip dependencies, executes the provided code, and captures stdout/stderr. This containment prevents host environment pollution while enabling dynamic dependency management.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →