# How Selector Tools Route Multi‑Provider Capabilities in OpenMontage

> Discover how OpenMontage selector tools route multi-provider capabilities. Learn about auto-discovery, filtering, scoring, and execution for optimal resource management.

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

---

**OpenMontage selector tools route multi-provider capabilities by auto-discovering providers, filtering candidates by capability, scoring them against task context, and executing the best match while preserving user preferences and fallback options.**

OpenMontage implements a sophisticated routing layer that decouples capability-level requests from concrete provider implementations. The **selector pattern** enables dynamic multi-provider routing without hard-coding provider names, allowing new video, image, or text-to-speech providers to be added simply by dropping new tool files into the `tools/` directory. This architecture ensures that requests like "create a video" automatically reach the most appropriate backend based on current availability, task requirements, and user preferences.

## The Selector Pattern Architecture

The selector architecture in OpenMontage relies on a thin orchestration layer that sits between the application logic and provider-specific implementations. Each capability—such as video generation, image creation, or text-to-speech—has its own selector class (e.g., `VideoSelector`) that manages a pool of provider tools.

At initialization, selectors invoke `ToolRegistry.ensure_discovered()` to populate the provider pool. This method walks the `tools/` package, imports every module, and registers any concrete subclass of `BaseTool`. This auto-discovery mechanism means developers can add new providers by creating new tool files without modifying selector code.

## Step‑by‑Step Routing Process

The routing logic follows a deterministic pipeline from discovery to execution.

### 1. Auto‑Discovery of Providers

When a selector is instantiated, it triggers `ToolRegistry.ensure_discovered()` defined in [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py). This method scans the filesystem and imports modules to register all available tools, making them available for the selection process.

### 2. Filtering Capable Candidates

The selector examines incoming payloads and removes providers that cannot satisfy the requested operation. In [`tools/video/video_selector.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/video/video_selector.py), the `_filter_candidates` method (lines 84‑132) eliminates providers missing required model hints or lacking support for custom workflows when such capabilities are requested.

### 3. Building the Scoring Context

Selectors normalize task context through `_prepare_task_context`, generating a structured representation from the prompt, capability, and operation type. This context feeds into `rank_providers` (defined in [`lib/scoring.py`](https://github.com/calesthio/OpenMontage/blob/main/lib/scoring.py)), which returns a list of `ProviderScore` objects ordered by weighted suitability scores.

### 4. Honoring User Preferences

If the caller specifies a `preferred_provider`, the selector checks whether that provider's score falls within `preferred_provider_gap` (default **0.15**) of the top score. As implemented in [`tools/video/video_selector.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/video/video_selector.py) (lines 21‑33), if the gap condition is met, the preferred provider wins; otherwise, the highest-scored provider executes.

### 5. Tool Selection and Schema Adaptation

The selector maps rankings to actual tool instances via `_tool_for` and selects the best candidate through `_select_best_tool`. Before delegation, the selector adapts input schemas—rewriting keys like `prompt` to `query` for stock tools or uploading reference images when providers require URLs instead of local paths.

### 6. Execution and Result Enrichment

After the provider's `execute` method runs, the selector annotates the result with metadata including `selected_tool`, `selected_provider`, scoring explanations, and alternative providers considered. This enrichment appears in [`tools/video/video_selector.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/video/video_selector.py) (lines 52‑66).

## Provider Preference and Fallback Handling

OpenMontage selectors implement graceful degradation through intelligent fallback mechanisms. The `fallback_tools` and `fallback_tools_for` properties supply static fallback lists, while the routing logic automatically drops image-only fallbacks for operations requiring motion.

If a provider becomes unavailable due to missing API keys or network issues, the selector automatically falls back to the next best-scoring option. This ensures robust multi-provider routing that maintains service continuity even when individual providers fail.

## Practical Code Examples

### Basic Text‑to‑Video Routing

```python
from tools.video.video_selector import VideoSelector

result = VideoSelector().execute({
    "prompt": "A sunrise over a misty forest",
    "operation": "text_to_video",
    "preferred_provider": "auto",      # let the selector choose

})
print(result.data["selected_provider"])   # e.g. "fal"

print(result.data["selection_reason"])    # human‑readable scoring explanation

```

