Version: 0.1.0
Created: 2026-04-26
PRD Reference: docs/prd.md
Status: Shipped (Path A)
Target Release: v0.9.3
Milestone: none (standalone v0.9.x)
Depends-on: specs/telemetry-jsonl.md (parent contract)
Coordinates-with: PR #6 — references: frontmatter mechanism for skills
ADD v0.9.0 shipped core/rules/telemetry.md (auto-loaded) and the
companion JSONL contract in specs/telemetry-jsonl.md. The rule defines
the OpenTelemetry GenAI-aligned schema, file location, rotation, and the
Pre-Flight / Post-Flight Contract every skill must follow. The rule
is loaded into every session via autoload: true, so every skill can
read the contract.
What's missing — and what Swarm F deferred at M3 — is the per-skill
acknowledgement that participation is intentional. Today the rule
documents the contract but no core/skills/*/SKILL.md declares "this
skill emits telemetry per core/rules/telemetry.md". The rule's own
body says it plainly:
Every ADD SKILL.md should reference this rule in its pre-flight block (implicit via autoload today; explicit
@reference core/rules/telemetry.mdsweep is a deferred post-M3 follow-up).
That deferred follow-up is this spec.
Swarm F deferred the per-skill sweep during M3 because every Wave-2 and Wave-3 swarm was modifying SKILL.md files (cache-discipline audited four skills, test-deletion-guardrail edited tdd-cycle/verify, agents-md-sync touched init/spec/verify). A 27-file mass-append during M3 would have collided with every parallel branch and produced a merge-conflict storm. With M3 now merged, the file-set is quiet and a mechanical sweep is safe.
Touching all 27 SKILL.md files is a cross-cutting change. The design is cheap (one frontmatter key or one prose line per file); the audit discipline is the work — every skill must be inspected, status recorded, and the change verified by a fixture test that fails when a future skill ships without the reference.
As an ADD methodology owner, I want every skill to declare its participation in the telemetry contract, so that (a) the contract is discoverable from any individual SKILL.md, (b) compile-time validation can detect a future skill that ships without telemetry participation, and (c) the dashboard's Cost & Velocity panel can trust that 100% of skill invocations emit a JSONL line.
Three options exist for declaring telemetry participation:
| Option | Mechanism | Pros | Cons |
|---|---|---|---|
| (a) | references: [rules/telemetry.md] in SKILL.md frontmatter (PR #6 mechanism) |
Structured, machine-readable, validated by frontmatter validator, no body churn | Hard dependency on PR #6 landing |
| (b) | Body-prose @reference core/rules/telemetry.md line in pre-flight |
Zero dependency, works today, matches existing prose conventions | Not machine-validated beyond grep; cosmetic noise per file |
| (c) | Compile-time auto-injection (every skill auto-gets the line) | Zero per-file edit | Magic — invisible to skill authors, hides the contract, contradicts ADD's "source is canonical" ethos |
Recommendation: (a) when PR #6 lands; (b) as a hard fallback if PR #6 slips beyond v0.9.2. Option (c) is rejected — too magic for a methodology plugin where every contract should be readable in the source file an author edits. ADD's value proposition is that the markdown is the system; auto-injection would erode that.
The acceptance criteria below mandate one path or the other; the implementation plan picks the path live based on PR #6 status at start.
| ID | Criterion | Priority |
|---|---|---|
| AC-001a | Every core/skills/*/SKILL.md adds references: [rules/telemetry.md] to its YAML frontmatter |
Must |
| AC-002a | scripts/validate-frontmatter.py recognises the key (PR #6 contract) and validates the path resolves to an existing rule |
Must |
| AC-003a | references: accepts a list — adding telemetry does not displace any future entries |
Must |
| ID | Criterion | Priority |
|---|---|---|
| AC-001b | Every core/skills/*/SKILL.md includes a single line @reference core/rules/telemetry.md inside its ## Pre-Flight section (or the equivalent first-numbered-list pre-flight block) |
Must |
| AC-002b | The line is the first or last bullet of the pre-flight list, consistently positioned across all skills | Must |
| AC-003b | A future migration to (a) is mechanical — when PR #6 lands, a follow-up commit replaces the prose line with the frontmatter key | Should |
| ID | Criterion | Priority |
|---|---|---|
| AC-010 | Every skill in core/skills/*/SKILL.md (all 27) declares the telemetry reference via the chosen mechanism |
Must |
| AC-011 | Skills that already emit telemetry implicitly via autoload are still edited — there is no implicit-emission shortcut. Every skill is explicit. | Must |
| AC-012 | The audit checklist in spec § 7 enumerates all 27 skills with status: swept, skipped (with reason) |
Must |
| AC-013 | No skill is skipped without a documented reason in the checklist | Must |
| ID | Criterion | Priority |
|---|---|---|
| AC-014 | The declaration causes the skill's pre-flight to emit a JSONL line in the format defined by core/rules/telemetry.md |
Must |
| AC-015 | No skill behavior changes beyond the telemetry emission — no AC reordering, no prose rewrites, no allowed-tools changes | Must |
| AC-016 | The reviewer (/add:reviewer) sub-agent receives the same telemetry contract via inheritance from the dispatching skill — emission is single, not double |
Must |
| AC-017 | Nested skill invocations emit gen_ai.operation.name = "skill_invocation.nested" per the parent contract; double-emission of the outer skill is forbidden |
Must |
| ID | Criterion | Priority |
|---|---|---|
| AC-020 | New fixture test tests/telemetry-sweep/test-skill-reference-coverage.sh asserts every core/skills/*/SKILL.md has the chosen reference declaration |
Must |
| AC-021 | Test fails (exit 1) if any new skill is added to core/skills/ without the reference |
Must |
| AC-022 | Test runs in under 2 seconds on a developer laptop (fixture-style, no model calls) | Should |
| AC-023 | python3 scripts/compile.py --check remains clean after the sweep — generated plugins/add/ and dist/codex/ regenerate deterministically |
Must |
| AC-024 | python3 scripts/validate-frontmatter.py passes for all 27 SKILL.md files |
Must |
| ID | Criterion | Priority |
|---|---|---|
| AC-030 | Project-level opt-out remains controlled by .add/config.json:telemetry.enabled = false (already defined by parent spec, AC-019) |
Must |
| AC-031 | This sweep does not introduce a per-skill opt-out flag — telemetry participation is universal at the methodology level; project-level opt-out covers the use case | Must |
| AC-032 | When telemetry.enabled = false, the reference is still present in the SKILL.md (declarative correctness) but the runtime emits nothing — consistent with parent spec AC-019 |
Must |
Precondition: Sweep has been applied to all 27 skills via the chosen path. Steps:
- Run
bash tests/telemetry-sweep/test-skill-reference-coverage.sh - Test enumerates
core/skills/*/SKILL.md - Test asserts the chosen declaration is present in each file Expected Result: All 27 assertions pass. Exit code 0. Maps to: TBD
Precondition: Sweep applied. Repo state clean. Steps:
python3 scripts/compile.pypython3 scripts/compile.py --check- Inspect a sample compiled output (
plugins/add/skills/spec/SKILL.md) for the reference Expected Result:--checkreturns 0 (no drift). Compiled SKILL.md includes the declaration verbatim (path-A: frontmatter key; path-B: prose line). Codexdist/mirrors. Maps to: TBD
Precondition: A clean ADD-managed test project with telemetry.enabled = true. Today is 2026-MM-DD.
Steps:
- Agent runs
/add:plan specs/example.md - Skill completes (success or fail)
- Inspect
.add/telemetry/2026-MM-DD.jsonlExpected Result: Exactly one line appended for this invocation. JSON parses.skill: "plan".outcome∈ {success, failed, aborted, partial}. The line is produced because the SKILL.md's reference declaration triggered pre-flight + post-flight emission percore/rules/telemetry.md. Maps to: TBD
Precondition: /add:tdd-cycle specs/foo.md dispatches /add:reviewer as a sub-agent.
Steps:
- tdd-cycle starts; pre-flight captures state
- tdd-cycle dispatches reviewer via Task tool
- reviewer runs to completion
- Reviewer post-flight emits one line with
gen_ai.operation.name = "skill_invocation.nested"and the samesession_id - tdd-cycle post-flight emits one line with
gen_ai.operation.name = "skill_invocation" - Inspect today's JSONL file
Expected Result: Exactly two lines for this invocation pair: one outer, one nested. Same
session_id. No third line. No skill double-emits its own outcome. Maps to: TBD
Precondition: Test project has .add/config.json:telemetry.enabled = false.
Steps:
- Agent runs any swept skill
- Skill completes
- Inspect
.add/telemetry/Expected Result: Directory is not created. No JSONL written. Skill behavior is otherwise identical. Confirms AC-030, AC-032 — the reference declaration is still present in the SKILL.md (declarative), but runtime is silent because the rule honours the config. Maps to: TBD
None beyond what specs/telemetry-jsonl.md already defines. This sweep
adds no new fields to the JSONL schema and no new config keys to
.add/config.json. It is a methodology-level change that makes the
existing contract explicit at every skill site.
| Case | Expected Behavior |
|---|---|
| Skill fails before reaching post-flight | Parent-spec AC-017 already requires emission with outcome: "failed"; the reference declaration carries that same contract |
| Skill aborted by user mid-flight | Emit outcome: "aborted"; same guarantee as above |
Very-fast skill (e.g. /add:back, sub-100ms) |
Still emits one line. duration_ms is small but accurate. No floor on emission |
| Reviewer sub-agent runs as a Task dispatch | Reviewer's own SKILL.md carries the reference; reviewer emits its own nested line. The dispatching skill emits its own outer line. Two lines total, same session_id |
Skill that only documents (e.g. /add:learnings show) |
Still swept — every skill gets the reference. Read-only skills emit a line with tool_calls reflecting only Read calls |
Future skill added to core/skills/ |
Fixture test (AC-020) fails until the new skill carries the reference. CI gate prevents drift |
Path-A chosen but PR #6's references: value list contains a non-existent path |
PR #6's frontmatter validator already rejects this; not in scope here |
| Path-B chosen, then PR #6 lands later | Migration is mechanical: replace the body-prose line with the frontmatter key. No spec amendment needed; treat as a chore commit |
Skill body has multiple ## Pre-Flight sections (none today, but possible) |
Place the reference in the first section. Validator confirms presence anywhere in the file when path-B; presence in frontmatter when path-A |
| Compile-drift CI flags newline differences after sweep | Run python3 scripts/compile.py after the SKILL.md edits, commit the regenerated plugins/add/ and dist/codex/ in the same change |
Populated during implementation. Status values:
swept— reference declaration added.skipped (reason)— explicitly excluded with documented rationale.
| # | Skill | Status | Notes |
|---|---|---|---|
| 1 | agents-md | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 2 | away | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 3 | back | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 4 | brand | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 5 | brand-update | swept | Path A — appended to existing references: (had design-system.md) |
| 6 | changelog | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 7 | cycle | swept | Path A — appended to existing references: (had learning-reference.md, swarm-protocol.md) |
| 8 | dashboard | swept | Path A — appended to existing (had design-system.md). dashboard READS telemetry but never INTO LLM context; the skill invocation still emits |
| 9 | deploy | swept | Path A — appended to existing references: |
| 10 | docs | swept | Path A — appended to existing references: |
| 11 | implementer | swept | Path A — fresh; sub-agent only, emits nested line |
| 12 | infographic | swept | Path A — appended to existing (had design-system.md, image-gen-detection.md) |
| 13 | init | swept | Path A — fresh. Bootstrap skill — runs before .add/ exists; rule creates the telemetry dir on first emission |
| 14 | learnings | swept | Path A — fresh. learnings archive triggers telemetry retention pruning per parent AC-028; still emits its own line |
| 15 | milestone | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 16 | optimize | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 17 | plan | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 18 | promote | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 19 | retro | swept | Path A — appended to existing (had learning-reference.md) |
| 20 | reviewer | swept | Path A — fresh; sub-agent only, emits nested line; AC-016 forbids double-emit by parent |
| 21 | roadmap | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 22 | spec | swept | Path A — fresh references: ["rules/telemetry.md"] |
| 23 | tdd-cycle | swept | Path A — appended to existing references: (dispatches multiple sub-agents; outer line + N nested) |
| 24 | test-writer | swept | Path A — fresh; sub-agent only, emits nested line |
| 25 | ux | swept | Path A — appended to existing (had design-system.md) |
| 26 | verify | swept | Path A — appended to existing (had learning-reference.md, quality-checks-matrix.md). Sub-agent when invoked from tdd-cycle; top-level /add:verify emits outer |
| 27 | version | swept | Path A — fresh references: ["rules/telemetry.md"] |
All 27 swept via Path A (PR #6 references: mechanism) on 2026-04-26. None skipped. 17 fresh entries; 10 appended to existing references: lists (cycle, verify, deploy, docs, retro, tdd-cycle, infographic, dashboard, brand-update, ux). Coverage enforced by tests/telemetry-sweep/test-skill-reference-coverage.sh.
- Re-defining the JSONL schema. Schema lives in
specs/telemetry-jsonl.md; this sweep is purely declarative. - Adding new fields to the schema (e.g. no new "reference_source" field).
- Shipping a runtime emitter. The rule is the contract; skills follow it. No code is added.
- Modifying
core/rules/telemetry.md. The rule already documents the deferred sweep — no rule edits needed beyond removing that "deferred" parenthetical once the sweep ships. - Per-skill opt-out flags. Project-level
.add/config.json:telemetry.enabledcovers the use case. - Touching
plugins/add/**ordist/codex/**directly — generated. - Backfilling historical telemetry. Sweep is forward-looking only.
| Question | Recommendation |
|---|---|
| Final design choice (a/b/c) | (a) if PR #6 has merged at v0.9.2 cut; (b) as a hard fallback. (c) rejected as too magic. Resolved at implementation start. |
| Opt-out flag location | .add/config.json:telemetry.enabled already exists and already gates emission. No new opt-out is added. |
| Reviewer-emission semantics | Reviewer (and any sub-agent) emits exactly one nested line per invocation; the dispatching skill emits one outer line. Same session_id. AC-016 + AC-017 + parent AC-011 are the contract. |
Should core/rules/telemetry.md be edited to remove the "deferred" parenthetical? |
Yes — a one-line edit at the end of this sweep, in the same commit that closes the deferral. Strictly out-of-scope of "no rule edits" since it is removing dead-letter prose, not changing the contract. |
| Migration path A→B if PR #6 reverts post-merge | Mechanical reverse of the same sweep. Spec does not need re-issue; treat as a chore. |
specs/telemetry-jsonl.md— parent contract; defines schema, rotation, opt-out, concurrent-write semantics.specs/cache-discipline.md— provides the precedent audit pattern (§ 4 audit checklist of all skills) this spec mimics.core/rules/telemetry.md— the rule whose reference is being declared. Authored in v0.9.0; no changes required beyond optional deferred-parenthetical cleanup.- Optional: PR #6 (
references:frontmatter mechanism) — preferred path-A enabler. If unmerged, fall back to path-B. scripts/validate-frontmatter.py— must accept the new key when path-A. PR #6 owns this.scripts/compile.py— must propagate the declaration verbatim intoplugins/add/anddist/codex/. No changes expected — frontmatter and body prose pass through.
| Category | Requirement |
|---|---|
| Environment variables | N/A |
| Registry images | N/A |
| Cloud quotas | N/A |
| Network reachability | N/A |
| CI status | compile.py --check, validate-frontmatter.py, tests/hooks/test-filter-learnings.sh, and the new tests/telemetry-sweep/test-skill-reference-coverage.sh must all pass |
| External secrets | N/A |
| Database migrations | N/A |
Verification before implementation: Confirm PR #6 status. If merged, proceed path-A. If not, proceed path-B and document the migration commit that will follow PR #6 merger.
Small. The design is the work; the edits are formulaic.
- 27 SKILL.md files × one-line edit each = ~27 lines of net change
- One new fixture test (~50 lines of bash)
- One audit-checklist update in this spec (~27 rows promoted from
pending→swept) - One regenerated
plugins/add/+dist/codex/pair via compile
Estimate: 0.5–1 day. Bulk-editable via parallel sub-agent dispatch (3 agents × 9 skills each) or a single mechanical pass.
| Date | Version | Author | Changes |
|---|---|---|---|
| 2026-04-26 | 0.1.0 | abrooke + Claude | Initial spec — closes Swarm F's M3-deferred per-skill reference sweep; articulates path-A (PR #6) and path-B (fallback) |
| 2026-04-26 | 1.0.0 | abrooke + Claude | Shipped via Path A. PR #6 merged at 3522a8f, unblocking the references: frontmatter mechanism. All 27 SKILL.md files swept; § 7 audit checklist filled; coverage test tests/telemetry-sweep/test-skill-reference-coverage.sh passes. Closes F-013. |