Overview
Tracing is the shared observability layer for AgentLane. It gives runtime code, model code, and application code a common trace and span model so the whole workflow can be understood as one unit instead of as isolated logs.
The public tracing surface stays small. A
TraceProvider creates
Trace and
Span values, the default
DefaultTraceProvider gives most
applications a ready-made implementation, and processors such as
MetricsProcessor or a
custom TracingProcessor
consume what was recorded.
What Tracing Captures
Tracing is built around a simple idea:
- a trace represents one workflow or run
- spans represent meaningful operations inside that workflow
- processors export or aggregate what happened
That makes tracing useful for both debugging and operations. You can see how a request moved through a system, and you can also aggregate metrics about that system over time.
ToolExecutor uses the active model’s ModelTracing mode when the harness
runs tools. ModelTracing.ENABLED_WITHOUT_DATA records function spans with
the tool name and timing, and leaves their input and output fields empty.
Getting Started
Tracing uses one global
TraceProvider. The usual setup is
to create a DefaultTraceProvider,
register one or more processors, and install it with set_trace_provider(...).
from agentlane.tracing import (
DefaultTraceProvider,
MetricsProcessor,
set_trace_provider,
)
provider = DefaultTraceProvider()
provider.register_processor(MetricsProcessor())
set_trace_provider(provider)
Once a provider is installed, traces and spans can be created anywhere in the process through the shared tracing API.
from agentlane.tracing import emit_metric, generation_span, trace
with trace("clinical_triage_run"):
with generation_span(model="gpt-5.4-mini"):
emit_metric("llm_calls", 1)
Context Propagation
Tracing becomes more interesting once work crosses task or process boundaries. That is why the package exposes explicit propagation helpers.
Use:
capture_parent_context(message_id)before handing work offwith adopt_parent_context(message_id):while processing that workdiscard_parent_context(message_id)when stored context should be dropped
adopt_parent_context is a context manager: the captured trace/span is active
only inside the with block. Adopting is single-shot — it pops the stored
snapshot, so the first adopt consumes it and any later reuse needs a fresh
capture_parent_context. Call discard_parent_context only when a captured
context is never adopted, to avoid leaking the stored snapshot.
capture_parent_context(message_id)
# ... hand work off ...
with adopt_parent_context(message_id):
with generation_span(model="gpt-5.4-mini"):
emit_metric("llm_calls", 1)
Those helpers are especially useful for runtime-driven message flows where the producer and consumer do not share the same synchronous call stack.
Metrics
Metrics are emitted inside spans and aggregated at the trace level. That keeps the metric story close to the trace story: one workflow can contain many metric events, but the final result can still be summarized once the workflow ends.
MetricsProcessor is the
default building block for that aggregation path. Aggregated metrics leave the
processor through an optional on_trace_metrics callback, invoked when a trace
ends with the trace_id and a dict[str, AggregatedMetric].
from agentlane.tracing import AggregatedMetric, MetricsProcessor
def on_metrics(trace_id: str, metrics: dict[str, AggregatedMetric]) -> None:
... # forward aggregated metrics to your sink
provider.register_processor(MetricsProcessor(on_trace_metrics=on_metrics))
Exporters And Processors
TracingProcessor is
the extension point for exporting traces and spans to another sink.
The common patterns are:
- use
MetricsProcessorwhen you need aggregated trace metrics - implement a custom processor when you need your own export destination
- install
agentlane[braintrust]and useBraintrustProcessorwhen you want Braintrust export
from agentlane.tracing import DefaultTraceProvider, set_trace_provider
from agentlane_braintrust import BraintrustProcessor
provider = DefaultTraceProvider()
provider.register_processor(
BraintrustProcessor(api_key="...", project_id="...")
)
set_trace_provider(provider)