Skip to content

fix: preserve rollout meta in the v0 -> v1 legacy bridge - #1586

Merged
mikasenghaas merged 1 commit into
feat/nano-as-v1from
feat/v0-bridge-meta
Jun 9, 2026
Merged

fix: preserve rollout meta in the v0 -> v1 legacy bridge#1586
mikasenghaas merged 1 commit into
feat/nano-as-v1from
feat/v0-bridge-meta

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

The v0 → v1 legacy bridge (rollout_output_to_trace) was producing a near-empty Turn.response: it kept only the token ids and dropped almost everything else a native v1 trace carries.

Root cause: a v0 RolloutOutput nests its data as pydantic objects in-process (SystemMessage/UserMessage/AssistantMessage, Response), and records finish_reason on response.message (not the response). The bridge's helpers only handled plain dicts (if not isinstance(m, dict): continue) and read finish_reason off the response top-level — so the message lists were silently skipped and the response fields came back null.

Fix:

  • _as_dict() coerces v0 pydantic objects to dicts before mapping, so the helpers read them whether they arrive as objects or dicts.
  • _to_v1_response now reads finish_reason from response.message (its v0 location), keeps usage, and mirrors the token ids onto response.tokens (as the native v1 client does).
  • New _to_wire_task carries the prompt's system message → system_prompt, user message(s) → instruction, and the reference answer (as a WireTask extra) onto the task.

A v0-bridged Trace now matches the native v1 schema.

Discrepancies fixed (v0-bridge vs native v1, reverse-text step 0)

Before, the bridge dropped: turn.prompt (empty), response.message.content / reasoning_content (null), finish_reason (null), usage (null), response.tokens (null), task.instruction (empty), task.system_prompt (null), task.answer (missing). is_truncated also stayed wrong because it's computed from finish_reason.

After, the only remaining schema differences are benign and not lost information:

  • rewards/metrics naming: v0 names its aggregate rewards.reward + metrics.lcs_reward_func; v1 names it rewards.lcs. The value is preserved and trace.reward resolves identically — an env-level naming difference the bridge shouldn't rewrite.
  • message.content str vs null|str: sampling artifact; the field is nullable in both.

Verification

Captured real v0 RolloutOutputs through the legacy path against a live vLLM server and diffed the resulting traces against native v1 reverse-text rollouts: prompt messages, response content/reasoning, finish_reason, usage, tokens (turn + response), and task.system_prompt/instruction/answer all now present and matching. finish_reason="length" correctly drives the computed is_truncated.


Note

Medium Risk
Changes only the legacy adapter path but affect training-visible trace shape (tokens, finish_reason, task fields); incorrect mapping could still skew truncation or RL reads if edge cases remain.

Overview
The v0→v1 legacy bridge in rollout_output_to_trace was dropping most trace metadata because mapping helpers only accepted plain dicts while in-process v0 rollouts use Pydantic models, and finish_reason was read from the wrong level.

_as_dict() normalizes v0 objects via model_dump() before _to_v1_messages, _tool_calls, and _to_v1_response run, so prompts, assistant content/reasoning/tool calls, and usage populate correctly instead of being skipped.

_to_v1_response now resolves finish_reason from response.message (v0) with a fallback to the response root, restricts values to stop / length / tool_calls, and attaches the same turn tokens on response.tokens to match native v1 clients.

_to_wire_task replaces flattening the whole prompt into instruction: system → system_prompt, user text → instruction, and optional reference answer as a WireTask extra.

Together, bridged traces align with native v1 for training-critical fields; is_truncated can compute correctly once finish_reason="length" is preserved.

Reviewed by Cursor Bugbot for commit fdf82e8. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix rollout metadata preservation in the v0 to v1 legacy bridge

  • Adds _as_dict helper in legacy.py to coerce pydantic-like v0 objects to dicts before mapping, fixing dropped messages and tool calls.
  • _to_v1_response now populates Response.tokens and sets finish_reason only when it is one of stop, length, or tool_calls; otherwise None.
  • rollout_output_to_trace now sets system_prompt from the first system message, builds instruction from user messages only, and carries answer in task extras.
  • Response.tokens is now populated per turn by passing computed TurnTokens through _to_v1_response.

Macroscope summarized fdf82e8.

The bridge only kept token ids: it dropped the prompt messages, the response
message (content / reasoning / tool calls), finish_reason, usage, and the task's
system prompt / answer — so a v0-bridged Trace was a near-empty skeleton next to a
native v1 Trace. The cause: v0 RolloutOutput nests these as pydantic objects
(messages, Response) and records finish_reason on response.message, but the mapping
only handled plain dicts and read finish_reason off the response.

Coerce v0 objects to dicts before mapping (_as_dict), read finish_reason/usage from
their v0 locations, mirror tokens onto the response (as the native client does), and
carry the prompt's system_prompt / instruction / answer onto the task. A v0-bridged
Trace now matches the native v1 schema (verified by diffing reverse-text rollouts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mikasenghaas
mikasenghaas marked this pull request as ready for review June 9, 2026 18:34
@mikasenghaas
mikasenghaas merged commit b2a2238 into feat/nano-as-v1 Jun 9, 2026
3 checks passed
@macroscopeapp

macroscopeapp Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved

Bug fix to preserve more metadata (tokens, finish_reason, system_prompt, answer) during v0->v1 trace conversion in the legacy bridge. Changes are self-contained to the conversion layer and authored by the module's original creator.

You can customize Macroscope's approvability policy. Learn more.

pull Bot pushed a commit to Stars1233/verifiers that referenced this pull request Jun 23, 2026
…tellect-ai#1586)

The bridge only kept token ids: it dropped the prompt messages, the response
message (content / reasoning / tool calls), finish_reason, usage, and the task's
system prompt / answer — so a v0-bridged Trace was a near-empty skeleton next to a
native v1 Trace. The cause: v0 RolloutOutput nests these as pydantic objects
(messages, Response) and records finish_reason on response.message, but the mapping
only handled plain dicts and read finish_reason off the response.

Coerce v0 objects to dicts before mapping (_as_dict), read finish_reason/usage from
their v0 locations, mirror tokens onto the response (as the native client does), and
carry the prompt's system_prompt / instruction / answer onto the task. A v0-bridged
Trace now matches the native v1 schema (verified by diffing reverse-text rollouts).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant