Skip to content

feat: first-class OpenTelemetry export (one span per boundary) - #33

Merged
tishachawla-jg merged 2 commits into
mainfrom
feat/otel-spans
Aug 3, 2026
Merged

feat: first-class OpenTelemetry export (one span per boundary)#33
tishachawla-jg merged 2 commits into
mainfrom
feat/otel-spans

Conversation

@tishachawla-jg

Copy link
Copy Markdown
Contributor

Chronicle already bootstrapped OTel and had an envelope→attributes mapper, but nothing turned a recorded run into spans. This makes OpenTelemetry first-class: each recorded boundary crossing becomes a span in Phoenix or any OTel backend.

What

with chronicle.record("run-1") as session:
    chronicle.instrument_otel()      # attaches to the active recording session
    run_agent(...)
  • One OpenTelemetry span per boundary crossing, using OpenInference semantic conventions: LLM/tool span kind, input.value / output.value, llm.model_name, llm.token_count.*, tool.name, plus chronicle.* ids.
  • Spans nest by the run's parent linkage, so the trace shows the agent's call graph.
  • Error envelopes set span status ERROR.
  • Returns an uninstrument callable.

Design

  • chronicle/otel.py: instrument_otel(tracer=None, session=None) wires a new session.on_record hook that emits and ends a span per crossing; envelope_span_attributes() maps one Envelope to OpenInference attributes.
  • session.py: new on_record hook fired with the full Envelope after each record (additive; default None).
  • __init__: instrument_otel / envelope_span_attributes exposed lazily via module __getattr__, so import chronicle never imports opentelemetry. The base install needs neither the OTel SDK nor OpenInference; they come with the existing [phoenix] extra.

Tests (tests/test_otel.py, in-memory exporter)

One span per crossing with OpenInference attrs; parent nesting; tool name; model + token-count mapping; ERROR status on a failing boundary; uninstrument stops spans; and a subprocess test proving import chronicle does not import opentelemetry. Full suite green; ruff 0.15.22 clean. No em/en dashes.

Tisha Chawla and others added 2 commits July 29, 2026 11:54
Chronicle already bootstrapped OTel and mapped envelope fields to OpenInference
attributes, but nothing turned a recorded run into spans. Add instrument_otel:
each recorded Envelope becomes an OpenTelemetry span with OpenInference semantic
conventions (LLM/tool span kind, input/output, model, token counts), nested by the
run's parent linkage, so recorded runs land in Phoenix or any OTel backend.

- chronicle/otel.py: instrument_otel(tracer=None, session=None) wires a session
  on_record hook that emits + ends a span per crossing, linking children to their
  parent span; envelope_span_attributes() maps one Envelope to OpenInference attrs.
  Error envelopes set span status ERROR. Returns an uninstrument callable.
- session.py: new on_record hook fired with the full Envelope after each record.
- __init__: instrument_otel / envelope_span_attributes exposed lazily via module
  __getattr__, so `import chronicle` never imports opentelemetry (base install
  needs neither the OTel SDK nor OpenInference).

Deps ship in the existing [phoenix] extra. Tests (tests/test_otel.py): one span per
crossing with OpenInference attrs, parent nesting, tool name, model + token
attributes, ERROR status on failure, uninstrument, and that importing chronicle
does not import opentelemetry (subprocess). Full suite green; ruff 0.15.22 clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Tisha Chawla <tisha.chawla2020@vitalum.ac.in>
@tishachawla-jg
tishachawla-jg merged commit 7438735 into main Aug 3, 2026
4 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