How Selector Tools Route Multi‑Provider Capabilities in OpenMontage

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

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

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

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

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 – Core selector implementation for the video_generation capability, containing _filter_candidates, _prepare_task_context, and preference handling logic.
  • tools/tool_registry.py – Auto-discovers and registers all provider tools via ensure_discovered(), supplying lookup APIs used by selectors.
  • tools/base_tool.py – Defines the BaseTool contract, status enums, and common metadata accessed by selectors during the routing process.
  • lib/scoring.py – Implements rank_providers and ProviderScore classes used for weighted provider ranking.
  • tests/tools/test_video_selector_routing.py – Verifies routing logic, preference handling, and fallback behavior.
  • 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 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, 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.

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 →