Skip to content

Releases: amiable-dev/skills-telemetry

0.9.1

Choose a tag to compare

@amiable-dev amiable-dev released this 03 Oct 09:58
c33a407

Fixes found watching a live session: usage on the dashboard, MCP and skill names through the collector, and
skills started by slash command. Restart the collector and Grafana after upgrading (docs/local-stack.md).

Fixed

  • A skill started by slash command is recorded, and a loop skill started that way opens its scope
    (#144).
    stdtel captured skills only through the Skill tool. A skill the user types as /name
    never goes through that tool, so it was invisible. Verified: /probe-echo produced a turn and no
    skill activation. A loop skill declaring telemetry.scope therefore never opened its container
    when started the usual way. The Stop hook now reads typed commands from the transcript:

    • A command is a skill when the harness loaded one for it (its "Base directory for this skill:"
      entry), or when the catalogue knows the name. Built-ins such as /model and /compact are
      ignored. A typed skill is recorded with trigger user-slash.
    • /loop /epic-loop …: the harness activates only loop, and epic-loop is its argument.
      By decision, a catalogued skill named first in /loop's argument is recorded as invoked with
      trigger loop, so the inference is labelled and can be filtered out.
    • Both are std.artefact.source=transcript. Only the command name and that first argument word
      are read. Replayed against the real sightline transcript, it finds the session's five
      /loop /epic-loop starts, the first on 31 August, and ignores /model, /compact and
      /advisor.
  • MCP server, MCP tool and skill names survive the collector (#142). With the detailed view on,
    Claude Code puts them only inside tool_parameters, beside shell commands. Every user-configured
    MCP call's tool_name is the literal "mcp_tool". The collector deleted tool_parameters whole,
    so the names a user opted in to never reached storage.

    • A new transform/tool_names lifts exactly four keys out first: mcp_server_name,
      mcp_tool_name, skill_name and subagent_type. Only values shaped like a name are lifted
      (short, no spaces); the content is then dropped as before. It runs on logs and traces, under both
      overlays, and is tested on the real image with a content marker beside the names.
    • Verified live: the first MCP call after the collector restart arrived as llm-council, and no
      content key appeared in any of 66 real events.
    • The operational dashboard gains tables for MCP calls by server and tool, skills invoked, and
      tool calls by tool and success. A hidden name reads (hidden: detailed view off).
    • Tables now show their value instead of a timestamp, and cache-read tokens sit on their own axis.
  • The operational dashboard shows usage (#140). During a live session that spent $15.43 over 167
    model requests, it showed none of it. Three causes:

    • Nothing read Loki, where Claude Code's own per-request records land. A new top row shows
      spend and requests in the range, spend per hour, tokens per hour by type, requests per hour by
      model, and spend by skill, agent and repo. All of it is live from Loki, with no loader in between.
    • "Claude Code tokens by type" read a native metric the collector drops by design since #110,
      so it could never show anything. It is removed. A test now fails any panel that queries a metric
      prefix in the collector's filter/native_metrics.
    • Seven "/ h" panels plotted per-second rates. "0.003 turns/h" was about 11. They are scaled to
      per hour, and a test holds every "/ h" rate to it.

    Skill panels now say "No skill ran in this range" instead of "No data". Every Loki panel's query is
    run against Loki in tests.

  • Grafana reads datasource provisioning only at startup, so a stack running since before 0.8.0
    has no Loki datasource and every usage panel fails with "Data source not found".
    docs/local-stack.md gives the one-line fix: restart Grafana alone.

0.9.0

Choose a tag to compare

@amiable-dev amiable-dev released this 02 Oct 08:47
0d36651

A skill's cost is now the harness's own requests named for it, and stdtel's tail-rule estimate is
retired (ADR-014 decision 10). Breaking on the wire and in the warehouse, as listed below; a minor
bump, as with 0.4.0 and 0.7.0, because this is pre-1.0 and the changelog says plainly what breaks.
Upgrade both halves: the package and the plugin (stdtel-query 1.2.0 and the analyst 1.1.0 ship in
it).

Changed — breaking, on the wire and in the warehouse

  • A skill's cost is the harness's own requests, and stdtel's estimate is retired (ADR-014
    decision 10, #117).
    Native records were verified live in 0.8.0, which started the clock the ADR
    set. A user will see three things:
    1. Skill spans carry no tokens and no model. std.skill.load_tokens, std.skill.tail_tokens,
      std.skill.tail_tokens_first_only, std.skill.llm_requests and the gen_ai.usage.* and
      gen_ai.request.model attributes are gone from kind=skill activations, and the allowlist
      refuses them. Turn, sub-agent, compaction and session spans keep their counts: those are what the
      transcript observed, not an attribution.
    2. Nine skill_invocation columns are dropped: the three estimates, llm_requests, the four
      token columns and model. They defaulted to 0, which for every new row would have read as
      "measured, and free". As at the ADR-013 cutover, the old values (estimates) are not migrated.
    3. Per-skill cost is NULL for any session without Claude Code's own telemetry on: every
      machine that has not run stdtel-install settings (or --native-only with the plugin) since
      0.8.0. Such runs are counted as unmeasured, never as free.
  • New views:
    • skill_request_cost, one row per (session, prompt, skill) over llm_request_attributed;
    • skill_activation_cost, the same with stdtel's version, plus abandoned, which is NULL when
      the prompt has no native record.
  • Rebuilt on the new views:
    • the scorecard: total_cost_usd, total_tokens, p50_tokens, requests_derived,
      abandonment_rate and unmeasured_activations replace p50_load_tokens, p50_tail_tokens
      and the old total_tokens;
    • Q5 (skill_tokens, n_prompts, n_unmeasured);
    • Q6, which counts each request in a scoped prompt once, plus external spend, and reports
      prompts_without_usage;
    • the loop and token-by-kind dashboard panels;
    • Q1, whose skill row now reports the harness's requests named for a skill in the session, and
      counts unmeasured runs, rather than showing every skill as usage-free;
    • the demo fleet, which now seeds llm_request. Every value it seeded before is unchanged: a full
      dump of generate(42) before and after, minus the retired fields, is byte-identical.
  • The scorecard panel is now held verbatim to scorecard.sql by a test, and every Warehouse panel
    is executed against the schema in tests. Before, a panel selecting a dropped column passed all of
    them.
  • stdtel-query 1.2.0 and the scorecard analyst 1.1.0 read cost from the views.
  • telemetry.emit: false means less than it did. It still suppresses stdtel's named span. It
    cannot stop Claude Code naming the skill on its own requests, which are now where a skill's cost is
    read from. docs/for-developers.md says so.
  • Copilot loses nothing it had. Copilot's own hook dialect sends transcriptPath, which the Stop
    hook never read, so the tail rule never produced Copilot data. Copilot per-skill tokens arrive with
    #115.

Privacy

  • The transcript reader no longer reads a Skill tool result's content. chars/4 was the only reason
    it ever did.

Fixed

  • stdtel-install settings no longer redirects Claude Code's telemetry without saying so (#133).
    It used to overwrite an OTEL_EXPORTER_OTLP_ENDPOINT already in the file. Run from a shell without
    STDTEL_OTLP_ENDPOINT, that pointed Claude Code at localhost. A different endpoint is now kept,
    with its protocol (the two only work as a pair), and reported. --replace-endpoint overwrites both.
  • The content check and the detailed-view gate judge the collector Claude Code's events actually
    go to (#133).
    An OTEL_EXPORTER_OTLP_LOGS_ENDPOINT takes precedence, as the harness resolves it:
    each variable comes from the highest-precedence settings file that sets it, then from the
    environment.
  • A session open across an upgrade no longer loses its skill windows. Saved state is loaded with
    unknown keys skipped. Without that, the removed load_tokens field would have made every 0.8
    state file raise inside a hook that exits 0, so the loss would have been silent.

0.8.1

Choose a tag to compare

@amiable-dev amiable-dev released this 01 Oct 14:15
d88a5cb

One fix for plugin installs, which are the common case.

Added

  • stdtel-install settings --native-only (#134). It writes Claude Code's own telemetry settings
    and no hooks. A plugin install needs exactly that: the plugin registers the hooks, and 0.8.0's
    settings wrote them into the settings file as well, so each hook fired twice. The command needs no
    stdtel-hook, and it cannot be combined with --no-native. The stdtel-setup skill (1.1.0) and
    the README now send plugin users to it.

0.8.0

Choose a tag to compare

@amiable-dev amiable-dev released this 01 Oct 12:07
d6f069b

Harness-native telemetry, ADR-014. Claude Code's own per-request records become the source of cost
and tokens, loaded into llm_request. stdtel-install settings now switches Claude Code's own
telemetry on as well as registering the hooks.
Use --no-native for hooks only.
docs/for-developers.md says what that sends and how to stop it. Nothing on
the wire or in the warehouse breaks: every schema change is additive.

Changed

  • The collector enforces privacy on every pipeline, for both harnesses (ADR-014 decision 3, #109).
    It had to be done before any native telemetry is switched on.
    • Identity is removed from spans, events and metric data points. The email becomes
      std.user.hash; account and organisation ids are deleted. Verified live, Claude Code puts these
      on every record rather than on the resource, so the old resource-level rule would not have seen
      them.
    • Content is deleted for both harnesses' native keys: Claude Code's tool_input,
      tool_parameters, full_command, prompt, response and similar; Copilot's
      github.copilot.tool.parameters.command and .file_path, and its GenAI message and tool-call
      keys.
    • Copilot's git remote and branch become std.branch.hash, computed exactly as the capture
      side computes it, and the plain values are deleted. A remote URL can carry a token.
    • All of this is tested against the pinned collector image, under both the default and the
      Langfuse configuration. Before this change, 22 of those 24 checks failed.

Added

  • Third-party skills are named from stdtel's own record (ADR-014 decision 10, #117). Without the
    detailed view, Claude Code names a third-party plugin's skill "third-party". The new view
    llm_request_attributed names such a request from the one skill that stdtel saw run on the same
    prompt and that the harness did not name itself, labelled derived. With more than one candidate
    it stays "third-party". It is read-time, so loader order cannot matter. Removing the tail rule and
    chars/4 load_tokens remains scheduled for one release after native records are verified live.
  • skills/stdtel-setup: interactive setup for both harnesses (ADR-014 decision 12, #114). An
    agent runs it with the user.
    • It installs the hooks and switches on Claude Code's and Copilot's own telemetry without content.
    • It offers Claude Code's detailed view and says plainly what it costs: shell commands, file paths
      and written file contents travel to the collector and are deleted there.
    • It enables the view only on an explicit yes, through the new
      stdtel-install detailed-view on. That command enforces the rest in code: the collector must
      be local or vouched for, and the content probe must pass on the spot. Otherwise it writes nothing.
      off removes the flag.
    • The gate and stdtel-doctor's content check now probe the endpoint Claude Code will send
      to
      , read from its settings files in its own precedence order (project local, then project,
      then user). Until now they probed stdtel's own endpoint. That is usually the same collector, but
      not after a hand edit, a reinstall with another endpoint, or a project override, and then the
      check proved something about a collector Claude Code would never use.
  • stdtel-install switches on both harnesses' own telemetry (ADR-014 decision 2, #113).
    • settings now writes Claude Code's env block alongside the hooks: events on, exported to the
      collector stdtel already uses, with repository attributes on and account uuid off.
    • It never writes OTEL_LOG_TOOL_DETAILS or a metrics exporter. A detailed view the user already
      chose survives a reinstall. --no-native writes the hooks only.
    • New stdtel-install copilot prints, or merges with --vscode-settings, the VS Code
      github.copilot.chat.otel.* settings with captureContent: false, and the Copilot CLI's
      COPILOT_OTEL_* variables. The keys come from the VS Code documentation; they have not yet been
      seen in a live trace (#115).
    • docs/for-developers.md now says what Claude Code sends and how to stop it: STDTEL_DISABLED
      does not. It also drops two stale ticket references left over from ADR-013.
  • One table of model requests, loaded from the harness's own records (ADR-014 decisions 5 and 7,
    #112).
    warehouse/load_requests.py reads Claude Code's api_request events from Loki into
    llm_request: tokens, exact cost, model, and the skill, agent, plugin and MCP server each request
    served. A live capture on Claude Code 2.1.285 confirmed that api_request carries the real skill
    name (probe-echo) even where skill_activated redacts it. That capture, scrubbed, is the
    replayed fixture.
    • llm_request_change_request joins each request to its change request through the stdtel turn
      with the same prompt_id, reusing activation_change_request rather than restating ADR-013's
      rule. Only turns bridge: a sub-agent in its own worktree shares the prompt id but not the
      branch.
    • session_cost_reconciliation compares the harness's session total with the sum of its
      requests. The two are never added together.
    • Cost is stored as unbounded NUMERIC. The harness sends seven decimals and more, which
      NUMERIC(12,4) would have rounded.
    • Loki's query_range stops at its limit silently, so the loader pages until a short page comes
      back. A request with no id fails the run and is counted, not dropped.
    • make load and the compose loader service now run it after the traces load. make load is
      an && chain, so a Loki outage stops the delivery half there, exactly as a Tempo outage
      already did.
  • stdtel-doctor proves the collector drops content (ADR-014 decision 13, #111). A new
    content dropped check sends a probe span and event holding a marker in content-shaped keys and a
    control id, then reads Tempo and Loki back. It passes only when the control arrived and the
    marker did not. "Not found" alone would also pass when nothing arrived, so a missing control
    fails as inconclusive. It runs by default only when OTEL_LOG_TOOL_DETAILS is on;
    stdtel-doctor --content-check runs it on demand and is what stdtel-setup will call before
    offering the detailed view. The trace loader ignores service.name=stdtel-probe.
  • Loki stores harness-native events (ADR-014 decision 4, #110). Claude Code's per-request cost,
    tokens and skill attribution exist only as events, and the logs pipeline used to export them to
    debug and nowhere else. Loki runs on host port 11010, this project's berth extra, and Grafana
    has it as a datasource. Native metrics are dropped at the collector: each carries session.id
    as a label, so it opens a series per session.

Fixed

  • Coding-agent PRs were being discarded as bots; they are now agent work for their harness
    (ADR-014 decision 9, #116).
    The fix was verified with the loader's own gh pr list call on real
    public PRs:

    • the Copilot coding agent is app/copilot-swe-agent and the Claude GitHub app is app/claude,
      both is_bot: true, so the #62 filter dropped every one;
    • both are now kept, including the gh search spellings Copilot and claude[bot];
    • dependency bots are still excluded.

    assisted_by now combines every signal: the arm labels, Anthropic's claude-code-assisted label,
    and an agent author.

    • One harness evidenced is that harness, even under no-ai, because a control an agent touched is
      not a control.
    • Two harnesses is mixed.
    • The absence of evidence is still unknown, never none.
  • A credential in Claude Code's repository URL no longer reaches Loki (#128). With
    OTEL_METRICS_INCLUDE_REPOSITORY on, every native event carries vcs.repository.url.full, and a
    remote such as https://x-access-token:...@github.com/... would have been stored verbatim. The
    collector now strips the userinfo, on the resource and on the record, for every signal. It is
    tested against the real image under both overlays.

  • A session with no repository now has branch_hash NULL, not '' (#122). The capture side
    sends an empty hash on purpose; the loader stored it verbatim, so branch_hash IS NULL
    undercounted sessions that cannot join a change request (ADR-005). schema.sql converts existing
    '' rows, touching nothing else.

  • The compose loader service's traces load had never run (#121). It called
    python -m warehouse.load_traces with no arguments while --tempo and --dsn were required, so
    every cycle ended in a usage error. That happened before a loader_run row could be written, and
    || echo "traces failed" was the only sign. Both flags now fall back to STDTEL_TEMPO and
    STDTEL_DSN, which the service sets, and a test parses the service's own invocation.

0.7.0

Choose a tag to compare

@amiable-dev amiable-dev released this 30 Sep 10:04
cf39e9e

Changed — breaking, on the wire and in the warehouse

  • Work joins change requests by branch identity, not a ticket parsed from the branch name
    (ADR-013).
    The regex requirement was never communicated. Measured, it missed 2 of 9 of this
    repository's own branches and every branch of a repository that puts the issue number last, and
    it invented tickets: RELEASE-0 on both releases, ANALYZE-4 and INIT-4 on Dependabot branches.
    • On the wire: spans carry std.branch.hash, a hash of the normalised repository and branch,
      never the branch name. std.ticket.id is gone. std.repo is now owner/name.
    • Commit evidence: the Stop hook records the git patch-id of each commit the session made, as
      std.artefact.commit_patch_ids. Patch-ids survive a rebase; the SHAs of the rebased PR #87 did not.
    • The join: activation_change_request (a view) assigns each activation to the change request
      its branch became, and records how: branch, branch+commit or commit. The scorecard's
      with-arm requires commit evidence, and a branch-only link is excluded from both arms.
    • Delivery data: it loads into a forge-neutral change_request record, with commits and ticket
      links, through a GitHub adapter. GitLab is #103. Closed-unmerged PRs are loaded.
    • Tickets: they are now enrichment, taken from the forge's issue links first and a fixed regex
      second. In a PR description, a key counts only after a linking word ("Fixes PLAT-9"). The first
      real run read ADR numbers cited in descriptions as tickets.
    • Scope unit: ticket is renamed branch.
    • Warehouse: pull_request and the ticket_id columns are dropped at cutover. Prior data is
      not migrated.

Fixed

  • The demo fleet's skill invocations never shared a span id with their activations, as the real
    loader's always do, so any join from one to the other found nothing.
  • stdtel-query gave a tool-failure query against session_cost.tool_failures, a column that has
    never existed.

0.6.0

Choose a tag to compare

@amiable-dev amiable-dev released this 28 Sep 21:18
9da0e62

Added

  • External contract version 3 (ADR-012).
    • std.external.tool_use_id joins an external run to the exact turn that made the MCP call. The
      Stop hook now records mcp__* tool-use ids from the transcript, including sub-agent transcripts,
      as std.artefact.mcp_tool_use_ids, and the loader writes them to mcp_tool_call. An MCP server's
      session.id goes stale after /clear, so the join is what attributes its spend correctly. Through
      the joined turn, external spend also gains a scope.
    • std.external.requests_unpriced keeps a partly priced run's observed cost as a lower bound
      instead of dropping it.
    • Query 7 version 3 reports partial runs apart from complete ones, and reports the join rate.
    • stdtel-conform --print-contract reports contract_version: 3. (#94)

Fixed

  • <synthetic> is no longer counted as a model request. Claude Code uses that name for
    placeholder assistant messages, such as interrupted or failed responses, which carry zero usage.
    Counting them inflated llm_requests, and when one came first in a turn it became the span's model:
    one Prometheus series was already labelled gen_ai_request_model="<synthetic>". In 1,157 real
    turns, every turn that appeared to use more than one model was really one model plus <synthetic>.
  • A span names a model only when its run used exactly one. This applies ADR-012 decision 5 to our
    own turns, sub-agents and skill tails. Genuinely multi-model runs were sub-agents, 5 of 200 sampled,
    and naming the first of two models said the run used one.
  • scrub() dropped every list attribute without a word. It now passes a list of strings and
    nothing richer.
  • stdtel-conform read an OTLP intValue as a string, and would have refused every correctly
    sent count.
  • Collector: spanmetrics now counts across resources instead of per resource, so dashboards no
    longer read zero after a branch switch, a project change or an SDK upgrade. (#92)
  • mise run load works again, having failed since #68, and the loader no longer reports keys that
    our own Langfuse overlay adds. (#93)

0.5.0

Choose a tag to compare

@amiable-dev amiable-dev released this 28 Sep 15:03
78df0d1

Changed

  • Activations are children of their turn, not flat roots (ADR-010). A skill or sub-agent
    activation now carries the span id of the turn it ran under, so a trace shows the turn and what happened
    inside it. Compactions, and activations whose turn is not in the batch, stay roots, because that is
    what was observed. Anyone querying Tempo for root spans will see fewer of them. The warehouse
    gains parent_span_id and is otherwise unaffected. A session is deliberately never a parent: one real
    session spans seven weeks, which no trace store will hold. (#75, #80)
  • The ticket follows the branch, not the session. The ticket key was read once, at session start,
    so a session that switched branches joined every later activation to the wrong PR. It is now
    refreshed on each event, and an empty read keeps the last known ticket rather than blanking it. (#77, #81)
  • A concurrent state write no longer ends a session's telemetry. Sub-agent hooks fire while the
    parent is still calling tools, so two hook processes could write the state file at once and leave it
    unparseable. The hook swallowed the error and exited 0, and that session emitted nothing more. Writes
    are now atomic (a finished file is renamed into place). A torn file is salvaged from its intact
    leading document, which keeps the transcript offset, and the salvage is reported. Sub-agent and
    compaction hooks update the state file under a lock, so parallel writers no longer drop each
    other's updates. (#73, #74)

Added

  • Containment across turns: scopes (ADR-010, ADR-011). A skill that drives many turns (a loop
    over tickets) can declare metadata.telemetry.scope: ticket. Everything recorded while its container
    is open carries std.scope.name / key / id, and warehouse/efficiency/06_scope_self_vs_inclusive.sql
    reports its own cost beside everything incurred under it. Self cost is stored; inclusive cost is
    derived
    — no row holds a total including its children. A rollup answers containment, never
    causation, and the analyst brief says so. stdtel-hook scope-close ends a scope explicitly. Nothing
    is required of any author: an undeclared skill is turn-scoped. (#78, #79, #82)
  • Decorated sub-agents. An agent file's metadata: block (version, owner, standard_id,
    policy_ids) is now captured on its activations. This works by tolerance, not contract: verified
    on Claude Code 2.1.277, undocumented upstream. Agents cannot declare a scope. MCP servers cannot be
    decorated at all and are overlay-only, which ADR-011 records as a non-goal. stdtel-onboard covers
    all three artefact types. (#83)
  • A fifth kind, external, for spend the harness cannot see (ADR-010 decision 7). A process an
    agent calls, such as an MCP server that pays for its own model calls, reports its spend as
    std.artefact.activation spans with kind=external. warehouse/efficiency/07_external_spend_and_coverage.sql
    reports the spend and, separately, how much of it is known. (#86, #87)
  • stdtel-conform, a new console script. It checks a foreign emitter's OTLP JSON against the
    external contract in their CI: span name, allowlist, no content, a named system, a well-formed
    session id, and costs that are numbers or absent. It reports cost coverage separately, because a
    shape-only check passes a file whose costs are missing. --print-contract prints the contract as JSON
    with a contract_version, for an emitter to diff its own copy against. (#87, #89)
  • External contract version 2: cost provenance. std.external.cost_usd keeps meaning observed;
    std.external.cost_source (provider | local) says how; std.external.cost_estimated_usd carries
    the part priced from a list, beside it and never inside it. One run can mix billed and estimated
    calls, so one label over one figure had no honest value. Additive: every version-1 span stays valid.
    (#88, #89)
  • The loader names what it cannot load. Tempo keeps an attribute nobody declared; the loader was
    where it vanished, without a word. It now names every unread key on an external span, with counts,
    and records them in loader_run.unknown_attr_keys. A renamed attribute therefore arrives as a named
    key rather than a column of NULLs. (#88, #89)
  • skills/stdtel-instrument, a new skill. It adapts an external process to the contract and starts
    by reconciling the process's own records against the provider's bill. It records the verified fact
    that Claude Code strips an inherited OTEL_* variable before starting an MCP server, so the
    endpoint must be set in the server's env block. (#87, #89)
  • The primary metric has an input. CI has uploaded a policy-results artefact on every PR run, and
    nothing ever collected it, so first-time OPA pass rate had no data while CI stayed green.
    warehouse/fetch_policy_results.py collects it. (#21, #72)

Fixed

  • The loader window is guarded. A sub-agent span is back-dated to the sub-agent's own start and is
    not searchable until Tempo's ingester flushes. A --since narrower than that delay stepped over
    such spans and never came back for them. It now warns. (#67, #71)
  • The release checks every console script. The wheel check named seven scripts by hand and missed
    stdtel-conform, which would have shipped without ever running in the build. The list now has to
    cover pyproject.toml, and a test enforces it.
  • The exporter timeout is tested by behaviour. opentelemetry-exporter-otlp-proto-http 1.45.0
    removed the private attribute the test read, so every CI run failed while the timeout still worked:
    1.00s against a collector that never answers. The test now measures exactly that. (#90)

0.4.0

Choose a tag to compare

@amiable-dev amiable-dev released this 16 Sep 07:16
a95d23a

Changed

  • The unit of capture is an artefact activation, not a skill invocation (ADR-009). The span
    std.skill.invocation is renamed to std.artefact.activation, discriminated by
    std.artefact.kind ∈ skill, subagent, compaction, turn. This is a wire-level rename and
    anyone querying Tempo or Prometheus for the old name must update; the shipped dashboards and
    deploy/smoke.sh already have. There is deliberately no dual-emission period — two spans would have
    doubled every traces_span_metrics_calls_total series and both dashboards. Compatibility lives one
    layer down: load_traces still writes skill_invocation from kind skill, so scorecard.sql,
    every existing query and every row recorded before the rename keep working unchanged, and spans
    still in Tempo under the old name keep loading. (#63)

Added

  • Sub-agent, compaction and turn capture. A real session of this project's own spent $366, spawned
    42 sub-agents and compacted twice, and recorded one skill row and session totals — the numbers being
    collected could not answer "where did it go". New hooks SubagentStart, SubagentStop and
    PostCompact; where a hook has never fired the value is read from the session directory instead and
    flagged std.artefact.source=transcript, so an inference is never mistaken for an observation.
  • The session's real cost. session_cost.cost_usd has been NULL for every row since the schema was
    written, because nothing read the harness's own cost-state entry. It and the harness's API and tool
    wall-clock totals are now loaded.
  • Per-hook latency on each turn, as std.turn.hook.<basename>.ms. The basename only — the payload
    carries an absolute path, which is filesystem layout rather than data.
  • warehouse/efficiency/: five versioned queries answering the named efficiency questions, and a
    session-efficiency brief in agents/skill-scorecard-analyst that runs them rather than writing its
    own. Efficiency is answerable for a single session at any volume; the primary effectiveness metric
    and its 30-merged-PRs-per-arm floor are unchanged.
  • Scheduled loaders. mise run load, mise run load-watch, and an opt-in loader compose
    profile. Tempo held four days of spans the warehouse had never seen because load_traces.py is run
    by hand. Every run writes a loader_run row, including failures, and a dashboard panel shows how
    stale everything else on the page is.
  • stdtel-doctor reports which of the new hook events have actually fired on this machine. Their
    payload shapes are transcribed from documentation, not captured, because hook configuration is read
    at session start and a session cannot observe a hook it just registered.

Fixed

  • Dependabot branches minted phantom tickets (upload-artifact-7 → ARTIFACT-7), on spans as well
    as in the ticket table, putting work no developer did into the without-skill cohort. Both the
    capture and delivery parsers now anchor a ticket key to the start of a branch segment, bot-authored
    PRs are skipped, and a test pins the two parsers together because ADR-002 joins them on equality.
    (#62)
  • span_id() silently mangled hex ids. A 16-character hex span id from Tempo's search API is also
    valid base64, so it was decoded into 12 bytes of a different id without raising. It now decodes only
    when the result is the length an id actually is.

Known issue

  • A sub-agent span stamped more than roughly an hour in the past never becomes searchable in Tempo and
    never reaches the warehouse, with no error anywhere. The exposure is background sub-agents, which are
    the expensive ones. (#67)

v0.3.3 — pinned actions and post-publish verification

Choose a tag to compare

@amiable-dev amiable-dev released this 14 Sep 21:32
c92cc85

0.3.3 — 2026-09-14

Changed

  • Every GitHub Action is pinned to a commit SHA, with the version in a trailing comment. A tag is a
    pointer somebody else can move, and the job that publishes holds id-token: write — so whoever
    controlled pypa/gh-action-pypi-publish@release/v1 controlled what shipped as stdtel,
    permanently. Two tests enforce it: no mutable reference anywhere, and every pin carrying the version
    it came from. (#34)
  • The published artefact is now verified after it lands. A new verify job installs
    stdtel==<tag> from PyPI, runs its console scripts, checks the reported version, and verifies the
    PEP 740 attestation. Everything before it proved the wheel we built worked; nothing proved the one
    on the index did, and the two have differed before. It is not granted id-token: write — reading an
    attestation does not require minting a credential.
  • A release must have a matching section in CHANGELOG.md or the build fails. Release notes are
    written from there, and a missing section means they are being improvised at the moment of shipping.
  • The sdist no longer ships tests/. It carried tests/*.py without conftest.py or the
    fixtures, so the suite could not even be collected — worse than shipping none, because it reads as a
    broken package.

v0.3.2 — report plugin/package drift

Choose a tag to compare

@amiable-dev amiable-dev released this 14 Sep 21:21
0538ac5

0.3.2 — 2026-09-14

Added

  • Drift between the plugin and the package is now reported, in both directions. They are separate
    installs and neither updates the other, so a developer updates one and assumes the other followed.
    Both failures are quiet: a missing package leaves the launcher exiting 0 in silence, and a stale
    plugin keeps working while the skills it ships lag the release.
    • stdtel-doctor gains a plugin in step check. Not having the plugin passes —
      stdtel-install settings is a supported install — but a version mismatch fails, naming the
      command that fixes it, which differs by direction.
    • session_start prints the same line once per session, on the only hook whose output a developer
      reads.
    • The plugin's launcher announces a missing package at session start. This closes a
      circularity: with the package absent nothing of ours runs, stdtel-doctor included, so the
      launcher is the only part of the install that can report it. Every other event stays silent — the
      same line on every tool call is noise, not information.

Fixed

  • stdtel.__version__ said 0.1.0. Hardcoded in the first commit and wrong for every release
    since; nothing read it, so nothing noticed. It is now derived from the installed package metadata,
    and the version-agreement test covers it — the skew check above depends on it being true.