Summary
Emit forge-neutral, Level 1 correlation attributes on every agent root span so operators can search, group, and sum fullsend.cost_usd across related traces — not only for a single run, but for:
- This work item (leaf / the entity that triggered the run)
- The parent work item (one level up in the work graph)
- The feature / root (top of the decomposition tree, typically two+ levels above the leaf)
This is a Fullsend telemetry capability, not an MLflow feature. Any OTLP backend (or offline run-telemetry.jsonl) must be able to answer “what did this task / epic / feature cost across triage → code → review → fix → retro?”
Why this is needed
Today each managed run already records accurate, cache-aware cost as fullsend.cost_usd and a per-run entity as fullsend.work_item_id (e.g. owner/repo#N). That is enough for cost of this job.
It is not enough for:
- Same work across issue → PR. GitHub issue numbers and PR numbers differ. Triage/code on issue
#100 and review/fix/retro on PR #105 do not share work_item_id, so operators cannot sum “cost of this PR’s Fullsend chain” without manual join.
- Fan-out. Triage (or a future refine/decompose agent) may close one issue and open several children. One flat id cannot express both “cost of the closed parent decision” and “cost of each child.”
- Decomposition hierarchies. We will have agents that break large features into smaller work. On GitHub that may be sub-issues; on Jira that may be epic → story → task (or similar). Operators will want cost at leaf, parent, and feature independently.
Related but not a substitute:
- #5361 — MLflow Name/Session/User/Source blanks + token/cost display fidelity. Adjacent enrichment; does not define hierarchical join keys or issue≠PR lifecycle semantics.
- W3C
TRACEPARENT — correct for stitching one dispatched pipeline into a single OTEL trace; wrong for independent GHA/GitLab jobs over days that should remain separate traces and still be summable.
- ADR 0050 Level 3 content capture (e.g. #6429) — prompts/completions/reasoning. This issue is Level 1 metadata (always-on attributes), not content.
Goals
A. Leaf lifecycle (same work unit across forge shape changes)
For the happy path “triage → code → review → (fix)* → retro” on one work unit, every related root span carries a stable leaf lifecycle id that does not change when the forge entity flips from issue → PR (or Jira equivalent).
fullsend.work_item_id remains “the entity that triggered this run.” The lifecycle id is the join key across those runs.
B. Hierarchical rollup (decomposition / sub-work)
Every leaf run also carries enough ancestry to answer three cost questions:
| Rollup |
Answers |
GitHub-ish examples |
Jira-ish examples |
| Work item (leaf) |
Cost of this task |
issue, PR, or sub-issue |
story, task, bug |
| Parent |
Cost of the container that owns these leaves |
parent issue of sub-issues |
epic (or immediate parent) |
| Feature / root |
Cost of the whole decomposition |
top-level issue / feature root |
initiative or feature-root epic |
Illustrative attribute names (exact names are an implementation decision; prefer portable OTel + fullsend.*):
fullsend.work_item_id — already exists (this run’s trigger)
fullsend.parent_work_item_id — immediate parent in the work graph (when known)
fullsend.feature_id or fullsend.root_work_item_id — top of the decomposition tree (when known)
- Optional:
fullsend.work_item_path — ordered ancestors if nesting can exceed two levels
C. Backend-agnostic emission
- Emit from the CLI / managed path as Level 1 span attributes (same class as today’s
work_item_id / cost_usd).
- Do not require
mlflow.* attributes in core. Orgs may use MLflow, Phoenix, Jaeger, a collector, or JSONL only.
- If we also set OTel
session.id so GenAI UIs (including MLflow’s Session column) light up, document which rollup key maps to session.id (leaf lifecycle vs feature). One Session column cannot express a three-level tree; hierarchy stays on explicit attributes.
D. Queryability (acceptance of the capability)
Given traces for a completed flow, an operator (or script) can:
- Filter/search by leaf lifecycle id and sum
fullsend.cost_usd across triage + N code/fix + review + retro.
- Filter by parent id and sum cost of all child leaves under that parent.
- Filter by feature/root id and sum cost of the whole decomposition.
Authoritative cost is always fullsend.cost_usd, not a backend’s derived Cost column (cache tokens are often omitted — see #5361).
Non-goals
- Baking vendor score/Assessment APIs or product-specific env into managed workflows (ADR 0087 / tool-agnostic core).
- Replacing
TRACEPARENT for same-job dispatched children.
- Level 3 content capture.
- A same-job
eval-measure scorer that invents cross-run totals from one artifact — chain rollup is a derived product over a store of traces (or offline JSONL), not EM-001.
Design questions to resolve in implementation / ADR notes
- Issue → PR continuity: Prefer keeping the same leaf lifecycle id when a PR is opened from an issue (PR continues the work unit; it is not a fork).
- Fan-out (1→N): When an agent closes
#100 and opens #101/#102/#103, prefer fork with parent link (each child gets a new leaf lifecycle id; parent/feature ids preserved or linked via previous / parent attributes) over forcing one id across unrelated children. Document how to answer both “cost of the triage decision on #100” and “cost of child #101.”
- Resolution at run time: How does the runner learn parent/feature when the event only names the leaf? (Forge APIs differ: GitHub sub-issues vs Jira epic link / parent. Agents that create children must stamp new leaves.)
- Depth: Is parent + feature enough, or do we need an explicit path for deeper trees?
session.id mapping: Which single key (if any) is duplicated into OTel session.id for UI grouping?
Proposed acceptance criteria
Related
- ADR 0050 — framework-native OTEL; Level 1 metadata;
TRACEPARENT for dispatched pipelines
- ADR 0087 — eval measurements as derived products; tool-agnostic export
- #5361 — MLflow column / cost display enrichment (related consumer)
- #2368 — harness content SHA / join-adjacent attrs
- Docs:
docs/guides/infrastructure/distributed-tracing.md (fullsend.work_item_id as today’s primary cross-run key)
Summary
Emit forge-neutral, Level 1 correlation attributes on every agent root span so operators can search, group, and sum
fullsend.cost_usdacross related traces — not only for a single run, but for:This is a Fullsend telemetry capability, not an MLflow feature. Any OTLP backend (or offline
run-telemetry.jsonl) must be able to answer “what did this task / epic / feature cost across triage → code → review → fix → retro?”Why this is needed
Today each managed run already records accurate, cache-aware cost as
fullsend.cost_usdand a per-run entity asfullsend.work_item_id(e.g.owner/repo#N). That is enough for cost of this job.It is not enough for:
#100and review/fix/retro on PR#105do not sharework_item_id, so operators cannot sum “cost of this PR’s Fullsend chain” without manual join.Related but not a substitute:
TRACEPARENT— correct for stitching one dispatched pipeline into a single OTEL trace; wrong for independent GHA/GitLab jobs over days that should remain separate traces and still be summable.Goals
A. Leaf lifecycle (same work unit across forge shape changes)
For the happy path “triage → code → review → (fix)* → retro” on one work unit, every related root span carries a stable leaf lifecycle id that does not change when the forge entity flips from issue → PR (or Jira equivalent).
fullsend.work_item_idremains “the entity that triggered this run.” The lifecycle id is the join key across those runs.B. Hierarchical rollup (decomposition / sub-work)
Every leaf run also carries enough ancestry to answer three cost questions:
Illustrative attribute names (exact names are an implementation decision; prefer portable OTel +
fullsend.*):fullsend.work_item_id— already exists (this run’s trigger)fullsend.parent_work_item_id— immediate parent in the work graph (when known)fullsend.feature_idorfullsend.root_work_item_id— top of the decomposition tree (when known)fullsend.work_item_path— ordered ancestors if nesting can exceed two levelsC. Backend-agnostic emission
work_item_id/cost_usd).mlflow.*attributes in core. Orgs may use MLflow, Phoenix, Jaeger, a collector, or JSONL only.session.idso GenAI UIs (including MLflow’s Session column) light up, document which rollup key maps tosession.id(leaf lifecycle vs feature). One Session column cannot express a three-level tree; hierarchy stays on explicit attributes.D. Queryability (acceptance of the capability)
Given traces for a completed flow, an operator (or script) can:
fullsend.cost_usdacross triage + N code/fix + review + retro.Authoritative cost is always
fullsend.cost_usd, not a backend’s derived Cost column (cache tokens are often omitted — see #5361).Non-goals
TRACEPARENTfor same-job dispatched children.eval-measurescorer that invents cross-run totals from one artifact — chain rollup is a derived product over a store of traces (or offline JSONL), not EM-001.Design questions to resolve in implementation / ADR notes
#100and opens#101/#102/#103, prefer fork with parent link (each child gets a new leaf lifecycle id; parent/feature ids preserved or linked viaprevious/ parent attributes) over forcing one id across unrelated children. Document how to answer both “cost of the triage decision on#100” and “cost of child#101.”session.idmapping: Which single key (if any) is duplicated into OTelsession.idfor UI grouping?Proposed acceptance criteria
work_item_idvs parent vs feature/root, including issue→PR and 1→N fork rules.SUM(fullsend.cost_usd)using OTLP backend search or offlinerun-telemetry.jsonl.mlflow.*(or other vendor) attributes required in core for this to work.session.idonce mapping is decided; this issue owns the hierarchy semantics.Related
TRACEPARENTfor dispatched pipelinesdocs/guides/infrastructure/distributed-tracing.md(fullsend.work_item_idas today’s primary cross-run key)