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
27 KiB
Autonomous Convoys with OpenHands & OmniRoute
Status: Draft (planning phase)
Date: 2026-09-07
Goal: Enable OpenHands + omniroute to autonomously execute Echodo convoys by mapping roles to model profiles and integrating Agent Skills format.
Executive Summary
Echodo's convoy system (defined in .convoys/*.md with role-based model_policy) currently requires Cursor to execute per-role briefs. This plan replaces Cursor with OpenHands as the runtime harness, using omniroute's model profiles (auto, auto/coding, auto/reasoning, auto/coding-pro, auto/reasoning-pro) to route each role to the appropriate model.
The main insight: Your convoy structure already works. We're just swapping the agent harness and making skills discoverable.
Key changes:
- Convert role-prompt rules to
.agents/skills/in SKILL.md format (Agent Skills spec) - Map omniroute profiles → roles (conductor/architect/implementer/reviewer)
- Wire OpenHands session bootstrap to inherit
agent_prompt+ skills + model policy - Document the flow so agents executing convoys follow consistent patterns
Outcome: By end of v0.1, a human can:
- Define a convoy in
.convoys/<slug>.mdwithmodel_policyroles mapped to omniroute profiles - Trigger the convoy from Mission Control
- OpenHands picks up each brief, inherits the task context, loads the right model via omniroute, and executes
- After success, worker logs role completion + model metadata back to
agent_runsandconvoy_events
Architecture: The Convoy-to-OpenHands Flow
┌─── Human / Mission Control ──────────────────────────────────────┐
│ "Start convoy: brief-1-implement-feature" │
│ (.convoys/feature-x.md, brief-1-*.md exists, ready to go) │
└────────────────────────┬────────────────────────────────────────┘
│
Orchestrator
(apps/orchestrator)
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Redis Job Queue (echodo:orchestrator:jobs) │
│ { roleId, harnessAlias, briefPath, workspaceId, ...} │
└──────────────────────┬──────────────────────────────────────┘
│
Worker dequeues
(apps/worker)
│
┌─────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ Resolve │ │ Resolve │ │ Load │
│ model via │ │ harness + │ │ session │
│ omniroute │ │ worktree │ │ bootstrap: │
│ profile │ │ │ │ • agent_prompt
│ (coding, │ │ (code/doc/ │ │ • skills from
│ reasoning) │ │ research │ │ .agents/ │
│ │ │ harness) │ │ • role rules │
└────────────┘ └──────────────┘ └──────────────┘
│ │ │
└────────────────┼──────────────────┘
│
OpenHands Session Start
(apps/web calls MCP claim_task)
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ Session context ready: │
│ • Model resolved (e.g. qwen-coder or claude-3-5-sonnet) │
│ • Task/brief context loaded (agent_prompt, file paths) │
│ • Skills available (.agents/skills/*.md files) │
│ • Tool allowlist set (code vs doc harness) │
│ • MCP tools: read_file, write_file, grep, run_command │
│ │
│ Agent now thinks: "I am role-implementer on brief-1. │
│ My success metric is: [from convoy frontmatter]. │
│ I should follow: [agent_prompt from task]. │
│ I can use these skills: [loaded from .agents/skills/]." │
└────────────────────┬─────────────────────────────────────────┘
│
Agent tool loop
(read_file, write_file,
grep, run_command)
│
┌───────────────┼───────────────┐
│ │ │
Success Failure Stall
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Log │ │ Log │ │ Orchestrator │
│ complete │ │ failure │ │ detects │
│ + model │ │ + │ │ stall, │
│ metadata │ │ tokens │ │ resumes from │
│ │ │ │ │ checkpoint │
└────┬─────┘ └────┬─────┘ └──────┬───────┘
│ │ │
└─────────────┼───────────────┘
│
MCP complete_task + convoy_events.role_complete
│
Orchestrator ticks
(determines next role)
│
┌─────────────┼──────────────┐
│ │ │
Next Brief Human Gate End Convoy
│ │ │
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌──────────┐
│ Back to │ │ Mission │ │ Released │
│ Redis │ │ Control │ │ or │
│ (loop) │ │ approve │ │ merged │
└──────────┘ └─────────┘ └──────────┘
Four Concrete Work Phases
Phase 1: Agent Skills Format Conversion (M estimate)
Goal: Make repo-integrated skills discoverable by OpenHands via SKILL.md format in .agents/skills/.
Why: Your current .cursor/rules/*.mdc files are Cursor-specific. OpenHands reads .agents/skills/*/SKILL.md (per Agent Skills spec). Converting moves you from Cursor-only to Cursor + OpenHands + any harness that supports Agent Skills.
Tasks:
-
Create
.agents/skills/backlog-sync/SKILL.md- When to invoke: When working with
plans/,markdown_backlog_items, or the Echodo markdown import - What it teaches: How backlog frontmatter maps to DB, how
pnpm --filter @tasks/database watch:markdown-backlogworks, the contract between.mdandmarkdown_backlog_items - Reference:
.cursor/rules/markdown-backlog.mdc+packages/database/src/markdown-backlog/*.ts+docs/Glossary.md - Output format:
SKILL.mdwith frontmatter (name, description) + sections (When to Activate, Core Rules, Common Patterns, Examples)
- When to invoke: When working with
-
Create
.agents/skills/convoy-lifecycle/SKILL.md- When to invoke: When working with convoys, briefs, transitions, gates, or the state machine
- What it teaches: Status enum (draft→ready→in_progress→pr_review→merged), gate actors (human-gate-1/2/3), MCP tools (create_convoy, transition_convoy_status, log_convoy_event), checkpoint/resume pattern
- Reference:
docs/convoy-lifecycle.md+packages/shared/src/convoy-lifecycle/*.ts+apps/mcp-server/src/tools/*convoy* - Output format: Same as backlog-sync
-
Create
.agents/skills/model-routing/SKILL.md- When to invoke: When routing models, configuring aliases, or understanding how the orchestrator selects models
- What it teaches: The resolution order (brief override → convoy policy → workspace routes → pipeline defaults), omniroute profile mapping, how
resolveModel(workspace, roleId)works, how model metadata flows toagent_runsandconvoy_events - Reference:
docs/adr-model-routing.md+packages/shared/src/orchestrator/*.ts+ this doc's Phase 2 section - Output format: Same as above
-
Create
.agents/skills/code-review-two-axis/SKILL.md(adapted from mattpocock/skills)- When to invoke: When
role-reviewerruns or a human asks for a PR review in Mission Control - What it teaches: Two-axis review (Standards vs Spec), running both as parallel sub-agents, how to aggregate findings, the code-smell baseline
- Reference: https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md (with Echodo-specific tweaks for Standards against
AGENTS.md+ repo rules) - Output format: Same as above
- When to invoke: When
-
Create
.agents/skills/tdd-red-green/SKILL.md(adapted from mattpocock/skills)- When to invoke: When implementing features or fixing bugs in a test-first flow
- What it teaches: Red-green-refactor loop, seams, anti-patterns, the tracer-bullet concept
- Reference: https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md
- Output format: Same as above
Acceptance criteria:
- Each SKILL.md has frontmatter (name, description) + at least 3 sections
skil --validate .agents/skills/*/SKILL.mdpasses (or equiv. local validation)- Skills are referenced in
AGENTS.mdunder the "Skills available" section - Agent can invoke them via
/skillcommand or model-invocation within OpenHands
Phase 2: OmniRoute Profile Mapping & Model Policy Doc (S estimate)
Goal: Document how omniroute profiles map to roles and how the orchestrator uses model_policy to route.
Why: Your model_policy.roles in convoy frontmatter lists role names + model aliases. Those aliases need to resolve to omniroute profiles (e.g., auto/coding, auto/reasoning) so the worker can ask omniroute "give me the model for claude-planning" and get back the current endpoint.
Tasks:
-
Create
docs/omniroute-role-mapping.md- Section 1: Profile inventory
- auto (default, fastest) - auto/coding (code-specialized) - auto/coding-pro (complex, high-quality) - auto/reasoning (reasoning/planning) - auto/reasoning-pro (complex reasoning, high-cost) - Section 2: Role → Profile mapping (recommended)
role-conductor → auto/reasoning (kickoff, classify, skip flags) role-architect → auto/reasoning-pro (design, brief decomposition) role-implementer → auto/coding (default) or auto/coding-pro (escalation) role-reviewer → auto/coding-pro (two-axis review) role-doc-writer → auto/coding (docs are simpler than code) gate-copilot → auto/reasoning (summary for human gate) - Section 3: How to configure in a convoy
model_policy: roles: role-conductor: claude-planning # resolves to auto/reasoning profile role-implementer: qwen-coder # resolves to auto/coding profile escalate_to: claude-planning # if implementer fails 2x, re-dispatch with this - Section 4: How the orchestrator uses this
- Reads
model_policy.roles[roleId]from the convoy - Calls
resolveModel(workspace, roleId)inpackages/shared/src/orchestrator/*.ts - Resolution order: brief override → convoy policy → workspace routes → pipeline defaults
- Result:
{ alias, provider_type, model_id, base_url, api_key_env_var } - Worker receives model spec in the job payload
- Reads
- Section 5: Escalation & retry logic
- If implementer brief fails, orchestrator can re-dispatch with
escalate_to: role-architect(higher-tier model) - Log tier change on
agent_runs
- If implementer brief fails, orchestrator can re-dispatch with
- Section 1: Profile inventory
-
Create
docs/omniroute-setup.md(operational guide)- How to register omniroute in the workspace (via Settings → AI → Providers or tRPC
model_providers.create) - How to set up the 5 profiles (assuming omniroute cloud endpoint; or local Ollama + MLX)
- How to test (
testProviderMCP call, test a single model) - How to debug (logs in
agent_runs,worker_checkpointsmetadata)
- How to register omniroute in the workspace (via Settings → AI → Providers or tRPC
-
Update
docs/adr-model-routing.md(add OmniRoute section)- Keep the existing conceptual model
- Add subsection: "OmniRoute Cloud Integration (v0.1)"
- Explain how
resolveModelcalls omniroute's API to get the actual endpoint + auth
Acceptance criteria:
docs/omniroute-role-mapping.mdcovers all 6 roles (conductor, architect, implementer, reviewer, doc-writer, gate-copilot)- Each role has a recommended default profile + an override example
docs/omniroute-setup.mdhas step-by-step instructions (create alias, test, configure workspace route)- ADR updated with omniroute context
- Convoy examples in
.convoys/*.mduse the recommended aliases (no hardcoded model names)
Phase 3: OpenHands Session Bootstrap & Worker Integration (L estimate)
Goal: Wire up OpenHands session to inherit task context, load skills, and respect model policy.
Why: When the worker dequeues a job and spawns an OpenHands session, the agent needs to know:
- What task it's working on (brief path, success metric, acceptance criteria)
- What role it is (role-implementer, role-reviewer, etc.)
- Which model to use (resolved from omniroute)
- Which skills are available (from
.agents/skills/) - Which tools are allowed (code harness:
pnpm lint,run_command; doc harness: read/write/grep only)
Tasks:
-
Extend
apps/worker/src/session/minimal-session.ts(or createopenhands-session.ts)- Input:
{ jobSpec, resolvedModel, harnessAlias, briefPath } - On session init:
- Load
brief-*.mdfrom.convoys/<slug>/(task context) - Extract
agent_prompt(from task → epic → plan inheritance, per AGENTS.md) - Inherit
title,success_metric, acceptance criteria from frontmatter - Construct a session bootstrap (initial system + first user message)
- Load
- Session bootstrap structure:
System message (shared context): - You are an AI assistant helping implement a software task - Your task: [title from brief frontmatter] - Success means: [success_metric] - You are playing the role of: [roleId] - Acceptance criteria: [from brief or inherited] - Constraints: - Use the [tool allowlist] only - If you make a file change, it will be pushed to a branch - After you succeed, claim_task + complete_task will be called - Principles: [karpathy-guidelines from AGENTS.md] First user message: - [inherited agent_prompt from task frontmatter] - [brief body content — the actual work description] - "Here are the skills you have available: [list from .agents/skills/]" - "Your model is: [resolved model name from omniroute]" - On session end:
- Collect metrics:
{ model, tokens, duration, outcome } - Return to worker for logging
- Collect metrics:
- Input:
-
Wire OpenHands integration into Worker job handler (
apps/worker/src/handle-job.ts)- Current flow:
MinimalSession→ tool loop → checkpoint - New flow:
1. Dequeue job from Redis 2. resolveHarnessForJob(job.roleId) → harness config 3. resolveModel(workspace, job.roleId) → model spec 4. Create OpenHands session with bootstrap (brief context + skills) 5. Run tool loop (agent interacts with codebase) 6. On success: push branch, call forge PR endpoint 7. Log complete_task + convoy_events.role_complete with model metadata 8. Return to step 1 - Config: Add env var
WORKER_SESSION_TYPE=openai-client | minimal | openhands(default: minimal for backward compat, set toopenhandsto use new flow)
- Current flow:
-
Create
.agents/integration test (apps/worker/src/openhands-integration.test.ts)- Test: Spawn a dry-run OpenHands session with a toy brief, verify:
- Brief context loads correctly
- Skills are discoverable
- agent_prompt is inherited
- Tool allowlist is respected
- Session logs metrics on exit
- Test: Spawn a dry-run OpenHands session with a toy brief, verify:
Acceptance criteria:
WORKER_SESSION_TYPE=openhandsenv var enables the flow- Integration test passes (dry-run with mock brief)
- Live E2E test (real brief, real repo) succeeds with model logging
agent_runsrow includes{ model_alias, model_tier, tokens_in, tokens_out }convoy_eventshasrole_completeentry withmetadata.harnessAlias
Phase 4: End-to-End E2E Test & Dogfood (L estimate)
Goal: Prove one convoy executes autonomously from start to finish using OpenHands + omniroute.
Why: This is the acceptance test for the whole milestone. A human should be able to:
- Define a convoy with roles mapped to omniroute profiles
- Click "Start" in Mission Control
- Watch OpenHands agents pick up and complete briefs without Cursor
- See model metadata logged correctly
Tasks:
-
Create a test convoy (
.convoys/test-e2e-autonomous.md)- Classification:
server-only(simple, no IA/UX/design) - 2–3 briefs (implementer + reviewer)
- Roles:
role-conductor(kickoff only),role-implementer(fix a small bug or add a minor feature),role-reviewer(review the PR) - Model policy: use omniroute profiles (e.g.,
role-implementer: auto/coding,role-reviewer: auto/coding-pro) - Success metric: "Bug is fixed, PR is drafted, review is complete"
- Classification:
-
Run the full loop (Manual walkthrough)
- Human creates convoy via MCP
create_convoyor Mission Control UI - Orchestrator picks up
role-conductor(dry-run or light task) - Orchestrator enqueues
role-implementerto Redis - Worker dequeues, spawns OpenHands session
- OpenHands loads brief, runs tool loop, fixes the bug, commits
- Worker logs
complete_task+role_complete - Orchestrator transitions convoy to
pr_review(or next gate) - Orchestrator enqueues
role-reviewerbrief - Worker dequeues, spawns OpenHands session, runs two-axis review
- Worker logs
complete_task+role_completewith review metadata - Convoy reaches
mergedorpublishedgate - Check
agent_runs: model aliases logged for both implementer + reviewer - Check
convoy_events: role_complete entries present with metadata
- Human creates convoy via MCP
-
Dogfood the pipeline (Optional: use this convoy to close a real task)
- Pick a backlog task that fits the convoy (small feature or bug)
- Map task to a convoy brief
- Run the E2E flow on real code
- Validate that the fix/feature is correct and the PR is mergeable
Acceptance criteria:
- Convoy completes all briefs without Cursor
agent_runshas entries for implementer + reviewer with model aliasesconvoy_eventshasrole_completeentries- PR is draft or merged (depending on gates)
- No manual intervention after "Start Convoy"
- Logs are clean (no auth errors, model resolution errors)
Dependencies & Ordering
Phase 1 (Skills) ──┐
Phase 2 (Routing) ─┼──→ Phase 3 (Worker) ──→ Phase 4 (E2E)
Phase 3 unblocked │ (can start once 1 + 2 done)
as soon as 1 + 2 │
complete
Critical path:
- Phase 1 + 2 are sequential (skills first, then routing docs)
- Phase 3 can start once Phase 1 + 2 are done
- Phase 4 requires Phase 3 complete
Agent Instructions for Each Phase
For Agents Working on Phase 1 (Skills)
- Before writing: Read the existing
.cursor/rules/*.mdcfiles in this repo + the referenced SKILL.md files from mattpocock/skills, anthropics/skills - Structure: Follow the Agent Skills spec (frontmatter + markdown sections)
- Validation: Each skill should be invokable by model (e.g.,
/skill backlog-syncor implied when needed) and have 3–4 substantive sections - Test: Run locally with OpenHands or Claude Code to verify skills load
- Acceptance: PR reviewer checks that skills have frontmatter + clear When-to-Activate section
For Agents Working on Phase 2 (Routing)
- Before writing: Read
docs/adr-model-routing.mdfully + understand howmodel_policyworks in.convoys/*.md - Assumption: OmniRoute cloud is the target (not local-only), and omniroute has 5 profiles (auto, auto/coding, auto/coding-pro, auto/reasoning, auto/reasoning-pro)
- Output: Two markdown files (mapping + setup), both human-readable and copy-pasteable examples
- Validation: Walk through the flow: convoy sets
model_policy.roles, orchestrator callsresolveModel, worker receives model spec - Test: No automated test needed; human walkthrough in Phase 4
For Agents Working on Phase 3 (Worker Integration)
- Before writing: Read
apps/worker/src/session/minimal-session.ts+apps/worker/src/handle-job.ts+ understand the worker dequeue loop - Pattern: Don't replace MinimalSession; extend it or wrap it in an adapter (prefer wrapping for backward compat)
- Assumption: OpenHands agent-server is running and reachable (via env var
AGENT_SERVER_URL) - Validation:
- Type-check passes (
pnpm type-check) - New env var
WORKER_SESSION_TYPEis optional (defaults tominimal) - Integration test dry-runs without errors
- Type-check passes (
- Test: Phase 4 (E2E) will exercise this
For Agents Working on Phase 4 (E2E)
- Before starting: Phase 1 + 2 + 3 must be done
- Pre-flight:
.agents/skills/SKILL.md files exist + load correctlydocs/omniroute-role-mapping.md+docs/omniroute-setup.mdexistWORKER_SESSION_TYPE=openhandsenv var works- Orchestrator + worker are running (or can dry-run)
- Test convoy: Create
.convoys/test-e2e-autonomous.mdwith 2 briefs (implementer + reviewer) - Manual walkthrough: Follow the flow in "Phase 4: Tasks" section, step by step
- Validation: Check
agent_runs,convoy_eventstables for logs; verify PR created + reviewed - Report: Summarize the run (timeline, model aliases used, any errors, fixes applied)
Success Criteria (Overall)
By end of Phase 4:
- One full convoy (2+ briefs) executes autonomously via OpenHands without Cursor
- Each role uses the model from omniroute (via model_policy)
agent_runs+convoy_eventslog model metadata for observability- No secrets or API keys leaked in logs
- Docs cover setup + usage (omniroute-role-mapping.md, omniroute-setup.md)
- Skills are discoverable + usable by agents (.agents/skills/*/SKILL.md)
- Worker integration test passes
- Code is linted + type-checked
Rollout & Scaling
Once Phase 4 succeeds:
- Publish to v0.1 — Mark the orchestrator + worker as production-ready for autonomous convoys
- Scale briefs — Extend to 5–6 briefs per convoy (architect → implementer → reviewer → ops-copilot → etc.)
- Scale roles — Add
role-docs-writer,role-security-auditor,role-performance-revieweras needed - Scale models — Extend omniroute integration to support Claude/OpenAI/Gemini APIs side-by-side
- Migrate Cursor convoys — For convoys that have historically run in Cursor, run them via OpenHands instead (backward compat: human-initiated, not automated)
Risk Mitigations
| Risk | Mitigation |
|---|---|
| Model tier mismatches (expensive model for simple task) | OmniRoute profiles + explicit role mapping in docs |
| Agent hallucinations in brief context | Agent_prompt inheritance from task → epic → plan keeps context tight; karpathy-guidelines skill |
| Network issues between worker + OpenHands | Checkpoint/resume logic already in place; retry logic in orchestrator |
| Secrets leaked in logs | Review agent_runs.metadata + worker_checkpoints.tool_history before logging; never log full API responses |
Convoy state desync (DB vs .convoys/*.md) |
reconcile_from_files MCP tool keeps them in sync; convoy_events is the source of truth |
References
- Agent Skills Spec: https://agentskills.io/specification
- Mattpocock Skills (code-review, tdd): https://github.com/mattpocock/skills
- Superpowers (bootstrap pattern): https://github.com/obra/superpowers
- Karpathy Guidelines (4 behavioral rules): https://github.com/multica-ai/andrej-karpathy-skills
- Echodo Convoys:
.convoys/README.md - Model Routing ADR:
docs/adr-model-routing.md - Worker Docs:
docs/worker.md - Orchestrator Docs:
docs/orchestrator.md
Questions for Review
Before agents start Phase 1, answer these:
- OmniRoute target: Are we assuming omniroute cloud endpoint, or should we support local Ollama + MLX as a fallback?
- Model fallback: If omniroute is down, should the worker use a hardcoded fallback model, or fail closed?
- Role coverage: Are 6 roles (conductor, architect, implementer, reviewer, doc-writer, gate-copilot) enough, or do we need more?
- Cost tracking: Should Phase 3 also wire up
cost_budgetchecks frommodel_providers, or leave that for a follow-on? - Backward compat: Should Phase 3 preserve the MinimalSession adapter, or fully replace it with OpenHands?
- v0.1 scope: Phase 4 tests one convoy. Should we aim for 2+ convoys running in parallel by end of Phase 4, or save that for post-v0.1?
Document Version: 1.0
Last Updated: 2026-09-07
Next Review: After Phase 1 completion