Small tasks. Small models. Hard problems. Heavy hitters.
Stop sending every delegated task to your biggest model. Task Router is an Oh My Pi (OMP) extension that reads a task's goal and solution space, then chooses a configurable model tier. Mechanical work can use your lightweight model; open-ended debugging can get more capable machinery. You choose the models behind each tier.
Task Router chooses model capability. OMP still chooses reasoning effort.
- Match the model to the problem. Route by ambiguity and reasoning demands, not file count or task length.
- Bring your own lineup. Use OMP role aliases or concrete model patterns; rename or add tiers without changing code.
- Keep control. Explicit model selections are preserved by default. OMP's reasoning effort and retry-fallback machinery stay in charge.
- See the decision. Inspect the selected tier, mapped model and classifier reason, or try a task without spawning an agent.
- Keep tasks moving. Classification failures fall back to your default tier. Unsafe routing leaves OMP's original model choice untouched.
Install into an existing OMP setup:
omp plugin install https://github.com/sdananjay/omp-task-router.gitFor a local checkout instead:
omp plugin link /path/to/omp-task-routerRestart OMP to load the extension. The defaults use these OMP roles:
| Tier | Default model | Typical fit |
|---|---|---|
cheap |
@tiny |
Mechanical changes with a clear recipe |
normal |
@task |
Scoped implementation with a known approach |
hard |
@slow |
Ambiguous debugging and consequential design choices |
Model availability and credentials come from your OMP configuration. Tier selection is a model judgment, not a fixed rule or a correctness guarantee.
/task-router opens the menu immediately—no delegated task is needed to
initialize it. Use /task-router status to inspect your configuration or
/task-router test to tune routing without launching an agent. After delegating
tasks, use /task-router history to inspect their routing decisions.
The classifier considers both task and solutionSpace: the goal and how
open-ended the route to a solution is.
task: Rename the authentication helper across all packages
solutionSpace: Mechanical rename; exact old and new names providedA large diff with a clear recipe is a candidate for cheap.
task: Fix intermittent deadlock in worker shutdown
solutionSpace: Cause unknown; multiple synchronization paths; no reliable reproA tiny eventual patch can still call for hard. The default prompt asks for the minimum tier capable of completing the task reliably, leaning toward capability when uncertain.
Defaults work without creating an extension config file. To customize them,
use the menu or edit ~/.omp/task-router/config.json (created on first save).
If XDG_CONFIG_HOME is set, the path is $XDG_CONFIG_HOME/task-router/config.json.
{
"enabled": true,
"classifier": {
"model": "@tiny",
"timeoutMs": 8000,
"cache": true
},
"defaultTier": "normal",
"respectExplicitModel": true,
"tiers": {
"cheap": { "model": "@tiny" },
"normal": { "model": "@task" },
"hard": { "model": "@slow" }
}
}Want local, cloud, and frontier instead? Those are just tier IDs. Each
model accepts an OMP role alias or model pattern. Point hard at a dedicated
role, for example:
# ~/.omp/agent/config.yml
modelRoles:
task_hard: openai-codex/gpt-6.1-sol:highThen set the extension's tiers.hard.model to @task_hard.
Omit classifier.prompt to keep the default prompt, or edit it with
/task-router prompt. Custom prompts use {tiers} to insert the configured
tier IDs. After manual config changes, use /task-router reload.
No. Ordinary @task delegations are still routed. The setting protects
specific model choices, not the generic task role. It defaults to true.
For safely correlated task spawns while routing is enabled:
| Model selection | With respectExplicitModel: true |
|---|---|
Inherited @task or @default |
Classify and route |
modelRoles.task points to a concrete model |
Classify and route; the role is still task |
Task explicitly passes "model": "@task" or "@default" |
Classify and route |
| Task selects a concrete model or pattern | Preserve the selection; skip classification |
Selected role matches a configured tier ID, such as @hard |
Classify and route |
Any other role alias, such as @smol |
Preserve the selection; skip classification |
OMP accepts an optional model selector on each tasks[] item (or on a flat
single-task call). The parent agent supplies it when constructing the task:
{
"context": "Investigate the shutdown deadlock.",
"tasks": [{
"name": "debug-shutdown",
"task": "Find the cause of the worker shutdown deadlock.",
"solutionSpace": "Unknown cause; inspect synchronization paths.",
"model": "openai/gpt-5.4"
}]
}With the default setting, that concrete selection is preserved. Agent model
definitions and per-agent overrides can also supply a protected selection.
The policy checks the effective role, not whether a model argument was
explicitly supplied; generic and tier-named roles remain routable either way.
Set respectExplicitModel to false to allow routing to replace protected
model selections too. The on/off toggle changes enabled only—it never
changes respectExplicitModel. When enabled is false, automatic routing
is bypassed regardless of the explicit-model setting.
| Command | What it does |
|---|---|
/task-router |
Open the interactive menu |
/task-router status |
Show the current configuration and history count |
/task-router history |
Show recent routing decisions |
/task-router test |
Classify a task and solution space without spawning an agent |
/task-router prompt |
Edit the classifier prompt |
/task-router tiers |
Edit tiers and model mappings |
/task-router enable / /task-router disable |
Toggle routing |
/task-router reload |
Reload the config file |
History is in-memory and session-scoped. Correlated decisions include the tier, model, effort, outcome and task preview. Test output also shows the classifier reason when one is supplied.
For a safely correlated task spawn, the extension:
- Captures the task text without changing tool arguments.
- Calls the classifier through OMP's
completeSimple, with reasoning disabled and a small output budget. - Maps the returned tier to your chosen model and returns it through OMP's
before_subagent_spawnhook. OMP handles model resolution and retry fallbacks.
The classifier adds a provider request on a cache miss. Cached classifications include task, solution space, classifier model/prompt and ordered tier IDs. Changing a model mapping reuses the classification; changing tier IDs reclassifies.
Task Router configuration is currently global, while OMP model roles remain project-aware.
Task Router maps tiers to OMP model selectors such as "@tiny", "@task", and "@slow". These selectors are resolved by OMP using the effective configuration for the current project.
This means projects can provide different "modelRoles" mappings through their project-specific OMP configuration without changing Task Router:
Task Router Project A Project B
cheap → @tiny → @tiny → model A @tiny → model B
normal → @task → @task → model C @task → model D
hard → @slow → @slow → model E @slow → model F
Task Router's own settings — including tiers, classifier model, classifier prompt, cache settings, and routing behaviour — are currently global.
Project-specific Task Router settings are not yet supported.
- Concrete model selectors and non-generic, non-tier role aliases are
preserved when
respectExplicitModelis enabled; generic@task/@defaultand tier-named roles remain routable, as described above. - Classifier failure, timeout or unusable output uses
defaultTier; an unresolvable fallback leaves OMP's original model in place. - Malformed config disables routing rather than silently rerouting tasks.
- Correlation never relies on FIFO order or names alone. Entries are matched by tool-call ID/index or progress-bound agent IDs. Rejected and failed calls purge their pending text; missing or ambiguous keys leave the spawn untouched.
- Not every spawn can be routed yet. Named synchronous and early speculative
launches without a progress-bound ID keep OMP's model. Complete coverage needs
origin call ID and batch index in OMP's spawn event. Eval
agent()spawns are also left untouched.
The extension does not rewrite task.effort, your model-role definitions, or
retry-fallback settings.
bun install
bun test
bunx tsc --noEmit
bun run tests/lifecycle.test.ts --smokeThe lifecycle test and standalone smoke use actual OMP SDK sessions, TaskTool execution and child agents with a local scripted inference provider.
For implementation details, see the design, OMP integration notes, and prior art.