Repository navigation
Add turn_span_id - #163
Add turn_span_id#163
Conversation
Translators mark the span that opens a user turn (SpanRow::turn_root). A shared TurnLineage wrapper, applied to every translator in Registry, copies that span's id into metadata.turn_span_id on the turn and all of its descendants, including subagent activity. The outermost turn wins, so nested agent turns roll up to the user turn that spawned them. Rows whose ancestry resolves late are re-emitted as late merges. This lets per-turn cost be aggregated with a flat filter instead of a trace tree walk. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A turn span emitted before the span that spawned it used to claim itself as a turn permanently, leaving its subtree attributed to the nested turn. Turns are now derived from current knowledge (parent's turn, else self if a turn root) and any change is propagated to affected descendants as late merges. Late merge keys include the turn id so successive corrections to a span are not deduplicated by the delivery ledger. Adds regression tests for the out-of-order subagent case and repeated corrections, and lineage checks for Antigravity, OpenCode (including child sessions) and Pi. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…uilding it Translators already track which native turn each operation belongs to in order to parent it, so they now record the owning user turn on each row (SpanRow::turn_span_id). Nested activity records the user turn that spawned it: Claude via Turn/AgentOrigin user_turn_id, Codex via a per-scope owner set at subagent start, and OpenCode via an owner inherited by child sessions. TurnLineage becomes a stateless step that publishes the value as metadata.turn_span_id after each translator's metadata allowlist. This removes the per-session lineage maps, late-merge corrections and emission-order dependence. New translator state is bounded like the existing native-id caches. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
shouldn't we do this in all of our sdks, not just the coding agent plugins? I feel like we should have a more general discussion about this in slack before implementing. |
|
Probably, yes. Maybe open a PR in the spec repo? |
Abhijeet Prasad (AbhiPrasad)
left a comment
There was a problem hiding this comment.
okay chatted offline. We decided that doing this just for the coding agent plugins is fine for now!
# Conflicts: # bt-daemon/src/lib.rs
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b05f8ebb5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".
Turns that native compaction hooks identify as maintenance (is_turn = false) no longer own themselves; in the main scope they and their synthetic compaction llm carry no turn_span_id, so per-turn cost rollups do not charge a fake turn. Child rows now follow their turn's recorded owner, keeping a span's turn_span_id equal to its parent's. Compaction discovered only during catch-up still opens as a turn, matching is_turn. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Stamp
metadata.turn_span_idon every span under a turnWhy
I'd like to be able to roll up metrics per turn rather than per trace, for example the cost or tokens of turns with a given label across many sessions.
That isn't easily queryable today.
span_parentsonly records the immediate parent, so anything below a turn's direct children can't be tied back to the turn with a filter. That covers subagent calls, their tools, Codex subagent turns and OpenCode child sessions. Some clients write a turn-like id, but the key differs per client (turn_id,prompt_id,generation_id,turn_number, and none for Claude), and it's usually missing from the LLM spans where cost is recorded.What
Every span that belongs to a turn now carries
metadata.turn_span_id: the span id of the user turn that owns it. Nested agent activity rolls up to the user turn that spawned it. That includes subagents, OpenCode child sessions and Claude continuation turns. Spans outside any turn (the session root, session-level compaction) have no key.Per-turn cost becomes a flat query:
Changes
Translators already track which native turn each operation belongs to, because they need it to choose parents. Each one now records the owning user turn on the row (
SpanRow::turn_span_id,#[serde(skip)]likelate_merge_key).TurnLineageis a stateless step inRegistrythat copies that value intometadata.turn_span_idafter each translator's metadata allowlist runs.row()helper: every non-root span is parented directly to its turnSubagentStart(viaturn_owners, so nested subagents roll up)Turn.user_turn_id/AgentOrigin.user_turn_id;span_ownersmaps turn and subagent spans to their user turn, for transcript rows parented through the existingrecord_turns/request_turns/tool_turnsmapsOwnership comes from the same translator state that chooses each row's parent, so it doesn't depend on the order rows are emitted in, and it needs no follow-up merges.
🤖 Generated with Claude Code
Co-authored by StarfolkAI (@starfolkai)[bot]