feat(convoys): add pipeline convoy scaffolding (Epic 2)
Installs .convoys/README.md, log-convoy-event.sh, convoy-planning Cursor rule, and gitignored metrics JSONL. Documents Echodo dogfood kickoff and adds a shipped convoy record for Epic 1 model provider registry. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2d309e8a75
commit
76dacff58d
10 changed files with 353 additions and 6 deletions
0
.convoys/.gitkeep
Normal file
0
.convoys/.gitkeep
Normal file
123
.convoys/README.md
Normal file
123
.convoys/README.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Convoys (Echodo / tasks repo)
|
||||
|
||||
A **convoy** is a multi-PR work-stream coordinated by the agent pipeline. One convoy = one feature, bug fix, or epic slice. Each convoy is a Markdown file in this directory plus an optional sub-directory of implementer briefs.
|
||||
|
||||
> **Not Cursor Plan mode.** Pipeline convoys live here (`.convoys/*.md`). Cursor's native Plan feature writes to `.cursor/plans/*.plan.md` — a different artifact. For pipeline work, invoke `role-conductor` (when L2 roles are installed) and write `.convoys/<slug>.md`. See [`.cursor/rules/convoy-planning.mdc`](../.cursor/rules/convoy-planning.mdc).
|
||||
|
||||
Product backlog items still live under `plans/` and sync to Echodo via markdown import. Convoys track **how** a slice is executed (roles, briefs, gates) — not the canonical plan tree.
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
.convoys/
|
||||
├── README.md (this file)
|
||||
├── .metrics.jsonl (local analytics — gitignored)
|
||||
├── <slug>.md (convoy file — role-conductor)
|
||||
└── <slug>/
|
||||
├── brief-1-<kebab-title>.md (role-architect)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Convoy file format
|
||||
|
||||
Frontmatter (set by `role-conductor`, appended by other roles):
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: <kebab-slug>
|
||||
classification: feature | hotfix | docs-only | infra-only | server-only | config-only
|
||||
success_metric: <one sentence>
|
||||
skip:
|
||||
- ia
|
||||
status: open | in-progress | merged | shipped | abandoned
|
||||
created: <YYYY-MM-DD>
|
||||
model_policy:
|
||||
roles:
|
||||
role-conductor: claude-planning
|
||||
role-implementer: qwen-coder
|
||||
escalate_to: claude-planning
|
||||
---
|
||||
```
|
||||
|
||||
Use **registry alias names** in `model_policy` (not Cursor model slugs). See [docs/adr-model-routing.md](../docs/adr-model-routing.md).
|
||||
|
||||
Body sections (typical order):
|
||||
|
||||
1. `## Why` (Conductor)
|
||||
2. `## Scope` (Conductor)
|
||||
3. `## Roles invoked` (Conductor)
|
||||
4. `## Todos` (Conductor → Architect)
|
||||
5. `## IA` (IA Architect — skip if `ia` in `skip:`)
|
||||
6. `## UX` (UX Reviewer)
|
||||
7. `## Architecture` (Architect)
|
||||
|
||||
After Architect, briefs live in `.convoys/<slug>/brief-N-*.md`. Implementers read only their brief.
|
||||
|
||||
## Skip flags
|
||||
|
||||
| Flag | Skips |
|
||||
| --- | --- |
|
||||
| `ia` | IA Architect |
|
||||
| `ux` | UX Reviewer |
|
||||
| `arch` | Architect (rare — use only for trivial hotfixes) |
|
||||
| `test` | Component tests |
|
||||
| `review` | Reviewer |
|
||||
| `visual` | Visual diff |
|
||||
| `a11y` | A11y auditor |
|
||||
| `design` | Design-system auditor |
|
||||
| `smoke` | Staging smoke |
|
||||
| `qa` | Manual QA |
|
||||
| `docs` | Doc Writer |
|
||||
| `flag` | Flag rollout |
|
||||
|
||||
Never skipped (human gates): `plan-approval`, `pr-merge`, `prod-promote`.
|
||||
|
||||
**Echodo server work** often uses `classification: server-only` with `skip: [ia, ux, design, a11y, visual, smoke]`.
|
||||
|
||||
## Status lifecycle
|
||||
|
||||
- `open` — convoy created; no briefs in flight
|
||||
- `in-progress` — at least one brief active or PR open
|
||||
- `merged` — all briefs merged; release pending
|
||||
- `shipped` — in production
|
||||
- `abandoned` — closed without shipping
|
||||
|
||||
## Kickoff ritual (this repo)
|
||||
|
||||
1. Open Cursor in the `tasks` repo on a feature branch.
|
||||
2. Prompt: *"Start a new convoy: \<idea\>. Success = \<metric\>."*
|
||||
3. Conductor writes `.convoys/<slug>.md` with `classification` and `skip` flags.
|
||||
4. **Echodo dogfood:** each implementer session should `claim_task` on the matching `plans/.../Task-*.md`, implement, then `complete_task` with `modelAlias` / `modelTier`.
|
||||
5. Architect maps briefs 1:1 to tasks when possible.
|
||||
|
||||
**Example (Epic 1 — done):** model provider registry convoy with `server-only`, briefs aligned to `Epic-model-provider-registry` tasks.
|
||||
|
||||
**Next convoys:** orchestrator (`Epic-apps-orchestrator`), convoy lifecycle MCP (`Epic-convoy-lifecycle-mcp`), connected repos (`Epic-connected-repos`).
|
||||
|
||||
## Self-analytics
|
||||
|
||||
Each L2 role appends one event via:
|
||||
|
||||
```bash
|
||||
bash scripts/log-convoy-event.sh role=role-implementer convoy=<slug> brief=1 \
|
||||
duration_s=120 model=qwen-coder model_tier=local outcome=complete
|
||||
```
|
||||
|
||||
`.convoys/.metrics.jsonl` is **gitignored** by default (metadata only, no prompts). Remove that line from `.gitignore` if you want team-visible metrics in git.
|
||||
|
||||
Validate a line was written:
|
||||
|
||||
```bash
|
||||
bash scripts/log-convoy-event.sh role=role-conductor convoy=test duration_s=1
|
||||
tail -1 .convoys/.metrics.jsonl | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Multitask (Cursor 3.2+)
|
||||
|
||||
Use `/multitask` for audit fan-out (`role-reviewer` + design + a11y) or parallel implementers when architect marks briefs as disjoint. Set `multitask_group=audit-<convoy>-<pr>` on log lines for wall-clock analytics.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Plan automation foundation](../plans/Plan-automation-foundation/Plan-automation-foundation.md)
|
||||
- [ADR model routing](../docs/adr-model-routing.md)
|
||||
- [AGENTS.md](../AGENTS.md) — repo commands and multitenancy rules
|
||||
53
.convoys/model-provider-registry.md
Normal file
53
.convoys/model-provider-registry.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
name: model-provider-registry
|
||||
classification: server-only
|
||||
success_metric: Operators configure Anthropic + local coder aliases and per-role routing from Settings; agent runs log modelAlias.
|
||||
skip:
|
||||
- ia
|
||||
- ux
|
||||
- design
|
||||
- a11y
|
||||
- visual
|
||||
- smoke
|
||||
status: shipped
|
||||
created: 2026-07-07
|
||||
model_policy:
|
||||
roles:
|
||||
role-conductor: claude-planning
|
||||
role-architect: claude-planning
|
||||
role-implementer: qwen-coder
|
||||
role-reviewer: qwen-coder
|
||||
escalate_to: claude-planning
|
||||
---
|
||||
|
||||
# Model provider registry (Epic 1)
|
||||
|
||||
## Why
|
||||
|
||||
Echodo needs a workspace-scoped model registry and `resolveModel()` contract before orchestrator/worker can dispatch unattended jobs. Epic 1 on `feat/automation-foundation` delivered the full slice.
|
||||
|
||||
## Scope
|
||||
|
||||
- `packages/shared` — schemas + `resolveModel()`
|
||||
- `model_providers` / `role_model_routes` tables
|
||||
- `api.ai.models` tRPC + Settings UI
|
||||
- `agent_runs` model columns + MCP `complete_task` fields
|
||||
|
||||
## Roles invoked
|
||||
|
||||
Conductor → Architect (briefs = Epic 1 tasks) → Implementer (per task) → Reviewer (PR).
|
||||
|
||||
## Outcome
|
||||
|
||||
Shipped in PR on `feat/automation-foundation`. Epic 2 convoy scaffolding added after merge for ongoing automation-foundation work.
|
||||
|
||||
## Briefs (architect mapping)
|
||||
|
||||
| Brief | Task slug |
|
||||
| --- | --- |
|
||||
| 1 | `shared-schemas-resolve-model` |
|
||||
| 2 | `db-model-providers-migration` |
|
||||
| 3 | `trpc-models-router` |
|
||||
| 4 | `settings-ai-providers-ui` |
|
||||
| 5 | `settings-ai-role-routing-ui` |
|
||||
| 6 | `agent-runs-model-logging` |
|
||||
35
.cursor/rules/convoy-planning.mdc
Normal file
35
.cursor/rules/convoy-planning.mdc
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
description: Pipeline planning uses .convoys/*.md — not Cursor Plan files in .cursor/plans/
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Convoy planning (pipeline work)
|
||||
|
||||
When scoping, planning, or kicking off **pipeline work** (features, hotfixes, epics tracked by L2 roles), use **convoy files** — not Cursor's native Plan mode.
|
||||
|
||||
## Where plans live
|
||||
|
||||
| Use case | Location | Who writes it |
|
||||
| --- | --- | --- |
|
||||
| **Product backlog** (Plan/Epic/Task) | `plans/` → Echodo DB | Humans + markdown import |
|
||||
| **Pipeline convoy** (idea → briefs → PR) | `.convoys/<slug>.md` | `role-conductor`, then other L2 roles |
|
||||
| **Implementer briefs** | `.convoys/<slug>/brief-<N>-<title>.md` | `role-architect` |
|
||||
| **Cursor Plan mode** (ad-hoc, non-pipeline) | `.cursor/plans/*.plan.md` | Cursor UI — **not** the agent-pipeline |
|
||||
|
||||
## Do
|
||||
|
||||
- Start pipeline work: *"Run role-conductor: start a new convoy for [idea]. Success = [metric]."*
|
||||
- Read [`.convoys/README.md`](mdc:.convoys/README.md) for frontmatter, skip flags, and lifecycle.
|
||||
- Use `model_policy.roles` with **registry alias names** per [docs/adr-model-routing.md](mdc:docs/adr-model-routing.md).
|
||||
- Log role completion: `bash scripts/log-convoy-event.sh role=... convoy=...` when the script exists.
|
||||
- Dogfood Echodo: `claim_task` / `complete_task` on the matching `plans/**/Task-*.md` during implementer sessions.
|
||||
|
||||
## Do not
|
||||
|
||||
- Create `.cursor/plans/` files for convoy or L2-role work.
|
||||
- Store convoy execution state only in chat when a `.convoys/<slug>.md` file should exist.
|
||||
- Put secrets or API keys in convoy frontmatter — use `model_policy` alias names only.
|
||||
|
||||
## If a `.cursor/plans/` file already exists
|
||||
|
||||
That is Cursor's native Plan artifact, not a pipeline convoy. Copy useful content into `.convoys/<slug>.md` and continue from the convoy file.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -55,3 +55,6 @@ docker/**/data/
|
|||
# misc
|
||||
*.pem
|
||||
coverage/
|
||||
|
||||
# Convoy pipeline metrics (local-only by default; see .convoys/README.md)
|
||||
.convoys/.metrics.jsonl
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ kind: epic
|
|||
slug: pipeline-convoy-scaffolding
|
||||
title: Pipeline convoy scaffolding — .convoys/ and metrics in tasks repo
|
||||
plan_slug: automation-foundation
|
||||
status: draft
|
||||
status: done
|
||||
priority: P1
|
||||
tenant_id: global
|
||||
cursor_epic_id: null
|
||||
updated_at: "2026-07-03"
|
||||
updated_at: "2026-07-07"
|
||||
agent_prompt: |
|
||||
Epic: pipeline convoy scaffolding. Install .convoys/, log-convoy-event.sh,
|
||||
convoy-planning.mdc. No MCP or orchestrator code. claim_task before work.
|
||||
---
|
||||
|
||||
# Epic objective
|
||||
|
|
@ -38,9 +41,12 @@ Can run **in parallel** with Epic 1 (model provider registry).
|
|||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] `bash scripts/log-convoy-event.sh role=role-conductor convoy=test` appends valid JSONL.
|
||||
- [ ] `role-conductor` can write `.convoys/<slug>.md` without conflicting rules.
|
||||
- [x] `bash scripts/log-convoy-event.sh role=role-conductor convoy=test` appends valid JSONL.
|
||||
- [x] `role-conductor` can write `.convoys/<slug>.md` without conflicting rules.
|
||||
|
||||
## Tasks
|
||||
|
||||
_To be decomposed by architect when epic graduates to `ready`._
|
||||
| Task | Link |
|
||||
|------|------|
|
||||
| log-convoy-event script | `./Task-log-convoy-event-script.md` |
|
||||
| README + planning rule | `./Task-convoy-readme-and-planning-rule.md` |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
kind: task
|
||||
slug: convoy-readme-and-planning-rule
|
||||
title: .convoys/README.md and convoy-planning Cursor rule
|
||||
plan_slug: automation-foundation
|
||||
epic_slug: pipeline-convoy-scaffolding
|
||||
status: done
|
||||
priority: P1
|
||||
tenant_id: global
|
||||
owner: unassigned
|
||||
cursor_todo_id: null
|
||||
updated_at: "2026-07-07"
|
||||
---
|
||||
|
||||
# Task summary
|
||||
|
||||
Document convoy layout, kickoff ritual, and Echodo dogfood (`claim_task` / registry aliases). Add always-on `.cursor/rules/convoy-planning.mdc` so agents use `.convoys/` instead of Cursor Plan mode.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] README covers frontmatter, skip flags, model_policy aliases (ADR)
|
||||
- [x] `convoy-planning.mdc` distinguishes `plans/` vs `.convoys/` vs `.cursor/plans/`
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
kind: task
|
||||
slug: log-convoy-event-script
|
||||
title: scripts/log-convoy-event.sh and .gitignore metrics entry
|
||||
plan_slug: automation-foundation
|
||||
epic_slug: pipeline-convoy-scaffolding
|
||||
status: done
|
||||
priority: P1
|
||||
tenant_id: global
|
||||
owner: unassigned
|
||||
cursor_todo_id: null
|
||||
updated_at: "2026-07-07"
|
||||
---
|
||||
|
||||
# Task summary
|
||||
|
||||
Add `scripts/log-convoy-event.sh` (adapted from agent-pipeline L3) and gitignore `.convoys/.metrics.jsonl`.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] `bash scripts/log-convoy-event.sh role=role-conductor convoy=test` appends valid JSONL
|
||||
- [x] Script is executable and uses python3 for safe JSON encoding
|
||||
|
|
@ -163,7 +163,7 @@ flowchart LR
|
|||
| # | Epic | Status | Delivers | Depends on |
|
||||
|---|------|--------|----------|------------|
|
||||
| 1 | [Model provider registry](./Epic-model-provider-registry/Epic-model-provider-registry.md) | **ready** (6 tasks) | `resolveModel()`, Settings AI pages, run model logging | — |
|
||||
| 2 | [Pipeline convoy scaffolding](./Epic-pipeline-convoy-scaffolding/Epic-pipeline-convoy-scaffolding.md) | draft | `.convoys/`, `log-convoy-event.sh`, `convoy-planning.mdc` — dogfood metrics | — (parallel with 1) |
|
||||
| 2 | [Pipeline convoy scaffolding](./Epic-pipeline-convoy-scaffolding/Epic-pipeline-convoy-scaffolding.md) | done | `.convoys/`, `log-convoy-event.sh`, `convoy-planning.mdc` — dogfood metrics | — (parallel with 1) |
|
||||
| 3 | [Connected repos](./Epic-connected-repos/Epic-connected-repos.md) | draft | `repo_links` table, “paste remote URL”, forge adapter interface | — |
|
||||
| 4 | [Convoy lifecycle MCP](./Epic-convoy-lifecycle-mcp/Epic-convoy-lifecycle-mcp.md) | draft | `create_convoy`, `transition_convoy_status`, state machine §7, `convoy_events` | 2, 3 |
|
||||
| 5 | [Mission control gates UI](./Epic-mission-control-gates-ui/Epic-mission-control-gates-ui.md) | draft | Convoy kanban, gate inbox, approve/deny/revise, gate co-pilot chat | 4 |
|
||||
|
|
|
|||
83
scripts/log-convoy-event.sh
Executable file
83
scripts/log-convoy-event.sh
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env bash
|
||||
# log-convoy-event.sh — append one convoy event to .convoys/.metrics.jsonl.
|
||||
#
|
||||
# Used by L2 roles to emit lightweight metrics for self-analytics.
|
||||
# Schema: github.com/varutasu/agent-pipeline/analytics/schemas/convoy-event.json
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/log-convoy-event.sh role=role-conductor convoy=bookmark-badge \
|
||||
# classification=feature 'skip_flags=visual,smoke' duration_s=42 \
|
||||
# model=claude-planning model_tier=premium
|
||||
#
|
||||
# All args are key=value. Required: role, convoy.
|
||||
# Optional: brief, classification, skip_flags (comma-separated), duration_s,
|
||||
# stack_class, outcome, multitask_group, model, model_tier,
|
||||
# estimated_cost_usd.
|
||||
#
|
||||
# model / model_tier: use registry alias names per docs/adr-model-routing.md.
|
||||
#
|
||||
# Privacy: this file is gitignored by default; events contain only metadata,
|
||||
# no code or prompts.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
REPO_NAME="$(basename "$REPO_ROOT")"
|
||||
METRICS_FILE="$REPO_ROOT/.convoys/.metrics.jsonl"
|
||||
mkdir -p "$REPO_ROOT/.convoys"
|
||||
|
||||
ROLE=""; CONVOY=""; BRIEF=""; CLASSIFICATION=""
|
||||
SKIP_FLAGS=""; DURATION_S=""; STACK_CLASS=""; OUTCOME=""; MULTITASK_GROUP=""
|
||||
MODEL=""; MODEL_TIER=""; ESTIMATED_COST_USD=""
|
||||
|
||||
for arg in "$@"; do
|
||||
k="${arg%%=*}"
|
||||
v="${arg#*=}"
|
||||
case "$k" in
|
||||
role) ROLE="$v" ;;
|
||||
convoy) CONVOY="$v" ;;
|
||||
brief) BRIEF="$v" ;;
|
||||
classification) CLASSIFICATION="$v" ;;
|
||||
skip_flags) SKIP_FLAGS="$v" ;;
|
||||
duration_s) DURATION_S="$v" ;;
|
||||
stack_class) STACK_CLASS="$v" ;;
|
||||
outcome) OUTCOME="$v" ;;
|
||||
multitask_group) MULTITASK_GROUP="$v" ;;
|
||||
model) MODEL="$v" ;;
|
||||
model_tier) MODEL_TIER="$v" ;;
|
||||
estimated_cost_usd) ESTIMATED_COST_USD="$v" ;;
|
||||
*) echo "log-convoy-event: ignoring unknown arg '$k'" >&2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$ROLE" ] || [ -z "$CONVOY" ]; then
|
||||
echo "log-convoy-event: role and convoy are required" >&2
|
||||
echo "Usage: $0 role=<role> convoy=<slug> [classification=...] [skip_flags=a,b] [duration_s=N] [brief=N] [model=...] [model_tier=premium|fast|local|auto] [estimated_cost_usd=N]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
python3 - <<PY >> "$METRICS_FILE"
|
||||
import json, sys
|
||||
ev = {
|
||||
"ts": "$ts",
|
||||
"role": $(printf '%s' "$ROLE" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))'),
|
||||
"convoy": $(printf '%s' "$CONVOY" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))'),
|
||||
"repo": $(printf '%s' "$REPO_NAME" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))'),
|
||||
"skip_flags": [s for s in "$SKIP_FLAGS".split(",") if s],
|
||||
}
|
||||
if "$BRIEF": ev["brief"] = int("$BRIEF")
|
||||
if "$CLASSIFICATION": ev["classification"] = "$CLASSIFICATION"
|
||||
if "$DURATION_S": ev["duration_s"] = int("$DURATION_S")
|
||||
if "$STACK_CLASS": ev["stack_class"] = "$STACK_CLASS"
|
||||
if "$OUTCOME": ev["outcome"] = "$OUTCOME"
|
||||
if "$MULTITASK_GROUP": ev["multitask_group"] = "$MULTITASK_GROUP"
|
||||
if "$MODEL": ev["model"] = "$MODEL"
|
||||
if "$MODEL_TIER": ev["model_tier"] = "$MODEL_TIER"
|
||||
if "$ESTIMATED_COST_USD":
|
||||
ev["estimated_cost_usd"] = float("$ESTIMATED_COST_USD")
|
||||
print(json.dumps(ev))
|
||||
PY
|
||||
|
||||
echo "Logged: role=$ROLE convoy=$CONVOY → .convoys/.metrics.jsonl"
|
||||
Loading…
Reference in a new issue