Skip to content

v0.23.0 — the wire format for §7, and the three things it will not carry - #24

Merged
sshlg merged 1 commit into
mainfrom
feat/otel-genai
Sep 1, 2026
Merged

v0.23.0 — the wire format for §7, and the three things it will not carry#24
sshlg merged 1 commit into
mainfrom
feat/otel-genai

Conversation

@sshlg

@sshlg sshlg commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

agent-evals §7 said what to instrument in four bullets and named no format. A grep for opentelemetry over the whole pack returned one table cell.

references/otel-genai.md is that format — and it earns its own file on the refusals, not the field list.

Read the status before encoding any of it

The conventions moved out of the main semantic-conventions repository into their own, whose README lists Schema URL: TODO. Every document is Status: Development; on the inference span the only Stable attributes are those borrowed from core semconv. Every gen_ai.* attribute is Development.

Adopt it anyway — a moving standard beats a private vocabulary nobody else's tooling reads — but pin the version and treat any branch on a gen_ai.* name as code with an expiry date.

Three things it will not carry

1. Who produced the score. gen_ai.evaluation.result has a name, a value, a low-cardinality label and an explanation — and no attribute for the scorer. §7 requires exactly that (human | llm_judge | code_check), because a score with no source cannot be calibrated or audited. OpenInference has the field as annotation.annotator_kind with our three values. Carry it as a documented extension rather than drop the requirement: a judge score and a human label indistinguishable on the wire get averaged downstream, which is what §5's calibration exists to prevent.

2. Money. There is no cost attribute at all, and usage is eleven numbers — input_tokens, output_tokens, reasoning.output_tokens, cache_read.input_tokens, cache_write.input_tokens, plus per-modality splits. A cost from input + output alone is wrong in both directions: it bills cache reads at full price and misses reasoning tokens and cache writes. gen_ai.client.token.usage carries a hard MUST NOT report when counts are unobtainable — absence over a fabricated zero, matching audit.md.

3. A safe default you can rely on. The spec's default is to record nothing. Checked against a shipping implementation rather than assumed: OpenLLMetry's is_content_tracing_enabled() returns (os.getenv("TRACELOOP_TRACE_CONTENT") or "true") — unset means true, so prompts are captured unless you turn them off (traceloop-sdk/traceloop/sdk/config/__init__.py, read 2026-08-31). An agent's trace carries prompts, prompts carry secrets, and the most-used implementation ships the inverse of the spec.

Two seams that decide a design, not a field

  • The content-upload hook "SHOULD operate independently of the opt-in flags" and "SHOULD be invoked regardless of the span sampling decision" — the last scrubbing point, firing on spans nobody will read.
  • A masking hook cannot change the span name, which is built from tool and MCP target names. Redaction and naming are one decision.

Two words that mean three things each

"OpenTelemetry-based" answers a transport question and no semantic one — Phoenix emits OpenInference, OpenLLMetry emits gen_ai.* + traceloop.*, neither a subset of the other, and a trace from both is two disconnected halves under one trace id.

"Replay" is durable execution (recorded results, nothing re-runs), a trace playground (the call re-runs live), or this skill's fixture replay. audit.md asks whether a run can be replayed without saying which; the reference names the three.

Evidence

Check Result
npm test rc=0
Auditor 0 GAP, 14 PASS, body 4631/4750
Placement one reference + one declared row + one pointer sentence in §7

Board B-134. Ledger OT-1 … OT-8.

🤖 Generated with Claude Code

…ill not carry

Section 7 said what to instrument and named no format; a grep for opentelemetry
over the pack returned one table cell. references/otel-genai.md is the format,
and it earns its own file on the refusals rather than the field list.

- The conventions moved repositories, their Schema URL reads TODO, and every
  gen_ai.* attribute is Development. Adopt and pin; treat a branch on any of
  those names as code with an expiry date.
- gen_ai.evaluation.result has no attribute for who produced the score — the
  field section 7 requires by name. OpenInference has it as annotation.
  annotator_kind with our three values; carry it as a documented extension
  rather than drop the requirement.
- There is no cost attribute, and usage is eleven numbers. A cost from
  input + output is wrong in both directions: it bills cache reads at full price
  and misses reasoning tokens and cache writes.
- The content-upload hook runs independently of the opt-in flags AND of the
  sampling decision, so it is the last scrubbing point and fires on spans nobody
  reads; a masking hook cannot change the span name. Checked against a shipping
  implementation: OpenLLMetry captures content when its env var is unset, the
  inverse of the spec's default.
- "OpenTelemetry-based" is not one vocabulary, and "replay" is three guarantees.

B-134, OT-1..8. Gate: npm test rc=0. Body 4631/4750.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sshlg
sshlg merged commit 5f2ce3a into main Sep 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant