Skip to content

feat(framework): a skill that reports cost per task and per step #629

Description

@blafourcade

As a developer or a tech lead
I want to see what a task cost, broken down by step and by model
So that I can decide where to spend optimisation effort instead of guessing

Acceptance

  • Steps sum to the task total, with a residual bucket so the sum reconciles exactly.
  • A task with no session prints zeros and exits 0, never an error.
  • A run whose identifier joins nothing is counted as unattributed and named, never dropped.
  • Telemetry whose session id matches no run file is also named — the mirror case, and the one the epic exists to catch.
  • A session that emitted a run file but no datapoints is distinguished from a session that was never journaled.
  • The unattached share over the period is printed, and the period is a named flag with a documented default.
  • When two skills interleave, the output says step attribution is approximate rather than presenting it as exact.
  • No prompt, code or diff content appears anywhere in the output.

Expected output

task 2026_08_14_telemetry-v1

  sessions            6
  active time         47 min          (per session; not attributable to steps)
  tokens              310,400         34% cache
  cost                $4.20

  by step
    aidd-dev:02-implement    61%   $2.56
    aidd-dev:05-review       19%   $0.80
    aidd-dev:01-plan         12%   $0.50
    residual                  8%   $0.34

  by model
    claude-opus-5            78% of cost for 31% of calls

  unattached          39% of the period

How the join actually works

Measured, and not what the first design assumed.

Per-session totals come from the metrics: claude_code.token.usage and claude_code.cost.usage both carry session.id, and claude_code.active_time.total gives the time.

Per-step breakdown cannot come from the metrics. skill.name on both counters reads the literal string third-party for every AIDD skill, because the docs replace third-party plugin skill names, and OTEL_LOG_TOOL_DETAILS=1 does not lift it on metrics. The real name appears only on the skill_activated log event.

So the breakdown is an exact event correlation, not a time window. Measured on a real session, both events carry the same correlation keys:

Event Carries
skill_activated the real skill.name, with session.id, prompt.id, event.sequence
api_request input_tokens, output_tokens, cache_*, cost_usd, model, query_source, with session.id, prompt.id, event.sequence

The rule: within a session, order by event.sequence and carry the last skill_activated forward onto the api_request records that follow, until the next one.

api_request has its own skill.name, and it is redacted to third-party exactly like the metrics — with the flag on as well. It is not usable, and the carry-forward is what replaces it. That is not a workaround: it mirrors the provider's sticky attribution instead of fighting it.

The same mechanism serves the other four tools later, with one difference: none of them emits a skill_activated equivalent, so there the step boundaries must be emitted by the framework itself.

Two measured limits the output must respect:

  • skill.name is sticky. Once activated it rides the following datapoints, including subagents launched afterwards. Correct for sequential steps, wrong for interleaved ones.
  • active_time.total carries no skill attribute. Time is per session only. Any percentage in the by-step block is cost, never time.

Why a skill and not a CLI command

The question is asked from inside a session, about work in progress. The plugin already carries the diagnostic; the figure belongs beside it. This also satisfies #297's requirement that at least one skill consume the data and produce something a user acts on, without sending anything anywhere.

Out of scope

  • The four other tools, and the price table the two that export no amount will need.
  • Any aggregation by person, team or epic.
  • Currency conversion. Costs print in USD, as exported.

Relations

Field Value
parent #631
depends_on #620, #646, #647
related #617, #632

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    Priority

    None yet

    Projects

    Status
    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions