Submatter/docs/omniroute-role-mapping.md
Randall Stillwell 3fc7d01585 feat(worker): OpenHands session bootstrap + 5 Agent Skills
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
2026-09-07 15:10:02 -05:00

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

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

  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