--- name: convoy-lifecycle description: 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/*.md` files - 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_events` or `agent_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-implementer` cannot approve PR merge (must be `human-gate-2`) - ❌ `role-architect` cannot release to production (must be `human-gate-3`) - ❌ `role-conductor` cannot approve convoy_review (must be `human-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): ```typescript // 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/.md`. **Frontmatter structure:** ```yaml --- name: 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//brief--.md` ### 6. Checkpoint/Resume (Worker Feature) When a worker job runs, it can checkpoint state to `worker_checkpoints`: ```typescript // 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_MINUTES` default 120) - Next dequeue reads checkpoint for same job → resumes from where it left off - `toolHistory` enables context replay without re-running everything ### 7. Events Table (`convoy_events`) Every transition and role completion is logged to `convoy_events`: ```sql -- 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 another - `role_complete` — a brief finished with metrics (model, tokens, duration) - `stall_recovered` — orchestrator resumed from checkpoint - `gate_action` — human approved/requested changes ## MCP Tools ### create_convoy **Purpose:** Create a new convoy at `draft` status with metadata. **Signature:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript reconcile_from_files({ repoPath?: string, // Optional: scan specific path }) ``` **Returns:** Summary of created/updated convoys, any sync errors. ## Common Patterns ### Creating a Convoy 1. **Define the convoy file:** `.convoys/feature-x.md` 2. **Write frontmatter:** name, classification, success_metric, model_policy 3. **Write body:** Why, Scope, Roles, Todos 4. **Create briefs:** `.convoys/feature-x/brief-1-*.md` for each task 5. **Import to DB:** `reconcile_from_files` or `create_convoy` MCP 6. **Start work:** `transition_convoy_status` to `convoy_review` ### Running Through Gates 1. **Draft → Convoy Review:** `role-architect` submits (or `create_convoy` with status) 2. **Convoy Review:** `human-gate-1` approves (→ ready) or rejects (→ draft) 3. **Ready → In Progress:** `role-implementer` starts work 4. **In Progress → PR Review:** `role-implementer` submits code 5. **PR Review:** `human-gate-2` approves (→ merged) or requests changes (→ in_progress) 6. **Merged → Released:** `human-gate-3` promotes to production ### Logging Role Completion When an agent finishes a brief: ```typescript // 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): ```typescript 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: ```typescript 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`:** ```yaml --- 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` ```markdown ## 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 ```yaml --- 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_items - `model-routing` — how model_policy aliases resolve via omniroute - `openhands-worker` — worker implementation that uses this lifecycle - `code-review-two-axis` — what role-reviewer does during pr_review