feat: delta-native message-graph trajectory - #1606
Merged
Merged
Conversation
Replace the flat `trajectory: list[Turn]` — where every turn restated the whole prompt, so storage was quadratic in turns — with a graph of `MessageNode`s, one per distinct message linked to its predecessor. `Trace.nodes` is the ground truth; `trajectory`/`branches` are views over the graph, and branching falls out of walking parent links (no post-hoc prefix-matching). Each node stores only the tokens it adds (per-message spans from the renderer; the generation-prompt scaffold + sampled completion on the assistant node), so a branch's training sample is a cheap concat and in-memory/on-disk/wire size is linear. - new verifiers/v1/graph.py: MessageNode, message_hash (mirrors branching.same_message), add_turn (build), the walk (branches/trajectory views), branch_token_sequences (concat). - trace.py: `nodes` field; trajectory/branches/num_* become graph-walk views; legacy trajectory dicts tolerated on load. - interception.py / legacy.py build via graph.add_turn; the renderer client threads per-message token spans (prompt_attribution). - branching.segment kept as the legacy / conformance oracle. On-disk size scales linearly: ~9x smaller for a 20-turn agentic trace, ~18x at 40 turns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…trings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`Trace.trajectory` and the `Turn` view are gone; the message graph (`Trace.nodes`) plus `branches` (root→leaf node paths) are the whole model. `Branch` now holds `nodes` (not `Turn`s) and exposes `messages`/`num_turns`/`completion_len`/`prompt_len`/`total_tokens`; the trace's `assistant_messages`/`tool_messages`/`has_response`/`is_truncated` read the graph directly. `MessageNode` gains `finish_reason` (it only lived on `Turn.response`) so truncation detection survives. `branching.py` is deleted — `graph` supersedes it (`message_hash` is the message-equality). Dashboard + rollout log read node/branch fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-message-graph # Conflicts: # verifiers/v1/interception/server.py
- Drop `_drop_legacy_trajectory`: a pre-graph dict fails strict validation anyway (the computed fields it also carried), and the wire form has no `trajectory` — so the validator never actually did anything. - Drop `_view_cache`/`_cached`: the per-turn limit checks grow `nodes` each turn and invalidate it, so it bought a constant at best; `branches` is now a plain property. - Drop a stale doc reference to the removed test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Highlights bullet for the message-graph trace (linear-not-quadratic storage, branches from the walk, training sample = concat along a path) and refresh the body references to the removed `branching` module / `trajectory` field. Format the touched files with ruff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give `Branch` self-describing token views — `token_ids`, `sampled_mask` and `logprobs` (aligned, 0.0 on non-sampled tokens) — so a branch is the single first-class unit for building a training sample or logging, no graph walking at the call site. Drop `graph.branch_token_sequences` (superseded by the Branch accessors). Ruff-format `types.py`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mikasenghaas
marked this pull request as ready for review
June 10, 2026 21:16
Contributor
ApprovabilityVerdict: Needs human review This PR replaces core data structures (flat trajectory list → message graph) and changes how traces are stored, branches are computed, and training samples are built. Major refactors that restructure shared abstractions warrant human review. You can customize Macroscope's approvability policy. Learn more. |
mikasenghaas
added a commit
that referenced
this pull request
Jun 10, 2026
…-> nodes) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mikasenghaas
added a commit
that referenced
this pull request
Jun 10, 2026
* chore(v1): add end-to-end eval test suite under tests/v1 - e2e reward-1 runs across the runtime matrix (subprocess/docker/prime, modal excluded): single-turn (echo), multi-turn (alphabet-sort), multi-turn + tools (glossary), agentic (agentic-echo: bash writes a file, verified in the runtime) - v0 backwards-compat: reverse_text + alphabet_sort bridged, shape parity vs a v1 run - test_configs: every root configs/*.toml parses as EvalConfig - echo + agentic-echo fixture tasksets (deterministic, no dataset/Dockerfile) - conftest: run_v1/run_v0 helpers (greedy temperature=0, generous caps), runtime fixture, e2e + prime markers, skip-without-API-key, on-demand v0 install Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): move fixture tasksets to tests/v1/fixtures; add cross-harness tests - move echo-v1 + agentic-echo-v1 out of the test dir into tests/v1/fixtures, resolved by id via pytest's pythonpath ini (drops the conftest sys.path insert) - test_harnesses.py: run echo (single-turn) and glossary (multi-turn + tools) under default + compact; assert rlm (no task-tool support) is rejected when paired with a tools taskset Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): satisfy ruff (E731 lambda -> def, ruff format) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): merge harness+runtime into one matrix; gate tools on SUPPORTS_TASK_TOOLS - fold test_harnesses into test_e2e: the trivial tasks fan across the harness x runtime matrix (built-in default + rlm; compact is an example harness, excluded; rlm marked slow as it installs an agent binary) - the tools test reads each harness's SUPPORTS_TASK_TOOLS to expect a raise (rlm) vs a run (default), instead of a separate hardcoded test - alphabet-sort: similarity_power=1 (drop power scaling) so a near-perfect sort isn't sharply penalized Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): self-contained v0 legacy tests via echo fixtures; inline run helpers - add v0 echo fixtures (echo-v0 SingleTurnEnv, echo-multi-v0 MultiTurnEnv); the legacy tests use them instead of example envs, so the v0 path is exercised deterministically - drop ensure_v0: the legacy bridge imports a fixture by id off pythonpath (no runtime uv pip install, no environments/ dependency), same as the v1 fixtures - inline the run_v1/run_v0 helpers into their fixtures Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(v1): adapt legacy shape test to #1606 message-graph (trajectory -> nodes) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(v1): SUPPORTS_USER_SIM harness flag + container-safe multi-turn user-sim fixture - add SUPPORTS_USER_SIM ClassVar on Harness (default False; the default harness opts in) - a user simulator is a distinct capability from task MCP tools (rlm supports neither: it takes a single instruction, no message history) - e2e: container-safe echo-multi-v1 user-sim fixture (vf.User shipped as a uv script, staged + run via uv in any runtime); the multi-turn test is gated by SUPPORTS_USER_SIM (skips rlm) - generalize the test capability lookup to harness_supports(id, flag) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(v1): run the user simulator in its own runtime, reached host-side The user simulator is driven by the framework on the host (connect_user), not by the model. It was served colocated in the agent's runtime with a localhost URL, which the host can't reach when the agent runs in a remote prime sandbox (ConnectError; via the agent tunnel, a 421). Now serve_user runs it in its OWN runtime (host subprocess by default, or its own sandbox via TasksetConfig.user.runtime) and publishes the port back to the host (serve_tools host_reachable: a remote sandbox's public_url, else localhost). - add UserConfig(runtime) to TasksetConfig; serve_tools gains host_reachable for a host-consumed colocated server - e2e: the multi-turn user-sim test now passes on prime; add a test for the user-sim in its own (docker) sandbox Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(v1): matrix task-tools + user-sim across server runtimes - server_runtime fixture + test_task_tools_own_runtime / test_user_own_runtime: a tool/user-sim server in its OWN runtime (subprocess/docker/prime), agent on subprocess - skip_if_unexposable: skip the prime server case when the sandbox region can't publish a port (a known prime infra limit, surfaced by the matrix) - TODO in prime.public_url to lift that limit (then drop the skip) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(v1): rename agentic-echo fixture -> echo-agentic-v1 (echo_* naming) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mikasenghaas
added a commit
that referenced
this pull request
Jun 11, 2026
The --rich dashboard's per-rollout "X/Y tokens" was derived from the branch's token-id lengths, which are 0 for endpoints that don't return token ids (e.g. plain OpenAI completions) — so eval rows showed "0/0 tokens". Carry the response's usage onto the assistant node (transient, excluded from wire/disk, like the multimodal sidecar) and fall back to it when token ids are absent: input = final turn's usage.prompt_tokens, output = sum of usage.completion_tokens across turns. The renderer path (token ids present) is unchanged. Regression from #1606, which switched _tokens off response.usage. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to Stars1233/verifiers
that referenced
this pull request
Jun 23, 2026
* feat(v1): delta-native message-graph trajectory Replace the flat `trajectory: list[Turn]` — where every turn restated the whole prompt, so storage was quadratic in turns — with a graph of `MessageNode`s, one per distinct message linked to its predecessor. `Trace.nodes` is the ground truth; `trajectory`/`branches` are views over the graph, and branching falls out of walking parent links (no post-hoc prefix-matching). Each node stores only the tokens it adds (per-message spans from the renderer; the generation-prompt scaffold + sampled completion on the assistant node), so a branch's training sample is a cheap concat and in-memory/on-disk/wire size is linear. - new verifiers/v1/graph.py: MessageNode, message_hash (mirrors branching.same_message), add_turn (build), the walk (branches/trajectory views), branch_token_sequences (concat). - trace.py: `nodes` field; trajectory/branches/num_* become graph-walk views; legacy trajectory dicts tolerated on load. - interception.py / legacy.py build via graph.add_turn; the renderer client threads per-message token spans (prompt_attribution). - branching.segment kept as the legacy / conformance oracle. On-disk size scales linearly: ~9x smaller for a 20-turn agentic trace, ~18x at 40 turns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(v1): rename MessageNode.sampled_mask -> mask; per-field docstrings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(v1): graph.py module docstring describes the current design only Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(v1): drop Turn/trajectory — nodes + branches are the model `Trace.trajectory` and the `Turn` view are gone; the message graph (`Trace.nodes`) plus `branches` (root→leaf node paths) are the whole model. `Branch` now holds `nodes` (not `Turn`s) and exposes `messages`/`num_turns`/`completion_len`/`prompt_len`/`total_tokens`; the trace's `assistant_messages`/`tool_messages`/`has_response`/`is_truncated` read the graph directly. `MessageNode` gains `finish_reason` (it only lived on `Turn.response`) so truncation detection survives. `branching.py` is deleted — `graph` supersedes it (`message_hash` is the message-equality). Dashboard + rollout log read node/branch fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(v1): remove dead code (legacy-trajectory validator, view cache) - Drop `_drop_legacy_trajectory`: a pre-graph dict fails strict validation anyway (the computed fields it also carried), and the wire form has no `trajectory` — so the validator never actually did anything. - Drop `_view_cache`/`_cached`: the per-turn limit checks grow `nodes` each turn and invalidate it, so it bought a constant at best; `branches` is now a plain property. - Drop a stale doc reference to the removed test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: highlight the delta-native trace graph + ruff format Add a Highlights bullet for the message-graph trace (linear-not-quadratic storage, branches from the walk, training sample = concat along a path) and refresh the body references to the removed `branching` module / `trajectory` field. Format the touched files with ruff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: first-class Branch token accessors Give `Branch` self-describing token views — `token_ids`, `sampled_mask` and `logprobs` (aligned, 0.0 on non-sampled tokens) — so a branch is the single first-class unit for building a training sample or logging, no graph walking at the call site. Drop `graph.branch_token_sequences` (superseded by the Branch accessors). Ruff-format `types.py`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to Stars1233/verifiers
that referenced
this pull request
Jun 23, 2026
* chore(v1): add end-to-end eval test suite under tests/v1 - e2e reward-1 runs across the runtime matrix (subprocess/docker/prime, modal excluded): single-turn (echo), multi-turn (alphabet-sort), multi-turn + tools (glossary), agentic (agentic-echo: bash writes a file, verified in the runtime) - v0 backwards-compat: reverse_text + alphabet_sort bridged, shape parity vs a v1 run - test_configs: every root configs/*.toml parses as EvalConfig - echo + agentic-echo fixture tasksets (deterministic, no dataset/Dockerfile) - conftest: run_v1/run_v0 helpers (greedy temperature=0, generous caps), runtime fixture, e2e + prime markers, skip-without-API-key, on-demand v0 install Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): move fixture tasksets to tests/v1/fixtures; add cross-harness tests - move echo-v1 + agentic-echo-v1 out of the test dir into tests/v1/fixtures, resolved by id via pytest's pythonpath ini (drops the conftest sys.path insert) - test_harnesses.py: run echo (single-turn) and glossary (multi-turn + tools) under default + compact; assert rlm (no task-tool support) is rejected when paired with a tools taskset Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): satisfy ruff (E731 lambda -> def, ruff format) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): merge harness+runtime into one matrix; gate tools on SUPPORTS_TASK_TOOLS - fold test_harnesses into test_e2e: the trivial tasks fan across the harness x runtime matrix (built-in default + rlm; compact is an example harness, excluded; rlm marked slow as it installs an agent binary) - the tools test reads each harness's SUPPORTS_TASK_TOOLS to expect a raise (rlm) vs a run (default), instead of a separate hardcoded test - alphabet-sort: similarity_power=1 (drop power scaling) so a near-perfect sort isn't sharply penalized Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(v1): self-contained v0 legacy tests via echo fixtures; inline run helpers - add v0 echo fixtures (echo-v0 SingleTurnEnv, echo-multi-v0 MultiTurnEnv); the legacy tests use them instead of example envs, so the v0 path is exercised deterministically - drop ensure_v0: the legacy bridge imports a fixture by id off pythonpath (no runtime uv pip install, no environments/ dependency), same as the v1 fixtures - inline the run_v1/run_v0 helpers into their fixtures Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(v1): adapt legacy shape test to PrimeIntellect-ai#1606 message-graph (trajectory -> nodes) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(v1): SUPPORTS_USER_SIM harness flag + container-safe multi-turn user-sim fixture - add SUPPORTS_USER_SIM ClassVar on Harness (default False; the default harness opts in) - a user simulator is a distinct capability from task MCP tools (rlm supports neither: it takes a single instruction, no message history) - e2e: container-safe echo-multi-v1 user-sim fixture (vf.User shipped as a uv script, staged + run via uv in any runtime); the multi-turn test is gated by SUPPORTS_USER_SIM (skips rlm) - generalize the test capability lookup to harness_supports(id, flag) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(v1): run the user simulator in its own runtime, reached host-side The user simulator is driven by the framework on the host (connect_user), not by the model. It was served colocated in the agent's runtime with a localhost URL, which the host can't reach when the agent runs in a remote prime sandbox (ConnectError; via the agent tunnel, a 421). Now serve_user runs it in its OWN runtime (host subprocess by default, or its own sandbox via TasksetConfig.user.runtime) and publishes the port back to the host (serve_tools host_reachable: a remote sandbox's public_url, else localhost). - add UserConfig(runtime) to TasksetConfig; serve_tools gains host_reachable for a host-consumed colocated server - e2e: the multi-turn user-sim test now passes on prime; add a test for the user-sim in its own (docker) sandbox Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(v1): matrix task-tools + user-sim across server runtimes - server_runtime fixture + test_task_tools_own_runtime / test_user_own_runtime: a tool/user-sim server in its OWN runtime (subprocess/docker/prime), agent on subprocess - skip_if_unexposable: skip the prime server case when the sandbox region can't publish a port (a known prime infra limit, surfaced by the matrix) - TODO in prime.public_url to lift that limit (then drop the skip) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(v1): rename agentic-echo fixture -> echo-agentic-v1 (echo_* naming) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to Stars1233/verifiers
that referenced
this pull request
Jun 23, 2026
…eIntellect-ai#1627) The --rich dashboard's per-rollout "X/Y tokens" was derived from the branch's token-id lengths, which are 0 for endpoints that don't return token ids (e.g. plain OpenAI completions) — so eval rows showed "0/0 tokens". Carry the response's usage onto the assistant node (transient, excluded from wire/disk, like the multimodal sidecar) and fall back to it when token ids are absent: input = final turn's usage.prompt_tokens, output = sum of usage.completion_tokens across turns. The renderer path (token ids present) is unchanged. Regression from PrimeIntellect-ai#1606, which switched _tokens off response.usage. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Move the v1 trajectory from a flat
trajectory: list[Turn]— where every turn restated the whole prompt (and its tokens), so size was quadratic in turns — to a delta-native message graph.Trace.nodes(oneMessageNodeper distinct message, each linked to its predecessor) is the only stored model;branchesand the conversation accessors are computed views over it. Each message is stored once, so in-memory / on-disk / wire size is linear; branching falls out of walking the graph; a training sample is a cheap concat along a branch path.Changes
verifiers/v1/graph.py(new) —MessageNode(parent,message,token_ids,mask,logprobs,finish_reason);message_hash(message equality);add_turn(reuse prefix nodes by(parent, hash), attribute each new message's tokens from the renderer's per-message spans, append the assistant node = generation-prompt scaffold + sampled completion);branches_from_nodes/leaves(the per-branch token views live onBranch).trace.py—Trace.nodesis the ground truth.TurnandTrace.trajectoryare removed.Branchholdsnodesand is self-describing:messages+ the aligned token viewstoken_ids/sampled_mask/logprobs, plusnum_turns/prompt_len/completion_len/total_tokens;assistant_messages/tool_messages/has_response/is_truncatedread the graph directly.interception/server.py,legacy.pybuild viagraph.add_turn;clients/renderer.pythreads the per-message token spans (message_token_spans()) it previously discarded.branching.pydeleted — the graph supersedes it (message_hashis the message-equality).concat(node.token_ids along a path) == prompt_ids + completion_idsthe model saw (the previous assistant's closing scaffold anchors to the cumulative stored prefix, not message spans).Data structure
A trace is a flat list of
MessageNodes — one per distinct message — each pointing at its predecessor (parent, an index intonodes). The conversation is a root→leaf path; branches (compaction / subagents / resampling) are simply multiple leaves, so branching falls out of walking the graph. Each node stores only the tokens it adds (token_ids), so concatenating a path reproduces the full sequence — storage is linear, not quadratic.On disk (
results.jsonl— one trace per line){ "id": "…", "task": { … }, "nodes": [ { "message": {"role":"system","content":"You sort."}, // root: "parent" omitted (= None) "token_ids":[1,2,3], "mask":[false,false,false], "logprobs":[] }, { "parent":0, "message": {"role":"user","content":"cba?"}, "token_ids":[4,5], "mask":[false,false], "logprobs":[] }, { "parent":1, "message": {"role":"assistant","content":"abc"}, // a turn (assistant node) "token_ids":[9,8], "mask":[true,true], "logprobs":[-0.2,-0.2], "finish_reason":"stop" } ], "rewards": { … }, "reward": …, "metrics": { … }, "is_completed": true, "stop_condition": "…", "is_truncated": false, "timing": { … }, "errors": [] }parent— index of the predecessor node; omitted for a root (None).token_ids— this message's delta contribution (leading template scaffold + its own tokens; for an assistant, the generation-prompt scaffold + the sampled completion).mask[i]—True⇒ a model-sampled (trainable) token;False⇒ scaffold / input. Only assistant nodes haveTrues.logprobs— sampling logprobs, aligned to theTrueentries.finish_reason— assistant nodes only (truncation detection).branches/num_turns/assistant_messages/ … are not stored — they're computed views overnodes.Loading + building a training sample
This is exactly what prime-rl's
trace_to_samplesdoes — oneTrainingSampleper branch.Verification
results.jsonlvs the old per-turn-full-prompt + expanded-brancheslayout:real terminal-bench-2 (bash agent), same trace both ways:
8/16/32 are measured (same trace, both layouts); 64/128/256 are projected (old ∝ turns², new ∝ turns). The reduction tracks turn count — at 256 turns the old layout would be ~0.5 GB/trace vs ~1.8 MB.
synthetic 5→40 turns: 2.6× → 17.6×.
trace_to_samples→ trainer at 0% error on both v1 (native renderer path, 3.2 turns, 432/512 trainable) and v0 (legacy bridge, 2 turns, 368/512).Notes
trace_to_samples).feat/nano-as-v1);MessageNodeis trivially mm-extendable when mm lands.Note
High Risk
Breaking change to v1 trace shape and serialization (
trajectory/Turnremoved); training and downstream consumers must readnodes/Branchinstead of per-turn lists.Overview
Replaces the v1 trace’s flat
trajectory: list[Turn](full prompt restated every turn → quadratic storage) with a delta-native message graph:Trace.nodesis the only persisted model;Turnandbranching.pyare removed.verifiers/v1/graph.pyaddsMessageNode(parent link, per-messagetoken_ids/mask/logprobs),add_turn(prefix dedup viamessage_hash, renderer span attribution), and graph walks forbranches.Branchnow holdsnodesand exposes training-readytoken_ids,sampled_mask, andlogprobsby concatenating the path. The interception server and legacy v0 bridge record turns viagraph.add_turn; the renderer client threadsmessage_spansonTurnTokens(transient,exclude=Trueon persist).Docs and the eval dashboard are updated for graph-based branching and token display (
b.prompt_len/b.completion_len).Reviewed by Cursor Bugbot for commit dae561f. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Replace flat trajectory with a delta-native message graph in trace representation
MessageNodeingraph.pyas the atomic unit of the trace, storingtoken_ids,mask,logprobs, andfinish_reasonper message.Trace.trajectory: list[Turn]withTrace.nodes: list[MessageNode]; branches, turn counts, and token aggregations are now derived from root→leaf paths through this graph.graph.add_turnreuses shared prompt prefixes via a hash-keyed head index, avoiding duplication of repeated messages across turns.interception/server.pyandlegacy.pyto write into the message graph rather than appendingTurnobjects.TurnandTrace.trajectoryare removed; any code referencing these will break.Macroscope summarized dae561f.