How Model Roles and Routing (default, smol, slow, plan) Are Configured in Oh-My-Pi
Oh-My-Pi uses a role-based routing system where abstract roles like "smol," "slow," and "plan" are mapped to concrete LLM models through user-defined aliases and priority chains, resolved at runtime by the model-resolver module.
The omp CLI in the can1357/oh-my-pi repository implements a sophisticated model roles and routing architecture that decouples task requirements from specific provider implementations. This design allows you to configure speed tiers and reasoning levels centrally, ensuring lightweight tasks use fast models while complex reasoning flows through capable alternatives.
What Are Model Roles?
Model roles are named slots that represent different capability or speed requirements. According to packages/coding-agent/src/config/model-registry.ts (line 81), the system recognizes eight predefined role identifiers:
defaultsmol(fast, lightweight)slow(capable, reasoning-heavy)vision(multimodal)plan(architecture/design)designer(UI/UX generation)commit(commit message generation)task(general task execution)
Each role is stored as a key in the modelRoles record defined in packages/coding-agent/src/config/settings-schema.ts (lines 317–323), where the value is a model pattern string that may include an optional thinking-level suffix (e.g., :high or :xhigh).
The Model Routing Pipeline
When omp needs to resolve a model for a specific task, it executes a four-stage pipeline defined in packages/coding-agent/src/config/model-resolver.ts:
1. Role Lookup and Alias Expansion
The system first retrieves the configured pattern for the requested role. If you specify an alias like pi/smol, the expandRoleAlias function (lines 15–30 in model-resolver.ts) expands this to the actual model pattern stored in your settings.
2. Pattern Parsing
The resolveModelRoleValue function (lines 601–640) parses the model string, extracting the base identifier and any thinking-level selectors (e.g., anthropic/claude-sonnet-4:xhigh). This produces a ResolvedModelRoleValue object containing the target model and metadata.
3. Priority Chain Resolution
For the smol and slow roles, the system consults packages/coding-agent/src/priority.json. This file contains ordered arrays listing preferred model identifiers for each speed tier. For example, the smol array might list "cerebras/zai-glm-4.7" as the first preference, ensuring the fastest available model is selected.
4. Concrete Model Selection
Finally, resolveModelFromString → parseModelPattern → parseModelPatternWithContext (lines 730–773) performs fuzzy matching against the available model catalog. If multiple roles are specified (e.g., ["smol","default","slow"]), resolveRoleSelection (lines 998–1015) walks the ordered list and returns the first resolvable model.
Configuring Roles via CLI
You can override role assignments directly from the command line. The CLI parser in packages/coding-agent/src/cli/args.ts (lines 28–33) handles flags that write to the modelRoles record:
# Assign a fast model to the "smol" role for quick tasks
omp --smol anthropic/claude-haiku-4-5
# Set a high-capability model for the "slow" role
omp --slow gpt-5.4
# Configure the "plan" role with explicit thinking level
omp --plan anthropic/claude-sonnet-4-5:xhigh
These flags update the runtime settings, which persist according to your configuration storage preference.
Programmatic Configuration
For embedded usage or plugins, modify role bindings through the Settings API:
import { Settings } from "@oh-my-pi/pi-coding-agent/config";
const settings = Settings.getInstance();
// Use an alias with explicit thinking level
settings.setModelRole("smol", "pi/smol:high");
// Configure the planner role
settings.setModelRole("plan", "anthropic/claude-sonnet-4-5:xhigh");
The setModelRole method writes directly into the modelRoles record, which is then referenced during the resolution pipeline.
Internal Resolution API
Extensions and internal utilities can resolve models explicitly using the resolver module:
import { resolveModelRoleValue, findSmolModel } from "@oh-my-pi/pi-coding-agent/config/model-resolver";
import { Settings } from "@oh-my-my-pi/pi-coding-agent/config";
const settings = Settings.getInstance();
const availableModels = modelRegistry.getAvailable();
// Full resolution with context
const roleValue = settings.getModelRole("slow");
const resolved = resolveModelRoleValue(roleValue, availableModels, { settings });
if (resolved.model) {
console.log(`Provider: ${resolved.model.provider}`);
console.log(`Thinking level: ${resolved.thinkingLevel ?? "default"}`);
}
// Fast helper for title generation (findSmolModel, lines 1325-1362)
const quickModel = await findSmolModel(modelRegistry, settings.getModelRole("smol"));
const title = await generateTitle(quickModel, diffContent);
The findSlowModel helper (lines 1460–1480) provides similar functionality for deep-reasoning tasks, selecting the most capable available model from the slow priority chain.
Default Cycle Order
When cycling through models via the UI button, omp uses the DEFAULT_CYCLE_ORDER defined in packages/coding-agent/src/config/settings-schema.ts (lines 890–894):
["smol", "default", "slow"]
This order also serves as the fallback chain when a specific role is undefined in user settings. If no role matches, the system defaults to the first enabled model with a valid API key.
Summary
- Model roles (
default,smol,slow,plan, etc.) are abstract identifiers defined inmodel-registry.tsand stored in themodelRolessettings record. - Routing occurs through the
model-resolver.tspipeline: role lookup → alias expansion → pattern parsing → priority chain resolution. - Priority chains for
smolandslowroles are configured inpriority.json, ensuring appropriate model selection based on speed or capability requirements. - CLI flags (
--smol,--slow,--plan) and the Settings API allow runtime configuration of role-to-model mappings. - Fallback behavior follows the
DEFAULT_CYCLE_ORDER(smol→default→slow) when roles are unspecified.
Frequently Asked Questions
What is the difference between the "smol" and "slow" model roles?
The smol role targets fast, low-latency models suitable for quick tasks like title generation or lightweight completions, while the slow role targets high-capability models optimized for deep reasoning and complex planning tasks. According to priority.json, each maintains a separate ordered list of preferred providers and model identifiers.
How do I set a default model if a specific role is not configured?
If a role entry is missing from the modelRoles record, the system falls back to the DEFAULT_CYCLE_ORDER (defined in settings-schema.ts lines 890–894), checking smol, then default, then slow. If none resolve, omp selects the first enabled model in the catalog that has a valid API key configured.
What happens if my configured role model is unavailable?
The resolution pipeline in resolveRoleSelection (lines 998–1015) walks the ordered priority list for the role until it finds a model that matches the pattern and is available in the current catalog. If no matches exist, the function returns null, and the calling utility typically falls back to the next role in the default cycle.
How do thinking level selectors (:high, :xhigh) affect model selection?
When parsing the model string, resolveModelRoleValue extracts thinking-level suffixes (e.g., :high, :xhigh) and stores them in the thinkingLevel property of the resolved value. These selectors instruct compatible providers to use extended reasoning modes, though the final availability depends on whether the selected model and provider support the requested thinking level.
Where are role aliases like "pi/smol" defined?
Role aliases are expanded by the expandRoleAlias function in model-resolver.ts (lines 15–30). The pi/smol alias specifically maps to the value stored in the smol key of the modelRoles record, allowing you to reference role configurations indirectly without hardcoding specific model identifiers throughout your code or CLI commands.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →