Submatter/docs/omniroute-role-mapping.md

314 lines
10 KiB
Markdown
Raw Permalink Normal View History

# OmniRoute Role Mapping
**Status:** Draft
**Date:** 2026-09-07
**Purpose:** Map Echodo pipeline roles to OmniRoute profiles so the orchestrator/worker can route each role to the right model automatically.
---
## Overview
OmniRoute is an OpenAI-compatible gateway that fronts **929+ models** with a small set of **dynamic profiles**. Instead of configuring individual model IDs per role, we configure **profiles** that OmniRoute resolves to a live model endpoint:
| Profile | Purpose | Typical Resolves To |
|---------|---------|---------------------|
| `auto` | Fastest general-purpose | Small/fast model |
| `auto/coding` | Code-specialized | Coding-tuned model |
| `auto/coding-pro` | Complex code tasks, reviews | High-quality coding model |
| `auto/reasoning` | Planning, reasoning, architecture | Reasoning-tuned model |
| `auto/reasoning-pro` | Complex reasoning, escalation | Top reasoning model |
OmniRoute is integrated into Echodo as an **`openai_compatible` provider** — the same way Ollama and MLX are, but pointing at the OmniRoute base URL and using profile names as `modelId`.
---
## Profile → Role Matrix
### Recommended Defaults
| Role ID | OmniRoute Profile | Why |
|---------|-------------------|-----|
| `role-conductor` | `auto/reasoning` | Convoy kickoff: classify, skip flags, success metric |
| `role-architect` | `auto/reasoning` | Design, brief decomposition, architecture |
| `role-architect` (escalation) | `auto/reasoning-pro` | Complex/hard design problems |
| `role-ia-architect` | `auto/reasoning` | Information architecture planning |
| `role-implementer` | `auto/coding` | Default code implementation |
| `role-implementer` (escalation) | `auto/coding-pro` | After 2 failed attempts |
| `role-reviewer` | `auto/coding-pro` | Two-axis code review needs quality |
| `role-ux-reviewer` | `auto/coding` | UI review |
| `role-doc-writer` | `auto/coding` | Docs are simpler than code |
| `role-design-system-auditor` | `auto/coding` | Design system review |
| `role-a11y-auditor` | `auto/coding` | Accessibility review |
| `gate-copilot` | `auto/reasoning` | Summarize for human gate |
| `chat-default` | `auto` | Web chat default |
### OmniRoute-Only Workspace (Simple Setup)
If a workspace does **not** use local models (no Ollama/MLX), just create **5 OmniRoute providers** (one per profile) and route every role to the relevant profile:
```yaml
# Workspace model providers (via Settings → AI → Providers)
- alias: omni-auto
provider_type: openai_compatible
model_id: auto
base_url: ${OMNIROUTE_BASE_URL} # e.g. https://omniroute.example.com/v1
api_key_env_var: OMNIROUTE_API_KEY
metadata: { tier: auto, provider: omniroute }
- alias: omni-coding
provider_type: openai_compatible
model_id: auto/coding
base_url: ${OMNIROUTE_BASE_URL}
api_key_env_var: OMNIROUTE_API_KEY
metadata: { tier: auto, provider: omniroute }
- alias: omni-coding-pro
provider_type: openai_compatible
model_id: auto/coding-pro
base_url: ${OMNIROUTE_BASE_URL}
api_key_env_var: OMNIROUTE_API_KEY
metadata: { tier: auto, provider: omniroute }
- alias: omni-reasoning
provider_type: openai_compatible
model_id: auto/reasoning
base_url: ${OMNIROUTE_BASE_URL}
api_key_env_var: OMNIROUTE_API_KEY
metadata: { tier: auto, provider: omniroute }
- alias: omni-reasoning-pro
provider_type: openai_compatible
model_id: auto/reasoning-pro
base_url: ${OMNIROUTE_BASE_URL}
api_key_env_var: OMNIROUTE_API_KEY
metadata: { tier: auto, provider: omniroute }
```
Then map roles:
| Role ID | Alias |
|---------|-------|
| `role-conductor` | `omni-reasoning` |
| `role-architect` | `omni-reasoning` |
| `role-ia-architect` | `omni-reasoning` |
| `role-implementer` | `omni-coding` |
| `role-reviewer` | `omni-coding-pro` |
| `role-ux-reviewer` | `omni-coding` |
| `role-doc-writer` | `omni-coding` |
| `role-design-system-auditor` | `omni-coding` |
| `role-a11y-auditor` | `omni-coding` |
| `gate-copilot` | `omni-reasoning` |
| `chat-default` | `omni-auto` |
### Hybrid Setup (Local + OmniRoute)
For a homelab that wants free local inference AND cloud fallback:
| Role ID | Default | Escalation |
|---------|---------|------------|
| `role-conductor` | `omni-reasoning` (cloud) | — |
| `role-architect` | `omni-reasoning-pro` (cloud) | — |
| `role-implementer` | `qwen-coder-mlx` (local) | `omni-coding-pro` (cloud) |
| `role-reviewer` | `qwen-coder-mlx` (local) | `omni-coding-pro` (cloud) |
| `role-ux-reviewer` | `qwen-coder-mlx` (local) | `omni-coding` |
| `role-doc-writer` | `qwen-coder-mlx` (local) | `omni-coding` |
| `gate-copilot` | `omni-reasoning` (cloud) | — |
| `chat-default` | `omni-auto` (cloud) | — |
Hardcoded in convoy `model_policy`:
```yaml
model_policy:
roles:
role-conductor: omni-reasoning
role-architect: omni-reasoning-pro
role-implementer: qwen-coder-mlx
role-reviewer: omni-coding-pro
role-doc-writer: qwen-coder-mlx
escalate_to: omni-coding-pro
```
---
## How Resolution Works
Echodo's `resolveModel(workspaceId, roleId, ctx?)` (pure function in `packages/shared/src/model-routing/resolve-model.ts`) applies this order:
```
1. Brief override → recommended_model in .convoys/<slug>/brief-*.md
2. Convoy override → model_policy.roles[roleId] in .convoys/<slug>.md
3. Workspace route → role_model_routes row in DB
4. Pipeline template → PIPELINE_DEFAULT_ROLE_ROUTES (shipped defaults)
```
The resolved `ResolvedModel` object is passed to the worker. The worker creates a Vercel AI SDK `LanguageModel` via `createLanguageModelFromResolved` (`packages/ai/src/providers/resolve-client.ts`).
**Because OmniRoute is `openai_compatible`:** it flows through the existing `case "openai_compatible"` branch, so **no code change is required** in `resolve-client.ts` — it uses `createOpenAI({ apiKey, baseURL })` with `modelId` = profile name.
---
## Resolved Model Example
For `role-implementer` → `omni-coding`:
```typescript
{
alias: "omni-coding",
providerType: "openai_compatible",
modelId: "auto/coding",
baseUrl: "https://omniroute.example.com/v1",
apiKey: "sk-...", // read from OMNIROUTE_API_KEY at runtime
tier: "auto"
}
```
The worker's `createLanguageModelFromResolved` builds:
```typescript
const openai = createOpenAI({
apiKey: "sk-...",
baseURL: "https://omniroute.example.com/v1",
});
const model = openai("auto/coding");
```
OmniRoute then routes `auto/coding` to an appropriate live model per its own logic.
---
## Convoy Frontmatter
### OmniRoute-Only Convoy
```yaml
---
name: feature-auth
classification: server-only
success_metric: POST /auth/register works and returns 201
skip:
- ia
- ux
- design
- a11y
- visual
- smoke
status: draft
created: 2026-09-07
model_policy:
roles:
role-conductor: omni-reasoning
role-architect: omni-reasoning
role-implementer: omni-coding
role-reviewer: omni-coding-pro
escalate_to: omni-coding-pro
---
```
### Hybrid Convoy
```yaml
---
name: feature-local-first
model_policy:
roles:
role-conductor: omni-reasoning
role-architect: omni-reasoning
role-implementer: qwen-coder-mlx
role-reviewer: qwen-coder-mlx
escalate_to: omni-coding-pro
---
```
### Brief Override
```yaml
---
brief: complex-db-migration
recommended_model: omni-reasoning-pro # overrides convoy + workspace
model_tier: auto
---
```
---
## Environment Variables
| Variable | Where | Purpose |
|----------|-------|---------|
| `OMNIROUTE_BASE_URL` | Worker + web process env | OmniRoute endpoint (must end in `/v1`) |
| `OMNIROUTE_API_KEY` | Worker + web process env | Autogenerated by OpenHands/OmniRoute |
These names are **referenced** in `model_providers.apiKeyEnvVar` — the actual values live in deploy env (Coolify/Dokploy/Mac), never in the DB or committed files.
---
## Testing the Mapping
### Via tRPC
```typescript
// Debug endpoint: resolve a role to a model
resolveForRole({ roleId: "role-implementer" })
// → { ok: true, model: { alias: "omni-coding", modelId: "auto/coding", ... } }
```
### Via MCP testProvider
```typescript
testProvider({ alias: "omni-coding" })
// → { success: true, latency_ms: 320, model: "auto/coding" }
```
### Via direct OpenAI-compatible call
```bash
curl ${OMNIROUTE_BASE_URL}/models \
-H "Authorization: Bearer ${OMNIROUTE_API_KEY}"
# → List of available models (929+ for OmniRoute)
```
---
## Default Pipeline Template After OmniRoute
Update `PIPELINE_DEFAULT_ROLE_ROUTES` in `packages/shared/src/model-routing/constants.ts` if OmniRoute is the default for new workspaces:
```typescript
export const PIPELINE_DEFAULT_ROLE_ROUTES: Readonly<Record<string, string>> = {
"role-conductor": "omni-reasoning",
"role-architect": "omni-reasoning",
"role-ia-architect": "omni-reasoning",
"role-ux-reviewer": "omni-coding",
"role-implementer": "omni-coding",
"role-reviewer": "omni-coding-pro",
"role-design-system-auditor": "omni-coding",
"role-a11y-auditor": "omni-coding",
"role-doc-writer": "omni-coding",
"gate-copilot": "omni-reasoning",
"chat-default": "omni-auto",
};
```
*(Note: changing the template only affects new workspaces — existing workspace routes in the DB take precedence.)*
---
## Open Questions
1. **Should the template switch to OmniRoute aliases by default, or stay Claude/Qwen and let operators pick OmniRoute?** Recommend: keep Claude/Qwen seed, document OmniRoute as an option until it's proven.
2. **Does OmniRoute support streaming + function calling?** Needed for the worker tool loop. Verify via a live test before routing implementer roles to OmniRoute.
3. **Rate limits / cost:** OmniRoute `auto/*` profiles rotate models — need token/cost tracking in `agent_runs` to monitor spend.
---
## References
- **Resolver:** `packages/shared/src/model-routing/resolve-model.ts`
- **Schemas:** `packages/shared/src/model-routing/schemas.ts`
- **Constants:** `packages/shared/src/model-routing/constants.ts`
- **Client builder:** `packages/ai/src/providers/resolve-client.ts`
- **Provider schema:** `packages/database/src/schema/model_providers.ts`
- **Route schema:** `packages/database/src/schema/role_model_routes.ts`
- **ADR:** `docs/adr-model-routing.md`
- **Skill:** `.agents/skills/model-routing/SKILL.md`