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

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). 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.


# 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.

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 or imported via 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.


# 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:


# 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 automatically discovers 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:

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:

from tools.tool_registry import list_tools

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

Example output:

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.


# 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 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). This module handles the package scanning, dynamic imports, and dictionary population. The BaseTool abstract class is typically defined in tools/base.py, while 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →