Submatter/docs/AGENT-AUTONOMOUS-CONVOYS.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

27 KiB
Raw Blame History

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:

  1. Convert role-prompt rules to .agents/skills/ in SKILL.md format (Agent Skills spec)
  2. Map omniroute profiles → roles (conductor/architect/implementer/reviewer)
  3. Wire OpenHands session bootstrap to inherit agent_prompt + skills + model policy
  4. 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>.md with model_policy roles 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_runs and convoy_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:

  1. 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-backlog works, the contract between .md and markdown_backlog_items
    • Reference: .cursor/rules/markdown-backlog.mdc + packages/database/src/markdown-backlog/*.ts + docs/Glossary.md
    • Output format: SKILL.md with frontmatter (name, description) + sections (When to Activate, Core Rules, Common Patterns, Examples)
  2. 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
  3. 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 to agent_runs and convoy_events
    • Reference: docs/adr-model-routing.md + packages/shared/src/orchestrator/*.ts + this doc's Phase 2 section
    • Output format: Same as above
  4. Create .agents/skills/code-review-two-axis/SKILL.md (adapted from mattpocock/skills)

    • When to invoke: When role-reviewer runs 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
  5. Create .agents/skills/tdd-red-green/SKILL.md (adapted from mattpocock/skills)

Acceptance criteria:

  • Each SKILL.md has frontmatter (name, description) + at least 3 sections
  • skil --validate .agents/skills/*/SKILL.md passes (or equiv. local validation)
  • Skills are referenced in AGENTS.md under the "Skills available" section
  • Agent can invoke them via /skill command 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:

  1. 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) in packages/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
    • 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
  2. 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 (testProvider MCP call, test a single model)
    • How to debug (logs in agent_runs, worker_checkpoints metadata)
  3. Update docs/adr-model-routing.md (add OmniRoute section)

    • Keep the existing conceptual model
    • Add subsection: "OmniRoute Cloud Integration (v0.1)"
    • Explain how resolveModel calls omniroute's API to get the actual endpoint + auth

Acceptance criteria:

  • docs/omniroute-role-mapping.md covers all 6 roles (conductor, architect, implementer, reviewer, doc-writer, gate-copilot)
  • Each role has a recommended default profile + an override example
  • docs/omniroute-setup.md has step-by-step instructions (create alias, test, configure workspace route)
  • ADR updated with omniroute context
  • Convoy examples in .convoys/*.md use 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:

  1. Extend apps/worker/src/session/minimal-session.ts (or create openhands-session.ts)

    • Input: { jobSpec, resolvedModel, harnessAlias, briefPath }
    • On session init:
      • Load brief-*.md from .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)
    • 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
  2. 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 to openhands to use new flow)
  3. 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

Acceptance criteria:

  • WORKER_SESSION_TYPE=openhands env var enables the flow
  • Integration test passes (dry-run with mock brief)
  • Live E2E test (real brief, real repo) succeeds with model logging
  • agent_runs row includes { model_alias, model_tier, tokens_in, tokens_out }
  • convoy_events has role_complete entry with metadata.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:

  1. Define a convoy with roles mapped to omniroute profiles
  2. Click "Start" in Mission Control
  3. Watch OpenHands agents pick up and complete briefs without Cursor
  4. See model metadata logged correctly

Tasks:

  1. 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"
  2. Run the full loop (Manual walkthrough)

    • Human creates convoy via MCP create_convoy or Mission Control UI
    • Orchestrator picks up role-conductor (dry-run or light task)
    • Orchestrator enqueues role-implementer to 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-reviewer brief
    • Worker dequeues, spawns OpenHands session, runs two-axis review
    • Worker logs complete_task + role_complete with review metadata
    • Convoy reaches merged or published gate
    • Check agent_runs: model aliases logged for both implementer + reviewer
    • Check convoy_events: role_complete entries present with metadata
  3. 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_runs has entries for implementer + reviewer with model aliases
  • convoy_events has role_complete entries
  • 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)

  1. Before writing: Read the existing .cursor/rules/*.mdc files in this repo + the referenced SKILL.md files from mattpocock/skills, anthropics/skills
  2. Structure: Follow the Agent Skills spec (frontmatter + markdown sections)
  3. Validation: Each skill should be invokable by model (e.g., /skill backlog-sync or implied when needed) and have 3–4 substantive sections
  4. Test: Run locally with OpenHands or Claude Code to verify skills load
  5. Acceptance: PR reviewer checks that skills have frontmatter + clear When-to-Activate section

For Agents Working on Phase 2 (Routing)

  1. Before writing: Read docs/adr-model-routing.md fully + understand how model_policy works in .convoys/*.md
  2. 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)
  3. Output: Two markdown files (mapping + setup), both human-readable and copy-pasteable examples
  4. Validation: Walk through the flow: convoy sets model_policy.roles, orchestrator calls resolveModel, worker receives model spec
  5. Test: No automated test needed; human walkthrough in Phase 4

For Agents Working on Phase 3 (Worker Integration)

  1. Before writing: Read apps/worker/src/session/minimal-session.ts + apps/worker/src/handle-job.ts + understand the worker dequeue loop
  2. Pattern: Don't replace MinimalSession; extend it or wrap it in an adapter (prefer wrapping for backward compat)
  3. Assumption: OpenHands agent-server is running and reachable (via env var AGENT_SERVER_URL)
  4. Validation:
    • Type-check passes (pnpm type-check)
    • New env var WORKER_SESSION_TYPE is optional (defaults to minimal)
    • Integration test dry-runs without errors
  5. Test: Phase 4 (E2E) will exercise this

For Agents Working on Phase 4 (E2E)

  1. Before starting: Phase 1 + 2 + 3 must be done
  2. Pre-flight:
    • .agents/skills/ SKILL.md files exist + load correctly
    • docs/omniroute-role-mapping.md + docs/omniroute-setup.md exist
    • WORKER_SESSION_TYPE=openhands env var works
    • Orchestrator + worker are running (or can dry-run)
  3. Test convoy: Create .convoys/test-e2e-autonomous.md with 2 briefs (implementer + reviewer)
  4. Manual walkthrough: Follow the flow in "Phase 4: Tasks" section, step by step
  5. Validation: Check agent_runs, convoy_events tables for logs; verify PR created + reviewed
  6. 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_events log 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:

  1. Publish to v0.1 — Mark the orchestrator + worker as production-ready for autonomous convoys
  2. Scale briefs — Extend to 5–6 briefs per convoy (architect → implementer → reviewer → ops-copilot → etc.)
  3. Scale roles — Add role-docs-writer, role-security-auditor, role-performance-reviewer as needed
  4. Scale models — Extend omniroute integration to support Claude/OpenAI/Gemini APIs side-by-side
  5. 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


Questions for Review

Before agents start Phase 1, answer these:

  1. OmniRoute target: Are we assuming omniroute cloud endpoint, or should we support local Ollama + MLX as a fallback?
  2. Model fallback: If omniroute is down, should the worker use a hardcoded fallback model, or fail closed?
  3. Role coverage: Are 6 roles (conductor, architect, implementer, reviewer, doc-writer, gate-copilot) enough, or do we need more?
  4. Cost tracking: Should Phase 3 also wire up cost_budget checks from model_providers, or leave that for a follow-on?
  5. Backward compat: Should Phase 3 preserve the MinimalSession adapter, or fully replace it with OpenHands?
  6. 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