# How to Add New Providers to OpenMontage Without Code Changes Using the Selector-Provider Pattern

> Effortlessly add new providers to OpenMontage with zero code changes. Leverage the selector-provider pattern to automatically discover and integrate your custom capabilities.

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

---

**You can add new providers to OpenMontage by creating a single Python file that subclasses `BaseTool` and declares the target capability; the `ToolRegistry` automatically discovers it at runtime, enabling selectors to route requests to your new provider without modifying any existing code.**

OpenMontage separates request routing from service implementation through the **selector-provider pattern**, a capability-based architecture that eliminates hard-coded provider dependencies. This design allows developers to integrate new AI video, image, or audio generation services simply by dropping a new module into the `tools/` directory tree, without touching selectors or registry configuration.

## Understanding the Selector-Provider Architecture

OpenMontage organizes tools into two distinct roles that communicate through **capabilities**.

**Selectors** act as routing façades. For example, `VideoSelector` in [`tools/video/video_selector.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/video/video_selector.py) declares `provider = "selector"` and the capability `"video_generation"`. It never hard-codes provider names. Instead, its `_providers()` method queries the registry at runtime:

```python
def _providers(self) -> list[BaseTool]:
    from tools.tool_registry import registry
    registry.ensure_discovered()                     # discovers all tool modules

    return [t for t in registry.get_by_capability("video_generation")
            if t.name != self.name]                  # excludes self, returns providers

```

**Providers** are concrete implementations of `BaseTool` that declare the same capability (e.g., `"video_generation"`) but unique provider identifiers (e.g., `"fal"`, `"heygen"`, `"custom_ai"`). When `VideoSelector` receives a request, it builds a candidate list from these discovered providers, scores them using `lib.scoring.rank_providers`, and executes the best match.

## Step-by-Step Guide to Adding a New Provider

### 1. Create the Provider Module

Create a new Python file under the appropriate sub-package. For video generation, place it under `tools/video/`:

```bash
touch tools/video/my_new_provider.py

```

### 2. Subclass BaseTool and Declare Metadata

Inherit from `BaseTool` (defined in [`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py)) and set the required class attributes. The `capability` must match the selector you wish to integrate with:

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

class MyNewProvider(BaseTool):
    # Identity

    name = "my_new_provider"
    provider = "mynew"
    capability = "video_generation"
    tier = ToolTier.GENERATE
    runtime = ToolRuntime.HYBRID
    stability = ToolStability.BETA
    
    # Optional metadata for scoring

    best_for = ["quick_prototypes", "low_cost"]
    
    # Input validation schema

    input_schema = {
        "type": "object",
        "required": ["prompt"],
        "properties": {"prompt": {"type": "string"}},
    }

```

### 3. Implement Required Methods

You must implement `estimate_cost`, `estimate_runtime`, and `execute`. These enable the selector to perform resource estimation and actual generation:

```python
    def estimate_cost(self, inputs: dict) -> float:
        """Return estimated cost in USD."""
        return 0.02

    def estimate_runtime(self, inputs: dict) -> float:
        """Return estimated execution time in seconds."""
        return 5.0

    def execute(self, inputs: dict) -> ToolResult:
        """Call the provider API and return results."""
        # Integration with your AI service happens here

        video_url = self._call_external_api(inputs["prompt"])
        return ToolResult(success=True, data={"video_url": video_url})

```

### 4. Verify Registration

Once the file is saved, the registry will discover it automatically. You can verify registration at runtime:

```python
from tools.tool_registry import registry

registry.ensure_discovered()
providers = [t.name for t in registry.get_by_capability("video_generation")]
print(providers)

# Output includes: ['fal', 'heygen', 'my_new_provider', ...]

```

## How Runtime Discovery Works

The **tool registry** in [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) implements auto-discovery using Python's `pkgutil.walk_packages`. When `registry.ensure_discovered()` runs, it imports every module under the `tools/` directory tree once per process, building an in-memory catalog of all `BaseTool` subclasses.

Because selectors retrieve providers via `registry.get_by_capability("video_generation")` rather than static imports, the candidate list updates dynamically whenever new modules are added. The ranking and fallback mechanisms in `lib.scoring.rank_providers` operate on this dynamic list, ensuring the selector's routing logic remains unchanged regardless of how many providers exist.

This architecture means extending OpenMontage requires only adding a new file that fulfills the `BaseTool` contract. No configuration files, import statements, or selector modifications are necessary.

## Summary

- **Capability-based discovery**: Selectors query the registry by capability (e.g., `"video_generation"`), not by hard-coded provider names.
- **Zero-modification extension**: Adding [`my_new_provider.py`](https://github.com/calesthio/OpenMontage/blob/main/my_new_provider.py) under `tools/video/` automatically registers the provider without touching `VideoSelector` or `ToolRegistry`.
- **Auto-import mechanism**: `ToolRegistry.ensure_discovered()` uses `pkgutil.walk_packages` to load all modules under `tools/` at startup.
- **Consistent interface**: All providers must subclass `BaseTool` and implement `estimate_cost`, `estimate_runtime`, and `execute`.
- **Dynamic routing**: The selector's `_providers()` method builds candidate lists at runtime, enabling immediate participation of new providers in the ranking and execution flow.

## Frequently Asked Questions

### Do I need to register my new provider in a configuration file?

No. The `ToolRegistry` automatically discovers any Python module placed under the `tools/` directory using `pkgutil.walk_packages`. As long as your class subclasses `BaseTool` and is syntactically valid, it will be imported and registered when `registry.ensure_discovered()` executes.

### What capability value should I use for my provider?

You must declare a `capability` string that matches the selector's declared capability. For example, use `"video_generation"` to be discovered by `VideoSelector` in [`tools/video/video_selector.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/video/video_selector.py), or `"image_generation"` for image selectors. The capability string acts as the routing contract between selectors and providers.

### Can I override the selector's automatic provider choice?

Yes. Pass a `preferred_provider` parameter in your execution inputs (e.g., `{"preferred_provider": "mynew"}`). The selector's ranking engine in `lib.scoring.rank_providers` weighs this preference heavily when scoring candidates, though it may override the preference if the specified provider cannot fulfill the request's technical constraints or estimated cost limits.

### What methods must I implement when subclassing BaseTool?

You must implement `estimate_cost`, `estimate_runtime`, and `execute`. Optionally, you can define `input_schema` as a JSON Schema dictionary to enable automatic input validation. These methods are defined in [`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py) and are required for the selector to estimate resource usage and execute generation tasks.