Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Customize Routing Strategies in OmniRoute

Things on this page are fragmentary and immature notes/thoughts of the author. Please read with your own judgement!

Built-in Routing Strategies

OmniRoute’s combo engine ships 19 built-in routing strategies (see 🎯 Combos — The Flagship) . OmniRoute also provides zero-config auto routing combos . More docs on routing strategies can be found at OmniRoute Docs - Routing .

Define Customized Routing Strategies

In OmniRoute, customized routing strategies are configured through Combos (chains of upstream providers/models with specific rules) or by registering a custom JavaScript/TypeScript strategy implementation. We focus on defining customized routing strategies through Combos in this article.

The Combo schema

Every combo — whatever built-in strategy it uses — shares one unified top-level schema:

{ name: string; strategy: RoutingStrategy; models: ComboStep[]; config?: {...} }

models[] is an array of ComboSteps (src/lib/combos/steps.ts) — the same union is used by every strategy, and a plain "provider/model" string is also accepted as shorthand (it auto-normalizes to the kind: "model" shape):

type ComboStep =
  | { kind: "model"; model; providerId?; connectionId?; allowedConnectionIds?; weight; label?; tags?;
      prompt?; fallbackOnlyOnQuotaExhaustion? }
  | { kind: "combo-ref"; comboName; weight; label?; fallbackOnlyOnQuotaExhaustion? }
  | { kind: "provider-wildcard"; providerId; modelPattern; connectionId?; allowedConnectionIds?; weight; label? }

What differs between strategies is only which of these fields they actually read, and which extra strategy-specific keys live under config:

StrategyReads from models[i]Extra strategy-specific config
priority, fill-firstarray order only
weightedweight (weighted draw)stickyWeightedLimit
round-robinarray order (rotation)concurrencyPerModel, queueTimeoutMs, queueDepth, stickyRoundRobinLimit
random, strict-randomorder ignored (shuffled/deck-drawn)
least-used, cost-optimized, context-optimized, p2c, lkgp, reset-awarere-sorted by its own metricreset-aware: resetAwareSessionWeight, resetAwareWeeklyWeight, resetAwareTieBandPercent, resetAwareExhaustionGuardPercent
context-relayorder + handoff eligibilityhandoffThreshold, handoffModel, handoffProviders, maxMessagesForSummary
autoscored via 9-factor engineconfig.auto.{weights,explorationRate,candidatePool,routerStrategy,modePack,budgetCap,...}

(reset-window, headroom, cache-optimized, fusion, pipeline are the remaining strategies from the full list in the previous section and aren’t detailed here.)


Nesting: a models[] entry can itself be another Combo

A models[] entry isn’t limited to a single provider/model — it can also be {"kind": "combo-ref", "comboName": "..."}, referencing another, already-created combo by name. This is the entire nesting mechanism: there’s no inline/anonymous {strategy, models} sub-object, and no nodes/targets key — comboName is always a lookup against existing combos, so the inner combo has to be created first, then referenced.

config.nestedComboMode (default "flatten") controls what happens to that reference:

  • "flatten" — the referenced combo’s models[] is expanded inline into the parent’s flat list, in the child’s declared order. The child’s own strategy is not honored — it behaves like a priority list once flattened.

  • "execute" — the combo-ref becomes a black-box unit: the parent strategy picks it as one opaque target, and if selected, the child combo actually runs its own strategy, retries, and sticky state (open-sse/services/combo/runtimeUnits.ts::executeRuntimeUnitCombo). This is what genuinely enables hierarchical/nested strategies. Only honored when the parent’s strategy is one of: priority, round-robin, random, strict-random, weighted, fill-first — scoring strategies (cost-optimized, context-optimized, reset-aware, auto) and legacy ones (lkgp, p2c, least-used) still flatten.

Safety rails: maxComboDepth (default 3, hard cap 10), cycle detection (validateComboDAG, visitedComboNames; diamond graphs allowed, back-references rejected), and a shared attemptBudget across the whole tree so nested retries can’t blow up request cost.

For example, a combo that performs weighted load balancing between two primary providers, and if both fail, falls back via priority to a cheap local model — first create the inner combo, then reference it from the outer one:

// 1. create the inner combo first
{
  "name": "weighted-primary-pool",
  "strategy": "weighted",
  "models": [
    { "kind": "model", "model": "anthropic/claude-3-5-sonnet", "weight": 70 },
    { "kind": "model", "model": "openai/gpt-4o", "weight": 30 }
  ]
}
// 2. reference it from the outer combo, with execute mode so it runs its own strategy
{
  "name": "my-resilient-route",
  "strategy": "priority",
  "config": { "nestedComboMode": "execute" },
  "models": [
    { "kind": "combo-ref", "comboName": "weighted-primary-pool" },
    "groq/llama-3.3-70b"
  ]
}

