Skip to content

feat(cli): one output contract a skill can consume, identical whatever tool produced the work #690

Description

@blafourcade

As someone writing a skill that reports on AIDD work
I want one machine-readable shape, the same whatever tool ran, that says what each tool could and could not supply
So that a skill branches on a declared capability instead of guessing from a missing number, and two identical calls give two identical answers

Why now

#629 shipped aidd telemetry report. Its figures are right and proven across three tools end to end. But nothing about it is consumable by a program, and the flow that feeds it cannot be followed by a person.

Four things block a skill today, all measured

1. There is no flow. aidd telemetry read takes --session <uuid> as a required option, and nothing tells a user their session id. The journal has known every session id all along; RunJournalReader.list() has existed since #629 phase 1 and nothing calls it.

2. --days resolves against Date.now(). The same command run twice covers two different periods. A skill cannot cite a figure it cannot reproduce.

3. The attribution block's order is whatever order the records arrived in. Real output from the multi-tool e2e:

attribution    of tokens
    stated by the tool          3%
    unattributed               74%
    from a journal interval    24%

Three rows, arbitrary order, and a strength with no records vanishes from the list entirely — though zero really is the measurement there, not an absence.

4. The output is aligned text. A skill scraping columns breaks the first time one gets wider.

The decision this rests on

A skill reads the report's output, never the stored records. The two double-count rules, the split between the two record kinds, and the turn_id dedup would otherwise be re-implemented by every consumer, differently — the same failure the decision table in #687 names for vendor_field. One computation, in one place.

Equivalent information, per tool

The second half of this ticket. What each tool can supply is currently spread across document prose, source comments and a table inside the hook. A consumer cannot read any of it, so it has to infer capability from whether a number happened to be present — which is exactly the inference that turns a limit into a zero.

Measured today:

Tool Route Tokens Amount Step, stated by the tool Step, from the journal Task
Claude Code local yes no yes (attributionSkill) yes yes
Claude Code export yes yes no — the vendor attribute reads third-party no — the mapper runs while the journal is still open no
Codex local yes no no yes no — writes through apply_patch, no readable path
Codex export unmeasured unmeasured
OpenCode local yes no — info.cost is 0 everywhere and its denomination was never established no no — no captured payload carries its session id, so nothing joins no
Copilot local unsupported — outputTokens per turn, nothing else per request declared, but silent (#681) no
Copilot export identity lives on a span; the receiver listens on /v1/logs and /v1/metrics only
Cursor local unsupported — no token count in any file it writes turn-end never fires headless (#680) no
Cursor export unmeasured — an Enterprise team setting nobody here can enable

Two readings of that table:

  • No locally-read tool carries an amount. None. Claude Code's cost_usd reaches the sink only through its OTLP export. Proven by the multi-tool e2e, which asserts the report contains no $ at all. Equivalence on money is not reached here and is not meant to be: the rates live in the governor, and this repository's job is to emit tokens complete enough to price.
  • The journal is the only lever that raises equivalence, because the framework writes it rather than the tool. It already gives step boundaries on four hosts identically. Its real coverage is two, and fix(framework): Cursor's turn-end hook never fires headless #680 and fix(framework): the run journal never writes on Copilot #681 are why.

So the field is uniform, the presence is not, and forcing the second would be lying. What this ticket adds is that presence becomes declared rather than discovered — each tool states what it can supply, and the report emits that beside the figures.

Acceptance

  • aidd telemetry report --json prints one object, carrying a version a consumer can refuse.
  • The period is reported as it resolved, absolutely, never as it was asked.
  • --from and --to take absolute days; --days stays as the human shorthand and is defined in terms of them.
  • The same files and the same absolute period produce byte-identical output, twice.
  • All three attribution strengths always appear, in a fixed order, zero where zero is the measurement.
  • Every declared tool carries a capability block: readable by which route, carries an amount or not, states its own step or not, journal-attributable or not, task-attributable or not — from declarations, never inferred from whether a figure is present.
  • Adding a tool changes a declaration; neither the report nor the printer is touched.
  • aidd telemetry read with no --session reads every session the journal knows and has not already stored, and says how many it read.
  • --session still works, unchanged, for one session by name.
  • Records the read could not place, and lines it could not parse, are carried into the output so a partial read cannot read as a complete one.
  • The human output keeps every guarantee it has today; the JSON is a second rendering of one computation, never a second computation.

Out of scope

Relations

Field Value
parent #631
depends_on #629, #687
related #680, #681, #689, #676

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions