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
14 KiB
| name | description |
|---|---|
| convoy-lifecycle | Work with the convoy system - understand status transitions, gate actors, MCP tools, and checkpoint/resume. Use when creating/managing convoys, transitioning status, logging events, or implementing the worker harness. |
Convoy Lifecycle
Convoys are the pipeline execution model for Echodo. A convoy groups related briefs into a cohesive unit with a success metric, model policy, and human gates. This skill teaches you the status machine, actor rules, MCP tools, and how the orchestrator drives the flow.
When to Activate
Use this skill when:
- Creating or managing
.convoys/*.mdfiles - Transitioning convoy status (draft → ready → merged, etc.)
- Understanding gate actors and what they can/cannot do
- Implementing the worker harness (Epic 7)
- Logging events to
convoy_eventsoragent_runs - Checking checkpoint/resume behavior
- Debugging convoy state issues
Core Rules
1. Status Machine (The Contract)
Convoys have a strict status lifecycle. Transitions are enforced by the server.
Code flow (from → to):
draft ──→ convoy_review ──→ ready ──→ in_progress ──→ pr_review ──→ merged ──→ released
↑ │ ↑
│ │ │
│ └──→ blocked └──→ blocked
│ ↑
└──→ cancelled └──→ cancelled
Non-code variant (docs/SOPs):
draft ──→ convoy_review ──→ ready ──→ in_progress ──→ doc_review ──→ published ──→ released
↑ │ ↑
│ └──→ blocked └──→ blocked
│
└──→ cancelled
Terminal states: released, cancelled
Exceptional state: blocked (can resume from most states)
2. Actor Rules (Who Can Do What)
The ALLOWED_TRANSITIONS table in packages/shared/src/convoy-lifecycle/state-machine.ts defines explicit actor permissions. Pipeline roles cannot perform gate transitions.
| Actor | Can Do |
|---|---|
role-architect |
Submit draft to convoy_review |
role-conductor |
Cancel convoy, unblock from blocked |
role-implementer |
Submit ready to in_progress, move pr_review back to in_progress, block/unblock |
human-gate-1 |
Approve/reject convoy_review → ready, block/unblock, cancel |
human-gate-2 |
Approve pr_review → merged, doc_review → published, block/unblock, cancel |
human-gate-3 |
Approve merged → released |
doc-approver |
Alias for human-gate-2 on doc flows |
automation |
Orchestrator-driven transitions (checkpoint resume, stall recovery) |
Key principle: role-architect ≠ human-gate-1. The conductor/architect roles plan and kick off; humans approve and release.
3. Gate Actors are Not Pipeline Roles
This is a common mistake. Do NOT let pipeline roles perform gates:
- ❌
role-implementercannot approve PR merge (must behuman-gate-2) - ❌
role-architectcannot release to production (must behuman-gate-3) - ❌
role-conductorcannot approve convoy_review (must behuman-gate-1)
Exception: The orchestrator runs as automation actor for internal transitions (checkpoint resume, stall recovery).
4. Reason Requirement
Some transitions require a reason string (e.g., blocking, cancelling, unblocking):
// From state-machine.ts
{ from: "blocked", to: "ready", actors: ["human-gate-1", "role-conductor"], requiresReason: true }
When calling MCP transition_convoy_status for these, pass reason: "blocking for redesign".
5. File-Based Convoys (.convoys/*.md)
Convoys are defined in markdown with YAML frontmatter. Location: .convoys/<slug>.md.
Frontmatter structure:
---
name: <slug>
classification: server-only | web | full-stack
success_metric: One sentence explaining what "done" looks like
skip:
- ia # skip IA review
- ux # skip UX review
- design
- a11y
- visual
- smoke
status: draft | in_progress | merged | shipped | abandoned
created: YYYY-MM-DD
model_policy:
roles:
role-conductor: claude-planning
role-architect: claude-planning
role-implementer: qwen-coder
role-reviewer: qwen-coder
escalate_to: claude-planning
---
Body sections:
- Why: Problem statement and motivation
- Scope: What's in/out
- Roles invoked: Who runs which phases
- Todos: Brief mapping
- Architecture: Technical notes
Briefs live in: .convoys/<slug>/brief-<N>-<title>.md
6. Checkpoint/Resume (Worker Feature)
When a worker job runs, it can checkpoint state to worker_checkpoints:
// Checkpoint payload (example from docs/worker.md)
{
toolHistory: [{ tool: "read_file", input: { path: "..." }, output: "..." }],
status: "in_progress",
lastCheckpointAt: "2026-08-12T10:00:00Z"
}
Resume behavior:
- Worker kills → orchestrator detects stall (
ORCHESTRATOR_STALL_MINUTESdefault 120) - Next dequeue reads checkpoint for same job → resumes from where it left off
toolHistoryenables context replay without re-running everything
7. Events Table (convoy_events)
Every transition and role completion is logged to convoy_events:
-- Example log entries
| id | convoy_slug | event_type | actor | metadata |
|----|-------------|-----------------|---------------|-------------------------------|
| 1 | feature-x | status_change | role-architect| { from: "draft", to: "ready" }|
| 2 | feature-x | role_complete | role-architect| { brief: "brief-1", ... } |
| 3 | feature-x | role_complete | role-implementer| { brief: "brief-2", model: ...}|
| 4 | feature-x | status_change | human-gate-2 | { from: "pr_review", to: "merged" } |
Event types:
status_change— transition from one convoy status to anotherrole_complete— a brief finished with metrics (model, tokens, duration)stall_recovered— orchestrator resumed from checkpointgate_action— human approved/requested changes
MCP Tools
create_convoy
Purpose: Create a new convoy at draft status with metadata.
Signature:
create_convoy({
workspace: string, // Workspace UUID or slug
slug: string, // Convoy slug (kebab-case)
title: string,
successMetric: string, // Frontmatter success_metric
classification: string, // server-only | web | full-stack
skip?: string[], // Optional: ["ia", "ux", "design"]
modelPolicy?: { // Optional: set model routes
roles: Record<string, string>, // role -> alias
escalateTo?: string,
}
})
Returns: Convoy object with ID, slug, current status (draft), created_at.
Example usage:
"Create a convoy called 'test-omniroute-integration' with success metric 'OmniRoute routes all 5 roles successfully'. Classification: server-only. Skip IA, UX, design."
transition_convoy_status
Purpose: Move convoy between statuses. Enforces actor rules + reason requirements.
Signature:
transition_convoy_status({
workspace: string,
convoySlug: string,
fromStatus: string,
toStatus: string,
actor: string, // role-architect, human-gate-1, etc.
reason?: string, // Required for block/unblock/cancel
})
Returns: Updated convoy status, event log entry.
Example usage:
"Transition convoy 'test-e2e-autonomous' from 'ready' to 'in_progress'. Actor: role-implementer."
log_convoy_event
Purpose: Append a role completion or custom event to convoy_events.
Signature:
log_convoy_event({
workspace: string,
convoySlug: string,
eventType: string, // role_complete, gate_action, etc.
actor: string, // Who performed the event
metadata: Record<string, unknown>, // Additional context
})
Example usage:
"Log role_complete for convoy 'test-e2e-autonomous', actor: role-implementer, brief: brief-2-implement-feature, model: qwen-coder, tokens: 12345"
query_manifest_status
Purpose: Get convoy status, briefs, and recent events for Mission Control UI.
Signature:
query_manifest_status({
workspace: string,
convoySlug?: string, // Optional: filter to one convoy
})
Returns: Convoy object with status, briefs (with status), recent events.
reconcile_from_files
Purpose: Import .convoys/*.md into the DB and report pending sync outbox.
Signature:
reconcile_from_files({
repoPath?: string, // Optional: scan specific path
})
Returns: Summary of created/updated convoys, any sync errors.
Common Patterns
Creating a Convoy
- Define the convoy file:
.convoys/feature-x.md - Write frontmatter: name, classification, success_metric, model_policy
- Write body: Why, Scope, Roles, Todos
- Create briefs:
.convoys/feature-x/brief-1-*.mdfor each task - Import to DB:
reconcile_from_filesorcreate_convoyMCP - Start work:
transition_convoy_statustoconvoy_review
Running Through Gates
- Draft → Convoy Review:
role-architectsubmits (orcreate_convoywith status) - Convoy Review:
human-gate-1approves (→ ready) or rejects (→ draft) - Ready → In Progress:
role-implementerstarts work - In Progress → PR Review:
role-implementersubmits code - PR Review:
human-gate-2approves (→ merged) or requests changes (→ in_progress) - Merged → Released:
human-gate-3promotes to production
Logging Role Completion
When an agent finishes a brief:
// In worker, after complete_task MCP
await log_convoy_event({
workspace: workspaceId,
convoySlug: convoy.slug,
eventType: "role_complete",
actor: roleId, // e.g., "role-implementer"
metadata: {
briefSlug: brief.slug,
model: modelSpec.alias, // e.g., "qwen-coder"
modelTier: tier, // "coding" or "coding-pro"
tokensIn: tokens.input,
tokensOut: tokens.output,
durationMs: duration,
outcome: "success" | "failure",
}
});
Handling Blocks
To block a convoy (any gate actor or role-implementer):
await transition_convoy_status({
workspace: workspaceId,
convoySlug: "feature-x",
fromStatus: "in_progress",
toStatus: "blocked",
actor: "human-gate-2", // or role-implementer
reason: "Design changed - need API redesign before continuing"
});
To unblock:
await transition_convoy_status({
workspace: workspaceId,
convoySlug: "feature-x",
fromStatus: "blocked",
toStatus: "in_progress",
actor: "human-gate-2", // or role-conductor
reason: "API redesign complete"
});
Examples
Example 1: Simple Code Convoy
.convoys/add-auth-endpoint.md:
---
name: add-auth-endpoint
classification: server-only
success_metric: POST /api/auth/register returns 201 and creates user row
skip:
- ia
- ux
- design
status: draft
created: 2026-08-12
model_policy:
roles:
role-architect: claude-planning
role-implementer: qwen-coder
role-reviewer: qwen-coder
---
Brief: .convoys/add-auth-endpoint/brief-1-implement.md
## Description
Implement the POST /api/auth/register endpoint.
## Scope
- Create route handler at apps/web/app/api/auth/register/route.ts
- Add validation with zod
- Call database insert
- Return 201 on success, 400 on validation error
## Acceptance Criteria
- [ ] Route file exists at correct path
- [ ] zod schema validates email/password
- [ ] Database insert creates user row
- [ ] pnpm lint && pnpm type-check pass
- [ ] Brief is done
Example 2: Doc-Only Convoy
---
name: write-sops
classification: server-only
success_metric: 5 SOP documents are published under docs/sops/
skip:
- ia
- ux
- design
- visual
- smoke
status: draft
created: 2026-08-12
model_policy:
roles:
role-doc-writer: auto/coding
---
Use the template at docs/templates/doc-sop.md to write SOPs for:
- How to run a conveyor
- How to add a new model provider
- How to configure OmniRoute
- How to add a new harness
Example 3: Status Transition Flow
1. Architect submits: transition(ready → convoy_review) by role-architect
2. Gate approves: transition(convoy_review → ready) by human-gate-1
3. Implementer starts: transition(ready → in_progress) by role-implementer
4. Implementer submits: transition(in_progress → pr_review) by role-implementer
5. Gate merges: transition(pr_review → merged) by human-gate-2
6. Gate releases: transition(merged → released) by human-gate-3
References
- State machine:
packages/shared/src/convoy-lifecycle/state-machine.ts - Schemas:
packages/shared/src/convoy-lifecycle/schemas.ts - Kanban view:
packages/shared/src/convoy-lifecycle/kanban.ts - Tests:
packages/shared/src/convoy-lifecycle/state-machine.test.ts - MCP tools:
apps/mcp-server/src/tools/create-convoy.ts,transition_convoy_status.ts,log_convoy_event.ts - Worker docs:
docs/worker.md - Orchestrator docs:
docs/orchestrator.md - Convoy lifecycle doc:
docs/convoy-lifecycle.md - Example convoys:
.convoys/second-brain-os-v01.md,.convoys/polish-dag-tiers-cost.md
Related Skills
backlog-sync— when briefs map to markdown_backlog_itemsmodel-routing— how model_policy aliases resolve via omnirouteopenhands-worker— worker implementation that uses this lifecyclecode-review-two-axis— what role-reviewer does during pr_review