### Forcing a Specific Provider

```python
result = VideoSelector().execute({
    "prompt": "A futuristic city skyline at night",
    "operation": "text_to_video",
    "preferred_provider": "heygen",
    "preferred_provider_gap": 0.2,   # allow a slightly larger gap

})

```

### Routing to Workflow‑Capable Providers

```python
result = VideoSelector().execute({
    "operation": "text_to_video",
    "workflow_path": "my_workflow.json",
    "output_node": "OUTPUT_0",
})

# The selector will pick a provider that advertises `custom_workflow` support.

```

### Inspecting the Provider Matrix

```python
matrix = VideoSelector().provider_matrix

# {'fal': {'tool': 'fal_video', 'strength': 'high‑quality video generation'},

#  'heygen': {'tool': 'heygen_video', 'strength': 'AI avatar video'},

#  ...}

```

## Key Source Files

- **[`tools/video/video_selector.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/video/video_selector.py)** – Core selector implementation for the *video_generation* capability, containing `_filter_candidates`, `_prepare_task_context`, and preference handling logic.
- **[`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py)** – Auto-discovers and registers all provider tools via `ensure_discovered()`, supplying lookup APIs used by selectors.
- **[`tools/base_tool.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/base_tool.py)** – Defines the `BaseTool` contract, status enums, and common metadata accessed by selectors during the routing process.
- **[`lib/scoring.py`](https://github.com/calesthio/OpenMontage/blob/main/lib/scoring.py)** – Implements `rank_providers` and `ProviderScore` classes used for weighted provider ranking.
- **[`tests/tools/test_video_selector_routing.py`](https://github.com/calesthio/OpenMontage/blob/main/tests/tools/test_video_selector_routing.py)** – Verifies routing logic, preference handling, and fallback behavior.
- **[`docs/ARCHITECTURE.md`](https://github.com/calesthio/OpenMontage/blob/main/docs/ARCHITECTURE.md)** – High-level overview of the selector/provider architecture (section "Selector Pattern").

## Summary

- **Dynamic Discovery**: Selectors automatically register new providers by scanning the `tools/` directory, requiring no code changes to support new backends.
- **Intelligent Filtering**: The `_filter_candidates` method removes incapable providers based on operation requirements and model hints.
- **Preference Awareness**: User-specified providers are respected when competitive, using a configurable `preferred_provider_gap` tolerance.
- **Schema Adaptation**: Selectors rewrite input keys and handle media uploads to match provider-specific requirements before execution.
- **Graceful Degradation**: Automatic fallback to next-best providers ensures reliability when primary options fail.

## Frequently Asked Questions

### How does OpenMontage discover new providers automatically?

OpenMontage uses `ToolRegistry.ensure_discovered()` in [`tools/tool_registry.py`](https://github.com/calesthio/OpenMontage/blob/main/tools/tool_registry.py) to walk the `tools/` package at runtime. It imports every module and registers any concrete subclass of `BaseTool`, making new providers immediately available to selectors without code modifications.

### What happens if my preferred provider cannot handle the request?

If the preferred provider is filtered out during capability matching, or if its score falls outside the `preferred_provider_gap` (default 0.15) of the top-scoring provider, the selector automatically chooses the highest-scoring capable provider instead. The selection reason in the result metadata explains this decision.

### Can selectors handle custom workflows like ComfyUI?

Yes. When a request includes `workflow_path` and `output_node` parameters, the `VideoSelector._filter_candidates` method restricts candidates to providers advertising `custom_workflow` support. This ensures custom workflows route only to compatible backends.

### Where is the provider scoring logic implemented?

The scoring engine lives in [`lib/scoring.py`](https://github.com/calesthio/OpenMontage/blob/main/lib/scoring.py), which provides the `rank_providers` function. This function generates `ProviderScore` objects based on normalized task context from `_prepare_task_context`, enabling weighted comparison of provider suitability for specific operations.