feat(convoys): pipeline convoy scaffolding (Epic 2)
feat(convoys): pipeline convoy scaffolding (Epic 2)
This commit is contained in:
commit
2a9be5d4e1
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
|
# misc
|
||||||
*.pem
|
*.pem
|
||||||
coverage/
|
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
|
slug: pipeline-convoy-scaffolding
|
||||||
title: Pipeline convoy scaffolding — .convoys/ and metrics in tasks repo
|
title: Pipeline convoy scaffolding — .convoys/ and metrics in tasks repo
|
||||||
plan_slug: automation-foundation
|
plan_slug: automation-foundation
|
||||||
status: draft
|
status: done
|
||||||
priority: P1
|
priority: P1
|
||||||
tenant_id: global
|
tenant_id: global
|
||||||
cursor_epic_id: null
|
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
|
# Epic objective
|
||||||
|
|
@ -38,9 +41,12 @@ Can run **in parallel** with Epic 1 (model provider registry).
|
||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] `bash scripts/log-convoy-event.sh role=role-conductor convoy=test` appends valid JSONL.
|
- [x] `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] `role-conductor` can write `.convoys/<slug>.md` without conflicting rules.
|
||||||
|
|
||||||
## Tasks
|
## 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 |
|
| # | 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 | — |
|
| 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 | — |
|
| 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 |
|
| 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 |
|
| 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