Tools & Actions
Tools are how agents interact with the outside world. Learn how to design, implement, and use agent tools.
What are Tools?
Tools are interfaces that let agents take real actions. Without tools, an agent can only generate text; with tools, an agent can:
- Read and write files
- Execute code
- Call APIs
- Search information
- Interact with databases
- Send messages
Function Calling
Modern LLMs support tool use, which is the foundation of agent actions. In Anthropic's raw Messages API, the flow looks like this:
1. Define the tool schema
{
"name": "read_file",
"description": "Read the contents of a file",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file"
}
},
"required": ["path"]
}
}
2. The model emits a tool request
{
"type": "tool_use",
"id": "toolu_123",
"name": "read_file",
"input": {"path": "config.json"}
}
3. Execute the tool and return the result
{
"type": "tool_result",
"tool_use_id": "toolu_123",
"content": "{\"name\": \"my-app\", ...}"
}Boundary: Raw Tool Blocks vs SDK Messages
The raw API uses tool_use and tool_result content blocks. The Claude Agent SDK then exposes
higher-level stream messages such as tool_use_summary and result. If you need exact tool
inputs or outputs, inspect stream_event or the underlying message content blocks.
tool_use_summary is prose plus tool-use IDs; it is not a complete audit record.
import { query } from "@anthropic-ai/claude-agent-sdk";
const response = query({
prompt: "Read package.json and tell me the project name",
options: {
// Control which tools the agent can see
tools: ["Read", "Glob"],
// Auto-approve these visible tools without a permission prompt
allowedTools: ["Read", "Glob"],
// Or use preset + overrides
// tools: { type: 'preset', preset: 'claude_code' },
// disallowedTools: ["Write", "Bash"]
}
});
for await (const message of response) {
if (message.type === "tool_use_summary") {
console.log(`Tool run: ${message.summary}`);
}
if (message.type === "result" && message.subtype === "success") {
console.log(`Result: ${message.result.slice(0, 100)}...`);
}
}piPi equivalent— Lowercase allowlist, push events through subscribe
// tool-calling.ts
import { createAgentSession } from "@earendil-works/pi-coding-agent";
// Tool exposure uses lowercase names; built-ins ship with the session.
const { session } = await createAgentSession({ tools: ["read", "glob"] });
let lastText = "";
const unsubscribe = session.subscribe((event) => {
if (event.type === "tool_execution_start") {
console.log(`Tool run: ${event.toolName}`);
} else if (event.type === "agent_end") {
for (const m of event.messages) {
if (m.role !== "assistant" || !Array.isArray(m.content)) continue;
for (const b of m.content) if (b.type === "text") lastText = b.text;
}
}
});
try {
await session.prompt("Read package.json and tell me the project name");
console.log(`Result: ${lastText.slice(0, 100)}...`);
} finally {
unsubscribe();
}Key mappings
Pi tools use lowercase (["read", "glob"]); Claude Agent SDK tools use PascalCase (["Read", "Glob"]), and allowedTools only controls auto-approval.
tool_execution_start / tool_execution_end (per-call) vs tool_use_summary (rolled-up
summary).
agent_end event carries the final assistant messages — no dedicated result event.
Tool Categories
The lowercase calls below are conceptual, not SDK functions. SDK built-ins use Read, Write,
Bash, Grep, and Glob; custom and MCP tools require their implementations or servers.
File Tools
| Tool | Function |
|---|---|
read_file | Read file contents |
write_file | Create or overwrite a file |
edit_file | Edit part of a file |
list_directory | List directory contents |
Shell Tools
bash({
command: "npm install",
working_dir: "/project",
timeout: 60,
});Search Tools
grep- Text searchglob- Filename matchingsemantic_search- Semantic searchweb_search- Web search
API Tools
- HTTP requests (GET/POST/PUT/DELETE)
- Database queries
- Third-party service integrations (GitHub, Slack, etc.)
Tool Design Principles
Atomicity
Each tool should do only one thing:
// BAD: too many responsibilities
create_and_run_test(file, test_content, run_args);
// GOOD: split into atomic operations
write_file(path, content);
run_command("pytest " + path);Clear Descriptions
Descriptions should be detailed enough for the LLM to know when to use them:
// BAD
const badDescription = "Search files";
// GOOD
const goodDescription = `Search for files by name pattern using glob syntax.
Use this when you need to find files matching a pattern like '*.py'
or 'src/**/*.ts'. Returns a list of matching file paths.`;Reasonable Parameters
{
"name": "edit_file",
"parameters": {
"path": "string (required) - File to edit",
"old_string": "string (required) - Exact text to replace",
"new_string": "string (required) - Replacement text",
"expected_count": "number (optional) - Expected match count"
}
}Helpful Error Messages
// BAD
{"error": "Failed"}
// GOOD
{
"error": "FileNotFoundError",
"message": "File 'config.json' not found. Available files in current directory: package.json, tsconfig.json, src/"
}declare function isSafeRead(tool: string, input: unknown): Promise<boolean>;
const response = query({
prompt: "Clean up the temp directory",
options: {
tools: ["Read", "Bash", "Glob"],
allowedTools: ["Read", "Glob"],
// Custom permission gate
canUseTool: async (toolName, input) => {
// Read-only still needs workspace and sensitive-path validation
if (["Read", "Glob", "Grep"].includes(toolName) && await isSafeRead(toolName, input)) {
return { behavior: "allow" };
}
// This read-only workflow exposes no general shell escape hatch.
if (toolName === "Bash") {
return { behavior: "deny", message: "Shell access is not permitted" };
}
// Deny anything else with a clear reason
return {
behavior: "deny",
message: `${toolName} is not permitted in this workflow`
};
}
}
});piPi equivalent— Policy lives at the tool boundary, not a global hook
// can-use-tool.ts
import { createAgentSession, defineTool } from "@earendil-works/pi-coding-agent";
import { Type } from "@earendil-works/pi-ai";
import { execFile } from "node:child_process";
// Prefer structured intent to filtering shell strings. This tool can remove
// plain filenames only, from one application-owned temp directory.
const cleanTemp = defineTool({
name: "clean_temp",
label: "Clean temp files",
description: "Delete named files from the application temp directory.",
parameters: Type.Object({ files: Type.Array(Type.String()) }),
async execute(_id, { files }) {
if (files.some((file) => !/^[A-Za-z0-9._-]+$/.test(file))) {
return {
content: [{ type: "text", text: "DENIED: filenames only" }],
details: null,
isError: true,
};
}
const result = await new Promise<{ text: string; isError: boolean }>((res) =>
execFile("rm", ["-f", "--", ...files], { cwd: "./tmp" }, (error, stdout, stderr) =>
res({ text: error?.message || stderr || stdout || "Done", isError: !!error })
)
);
return {
content: [{ type: "text", text: result.text }],
details: null,
isError: result.isError,
};
},
});
const { session } = await createAgentSession({
customTools: [cleanTemp],
tools: ["clean_temp"], // add reads only behind workspace + secret-path policy
});
await session.prompt("Clean up the temp directory");Key mappings
tool boundary (execute() of a custom tool) vs callback hook (canUseTool).
omit the built-in (e.g. bash) and expose only the gated wrapper, vs allowlist + decision
callback per call.
TypeBox Type.Object(…) vs JSON Schema in input_schema.
SDK Insight: Two Permission Behaviors
canUseTool returns one of two decisions: allow (auto-execute, optionally with updatedInput)
or deny (block with a message). It fires precisely when a prompt would otherwise be shown, so
"ask" is configured via permission rules or a PreToolUse hook — expanded in P11 (HITL).
Model Context Protocol (MCP)
MCP is a tool standardization protocol proposed by Anthropic that lets different agents share tools:
// MCP tools/list result item (the server identity lives in client configuration)
{
"name": "github_create_issue",
"description": "Create a GitHub issue",
"inputSchema": {
"type": "object",
"properties": {
"repo": { "type": "string" },
"title": { "type": "string" },
"body": { "type": "string" },
},
},
}MCP advantages:
- Reusable tools
- Cross-agent compatibility
- Standardized interface
- Rich ecosystem
Safety Considerations
- Sandboxed execution - Limit the tool runtime environment
- Permission control - Distinguish read-only vs writable tools
- Confirmation mechanism - Dangerous actions require user confirmation
- Audit logs - Record actor, tool, decision, and redacted evidence with retention limits
- Rate limits - Prevent runaway loops
Production Gap
Tool definitions here use simple schemas. Production tools need input sanitization, timeout handling, retry logic, and rate limiting — especially for tools that call external APIs.
Rolling it out at your company. A scoped tool catalog is the safest first agent surface:
- Pick one read-only pilot before any writes. A "Slack-archive search + JIRA cross-link" agent
with
tools: ["search_slack", "search_jira"]plusallowedToolsfor those read-only tools ships in a day, has zero blast radius, and proves the action-space model to security review. - Treat
toolsas the service contract andallowedToolsas pre-approval. Document each tool's scope, rate limit, and side effects in the same place you document REST endpoints — security reviewers will ask, and the spec doubles as the LLM's API doc. - Keep writes out of
allowedToolsfor the first month. Let permission rules, host approval, or acanUseTooldeny path force review. Every approval prompt is a free labeled training example for tightening the policy later. - Use
tool_use_summaryfor readable telemetry, not audit. Build protected, redacted audit records from tool blocks or hooks, including actor, decision, and timestamp.
Transitioning into an agent engineering role. Action-space design is the central skill:
- Senior agent engineers spend the majority of their design time on which tools exist, what their inputs look like, and what scopes they hold — not on prompts. A clean 6-tool catalog beats a sloppy 30-tool one every time.
- Interviewers probe: "Why is this one tool instead of three? How did you pick the schema? What happens when the tool times out? How does the LLM recover from a 4xx-shaped error?" Have answers.
- A portfolio-worthy tool design shows a typed schema, a
canUseToolpolicy, an error contract the LLM can recover from, and a logged tool-call trace for one real workflow. That's a tighter story than five MCP servers nobody uses.
Try ItTool Permission Gate
Build a permission-controlled agent for a support team investigating customer incidents.
- Create an agent with
tools: ["Read", "Bash", "Glob"]andallowedTools: ["Read", "Glob"] - Classify actions as read-only, reversible write, or irreversible; define who approves each tier
- Implement
canUseToolto log every request and blockrmorsudoas irreversible examples - Ask it to inspect
.tsfiles for a reported issue, then test one safe and one blocked request - Review the stream: count false blocks/approvals and decide whether the workflow is safe to pilot
Gate: P2 Complete — Tool calls are logged, canUseTool blocks dangerous ops, multi-tool
sequences observed in stream.