Tracing with MLflow
MLflow 3.6+ ingests OTLP/HTTP traces natively and provides experiment-based routing, a traces UI, and per-trace cost estimates. This guide covers MLflow-specific configuration on top of the general How To Emit Traces setup.
Before you begin
- MLflow 3.6 or later running with a traces endpoint at
{server}/v1/traces. - A completed How To Emit Traces setup (endpoint variable and header secret configured).
Endpoint and experiment routing
MLflow routes traces to an experiment via the x-mlflow-experiment-id header. Append it to the authentication header in the same secret:
gh secret set OTEL_EXPORTER_OTLP_TRACES_HEADERS \
--body "<authentication-header>,x-mlflow-experiment-id=<id>" \
--repo <owner/repo>Use OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (signal-specific) rather than the base endpoint, because MLflow's traces path is {server}/v1/traces and the SDK would otherwise append /v1/traces to the base URL:
gh variable set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT \
--body "https://mlflow.example.com/v1/traces" \
--repo <owner/repo>Basic auth encoding
Header values are URL-decoded by the OTel SDK. Encode spaces as %20:
# Base64-encode the credentials
CREDS_B64=$(echo -n "user:password" | base64)
# Set the header with URL-encoded space
gh secret set OTEL_EXPORTER_OTLP_TRACES_HEADERS \
--body "Authorization=Basic%20${CREDS_B64},x-mlflow-experiment-id=<id>" \
--repo <owner/repo>Organizing traces for an org
Two conventions keep a shared MLflow instance navigable as repos onboard:
One experiment per org. Create an experiment per org (e.g.
fullsend-<org>) and point the org's header secret at its ID. MLflow's per-experiment access controls then align with org boundaries.Slice inside the experiment with resource attributes. The standard
OTEL_RESOURCE_ATTRIBUTESvariable tags every trace:yaml# In workflow YAML (where ${{ github.* }} evaluates): env: OTEL_RESOURCE_ATTRIBUTES: "fullsend.repo=${{ github.repository }},fullsend.agent=triage,deployment.environment=prod"On the managed path, set the
OTEL_RESOURCE_ATTRIBUTESActions variable to a static value instead; variables are not expression-expanded.Enable these as columns in MLflow's Traces table.
fullsend.work_item_idon the rootrunspan correlates runs for the same issue.
Cost column caveat
MLflow's per-trace cost column is its own estimate: it extracts input/output token counts and prices them against MLflow's internal model table. This excludes cache-creation and cache-read tokens, which dominate agent-run cost.
The authoritative cost figure is the runtime-reported fullsend.cost_usd attribute on agent spans (also in run-telemetry.jsonl).
Level 3 content
With content capture enabled (see How To Emit Traces), the conversation lives on each agent span as gen_ai.output.messages: open the trace and select the span to read it. The trace list's Request/Response preview columns derive from the root span only (capped at 1000 characters), so they stay empty for fullsend traces — content is deliberately not duplicated onto the root span.
Local development
Start a local MLflow instance and point the exporter at it:
uvx mlflow server
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:5000/v1/traces"
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="x-mlflow-experiment-id=0"
fullsend run triage ...See Running Agents Locally for full flags. View traces at http://localhost:5000.
See also
- How To Emit Traces: general setup for any OTLP backend
- Tracing reference: span schemas, attribute tables, and environment variable reference
