440 lines
14 KiB
Markdown
440 lines
14 KiB
Markdown
|
|
---
|
||
|
|
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/<slug>.md`.
|
||
|
|
|
||
|
|
**Frontmatter structure:**
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
---
|
||
|
|
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`:
|
||
|
|
|
||
|
|
```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
|