diff --git a/.convoys/.gitkeep b/.convoys/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.convoys/README.md b/.convoys/README.md new file mode 100644 index 0000000..0991073 --- /dev/null +++ b/.convoys/README.md @@ -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/.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) +├── .md (convoy file — role-conductor) +└── / + ├── brief-1-.md (role-architect) + └── ... +``` + +## Convoy file format + +Frontmatter (set by `role-conductor`, appended by other roles): + +```yaml +--- +name: +classification: feature | hotfix | docs-only | infra-only | server-only | config-only +success_metric: +skip: + - ia +status: open | in-progress | merged | shipped | abandoned +created: +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//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: \. Success = \."* +3. Conductor writes `.convoys/.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= 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--` 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 diff --git a/.convoys/model-provider-registry.md b/.convoys/model-provider-registry.md new file mode 100644 index 0000000..89dd0eb --- /dev/null +++ b/.convoys/model-provider-registry.md @@ -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` | diff --git a/.cursor/rules/convoy-planning.mdc b/.cursor/rules/convoy-planning.mdc new file mode 100644 index 0000000..94c93eb --- /dev/null +++ b/.cursor/rules/convoy-planning.mdc @@ -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/.md` | `role-conductor`, then other L2 roles | +| **Implementer briefs** | `.convoys//brief--.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. diff --git a/.gitignore b/.gitignore index 52fbf2e..53edd5c 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docker/**/data/ # misc *.pem coverage/ + +# Convoy pipeline metrics (local-only by default; see .convoys/README.md) +.convoys/.metrics.jsonl diff --git a/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Epic-pipeline-convoy-scaffolding.md b/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Epic-pipeline-convoy-scaffolding.md index 0e2d460..cba5d01 100644 --- a/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Epic-pipeline-convoy-scaffolding.md +++ b/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Epic-pipeline-convoy-scaffolding.md @@ -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` | diff --git a/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Task-convoy-readme-and-planning-rule.md b/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Task-convoy-readme-and-planning-rule.md new file mode 100644 index 0000000..22c9a6a --- /dev/null +++ b/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/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/` diff --git a/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Task-log-convoy-event-script.md b/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Task-log-convoy-event-script.md new file mode 100644 index 0000000..bcebd00 --- /dev/null +++ b/plans/Plan-automation-foundation/Epic-pipeline-convoy-scaffolding/Task-log-convoy-event-script.md @@ -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 diff --git a/plans/Plan-automation-foundation/Plan-automation-foundation.md b/plans/Plan-automation-foundation/Plan-automation-foundation.md index 9e6f876..ef3a621 100644 --- a/plans/Plan-automation-foundation/Plan-automation-foundation.md +++ b/plans/Plan-automation-foundation/Plan-automation-foundation.md @@ -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) | done | `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 | diff --git a/scripts/log-convoy-event.sh b/scripts/log-convoy-event.sh new file mode 100755 index 0000000..3fd7c6c --- /dev/null +++ b/scripts/log-convoy-event.sh @@ -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"