# How OpenMontage Auto-Discovers Tools in the Tool Registry at Runtime

> Discover how OpenMontage dynamically imports tools at runtime using Python's importlib and pkgutil. Learn about the automatic registration of classes inheriting from BaseTool.

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

---

**OpenMontage uses Python's `importlib` and `pkgutil` libraries to scan the `tools/` package at startup, dynamically importing each module and registering any class inheriting from `BaseTool` that implements the required `name`, `description`, and `run()` contract.**

OpenMontage is an open-source media processing framework designed for extensible video workflows. The heart of its plugin architecture is a **tool registry** that enables **OpenMontage auto-discover tools** at runtime without hardcoded imports. When the application initializes, the registry automatically populates itself by inspecting the filesystem and loading compliant tool classes from the `tools/` directory.

## How the Tool Registry Scans for Modules

The discovery logic resides in [[`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py)](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py). This module orchestrates the entire auto-discovery pipeline through three distinct phases: filesystem enumeration, dynamic importing, and contract validation.

### Scanning the tools Package with pkgutil

The registry begins by treating the `tools/` directory as a Python package. It utilizes `pkgutil.iter_modules` (or `importlib.util.find_spec` in some implementations) to walk the package structure at runtime. This approach enumerates every Python file in the directory, including newly added tools that were not present during the previous execution.

```python

# Conceptual implementation based on tools/tool_registry.py

import pkgutil
import importlib
from tools import base

def scan_tools_package():
    modules = []
    for importer, modname, ispkg in pkgutil.iter_modules(tools.__path__):
        modules.append(modname)
    return modules

```

By walking the filesystem dynamically, the registry ensures that developers can add new tools to the `tools/` directory without updating a central manifest or configuration file.

### Dynamic Module Import and Error Isolation

For each module discovered during the scan, the registry calls `importlib.import_module` to load it into the Python interpreter. The implementation includes error handling to prevent a single faulty tool from crashing the entire discovery process. If a module fails to import due to missing dependencies or syntax errors, the registry logs the failure and continues processing the remaining tools.

```python
def import_tool_module(modname):
    try:
        module = importlib.import_module(f"tools.{modname}")
        return module
    except ImportError as e:
        print(f"Failed to import {modname}: {e}")
        return None

```

This defensive programming approach allows the OpenMontage system to remain stable even when individual tools contain errors or require optional dependencies that are not installed in the current environment.

### Validating the BaseTool Contract

After successfully importing a module, the registry inspects its global namespace for classes that inherit from the abstract `BaseTool` class (typically defined in [`tools/base.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base.py) or imported via [`tools/__init__.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/__init__.py)). A valid tool must implement three essential attributes:

- **`name`**: A unique string identifier used for registry lookups
- **`description`**: A human-readable summary of the tool's functionality
- **`run()`**: A method accepting keyword arguments that executes the tool's primary logic

The registry verifies inheritance and attribute presence before proceeding with registration.

## Registering Tools in the Runtime Dictionary

Once a class passes validation, the registry stores it in a central dictionary mapping tool names to their corresponding classes. This `registry` dictionary serves as the authoritative lookup mechanism for the rest of the application, including the pipeline loader in [`lib/pipeline_loader.py`](https://github.com/calesthio/OpenMontage/blob/main/lib/pipeline_loader.py).

```python

# tools/tool_registry.py

registry = {}

def register_tool(tool_class):
    if hasattr(tool_class, 'name') and hasattr(tool_class, 'run'):
        registry[tool_class.name] = tool_class

```

The registry stores class references rather than instances, enabling lazy instantiation and preventing heavy initialization from impacting application startup time.

## Example: Creating an Auto-Discovered Tool

To add a new tool to OpenMontage, create a Python file in the `tools/` directory and define a class inheriting from `BaseTool`. The following example demonstrates a simple video processing utility:

```python

# tools/my_custom_tool.py

from .base import BaseTool

class MyCustomTool(BaseTool):
    name = "my_custom"
    description = "Demo tool that prints a greeting."

    def run(self, **kwargs):
        print("Hello from MyCustomTool!")
        return {"status": "success"}

```

When OpenMontage restarts, [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) automatically discovers [`my_custom_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/my_custom_tool.py), imports it, detects the `MyCustomTool` class, and registers it under the name `"my_custom"`. No modifications to the registry source code are required.

## Retrieving and Executing Registered Tools

Other components interact with the registry through helper functions that abstract the underlying dictionary operations.

### Looking Up a Specific Tool

The `get_tool()` function retrieves a tool class by its registered name, allowing the pipeline loader to instantiate and execute tools dynamically:

```python
from tools.tool_registry import get_tool

# Retrieve the tool class by its registered name

ToolClass = get_tool("my_custom")   # Returns MyCustomTool class

tool = ToolClass()
result = tool.run(input_data="sample")

```

### Listing Available Capabilities

The `list_tools()` function returns all registered tool names, useful for generating documentation or user interfaces:

```python
from tools.tool_registry import list_tools

print("Registered tools:")
for name in list_tools():
    print(f" - {name}")

```

Example output:

```text
Registered tools:
 - wan_video
 - video_trimmer
 - my_custom

```

## Optimizing Performance with Lazy Loading

Tools that depend on large machine learning models or external APIs can slow down application startup if initialized immediately. The OpenMontage registry supports lazy loading patterns where the class is registered immediately but heavy resources are instantiated only upon first use.

```python

# tools/heavy_ai_tool.py

from .base import BaseTool

class HeavyAITool(BaseTool):
    name = "heavy_ai"
    description = "Runs a large AI model."
    
    _model = None

    @property
    def model(self):
        if self._model is None:
            from some_heavy_lib import Model
            self._model = Model.load()  # Expensive operation

        return self._model

    def run(self, **kwargs):
        result = self.model.predict(kwargs["input"])
        return result

```

In this implementation, [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) registers `HeavyAITool` during startup, but the `Model` class remains unloaded until the first call to `run()`.

## Summary

The OpenMontage **tool registry** implements a robust auto-discovery mechanism that eliminates manual plugin registration. Key implementation details include:

- **Filesystem Scanning**: Uses `pkgutil.iter_modules` to enumerate modules in `tools/` dynamically
- **Dynamic Importing**: Leverages `importlib.import_module` to load code at runtime with error isolation
- **Contract Validation**: Enforces the `BaseTool` interface requiring `name`, `description`, and `run()` attributes
- **Central Dictionary**: Stores classes in `registry[name]` for O(1) lookup by the pipeline loader
- **Lazy Loading**: Supports deferred initialization to maintain fast startup times for heavy tools

## Frequently Asked Questions

### Where is the tool registry logic implemented in OpenMontage?

The core discovery and registration logic resides in [[`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py)](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py). This module handles the package scanning, dynamic imports, and dictionary population. The `BaseTool` abstract class is typically defined in [`tools/base.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base.py), while [`lib/pipeline_loader.py`](https://github.com/calesthio/OpenMontage/blob/main/lib/pipeline_loader.py) consumes the registry to build execution pipelines.

### What interface must a class implement to be auto-discovered?

To be automatically registered, a class must inherit from `BaseTool` and implement three required elements: a `name` string attribute for registry lookups, a `description` string for documentation, and a `run(**kwargs)` method that executes the tool's logic. The registry inspects these attributes using `hasattr()` checks after importing the module.

### How does OpenMontage handle import errors during tool scanning?

The registry wraps each `importlib.import_module` call in a try-except block that catches `ImportError` exceptions. When a tool fails to import—due to missing dependencies or syntax errors—the error is logged and the scanner continues with the next module. This ensures that a single broken tool does not prevent the rest of the system from loading.

### Can tools be added without restarting the OpenMontage application?

The standard implementation scans for tools once during application startup when `tools/tool_registry` is first imported. To add tools without restarting, you would need to trigger a manual rescan of `pkgutil.iter_modules` and re-run the registration logic, though this is not the typical workflow. For production deployments, placing new tool files in the `tools/` directory and restarting the application is the recommended approach.