Skip to content

OpenTelemetry

Trace your decisions as OpenTelemetry spans so they sit on the same timeline as every other service in your stack.

otel For: platform & observability

Spans and decision records are complementary

These two layers answer different questions, and you usually want both.

LayerAnswersLives where
OTel span (timeline)When did it run, how long, in what order, alongside which other servicesYour tracing backend
Briefcase AI decision record (governance context)Why this output — inputs, outputs, confidence, timing, full reproducible contextYour exporter or store

The span is a lightweight timeline marker; the decision record is the deep governance context. They come from two different lines of your code: @capture writes the record, and a span you open writes the timeline.

flowchart LR
    A[classify_ticket] -->|"@capture"| D[Decision record: governance context]
    A -->|"start_as_current_span"| C[OTel span: timeline]
    C --> E[Tracing backend]
    D --> F[Exporter / store]

Install

Terminal window
pip install briefcase-ai[otel]

Without OTel vs with OTel

Decisions are still captured and exported — you can inspect them through an exporter. But there is no span on your distributed trace, so the decision is invisible to your tracing UI and you cannot see where it sat relative to upstream and downstream services.

from briefcase import capture
@capture(decision_type="ticket_triage")
def classify_ticket(text):
# call your model here
return "account_access"
classify_ticket("reset my password")

How it works

  1. Get a tracerget_tracer("briefcase") returns a standard OpenTelemetry tracer.

  2. Open a span around the decision and attach attributes that describe it.

  3. Correlate — propagate trace context to downstream services so spans join one trace.

  4. Inspect in your tracing backend, then match the decision span to the full decision record an exporter shipped.

Get a tracer

get_tracer() returns a standard OpenTelemetry tracer. Use it to open spans around the work you want to trace.

from briefcase.otel import get_tracer
tracer = get_tracer("briefcase")
with tracer.start_as_current_span("classify_ticket") as span:
span.set_attribute("briefcase.decision_type", "ticket_triage")
category = "account_access"
span.set_attribute("briefcase.outcome", category)

get_tracer(name="briefcase") is the only public symbol in briefcase.otel.

Semantic conventions

Briefcase AI ships span-attribute conventions under briefcase.semantic_conventions. Each submodule defines the attribute keys for one subsystem, so the attributes you emit are consistent across services instead of ad-hoc strings.

Two submodules you will reach for most on the triage path:

from briefcase.semantic_conventions import workflow, rag
# Tag the retrieval span on the triage path with RAG attribute keys
with tracer.start_as_current_span("retrieve-ticket-history") as span:
# use rag.* keys for the retrieval step, workflow.* keys for the workflow it runs in
...

The full set of submodules:

  • briefcase.semantic_conventions.lakefs
  • briefcase.semantic_conventions.workflow
  • briefcase.semantic_conventions.rag
  • briefcase.semantic_conventions.external_data
  • briefcase.semantic_conventions.cowork
  • briefcase.semantic_conventions.agent_state
  • briefcase.semantic_conventions.bitemporal
  • briefcase.semantic_conventions.routing_policy
  • briefcase.semantic_conventions.validation

Import the module for the subsystem you are instrumenting and use its attribute keys when setting span attributes. The workflow keys line up with the multi-agent correlation surface.

Ship decisions to an external observability sink

The span describes the work; the decision record carries the captured inputs, outputs, and timing. To forward those records into an external observability sink you already operate — a log aggregator, a message queue, an analytics pipeline — subclass BaseExporter and implement its three async methods.

from typing import Any
from briefcase import setup, capture
from briefcase.exporters import BaseExporter
class SinkExporter(BaseExporter):
async def export(self, decision: Any) -> bool:
# ship the decision record to your external observability sink here
# e.g. post to a collector, enqueue, or forward to a log pipeline
return True
async def flush(self) -> None:
pass
async def close(self) -> None:
pass
setup(exporter=SinkExporter())
@capture(decision_type="ticket_triage")
def classify_ticket(text):
# call your model here — span streams to your tracer, record streams to the sink
return "account_access"
classify_ticket("reset my password")

For the stock exporters (ConsoleExporter, JSONLFileExporter, MemoryExporter) and the one-line briefcase.observe() setup, see Exporters.

Limits

@capture itself does not emit a span. Open spans explicitly for arbitrary functions. For supported AI frameworks, Framework Auto-Instrumentation installs their native handler or trace hook and emits decision records without decorating each call.

Nothing links a span to its decision record. The record’s decision_id is generated inside the decorator and is not visible to the function body, so there is no field the two share. Correlate with an id you control: pass a request id into the function so it lands in inputs, and set the same value as a span attribute.

get_tracer is a thin passthrough. It returns whatever opentelemetry.trace.get_tracer returns, which is a no-op tracer until your application configures a TracerProvider. Spans then go wherever that provider sends them; Briefcase AI configures no exporter of its own.

Semantic conventions are constants, not behavior. Each submodule of briefcase.semantic_conventions is a set of uppercase key names such as workflow.WORKFLOW_AGENT_CHAIN and rag.RAG_DOCUMENT_VERSION. Importing one sets no attributes; you pass the keys to span.set_attribute yourself. The one surface that emits them for you is workflow correlation, which writes workflow.* keys onto its own root span.

API reference

briefcase.otel has the full surface, which is one function.

Key symbols

  • briefcase.otel.get_tracer(name="briefcase") — return an OpenTelemetry tracer.
  • briefcase.exporters.BaseExporter — base class for custom exporters; implement export, flush, close.
  • briefcase.semantic_conventions.* — attribute-key modules for each subsystem.

Best practices

  1. Sample high-volume paths — use sampling so a busy triage queue does not overwhelm your backend.

  2. Set resource attributes — identify the service and environment so spans are easy to filter.

  3. Use the semantic-convention keys — consistent attribute names make spans queryable across services.

  4. Pair spans with an exporter — the span gives you the timeline, the exported record gives you the governance context.

Where this fits

OpenTelemetry is part of operating a governed system in production: the timeline view that sits next to your cost and event signals.