# How to Add a Custom Tool to OpenMontage Using Inheritance and Auto-Discovery

> Learn to add custom tools to OpenMontage with inheritance and auto-discovery. OpenMontage automatically finds tools in the tools package, simplifying integration. No registration needed.

- Repository: [Calesthio/OpenMontage](https://github.com/calesthio/OpenMontage)
- Tags: how-to-guide
- Published: 2026-08-30

---

**OpenMontage automatically discovers every concrete tool class that inherits from `BaseTool` and resides within the `tools` package, requiring no explicit registration code.**

The OpenMontage framework provides a pluggable architecture that allows developers to extend functionality without modifying core source files. By leveraging Python inheritance and the built-in auto-discovery mechanism, you can add custom capabilities that agents and CLI commands consume immediately. This guide walks through the exact implementation details based on the current source code in `calesthio/OpenMontage`.

## Understanding the Tool Registry Architecture

The discovery engine lives in [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) and implements a singleton pattern that scans the entire `tools` package at runtime.

### The Discovery Mechanism

According to the source code, the `ToolRegistry.discover()` method (lines 18-33) uses `pkgutil.walk_packages` to iterate through every module under the `tools` directory. When it encounters a class that inherits from `BaseTool`, it instantiates the class and adds it to the internal `self._tools` mapping. The singleton `registry` instance (defined at lines 90-92) ensures this discovery happens once per application lifecycle, though you can trigger it manually via `registry.ensure_discovered()`.

### The Base Class Contract

All tools must inherit from `BaseTool`, defined in [`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py) (lines 27-45). This abstract base class enforces a strict contract:

- **Metadata attributes**: `name`, `version`, `capability`, `tier`, `stability`, `runtime`
- **Schema definitions**: `input_schema` and `output_schema` for validation
- **Dependency checking**: `dependencies` list and `check_dependencies()` method (lines 5-12)
- **Execution logic**: The abstract `execute(self, inputs: dict) -> ToolResult` method

## Creating a Custom Tool Class

To add a custom tool, create a new Python file anywhere under the `tools/` directory and subclass `BaseTool`.

### Required Implementation Steps

1. **Import the base class and supporting types**:

```python
from tools.base_tool import BaseTool, ToolResult, ToolTier, ToolStability, ToolRuntime

```

2. **Override metadata attributes** to describe your tool's identity and requirements:

```python
class MyAwesomeTool(BaseTool):
    name = "my_awesome_tool"
    version = "0.1.0"
    tier = ToolTier.CORE
    stability = ToolStability.EXPERIMENTAL
    runtime = ToolRuntime.LOCAL
    capability = "text_generation"
    provider = "my_company"

```

3. **Define input and output schemas** using JSON Schema format:

```python
    input_schema = {
        "type": "object",
        "properties": {"message": {"type": "string"}},
        "required": ["message"],
    }
    output_schema = {
        "type": "object",
        "properties": {"reply": {"type": "string"}},
    }

```

4. **Implement the `execute` method** containing your business logic:

```python
    def execute(self, inputs: dict) -> ToolResult:
        msg = inputs.get("message", "")
        reply = f"Echo: {msg}"
        return ToolResult(success=True, data={"reply": reply})

```

### Complete Working Example

Place this file at [`tools/custom/my_awesome_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/custom/my_awesome_tool.py):

```python
from tools.base_tool import BaseTool, ToolResult, ToolTier, ToolStability, ToolRuntime

class MyAwesomeTool(BaseTool):
    """A simple example that echoes a string back to the user."""
    
    # ---- Identity ---------------------------------------------------------

    name = "my_awesome_tool"
    version = "0.1.0"
    tier = ToolTier.CORE
    stability = ToolStability.EXPERIMENTAL
    runtime = ToolRuntime.LOCAL
    
    # ---- Capabilities ------------------------------------------------------

    capability = "text_generation"
    provider = "my_company"
    capabilities = ["echo"]
    best_for = ["quick demos"]
    
    # ---- Dependencies -------------------------------------------------------

    dependencies = []                     # No external deps

    install_instructions = "Just import – nothing to install."
    
    # ---- Schemas ------------------------------------------------------------

    input_schema = {
        "type": "object",
        "properties": {"message": {"type": "string"}},
        "required": ["message"],
    }
    output_schema = {
        "type": "object",
        "properties": {"reply": {"type": "string"}},
    }
    
    # ---- Execution ----------------------------------------------------------

    def execute(self, inputs: dict) -> ToolResult:
        msg = inputs.get("message", "")
        reply = f"Echo: {msg}"
        return ToolResult(success=True, data={"reply": reply})

```

## Registering Your Tool via Auto-Discovery

No explicit registration is required. The auto-discovery system finds your tool automatically when you place the subclass in a module under the `tools` package.

### File Placement Requirements

- **Location**: Any subdirectory of `tools/` (e.g., `tools/custom/`, `tools/contrib/`)
- **Module visibility**: The file must be importable as part of the `tools` package
- **Class visibility**: The class must be defined at module level (not nested inside functions)

The registry uses `pkgutil.walk_packages` (as implemented in [`tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tool_registry.py) lines 27-33) to locate modules, then inspects each for `BaseTool` subclasses using Python's `__subclasses__()` mechanism.

### Handling Dependencies

If your tool requires external binaries, environment variables, or Python packages, populate the `dependencies` list and `check_dependencies()` will validate availability at runtime (see [`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py) lines 4-9). This allows agents to skip unavailable tools gracefully.

## Verifying and Testing Your Tool

After adding your file, verify registration through the singleton registry instance.

### Check Registry Discovery

```python
from tools.tool_registry import registry

# Trigger discovery if not already done

registry.ensure_discovered()

# List all available tools

print(registry.list_all())

# Output: ['my_awesome_tool', ...other tools...]

# Retrieve specific tool instance

tool = registry.get("my_awesome_tool")
print(tool)

# Output: <MyAwesomeTool ...>

```

### Execute Direct Testing

Test the tool logic directly without the agent framework:

```python
tool = registry.get("my_awesome_tool")
result = tool.execute({"message": "Hello"})

print(result.success)  # True

print(result.data)     # {'reply': 'Echo: Hello'}

```

The tool immediately appears in the auto-generated provider menu used by agents and becomes available to CLI commands throughout OpenMontage.

## Summary

- **Inherit from `BaseTool`**: Define your tool class in [`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py) and override required metadata and the `execute()` method.
- **Place in tools package**: Save your module anywhere under the `tools/` directory; subdirectories are automatically scanned.
- **Auto-discovery activates**: The `ToolRegistry` in [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) uses `pkgutil.walk_packages` to find and instantiate your class without explicit registration.
- **Dependencies are optional**: Use the `dependencies` list to enable `check_dependencies()` validation for external requirements.
- **Immediate availability**: Once discovered, tools appear in `registry.list_all()`, `registry.get(name)`, and agent provider menus.

## Frequently Asked Questions

### Where exactly should I save my custom tool file?

Save your Python file in any subdirectory of the `tools/` folder, such as [`tools/custom/my_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/custom/my_tool.py). The registry recursively scans all subdirectories using `pkgutil.walk_packages`, so organizational subfolders are supported natively. Ensure the directory contains an [`__init__.py`](https://github.com/calesthio/OpenMontage/blob/main/__init__.py) file to make it a proper Python package.

### Do I need to import my tool anywhere or edit the registry?

No. The auto-discovery mechanism in [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) automatically imports every module in the `tools` package and registers any concrete subclass of `BaseTool` it finds. You do not need to modify [`tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tool_registry.py) or any initialization files.

### What methods and attributes are mandatory when subclassing BaseTool?

You must override the `execute(self, inputs: dict) -> ToolResult` method with your implementation logic. Required attributes include `name`, `version`, `capability`, `tier`, `stability`, `runtime`, `input_schema`, and `output_schema`. These are defined in [`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py) and enforced by the discovery system.

### How do I handle external dependencies like API keys or binaries?

Populate the `dependencies` class attribute with a list of requirement strings (e.g., `["ffmpeg", "OPENAI_API_KEY"]`). The `BaseTool.check_dependencies()` method (lines 5-12 in [`base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/base_tool.py)) validates these at runtime, allowing agents to filter out unavailable tools. Include installation instructions in the `install_instructions` attribute for user guidance.