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:
| Strategy | Reads from models[i] | Extra strategy-specific config |
|---|---|---|
priority, fill-first | array order only | — |
weighted | weight (weighted draw) | stickyWeightedLimit |
round-robin | array order (rotation) | concurrencyPerModel, queueTimeoutMs, queueDepth, stickyRoundRobinLimit |
random, strict-random | order ignored (shuffled/deck-drawn) | — |
least-used, cost-optimized, context-optimized, p2c, lkgp, reset-aware | re-sorted by its own metric | reset-aware: resetAwareSessionWeight, resetAwareWeeklyWeight, resetAwareTieBandPercent, resetAwareExhaustionGuardPercent |
context-relay | order + handoff eligibility | handoffThreshold, handoffModel, handoffProviders, maxMessagesForSummary |
auto | scored via 9-factor engine | config.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’smodels[]is expanded inline into the parent’s flat list, in the child’s declared order. The child’s ownstrategyis not honored — it behaves like aprioritylist 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 Anyimport ipywidgets as widgets
from IPython.display import displaypassword_box = widgets.Password(
description="Enter gopass password:",
disabled=False
)
display(password_box)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,
).stdoutdef 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 responsescombos = [
{
"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}]