Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/topics/ai-adoption-ladder/design/design-threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,11 @@ The contract (agnostic on every axis — machine, repo, user, org, tool):
short IDs collide across repos); resource-scoped on agent-session emission, span-scoped on
CI spans. WP2 PLAN.md is the governing record.
3. W3C TRACEPARENT propagates across trigger → CI → agent session: one causal tree.
Verified: headless agent sessions inherit TRACEPARENT natively. Unification by context
propagation, not sink merging.
Verified with a post-delivery correction (#351 audit): headless agent sessions inherit
TRACEPARENT natively only behind the enhanced-telemetry opt-in — the default surface
starts a fresh root and attaches query-side via the join attribute, so the
contract-authored wrapper span is the stable causal leg until spans graduate.
Unification by context propagation, not sink merging.

Sink binding is deployment-owned (out of contract): existing observability stack when the
org has one (Boris step-1 guardrail verbatim); free default when none = OTLP file
Expand Down
6 changes: 6 additions & 0 deletions docs/topics/ai-adoption-ladder/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ the PR is the PLAN's durable home.
| WP5 | #377 | T2 sandbox bar + T3 guardrail matrix + #241 instance |
| WP6 | #600 | T7 standing routines |
| WP7 | #676 | T4 runner charter (design pack; build stays trigger-gated) |

## Post-delivery decision records

- [`native-vs-hook-telemetry-audit.md`](native-vs-hook-telemetry-audit.md) — the WP2-deferred
emission-source decision (#351): hybrid, native-first, with version-pinned empirical probes
and revisit triggers.
61 changes: 61 additions & 0 deletions docs/topics/ai-adoption-ladder/native-vs-hook-telemetry-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Native-vs-hook telemetry audit — decision record

Closes the WP2 deferral (#351): which emission source the autonomy telemetry contract
([`plugins/autonomy/reference/telemetry.md`](../../../plugins/autonomy/reference/telemetry.md))
rides on long-term — the native Claude Code OTLP surface, hook/wrapper-authored emission, or a
hybrid. Audited 2026-07-20 against live captures from this machine's collector store
(`otelcol-contrib` service, OTLP file exports); the two decisive probes ran headless sessions
on Claude Code **2.1.215**.

## Field-by-field: native surface vs contract

| Contract requirement | Native surface (empirical, 2.1.215) | Verdict |
|---|---|---|
| Pillar 1 — `schema_url` pin on contract-authored emissions | Native declares NO `schemaUrl` at resource or scope level, on any signal (metrics, traces, logs) | Already handled: native output is consumed as-is per the contract's native-surface clause; the pin binds contract-authored emissions only |
| Pillar 1 — upstream semconv vocabulary | Native emits its own `claude_code.*` vocabulary (metrics `claude_code.session.count` …, spans `claude_code.llm_request` / `claude_code.interaction`, log events `hook_execution_start` / `plugin_loaded`) plus standard resource attrs (`service.name=claude-code`, `service.version`, `host.arch`, `os.*`) | Consumed as-is; no rewrite |
| Pillar 2 — `autonomy.work_item.url` RESOURCE-scope on agent-session emission | **Native honors `OTEL_RESOURCE_ATTRIBUTES`**: a headless run with `OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<url>` landed the attribute at resource scope on the session's native metrics export | **SATISFIED natively** via environment injection by the dispatching surface — no hook emission needed |
| Pillar 3 — inbound `traceparent` joining for headless contexts | **Beta-gated.** Default surface ignores an inbound `TRACEPARENT` (fresh root, no parent). With `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` the same probe JOINS: session spans carry the injected `traceId` and parent to the injected span | **Satisfied natively only behind the enhanced-telemetry beta**, whose span shapes the setup slice deliberately does not depend on — the contract-authored wrapper emission remains the stable conforming leg until spans graduate |

## Decision — hybrid, native-first

1. **Session signals (cost, tokens, spans, events): NATIVE emission.** The dispatching
surface injects the Pillar-2 join attribute through `OTEL_RESOURCE_ATTRIBUTES` at
launch. No hook re-derives anything the native surface already emits (native-surface
principle; a re-derivation would be non-conforming).
2. **Causal joining (Pillar 3): contract-authored wrapper emission.** The trigger/CI/runner
wrapper keeps emitting its own contract-authored span — inheriting inbound `traceparent`
and carrying the join attribute — because the DEFAULT native session does not join the
trace, and the beta path that does is explicitly non-load-bearing (the setup slice treats
spans as optional and never depends on beta span shapes). Native session signals join the
chain **query-side by the Pillar-2 resource attribute** (both surfaces carry the same
normalized item URL); a deployment running the enhanced-telemetry beta additionally gets
direct span parenting today, as a bonus rather than a dependency.
3. **No migration off hooks is pending, because no hook emission exists to migrate**: the
contract's conforming session path was already native; what this audit adds is the
empirically proven injection mechanism for the join attribute and the confirmation that
the wrapper leg cannot be retired yet.
4. **Contract text reconciled in this change**: the telemetry contract's Pillar 3 and the
setup CI template previously implied that exporting `traceparent` alone joins the agent
session's spans into the tree — contradicted by the probe. Both now state the
wrapper-joins / session-attaches-query-side model this audit proves, so an adopter
following the shipped wiring produces telemetry that matches the contract's words.

## Load-bearing empirical facts (version-pinned)

- `OTEL_RESOURCE_ATTRIBUTES` injection works on Claude Code 2.1.215 (standard OTel SDK env
var — expected stable, but the injection is contract-load-bearing, so regressions matter).
- Inbound `TRACEPARENT` is not read by 2.1.215 on the DEFAULT surface; with
`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` the same version reads it and parents session
spans correctly (probe: injected traceId carried, `claude_code.interaction` parented to
the injected span).
- No `schemaUrl` declared by 2.1.215 (re-confirms the WP2-era finding on 2.1.211).

## Revisit triggers

- Enhanced telemetry (spans) graduates from beta → plan retiring the wrapper span in favor
of one native causal tree; until then the beta's inbound-context support is a bonus, not
a dependency.
- Claude Code starts declaring a `schemaUrl` → re-evaluate against the contract's pin-match
rule (a declared URL anywhere in a conforming output set MUST match the pin).
- A Claude Code release breaks `OTEL_RESOURCE_ATTRIBUTES` injection → the join attribute has
no native path; fall back to wrapper-side session wrapping until restored.
2 changes: 1 addition & 1 deletion plugins/autonomy/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "autonomy",
"version": "0.7.1",
"version": "0.7.2",
"description": "Governed autonomous agent operation: role-topology, binding-seam, wiring-vs-advisor, telemetry, return-accounting, trigger-dispatch, per-work-class guardrail-matrix, standing-routine-catalog, and design-only runner-charter contracts for climbing the AI-adoption ladder, plus a guided-setup skill that discovers an adopting org's state, writes its schema-versioned binding, wires standards-pinned OTLP emission with a zero-cost file-artifact default, wires human-attested return capture at the task boundary, wires signal adapters with one governed dispatch entrypoint, binds the five-class guardrail matrix to an org's isolation substrates with an in-boundary live-validation probe before recording each fail-closed binding, and stands up standing-routine-catalog classes as scheduled temporal signal adapters behind the one governed queue with free scheduling defaults wired as reviewable changes and each routine's work-class mapping homed on the security surface.",
"author": {
"name": "Melodic Software",
Expand Down
14 changes: 14 additions & 0 deletions plugins/autonomy/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ All notable changes to the `autonomy` plugin are documented here. Format follows
Versions 0.1.0–0.7.0 predate this file (introduced with 0.7.1); their history lives in the
merged work-package PRs (#333, #343, #356, #372, #377, #600, #676).

## [0.7.2]

### Changed

- **Pillar 3 reconciled with the audited native-surface reality (`#351` audit).** The
causal-tree contract now states explicitly that `traceparent` propagation binds
CONTRACT-AUTHORED emissions, and that a native agent surface ignoring inbound context (a
default surface may, honoring it only behind an opt-in) does not break the tree — its
session emissions attach query-side through the Pillar 2 join attribute, and relying on
direct native span joining is a recorded migration trigger, not an assumption. The CI
OTLP template's trace-context-injection section carries the same surface-specific caveat
plus the `OTEL_RESOURCE_ATTRIBUTES` injection the setup flow already wires. No emission
or checker behavior changes.

## [0.7.1]

### Added
Expand Down
15 changes: 11 additions & 4 deletions plugins/autonomy/reference/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,17 @@ same reviewed-migration rule.
## Pillar 3 — one causal tree

W3C `traceparent` context propagates trigger → CI → agent session, forming one causal tree
per triggered chain. This is a headless/CI/runner property: conforming headless agent
surfaces read inbound trace context from their environment; interactive contexts are
explicitly excluded — the contract does not promise inbound trace joining for an interactive
session, which deliberately ignores ambient context.
per triggered chain. This is a headless/CI/runner property carried by CONTRACT-AUTHORED
emissions: each chain leg's wrapper emission (the writers and adapters an adoption wires)
reads inbound trace context from its environment and parents its span accordingly. A native
agent surface that ignores inbound context — empirically, a default native agent-session
surface can start a fresh root trace, honoring inbound context only behind an opt-in — does
not break the tree: the dispatching wrapper's contract-authored span joins the chain, and
the session's own native emissions ATTACH query-side through the Pillar 2 attribute, which
both surfaces carry. Where a native surface honors inbound context its spans join the tree
directly; relying on that is a recorded migration trigger, not an assumption. Interactive contexts are explicitly excluded — the
contract does not promise inbound trace joining for an interactive session, which
deliberately ignores ambient context.

## Sink binding — out of contract

Expand Down
8 changes: 5 additions & 3 deletions plugins/autonomy/skills/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ paid sinks are advisory + explicit opt-in with cost surfaced first.
`OTEL_RESOURCE_ATTRIBUTES` carrying `autonomy.work_item.url=<canonical item URL>` (the
vendor attaches resource attributes to every metric datapoint and event — verified against
the official monitoring doc). Headless `-p` sessions inherit `TRACEPARENT`/`TRACESTATE`
from the environment; interactive sessions deliberately ignore inbound trace context.
Traces stay beta behind `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`; the slice treats spans as
optional and never depends on beta span shapes.
from the environment only under the enhanced-telemetry beta — the default surface starts
a fresh root and joins query-side via the resource attribute (verified empirically) — and
interactive sessions deliberately ignore inbound trace context. Traces stay beta behind
`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`; the slice treats spans as optional and never
depends on beta span shapes.
4. **Record the binding** — sink class, endpoint or artifact path, and the semconv pin land
as the `telemetry` section of the schema-versioned binding.
5. **Conformance** — run
Expand Down
16 changes: 15 additions & 1 deletion plugins/autonomy/skills/setup/templates/ci-otlp-artifact.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,26 @@ The registry pinned by the contract owns the standard attribute names; add the r

## Trace-context injection for agent steps

Export W3C context before the headless agent step so its spans join the tree:
Export W3C context before the headless agent step so every contract-authored emission in
that step parents into the tree:

```sh
export TRACEPARENT="00-$trace_id-$span_id-01"
```

Whether the agent CLI's own native session emissions honor that context is
surface-specific — verify empirically (some read it only behind an opt-in flag, and a
default surface may start a fresh root). A session that does not join the trace still
attaches query-side through the `autonomy.work_item.url` resource attribute, which the
dispatching step injects via `OTEL_RESOURCE_ATTRIBUTES`:

```sh
export OTEL_RESOURCE_ATTRIBUTES="autonomy.work_item.url=<canonical-item-url>"
```

Keep exporting `TRACEPARENT` regardless — a surface that honors inbound context joins the
tree directly with no wiring change.

## Ephemeral per-job collector (agent-session capture)

The agent CLI exports OTLP over the network only, so the free default runs a per-job
Expand Down
7 changes: 1 addition & 6 deletions scripts/changelog-parity-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,4 @@
# plugin gains a CHANGELOG.md or drops its version, so an entry cannot outlive
# its debt. This never relaxes --check-bump — a listed plugin that bumps its
# version still must start its changelog in that same PR.
#
# autonomy: shipped 5 minor version bumps with no CHANGELOG.md while every other
# plugin maintains one — the parity gap this gate exists to flag. Reconstructing
# an accurate history is autonomy-plugin (WP-lane) work, not this repo-level CI
# change; grandfathered pending a dedicated autonomy CHANGELOG issue.
autonomy
# (empty - the last grandfathered gap, autonomy, started its CHANGELOG at 0.7.1)
Loading