Phase 1: Add 5 SKILL.md files to .agents/skills/ - backlog-sync: plans/ markdown ↔ DB contract - convoy-lifecycle: status machine, gates, MCP tools, checkpoints - model-routing: role→model via OmniRoute profiles + registry - code-review-two-axis: Standards vs Spec parallel sub-agent review - tdd-red-green: TDD loop with seams Phase 2: OmniRoute operational docs - docs/omniroute-role-mapping.md: profile→role matrix, convoy examples - docs/omniroute-setup.md: step-by-step operator guide Phase 3: Worker OpenHands integration - apps/worker/src/session/load-skills.ts: loads .agents/skills/ - apps/worker/src/session/openhands-session.ts: agent-server REST adapter - apps/worker/src/executor/index.ts: createOpenHandsExecutor() factory - apps/worker/src/env.ts: WORKER_SESSION_TYPE, AGENT_SERVER_URL, SESSION_API_KEY - apps/worker/src/handle-job.ts: routes to OpenHands when configured - apps/worker/src/session/minimal-session.ts: injects skills into system prompt - packages/shared/src/worker/index.ts: re-export WorkerExecutorOptions
10 KiB
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:
# 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:
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:
{
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:
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
---
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
---
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
---
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
// Debug endpoint: resolve a role to a model
resolveForRole({ roleId: "role-implementer" })
// → { ok: true, model: { alias: "omni-coding", modelId: "auto/coding", ... } }
Via MCP testProvider
testProvider({ alias: "omni-coding" })
// → { success: true, latency_ms: 320, model: "auto/coding" }
Via direct OpenAI-compatible call
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:
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
- 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.
- Does OmniRoute support streaming + function calling? Needed for the worker tool loop. Verify via a live test before routing implementer roles to OmniRoute.
- Rate limits / cost: OmniRoute
auto/*profiles rotate models — need token/cost tracking inagent_runsto 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