# How to Build a Combo with a Custom Strategy in OmniRoute Dashboard

> Learn how to build a combo with a custom strategy in the OmniRoute dashboard. Define your strategy and register a handler function for advanced routing control.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-09-01

---

**You can build a combo with a custom strategy in the OmniRoute dashboard by selecting "Custom" in the Strategy dropdown, entering your strategy identifier, then registering a matching handler function in [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts) that returns an ordered list of targets.**

A **Combo** in OmniRoute groups multiple provider-model targets and routes requests through a configurable strategy that determines which target gets invoked. The dashboard lets operators create combos using built-in strategies like `priority`, `random`, `weighted`, and `auto`—or define **custom routing strategies** tailored to specific workload requirements. According to the OmniRoute source code, the strategy system is built around a pluggable dispatcher that decouples the UI configuration from the execution logic.

## How Combos and Strategies Work Together

The combo architecture separates configuration from execution across four distinct layers. Understanding this flow is essential before implementing a custom strategy.

| Layer | Purpose | Key Source File |
|-------|---------|---------------|
| **Combo definition** | Stores static configuration (name, providers, strategy, timeouts) in the database and materializes it per-request | [[`comboSetup.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboSetup.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/comboSetup.ts) |
| **Strategy resolution** | Normalizes the raw strategy string via `normalizeRoutingStrategy`, allowing custom identifiers to pass through | [`src/shared/constants/routingStrategies.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/routingStrategies.ts) (imported in [`comboSetup.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboSetup.ts)) |
| **Target ordering** | Builds an ordered target list according to the resolved strategy; custom strategies use the same plugin point | [[`targetResolution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/targetResolution.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/targetResolution.ts) |
| **Strategy dispatch** | Contains a registry of handlers; adding a custom strategy means registering a new function returning `ResolvedComboUnit[]` | [[`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/strategyDispatch.ts) |
| **Live UI feedback** | Reduces WebSocket events into a serializable model that drives the React-Flow graph in the Routing Studio | [[`comboFlowModel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboFlowModel.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/src/app/(dashboard)/dashboard/combos/live/comboFlowModel.ts) |

### Custom Strategy Execution Flow

When you build a combo with a custom strategy in the OmniRoute dashboard, the backend processes it through this pipeline:

1. **User input** — The dashboard's *Strategy* field accepts any string value
2. **Normalization** — `normalizeRoutingStrategy` maps the string to a known identifier; unrecognized strings become *custom* keys
3. **Dispatch** — [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts) looks up the key in its registry
4. **Execution** — The handler returns an ordered `ResolvedComboUnit[]` list that feeds the downstream executor

The dispatcher is implemented as a plain JavaScript object, so **adding a new strategy requires no changes to the routing engine**—only a new registry entry.

## Step-by-Step: Building a Combo with Custom Strategy

Follow these steps to build a combo with a custom strategy in the OmniRoute dashboard:

1. **Access the dashboard** — Navigate to `http://localhost:20128/dashboard` (or your configured port)

2. **Open the Combos section** — Select **Combos** → **Live** from the left navigation

3. **Create a new combo** — Click **+ New Combo**

4. **Configure combo metadata**
   - **Name**: Enter a unique identifier
   - **Providers / Models**: Add the provider-model pairs to include
   - **Strategy**: Select **Custom** from the dropdown, then type your strategy identifier (e.g., `my-fair-share`)

5. **Save the combo** — Click **Create** to persist to the database

6. **Edit as needed** — Use the pencil icon to modify the strategy field, then **Update**

7. **Monitor live traffic** — Open the **Routing Studio** view to see your custom strategy in action as a flow graph

> **Important:** The dashboard accepts any custom identifier, but requests will fail unless a matching handler exists in [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts).

## Implementing a Custom Strategy Handler

To make your custom strategy functional, register a handler in [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts). Below is a complete example implementing a "fair-share" strategy that rotates through targets evenly.

```typescript
// File: open-sse/services/combo/strategyDispatch.ts
import type { ResolvedComboUnit } from "./targetResolution";

const customStrategyHandlers: Record<
  string,
  (units: ResolvedComboUnit[]) => ResolvedComboUnit[]
> = {
  // Existing built-in handlers...

  "fair-share": (units) => {
    // Round-robin based on current second
    const now = Date.now();
    const index = Math.floor(now / 1000) % units.length;
    return [units[index]];
  },
};

/**
 * Dispatches strategy name to appropriate handler.
 * Unknown strategies fall back to built-in priority strategy.
 */
export function dispatchStrategy(
  strategy: string,
  units: ResolvedComboUnit[]
): ResolvedComboUnit[] {
  if (customStrategyHandlers[strategy]) {
    return customStrategyHandlers[strategy]!(units);
  }
  // Default handling for built-in strategies...
  return defaultStrategyDispatch(strategy, units);
}

```

### How the integration works:

- The UI stores `fair-share` (or your custom identifier) in the combo's `strategy` field
- `phaseComboSetup` in [`comboSetup.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboSetup.ts) normalizes the string during request processing
- `dispatchStrategy` locates the handler in `customStrategyHandlers` and returns ordered targets
- The executor runs the first eligible target from the returned array

## Key Source Files for Custom Strategy Development

| File | Purpose | Location |
|------|---------|----------|
| [`comboSetup.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboSetup.ts) | Request context setup, strategy resolution, combo-wide settings | [[`open-sse/services/combo/comboSetup.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/services/combo/comboSetup.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/comboSetup.ts) |
| [`targetResolution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/targetResolution.ts) | Candidate target building and strategy application | [[`open-sse/services/combo/targetResolution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/services/combo/targetResolution.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/targetResolution.ts) |
| [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts) | Registry and dispatcher for all routing strategies | [[`open-sse/services/combo/strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/services/combo/strategyDispatch.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/strategyDispatch.ts) |
| [`comboFlowModel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboFlowModel.ts) | WS event reducer for Routing Studio visualization | [`src/app/(dashboard)/dashboard/combos/live/comboFlowModel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/src/app/(dashboard)/dashboard/combos/live/comboFlowModel.ts) |
| [`autoStrategy.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/autoStrategy.ts) | Session-scoped identifier helper for strategy extensions | [[`open-sse/services/combo/autoStrategy.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/services/combo/autoStrategy.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.51/open-sse/services/combo/autoStrategy.ts) |

## Summary

Building a combo with a custom strategy in the OmniRoute dashboard involves three core actions:

- **Configure in UI** — Select "Custom" and enter your strategy identifier when creating or editing a combo
- **Register handler** — Add a function to `customStrategyHandlers` in [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts) that implements your routing logic
- **Validate execution** — Use the Routing Studio's live flow graph to confirm your strategy routes traffic as expected

The pluggable dispatcher design keeps the UI-to-core contract stable while allowing unlimited extensibility for operator-defined routing behaviors.

## Frequently Asked Questions

### What happens if I enter a custom strategy identifier that isn't registered in the backend?

The request will normalize the string through `normalizeRoutingStrategy`, but `dispatchStrategy` will fall back to the built-in `priority` strategy when no matching handler exists in `customStrategyHandlers`. Your combo will still function, but not with your intended custom logic. Check the logs to confirm your handler is being invoked.

### Can I use custom strategies with the live flow visualization in Routing Studio?

Yes. The [`comboFlowModel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/comboFlowModel.ts) reducer processes WebSocket events into a `ComboRunModel` that displays your custom strategy identifier as the *Strategy* node in the React-Flow graph. The visualization shows actual routing decisions in real-time regardless of whether the strategy is built-in or custom.

### Is there performance overhead when using custom versus built-in strategies?

No measurable overhead. Both custom and built-in strategies execute through the same `dispatchStrategy` pathway. The only difference is that built-in strategies may have optimized implementations in `defaultStrategyDispatch`, while custom strategies run your registered handler function. The target resolution and execution phases remain identical.

### How do I update a custom strategy without restarting the OmniRoute service?

The `customStrategyHandlers` registry is evaluated at request time, not at startup. If your deployment supports hot-reloading of TypeScript/JavaScript modules (or you're running in development mode with file watching), updating [`strategyDispatch.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/strategyDispatch.ts) will apply to subsequent requests immediately. For production deployments, a rolling restart is required unless using a dynamic module loader.