Skip to content

Adding Tools

Kohlbern Jary edited this page Dec 9, 2025 · 1 revision

Adding Tools

This guide explains how to add new tools that Cass can use during conversations.

Overview

Tools allow Cass to perform actions: create calendar events, manage tasks, search journals, etc. Each tool has:

  1. Definition - Schema describing the tool's name, description, and parameters
  2. Handler - Function that executes the tool
  3. Routing - Logic to include the tool and route calls to the handler

Step-by-Step Guide

1. Create the Handler Module

Create a new file in backend/handlers/:

# backend/handlers/myfeature.py

"""
MyFeature tool handler - enables Cass to do X, Y, Z.
"""
from typing import Dict, Any

# Tool definitions (Anthropic format)
MYFEATURE_TOOLS = [
    {
        "name": "myfeature_action",
        "description": "Does something useful. Call this when the user asks about X.",
        "input_schema": {
            "type": "object",
            "properties": {
                "required_param": {
                    "type": "string",
                    "description": "What this parameter is for"
                },
                "optional_param": {
                    "type": "integer",
                    "description": "Optional setting",
                    "default": 10
                }
            },
            "required": ["required_param"]
        }
    },
    {
        "name": "myfeature_query",
        "description": "Retrieves information about X.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "What to search for"
                }
            },
            "required": ["query"]
        }
    }
]


async def execute_myfeature_tool(
    tool_name: str,
    tool_input: Dict,
    # Add any dependencies your tools need:
    # storage: MyFeatureStorage,
    # user_id: str,
) -> Dict[str, Any]:
    """
    Execute a myfeature tool.

    Returns dict with 'success' bool and either 'result' or 'error'.
    """
    try:
        if tool_name == "myfeature_action":
            return await _do_action(tool_input)

        elif tool_name == "myfeature_query":
            return await _do_query(tool_input)

        else:
            return {"success": False, "error": f"Unknown tool: {tool_name}"}

    except Exception as e:
        return {"success": False, "error": str(e)}


async def _do_action(tool_input: Dict) -> Dict[str, Any]:
    """Handle myfeature_action tool."""
    required_param = tool_input.get("required_param")
    optional_param = tool_input.get("optional_param", 10)

    # Do the actual work here
    result = f"Did action with {required_param}"

    return {
        "success": True,
        "result": result
    }


async def _do_query(tool_input: Dict) -> Dict[str, Any]:
    """Handle myfeature_query tool."""
    query = tool_input.get("query", "")

    # Do the query
    results = []  # Your query logic here

    return {
        "success": True,
        "result": f"Found {len(results)} results",
        "data": results
    }

2. Export from handlers/init.py

# backend/handlers/__init__.py

from .calendar import execute_calendar_tool, CALENDAR_TOOLS
from .tasks import execute_task_tool, TASK_TOOLS
from .journals import execute_journal_tool, JOURNAL_TOOLS
from .myfeature import execute_myfeature_tool, MYFEATURE_TOOLS  # Add this

3. Add Tool Selection Keywords

In backend/agent_client.py, add keywords that trigger your tools:

# Near the top with other keyword sets
MYFEATURE_KEYWORDS = frozenset({
    "myfeature", "related", "terms", "that", "users", "might", "say"
})

# In the function that checks keywords
def _should_include_myfeature_tools(message: str) -> bool:
    """Check if message might need myfeature tools."""
    message_lower = message.lower()
    return any(kw in message_lower for kw in MYFEATURE_KEYWORDS)

4. Add Tool Definitions to Agent

In agent_client.py, include tools when keywords match:

def _get_tools_for_message(message: str) -> List[Dict]:
    """Get relevant tools based on message content."""
    tools = BASE_TOOLS.copy()

    if _should_include_calendar_tools(message):
        tools.extend(CALENDAR_TOOLS)

    if _should_include_myfeature_tools(message):  # Add this
        tools.extend(MYFEATURE_TOOLS)

    return tools

5. Route Tool Calls in main_sdk.py

In the WebSocket handler, add routing for your tools:

# In the tool processing section of handle_websocket()

elif tool_name.startswith("myfeature_"):
    from handlers import execute_myfeature_tool
    result = await execute_myfeature_tool(
        tool_name,
        tool_input,
        # Pass any dependencies
    )

Tool Definition Best Practices

Naming Conventions

  • Use feature_action format: calendar_create_event, task_add, journal_search
  • Prefix groups related tools: wiki_create_page, wiki_search, wiki_update

Descriptions

Write descriptions that help the LLM understand when to use the tool:

# Good - explains when to use
"description": "Create a new calendar event. Use when the user wants to schedule something."

# Bad - just says what it does
"description": "Creates an event."

Parameter Descriptions

Be specific about expected values:

# Good
"date": {
    "type": "string",
    "description": "Date in YYYY-MM-DD format, e.g., '2025-01-15'"
}

# Bad
"date": {
    "type": "string",
    "description": "The date"
}

Required vs Optional

  • Mark parameters as required only if the tool can't function without them
  • Provide sensible defaults for optional parameters
  • Document defaults in the description

Return Value Format

Tools should return a dict that the LLM can understand:

# Success case
{
    "success": True,
    "result": "Human-readable result message",
    "data": {...}  # Optional structured data
}

# Error case
{
    "success": False,
    "error": "Human-readable error message"
}

Testing Tools

Manual Testing

  1. Start the backend
  2. Send a message that triggers your keywords
  3. Check logs for tool calls
  4. Verify tool result is returned to LLM

Automated Testing

# tests/test_myfeature_tools.py

import pytest
from handlers.myfeature import execute_myfeature_tool

@pytest.mark.asyncio
async def test_myfeature_action():
    result = await execute_myfeature_tool(
        "myfeature_action",
        {"required_param": "test"}
    )
    assert result["success"] is True
    assert "test" in result["result"]

Examples from Codebase

Simple Tool: Journal Search

# handlers/journals.py

JOURNAL_TOOLS = [
    {
        "name": "search_journals",
        "description": "Search through past journal entries...",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"},
                "limit": {"type": "integer", "description": "Max results", "default": 5}
            },
            "required": ["query"]
        }
    }
]

Complex Tool: Calendar Event Creation

# handlers/calendar.py

{
    "name": "create_event",
    "description": "Create a new calendar event...",
    "input_schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string"},
            "date": {"type": "string", "description": "YYYY-MM-DD"},
            "time": {"type": "string", "description": "HH:MM (24-hour)"},
            "duration_minutes": {"type": "integer", "default": 60},
            "description": {"type": "string"},
            "location": {"type": "string"}
        },
        "required": ["title", "date"]
    }
}

Troubleshooting

Tool Not Being Called

  1. Check keywords are being matched (add logging)
  2. Verify tool is in the tools list sent to LLM
  3. Check tool description is clear enough for LLM

Tool Errors Not Shown

  1. Check the return format includes success and error/result
  2. Verify error is being caught and returned, not raised

Tool Called with Wrong Parameters

  1. Check required array in schema
  2. Verify parameter descriptions are clear
  3. Check for typos in parameter names

Clone this wiki locally