Skip to main content
AgenticPal uses a centralized tool registry system that makes adding new tools straightforward. This guide walks you through the complete process.

Architecture Overview

The tool system has three layers:
  1. Tool Definitions (tool_definitions.py) - Declares tools with metadata and schemas
  2. Tool Registry (registry.py) - Wraps service methods and handles execution
  3. Pydantic Schemas (schemas.py) - Validates parameters and provides type safety

Adding a New Tool

1

Define the Pydantic Schema

Create a parameter schema in agent/schemas.py:
agent/schemas.py
from pydantic import BaseModel, Field
from typing import Optional

class SendEmailParams(BaseModel):
    """Parameters for sending an email."""
    to: str = Field(..., description="Recipient email address")
    subject: str = Field(..., description="Email subject line")
    body: str = Field(..., description="Email body content")
    cc: Optional[list[str]] = Field(None, description="CC recipients")
The schema provides:
  • Type validation via Pydantic
  • Field descriptions for LLM tool calling
  • Optional parameters with defaults
2

Register the Tool Definition

Add your tool to TOOL_DEFINITIONS in agent/tools/tool_definitions.py:
agent/tools/tool_definitions.py
from .. import schemas

TOOL_DEFINITIONS: Dict[str, ToolDefinition] = {
    # ... existing tools ...
    
    "send_email": ToolDefinition(
        name="send_email",
        summary="Send an email to specified recipients",
        description="Send an email via Gmail. Use this when the user wants to compose and send a message.",
        category="gmail",
        actions=["send", "write"],
        is_write=True,  # Requires confirmation
        schema=schemas.SendEmailParams,
    ),
}
Key fields:
  • name - Tool identifier (must match method name)
  • summary - Short description (~15 tokens) for discovery
  • description - Full description for LLM tool binding
  • category - Groups tools (“calendar”, “gmail”, “tasks”)
  • actions - Action types (“create”, “read”, “update”, “delete”, etc.)
  • is_write - Set True for destructive operations
  • schema - The Pydantic model from step 1
3

Implement the Tool Method

Add the implementation to AgentTools class in agent/tools/registry.py:
agent/tools/registry.py
class AgentTools:
    def __init__(self, calendar_service, gmail_service, tasks_service, ...):
        self.calendar = calendar_service
        self.gmail = gmail_service
        self.tasks = tasks_service
        # ...

    def send_email(
        self,
        to: str,
        subject: str,
        body: str,
        cc: Optional[list[str]] = None,
    ) -> dict:
        """Send an email via Gmail."""
        try:
            # Call the underlying service
            result = self.gmail.send_message(
                to=to,
                subject=subject,
                body=body,
                cc=cc,
            )
            return result
        except Exception as e:
            return {
                "success": False,
                "message": f"Failed to send email: {str(e)}",
                "error": str(e),
            }
Return format:
  • Always return a dict with success: bool
  • Include a human-readable message
  • On error, include error with details
  • On success, include relevant data (IDs, objects, etc.)
4

Implement the Service Method

If you need to add a new service integration, implement it in services/:
services/gmail.py
class GmailService:
    def send_message(
        self,
        to: str,
        subject: str,
        body: str,
        cc: Optional[list[str]] = None,
    ) -> dict:
        """Send an email using Gmail API."""
        try:
            message = self._create_message(to, subject, body, cc)
            sent = (
                self.service.users()
                .messages()
                .send(userId="me", body=message)
                .execute()
            )
            
            return {
                "success": True,
                "message_id": sent["id"],
                "message": f"Email sent to {to}",
            }
        except HttpError as error:
            return {
                "success": False,
                "message": f"Failed to send email: {error}",
                "error": str(error),
            }
5

Add to System Prompt (Optional)

If your tool requires special instructions or examples, update the system prompt in agent/prompts/.The meta-tools approach means the LLM discovers tools dynamically, but you can add guidance:
# For important behavioral notes
PLAN_ACTIONS_SYSTEM_PROMPT = """
...

When sending emails:
- Always ask for confirmation before sending
- Validate email addresses
- Keep subject lines concise
"""
6

Add Confirmation Logic (if destructive)

If is_write=True, add the tool to the destructive tools list in agent/graph/nodes/confirm_actions.py:
agent/graph/nodes/confirm_actions.py
DESTRUCTIVE_TOOLS = {
    "delete_calendar_event": "calendar event",
    "delete_task": "task",
    "send_email": "email",  # Add here
}
This ensures users confirm before the action executes.

Tool Definition Reference

Here’s a complete example from the codebase:
agent/tools/tool_definitions.py
"delete_task": ToolDefinition(
    name="delete_task",
    summary="Delete a task by its ID",
    description="Delete a task from Google Tasks. Always confirm with the user before deleting.",
    category="tasks",
    actions=["delete", "write"],
    is_write=True,
    schema=schemas.DeleteTaskParams,
),
With corresponding schema:
agent/schemas.py
class DeleteTaskParams(BaseModel):
    """Parameters for deleting a task."""
    task_id: str = Field(..., description="The unique ID of the task to delete")
    tasklist: Optional[str] = Field(None, description="Task list ID. If not provided, uses default list")
And implementation:
agent/tools/registry.py
def delete_task(
    self,
    task_id: str,
    tasklist: Optional[str] = None,
) -> dict:
    """Delete a task."""
    return self.tasks.delete_task(task_id=task_id, tasklist=tasklist)

Best Practices

Clear Descriptions

Write descriptions that help the LLM understand when to use the tool, not just what it does.

Validation

Use Pydantic Field constraints (ge, le, min_length, etc.) to validate parameters at the schema level.

Error Handling

Always return structured error responses with helpful messages for the user.

Idempotency

Design tools to be safely retryable when possible.

Testing Your Tool

Once implemented, test your tool:
  1. Direct invocation:
    from agent.tools.registry import AgentTools
    
    tools = AgentTools(calendar_service, gmail_service, tasks_service)
    result = tools.send_email(
        to="[email protected]",
        subject="Test",
        body="Hello!"
    )
    print(result)
    
  2. Through the agent:
    python main.py
    > Send an email to [email protected] with subject "Meeting Tomorrow"
    
  3. Verify discovery:
    from agent.tools.tool_definitions import get_tools_for_categories
    
    gmail_tools = get_tools_for_categories(["gmail"])
    assert "send_email" in gmail_tools
    

See Also

Service Integration

Learn how to integrate new external services

Graph Nodes

Understand the agent execution graph

Build docs developers (and LLMs) love