What happens
Now that L2 OTLP export is live to the pilot MLflow (post-#3749, fullsend-ai dogfood runs landing in experiment 1), the experiment Traces view populates only a few columns. Across the 50 most recent real traces:
| Column |
Populated |
Note |
| Name |
0/50 |
blank on every trace |
| Session |
0/50 |
can't group runs by issue/PR |
| User |
0/50 |
triggering actor unknown |
| Source |
0/50 |
repo / workflow / commit unknown |
| Request / Response |
0/50 |
content — L3, expected empty |
| Tokens |
50/50 |
derived from input_tokens/output_tokens only |
| Cost |
50/50 |
undercounts — see below |
Sample (tr-6d451616…, 218s run): tokenUsage {input: 30, output: 13536}, cost {input_cost: 0.00015, output_cost: 0.338}. The run consumed ~100k+ cached input tokens, but input_tokens: 30 (and thus input_cost: ~$0.0002) reflects only non-cached input — MLflow derives token/cost from gen_ai.usage.input_tokens/output_tokens and ignores the cache_read_input_tokens / cache_creation_input_tokens we emit.
Why it matters
Name / Session / User / Source are the columns that make the trace list navigable and groupable (per issue/PR, per actor, per repo). Cost is the pilot's headline metric; undercounting cached input by orders of magnitude undermines it.
Root cause
The exporter emits generic OTel attributes only — fullsend.*, gen_ai.usage.*, fullsend.cost_usd — and no mlflow.* trace-level attributes. MLflow auto-derives tokenUsage/cost from gen_ai.usage.input/output, but has no source for Name/Session/User/Source and does not fold cache tokens into the input figure.
Raw material we already emit (root span)
fullsend.agent, fullsend.work_item_id, gen_ai.request.model, gen_ai.usage.{input,output,cache_read,cache_creation}_input_tokens, fullsend.cost_usd (cache-aware, accurate), fullsend.tool_calls, fullsend.num_turns, fullsend.iterations. Not captured today: triggering actor and source repo/commit/run-url as span attributes.
Candidate mappings (illustrative, not prescriptive)
- Name ←
fullsend.agent (optionally + work_item_id)
- Session ←
fullsend.work_item_id (groups all runs for one issue/PR)
- User ← triggering actor (needs capture from CI context, e.g.
GITHUB_ACTOR / issue author)
- Source ← repo + commit + run URL (needs capture from CI env)
- Tokens/Cost ← include cache tokens in the input figure, and/or surface
fullsend.cost_usd directly
Open design question
Populating MLflow's Name/Session/User/Source requires emitting mlflow.*-prefixed trace attributes, which couples the backend-agnostic exporter to MLflow. Options to decide: emit mlflow.* attributes per ADR 0050; keep the exporter generic and accept blank MLflow columns; or map at a Collector/backend layer. The token/cost fidelity gap is partly upstream (MLflow's extractor ignores cache fields) and partly ours (we could emit a combined input count or the cost directly).
Out of scope
Request/Response = prompt/completion content = L3 (governance-gated). Not part of this issue.
Relates to #2862.
What happens
Now that L2 OTLP export is live to the pilot MLflow (post-#3749,
fullsend-aidogfood runs landing in experiment 1), the experiment Traces view populates only a few columns. Across the 50 most recent real traces:input_tokens/output_tokensonlySample (
tr-6d451616…, 218s run):tokenUsage {input: 30, output: 13536},cost {input_cost: 0.00015, output_cost: 0.338}. The run consumed ~100k+ cached input tokens, butinput_tokens: 30(and thusinput_cost: ~$0.0002) reflects only non-cached input — MLflow derives token/cost fromgen_ai.usage.input_tokens/output_tokensand ignores thecache_read_input_tokens/cache_creation_input_tokenswe emit.Why it matters
Name / Session / User / Source are the columns that make the trace list navigable and groupable (per issue/PR, per actor, per repo). Cost is the pilot's headline metric; undercounting cached input by orders of magnitude undermines it.
Root cause
The exporter emits generic OTel attributes only —
fullsend.*,gen_ai.usage.*,fullsend.cost_usd— and nomlflow.*trace-level attributes. MLflow auto-derives tokenUsage/cost fromgen_ai.usage.input/output, but has no source for Name/Session/User/Source and does not fold cache tokens into the input figure.Raw material we already emit (root span)
fullsend.agent,fullsend.work_item_id,gen_ai.request.model,gen_ai.usage.{input,output,cache_read,cache_creation}_input_tokens,fullsend.cost_usd(cache-aware, accurate),fullsend.tool_calls,fullsend.num_turns,fullsend.iterations. Not captured today: triggering actor and source repo/commit/run-url as span attributes.Candidate mappings (illustrative, not prescriptive)
fullsend.agent(optionally+ work_item_id)fullsend.work_item_id(groups all runs for one issue/PR)GITHUB_ACTOR/ issue author)fullsend.cost_usddirectlyOpen design question
Populating MLflow's Name/Session/User/Source requires emitting
mlflow.*-prefixed trace attributes, which couples the backend-agnostic exporter to MLflow. Options to decide: emitmlflow.*attributes per ADR 0050; keep the exporter generic and accept blank MLflow columns; or map at a Collector/backend layer. The token/cost fidelity gap is partly upstream (MLflow's extractor ignores cache fields) and partly ours (we could emit a combined input count or the cost directly).Out of scope
Request/Response = prompt/completion content = L3 (governance-gated). Not part of this issue.
Relates to #2862.