Add/Update Customized Combo Route Via the REST API

import os
import requests
import subprocess as sp
from typing import Any
import ipywidgets as widgets
from IPython.display import display
password_box = widgets.Password(
    description="Enter gopass password:",
    disabled=False
)
display(password_box)
Loading...
env = os.environ.copy()
env["GOPASS_AGE_PASSWORD"] = password_box.value
token = sp.run(
    ["gopass", "show", "-o", "omniroute/token"],
    #check=True,
    capture_output=True,
    text=True,
    env=env,
).stdout
def add_combo(combo, token: str, raise_for_status: bool = True) -> dict[str, Any]:
    resp = requests.post(
        "http://localhost:20128/api/combos",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {token}",
        },
        json=combo,
    )
    if raise_for_status:
        resp.raise_for_status()
    return resp.json()


def add_combos(combos, token: str, raise_for_status: bool = True) -> dict[str, Any]:
    responses = []
    for combo in combos:
        resp = add_combo(
            combo=combo,
            token=token,
            raise_for_status=raise_for_status,
        )
        responses.append(resp)
    return responses
combos = [
    {
        "name": "user/llm-flash-free",
        "strategy": "auto",
        "models": [
            "gemini/gemini-3.1-flash-lite",
            "gemini/gemini-3.5-flash",
            "oc/deepseek-v4-flash-free",
        ],
    },
    {
        "name": "user/llm-paid-cheap",
        "strategy": "auto",
        "models": [
            "ds/deepseek-v4-flash",
        ],
    },
    {
        "name": "user/vcs-commit-message",
        "strategy": "priority",
        "config": {
            "nestedComboMode": "execute",
        },
        "models": [
            {
                "kind": "combo-ref",
                "comboName": "user/llm-flash-free",
            },
            {
                "kind": "combo-ref",
                "comboName": "user/llm-paid-cheap",
            },
        ],
    },
]
add_combos(combos, token=token)
[{'name': 'user/llm-flash-free', 'models': [{'id': 'user-llm-flash-free-model-1-gemini-gemini-3-1-flash-lite', 'kind': 'model', 'model': 'gemini/gemini-3.1-flash-lite', 'providerId': 'gemini', 'weight': 0}, {'id': 'user-llm-flash-free-model-2-gemini-gemini-3-5-flash', 'kind': 'model', 'model': 'gemini/gemini-3.5-flash', 'providerId': 'gemini', 'weight': 0}, {'id': 'user-llm-flash-free-model-3-oc-deepseek-v4-flash-free', 'kind': 'model', 'model': 'oc/deepseek-v4-flash-free', 'providerId': 'oc', 'weight': 0}], 'strategy': 'auto', 'id': '2955928a-2578-449f-8513-3ed6bde2d900', 'config': {}, 'isHidden': False, 'sortOrder': 2, 'createdAt': '2026-08-18T04:50:58.508Z', 'updatedAt': '2026-08-18T04:50:58.508Z', 'version': 2}, {'name': 'user/llm-paid-cheap', 'models': [{'id': 'user-llm-paid-cheap-model-1-ds-deepseek-v4-flash', 'kind': 'model', 'model': 'ds/deepseek-v4-flash', 'providerId': 'ds', 'weight': 0}], 'strategy': 'auto', 'id': 'ddb9cefc-c4a4-489f-bad6-867a91f04fd5', 'config': {}, 'isHidden': False, 'sortOrder': 3, 'createdAt': '2026-08-18T04:50:58.512Z', 'updatedAt': '2026-08-18T04:50:58.512Z', 'version': 2}, {'name': 'user/vcs-commit-message', 'models': [{'id': 'user-vcs-commit-message-ref-1-user-llm-flash-free', 'kind': 'combo-ref', 'comboName': 'user/llm-flash-free', 'weight': 0}, {'id': 'user-vcs-commit-message-ref-2-user-llm-paid-cheap', 'kind': 'combo-ref', 'comboName': 'user/llm-paid-cheap', 'weight': 0}], 'strategy': 'priority', 'config': {'nestedComboMode': 'execute'}, 'id': 'd14c738d-40fe-473f-b574-39f39ffe6f52', 'isHidden': False, 'sortOrder': 4, 'createdAt': '2026-08-18T04:50:58.520Z', 'updatedAt': '2026-08-18T04:50:58.520Z', 'version': 2}]