Skip to content

feat: record per-call model call metadata on v1 traces - #2061

Merged
mikasenghaas merged 34 commits into
mainfrom
feat/per-call-trace-records
Jul 18, 2026
Merged

feat: record per-call model call metadata on v1 traces#2061
mikasenghaas merged 34 commits into
mainfrom
feat/per-call-trace-records

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Jul 17, 2026

Copy link
Copy Markdown
Member

Summary

Closes RES-1085: per-call trace records for platform integration.

  • New Trace.calls: list[ModelCall] — one lean typed record per real provider exchange, appended by the interception server on both the non-streaming and streaming (SSE relay) paths:
    • node: index of the assistant MessageNode this call committed (PendingTurn.commit now returns it) — the link into the message graph. The call's conversation is not repeated on the record: it is exactly the linked node's root-to-self path, so linkage holds even under branching (compaction forks, retokenized prefixes)
    • model: the model requested from the provider (equals agent.model by construction of the override; recorded per call because it's cheap and provable)
    • sampling: the call's effective settings, translated by the dialect (Dialect.parse_sampling, next to parse_request/parse_response): a per-dialect whitelist (sampling_fields) of what shapes generation — decoding knobs, budgets/stops, reasoning effort, output contract — with native aliases mapped onto the canonical SamplingConfig knobs (max_output_tokensmax_tokens, reasoning.effortreasoning_effort) and dialect-specific keys (seed, tool_choice, response_format, parallel_tool_calls, …) riding as extras. Whitelisted, so payload / conversation state / tracking fields can never leak into the record by omission. Captures the eval-imposed knobs plus whatever the harness set that the eval left alone
    • endpoint: the provider path (/chat/completions / /responses / /v1/messages), i.e. which wire dialect the exchange spoke
    • finish_reason + usage: exchange attributes, moved here from MessageNode exclusively. Trace.is_truncated reads the last successful call; Trace.usage aggregates over calls; branches keep their token accounting (Trace.branches attaches each branch's calls in path order, Branch.usage/last_usage/num_* read those), so the trace-level num_*_tokens prime-rl consumes and the platform push payload are unchanged; the v0 bridge records a per-step call
    • error: a failed exchange is recorded too — the error coupled to the call that raised it, with a real traceback for non-provider failures (cancellations included) and the upstream HTTP status on Error.status_code (also populated on rollout-level Trace.errors now)
    • time: wall-clock TimeSpan of the exchange (node timestamps can't provide this: they are all stamped at commit)
  • A transient failure the harness SDK retries leaves both records: the errored exchange (error set, no node) and the successful retry (linked to its node). Deliberately not recorded: retries that replay or coalesce onto an earlier attempt (no provider exchange happened), aux routes (count_tokens), and judge calls (outside the agent's graph).
  • Raw request/response bodies are deliberately not retained: they grew traces quadratically in turns (83% of a 10-turn tb2 trace was request bodies restating the conversation), and the conversation is already on the graph. With the typed records, calls is linear at ~250 B/call — the same 10-turn trace went 146 KB → 31 KB (calls 121 KB → 2.5 KB, 8%).

Example

Trace.calls from a 10-turn terminal-bench-2 fix-git rollout (first two and last call shown):

[
  {
    "node": 2,
    "model": "deepseek/deepseek-v4-flash",
    "sampling": { "temperature": 0.7, "parallel_tool_calls": false },
    "endpoint": "/chat/completions",
    "finish_reason": "tool_calls",
    "time": { "start": 1784337093.274, "end": 1784337101.452 }
  },
  {
    "node": 4,
    "model": "deepseek/deepseek-v4-flash",
    "sampling": { "temperature": 0.7, "parallel_tool_calls": false },
    "endpoint": "/chat/completions",
    "finish_reason": "tool_calls",
    "time": { "start": 1784337128.306, "end": 1784337132.204 }
  },
  {
    "node": 20,
    "model": "deepseek/deepseek-v4-flash",
    "sampling": { "temperature": 0.7, "parallel_tool_calls": false },
    "endpoint": "/chat/completions",
    "finish_reason": "tool_calls",
    "time": { "start": 1784337382.416, "end": 1784337401.394 }
  }
]

temperature is the eval-injected knob; parallel_tool_calls is rlm's own request field the eval never touched — the per-call sampling captures both.

Verification

Linkage checked per call: successful calls point at a sampled assistant node, every sampled node is covered by exactly one call, finish_reason matches the committed node, model/sampling present.

  • gsm8k-v1 (default harness, subprocess, PI deepseek/deepseek-v4-flash): reward 1.0, linkage OK.
  • gsm8k-v1 with the codex harness (streaming: Responses dialect over the SSE relay path): reward 1.0, node link correct.
  • terminal-bench-2 fix-git (rlm harness, prime VM runtime): 10-turn run — 10 calls, linkage OK, trace 31 KB with calls at 2.5 KB (8%); earlier full runs: no-compaction (16 turns, reward 1.0) and summarize_at_tokens=3000 (40 calls, 87 nodes, 7 branches from compaction forks) — every call links to the right node on its branch.
  • Transient-error path checked in-process (flaky client failing once with a 502, then succeeding on the SDK-style retry): both calls recorded — the failure with error (status_code=502) and node=None; the retry linked to its committed node. Non-provider failures record a real traceback (formatted from the exception object — the record is written in a finally, where ambient exception state is gone).
  • pytest tests/v1 -m "not e2e" green; test_single_turn (e2e) extended with per-call linkage assertions.

Notes

  • OverlongPromptError.status_code now defaults to 400 (the status the interception server surfaces for overlong prompts) instead of inheriting the 502; model_error keeps the provider's real status when the failure carried one.

  • MessageNode.finish_reason stays on the node although it is conceptually per-call (now also on ModelCall): removing it would make previously persisted traces unreadable under strict validation — a trace-version-bump follow-up if wanted. Same reasoning keeps usage on the node (it anchors per-branch token accounting and the v0 bridge).

  • prime-rl consumes the new field with its next verifiers re-pin; the calls field itself is additive.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core interception and trace schema (v2 bump) on every model turn; behavior is additive for consumers but changes where usage/truncation are sourced and how errors are recorded.

Overview
Adds Trace.calls — one ModelCall per real provider exchange (non-stream and SSE relay), bumped to TRACE_VERSION = 2. Each record links to the committed assistant node via node, captures model, dialect-whitelisted sampling (parse_sampling on chat/Anthropic/Responses), endpoint, finish_reason, usage, wall-clock time, and coupled error (with status_code on trace errors). The interception server appends calls in record_call from a finally on both paths; PendingTurn.commit now returns the assistant node id.

finish_reason and usage are no longer written on new MessageNode commits; Trace/Branch token accounting, is_truncated, and branch calls all read from calls instead. Dialects gain sampling_fields whitelists and format-specific alias normalization (e.g. max_completion_tokens, Anthropic output_config.effort, Responses reasoning.effort). Anthropic responses use widened ModdedAnthropicMessage for gateway service_tier; OverlongPromptError defaults to 400 with provider status preserved via model_error. The v0 bridge synthesizes minimal ModelCall rows per trajectory step. E2E test_single_turn asserts call↔sampled-node linkage.

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

Note

Summary

Closes RES-1085: per-call trace records for platform integration.

  • New Trace.calls: list[ModelCall] — one lean typed record per real provider exchange, appended by the interception server on both the non-streaming and streaming (SSE relay) paths:
    • node: index of the assistant MessageNode this call committed (PendingTurn.commit now returns it) — the link into the message graph. The call's conversation is not repeated on the record: it is exactly the linked node's root-to-self path, so linkage holds even under branching (compaction forks, retokenized prefixes)
    • model: the model requested from the provider (equals agent.model by construction of the override; recorded per call because it's cheap and provable)
    • sampling: the call's effective settings, translated by the dialect (Dialect.parse_sampling, next to parse_request/parse_response): a per-dialect whitelist (sampling_fields) of what shapes generation — decoding knobs, budgets/stops, reasoning effort, output contract — with native aliases mapped onto the canonical SamplingConfig knobs (max_output_tokensmax_tokens, reasoning.effortreasoning_effort) and dialect-specific keys (seed, tool_choice, response_format, parallel_tool_calls, …) riding as extras. Whitelisted, so payload / conversation state / tracking fields can never leak into the record by omission. Captures the eval-imposed knobs plus whatever the harness set that the eval left alone
    • endpoint: the provider path (/chat/completions / /responses / /v1/messages), i.e. which wire dialect the exchange spoke
    • finish_reason + usage: exchange attributes, moved here from MessageNode exclusively. Trace.is_truncated reads the last successful call; Trace.usage aggregates over calls; branches keep their token accounting (Trace.branches attaches each branch's calls in path order, Branch.usage/last_usage/num_* read those), so the trace-level num_*_tokens prime-rl consumes and the platform push payload are unchanged; the v0 bridge records a per-step call
    • error: a failed exchange is recorded too — the error coupled to the call that raised it, with a real traceback for non-provider failures (cancellations included) and the upstream HTTP status on Error.status_code (also populated on rollout-level Trace.errors now)
    • time: wall-clock TimeSpan of the exchange (node timestamps can't provide this: they are all stamped at commit)
  • A transient failure the harness SDK retries leaves both records: the errored exchange (error set, no node) and the successful retry (linked to its node). Deliberately not recorded: retries that replay or coalesce onto an earlier attempt (no provider exchange happened), aux routes (count_tokens), and judge calls (outside the agent's graph).
  • Raw request/response bodies are deliberately not retained: they grew traces quadratically in turns (83% of a 10-turn tb2 trace was request bodies restating the conversation), and the conversation is already on the graph. With the typed records, calls is linear at ~250 B/call — the same 10-turn trace went 146 KB → 31 KB (calls 121 KB → 2.5 KB, 8%).

Example

Trace.calls from a 10-turn terminal-bench-2 fix-git rollout (first two and last call shown):

[
  {
    "node": 2,
    "model": "deepseek/deepseek-v4-flash",
    "sampling": { "temperature": 0.7, "parallel_tool_calls": false },
    "endpoint": "/chat/completions",
    "finish_reason": "tool_calls",
    "time": { "start": 1784337093.274, "end": 1784337101.452 }
  },
  {
    "node": 4,
    "model": "deepseek/deepseek-v4-flash",
    "sampling": { "temperature": 0.7, "parallel_tool_calls": false },
    "endpoint": "/chat/completions",
    "finish_reason": "tool_calls",
    "time": { "start": 1784337128.306, "end": 1784337132.204 }
  },
  {
    "node": 20,
    "model": "deepseek/deepseek-v4-flash",
    "sampling": { "temperature": 0.7, "parallel_tool_calls": false },
    "endpoint": "/chat/completions",
    "finish_reason": "tool_calls",
    "time": { "start": 1784337382.416, "end": 1784337401.394 }
  }
]

temperature is the eval-injected knob; parallel_tool_calls is rlm's own request field the eval never touched — the per-call sampling captures both.

Verification

Linkage checked per call: successful calls point at a sampled assistant node, every sampled node is covered by exactly one call, finish_reason matches the committed node, model/sampling present.

  • gsm8k-v1 (default harness, subprocess, PI deepseek/deepseek-v4-flash): reward 1.0, linkage OK.
  • gsm8k-v1 with the codex harness (streaming: Responses dialect over the SSE relay path): reward 1.0, node link correct.
  • terminal-bench-2 fix-git (rlm harness, prime VM runtime): 10-turn run — 10 calls, linkage OK, trace 31 KB with calls at 2.5 KB (8%); earlier full runs: no-compaction (16 turns, reward 1.0) and summarize_at_tokens=3000 (40 calls, 87 nodes, 7 branches from compaction forks) — every call links to the right node on its branch.
  • Transient-error path checked in-process (flaky client failing once with a 502, then succeeding on the SDK-style retry): both calls recorded — the failure with error (status_code=502) and node=None; the retry linked to its committed node. Non-provider failures record a real traceback (formatted from the exception object — the record is written in a finally, where ambient exception state is gone).
  • pytest tests/v1 -m "not e2e" green; test_single_turn (e2e) extended with per-call linkage assertions.

Notes

  • OverlongPromptError.status_code now defaults to 400 (the status the interception server surfaces for overlong prompts) instead of inheriting the 502; model_error keeps the provider's real status when the failure carried one.

  • MessageNode.finish_reason stays on the node although it is conceptually per-call (now also on ModelCall): removing it would make previously persisted traces unreadable under strict validation — a trace-version-bump follow-up if wanted. Same reasoning keeps usage on the node (it anchors per-branch token accounting and the v0 bridge).

  • prime-rl consumes the new field with its next verifiers re-pin; the calls field itself is additive.

🤖 Generated with Claude Code


[!NOTE]
Medium Risk
Touches core interception and trace schema (v2 bump) on every model turn; behavior is additive for consumers but changes where usage/truncation are sourced and how errors are recorded.

Overview
Adds Trace.calls — one ModelCall per real provider exchange (non-stream and SSE relay), bumped to TRACE_VERSION = 2. Each record links to the committed assistant node via node, captures model, dialect-whitelisted sampling (parse_sampling on chat/Anthropic/Responses), endpoint, finish_reason, usage, wall-clock time, and coupled error (with status_code on trace errors). The interception server appends calls in record_call from a finally on both paths; PendingTurn.commit now returns the assistant node id.

finish_reason and usage are no longer written on new MessageNode commits; Trace/Branch token accounting, is_truncated, and branch calls all read from calls instead. Dialects gain sampling_fields whitelists and format-specific alias normalization (e.g. max_completion_tokens, Anthropic output_config.effort, Responses reasoning.effort). Anthropic responses use widened ModdedAnthropicMessage for gateway service_tier; OverlongPromptError defaults to 400 with provider status preserved via model_error. The v0 bridge synthesizes minimal ModelCall rows per trajectory step. E2E test_single_turn asserts call↔sampled-node linkage.

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

Changes since #2061 opened

  • Moved HTTP status code tracking from ModelCall.status field to Error.status_code field in trace recording [5062742]
  • Updated Trace section in v1 overview documentation to specify inclusion of per-call ModelCall records [cf18f49]
  • Added inspection checklist item for per-call calls records metadata [a02c7f5]
  • Changed the type annotation of the sampling field in the ModelCall model from SamplingConfig | None to Sampling | None within the verifiers.v1.trace module, including adding the corresponding Sampling import from verifiers.v1.types [ce55ead]
  • Removed inline documentation from the calls field in the Branch model within the verifiers.v1.trace module [ce55ead]

Every provider exchange behind a sampled turn lands on Trace.calls as an
untyped ModelCall: the request body as actually sent upstream (model +
sampling overrides applied), the native response object, provider response
headers, wall-clock span, time-to-first-token for streamed turns, and — for
a failed exchange — the error, coupled to the call that raised it.

Each successful call links into the message graph via the id of the
assistant node it committed (PendingTurn.commit now returns it), so per-call
data joins the graph exactly even under branching (compaction, retokenized
prefixes). Replayed/coalesced SDK retries record nothing — they are not
provider exchanges.

Closes RES-1085.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py
Comment thread verifiers/v1/interception/server.py Outdated
time_to_first_token was only measurable on the streamed relay path; drop it
until there's a consumer. The call's wire format is now a typed DialectName
literal (chat/responses/anthropic, exported by v1.dialects) instead of the
provider endpoint path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py
Two per-call recording gaps from review: a provider stream dying mid-relay
propagated out of the pump loop without landing on Trace.calls, and
apply_overrides ran outside the guarded try, so a malformed native field
escaped as an unshaped 500 with no record. Both now record the failed
exchange (request=None when the overrides themselves failed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The endpoint path already identifies the format; drop DialectName and
Dialect.name in favor of recording upstream_path on the call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas marked this pull request as ready for review July 17, 2026 20:28
Comment thread verifiers/v1/interception/server.py Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new tracing feature with a schema version bump (1→2) and restructures where usage/finish_reason data is stored (from nodes to calls). The schema change and structural reorganization warrant human review to verify compatibility.

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

A completed provider exchange whose turn commit raises was dropped from
Trace.calls on the non-streaming path (the streaming path already recorded
it). Record it with the response and the failure before propagating,
matching the stream path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py Outdated
A stream failure record now carries what the exchange already produced:
provider headers on a mid-relay death, plus the assembled native payload
when only the commit failed — matching the non-streaming path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py
Wrap each path's whole exchange (overrides -> call -> commit) in a single
try/finally whose finally appends the one ModelCall, instead of recording
at every failure site. Error handlers just stash the error; a harness
disconnect mid-stream now also records (the exchange happened), and a
post-commit delivery failure keeps the success record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py Outdated
Comment thread verifiers/v1/interception/server.py
Comment thread verifiers/v1/trace.py
Comment thread verifiers/v1/interception/server.py
Review follow-ups from the single-record refactor: tracebacks are formatted
from the exception object (the finally runs after handlers exit, where
format_exc sees nothing), a cancelled exchange is recorded with its
CancelledError instead of as a phantom error-free call, and the streaming
span now starts after prepare_turn like the non-streaming one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py

@kennethnym kennethnym left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ModelCall is really useful, i have some thoughts below.

some more thoughts:

  • model id is stored per rollout under Trace.agent.model. if in the future we support model switching on the fly, then the model id will have to move down a level (probably under ModelCall)
  • maybe useful to have ModelCall.id as well, pulled from provider's response?

Comment thread verifiers/v1/trace.py Outdated
Comment thread verifiers/v1/trace.py Outdated
Comment thread verifiers/v1/trace.py Outdated
Comment thread verifiers/v1/trace.py Outdated
Comment thread verifiers/v1/trace.py
Comment thread verifiers/v1/clients/eval.py Outdated
Review asks: a failed call records the HTTP status it surfaced
(ModelCall.status) and keeps the provider response headers when the failure
carried an HTTP response — ProviderError now carries them from the eval
client's three raise sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/errors.py Outdated
OverlongPromptError inherited ProviderError's 502 default, which record_call
then stamped onto context-length records. Default it to the 400 the
interception server surfaces, and let model_error keep the provider's real
status when the failure carried one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/dialects/anthropic.py
Comment thread verifiers/v1/dialects/responses.py Outdated
mikasenghaas and others added 3 commits July 18, 2026 01:28
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anthropic lifts output_config.effort onto the typed knob (where
apply_overrides puts the eval's reasoning effort), and Responses keeps
non-effort reasoning keys (e.g. summary) instead of dropping them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/interception/server.py Outdated
mikasenghaas and others added 2 commits July 18, 2026 01:34
No consumer reads MessageNode.finish_reason (prime-rl and
research-environments have none; is_truncated was the only reader) and it is
an exchange attribute, so it moves to ModelCall exclusively. is_truncated
reads the last successful call; the v0 bridge records a minimal per-step
call to keep it working. TRACE_VERSION bumps to 2 — nodes of previously
persisted traces no longer validate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/graph.py
Comment thread verifiers/v1/graph.py
Provider usage is an exchange attribute: it moves to ModelCall exclusively
(the v0 bridge records it on its per-step call). Branches keep their token
accounting — Trace.branches attaches each branch's calls in path order, and
Branch.usage/last_usage read those — so trace/branch num_* properties and
the platform push payload are unchanged. Joins the v2 trace schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/dialects/chat.py
mikasenghaas and others added 2 commits July 18, 2026 01:47
Both max_tokens and max_completion_tokens can ride one wire request (an
eval override on top of a harness's alias); the record keeps the canonical
knob only, override winning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread tests/v1/test_dialects.py Outdated
mikasenghaas and others added 5 commits July 18, 2026 01:53
Widen Usage.service_tier to a plain string (the ModdedChatCompletion
pattern) instead of stripping unknown tiers before validation, so the new
dialect tests' provisioned case round-trips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The status only exists for failures, so it belongs on the failure record:
Error gains status_code (populated on per-call records and rollout-level
capture_error alike) and ModelCall.status goes away. No 502 fallback — a
non-HTTP failure (commit error, cancellation) simply has none.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cf18f49. Configure here.

Comment thread docs/v1/overview.md
mikasenghaas and others added 2 commits July 18, 2026 02:04
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas merged commit 01a5b7e into main Jul 18, 2026
12 checks passed
mikasenghaas added a commit to PrimeIntellect-ai/prime-rl that referenced this pull request Jul 18, 2026
…plit (#3082)

* feat: re-pin verifiers for per-call trace records

Companion to PrimeIntellect-ai/verifiers#2061: Trace.calls per-call records,
with finish_reason and usage moved off MessageNode onto ModelCall (trace
schema v2). test_advantage's rollout builder attaches usage via a per-call
record instead of the removed node field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump verifiers pin to branch head

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump verifiers pin to the merged #2061 commit

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: export generation model/harness time split to wandb

Bumps the verifiers pin to d5320edcb (per-call ModelCall records #2061 +
model/harness generation-time split #2060). TimingMetrics gains
generation/model and generation/harness (from timing.generation.{model,
harness}.duration, stamped server-side by Rollout.split_generation and
carried on the wire), emitted as timing/generation/{model,harness}/*.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas added a commit that referenced this pull request Jul 29, 2026
One to_v<n> util per historical schema bump, chained by the migrate
hook: v1->v2 lifts node-level usage/finish_reason into synthesized
ModelCalls (#2061), v2->v3 nests the flat agent identity and top-level
runtime into AgentInfo (#2106), v3->v4 wraps float rewards as
Reward(score, weight=1) preserving reward sums (#2119), v4->v5 as
before. Steps copy what they mutate, so validating the same dict twice
is stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas added a commit that referenced this pull request Jul 30, 2026
* fix: pin exclude-newer-package cutoffs as UTC timestamps

Bare dates resolve to midnight in the machine's local timezone, so every
timezone relocks uv.lock with different exclude-newer timestamps and the
--locked pre-commit hooks fail for anyone outside the tz that produced
the lock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: align trace mutators on the record_* verb

stamp -> record_run, capture_error -> record_error, matching the
existing record_metric/record_reward/record_judge family and the trace's
own 'record' vocabulary (to_record, record schema). Also drop the
override warnings on record_metric/record_reward: overriding is defined
behavior, last write wins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: rename trace dump exclusions to EXCLUDE_FIELDS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: an expired agent timeout is an agent error, not a truncation

The rollout deadline expiring now records a HarnessError (ok=False, no
scoring) instead of the clean harness_timeout stop that scored the
partial trajectory: a timeout is the agent breaking its time budget,
not a healthy run cut short. The stop-condition name is gone from the
vocabulary; the legacy v0 bridge's timeout_reached maps through the
generic truncation fallback.

The concept is renamed harness timeout -> agent timeout throughout:
TaskTimeout.harness -> TaskTimeout.agent, the rollout plumbing, and
cap_remote_harness_timeout -> cap_remote_agent_timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: slim the Trace surface

Drop the agent_name/trainable/runtime passthroughs (read trace.agent
directly) and the duplicate error property (last_error is the one
reader); align tool_messages with assistant_messages (nodes-based,
branch-independent); require an explicit stop condition (the 'done'
default was never used); correct the stop_condition docstring to the
real vocabulary; tighten field docstrings and ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: require agent and verifiers on Trace, default tools to empty

agent: every producer sets a seat — the rollout its resolved config, the
debug CLI its synthetic seat, and now the v0 bridge and validate CLI
theirs — so the None guards at every consumer were dead weight.
verifiers: stamped by default_factory at construction; stored records
keep their serialized build. tools: the empty state was unreachable
(dialects normalize [] to None to avoid clearing a recording), so None
carried no signal over []. Bumps TRACE_VERSION to 5: old records
without an agent no longer validate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: hoist TRACE_VERSION to the module top

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: tighten trace docstrings, require RunInfo.id

Every consumer stamps a run id (the eval CLI its uuid, trainers their
own), so the None default was unreachable. Docstrings across the trace
models trimmed to the constraint they actually add; kept_tokens aligned
with routed_experts' shape-first style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: last trace.runtime accesses missed in the property removal

Agent.interaction's two borrowed-runtime stamps and the dashboard's
boot-vs-build stage probe still read the removed Trace.runtime
passthrough; all three now read trace.agent.runtime. Verified live:
eval with the rich dashboard renders and pushes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: migrate pre-v5 trace records on read

A version-gated before-validator upgrades v4 records to the v5 shape
(drop the explicit nulls v5 defaults now fill, seat records that
predate the required agent, drop an id-less run stamp) so eval resume
and replay keep reading old outputs. Current-version records validate
strictly: a v5 record with tools=null is rejected. Delete the validator
when v4 support is dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: restructure trace migration as chained per-version utils

The migrate hook sits at the bottom of Trace and chains one _to_v<n>
util per schema bump; the next bump adds _to_v6 and a new chain link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: retroactive trace migrations back to v1

One to_v<n> util per historical schema bump, chained by the migrate
hook: v1->v2 lifts node-level usage/finish_reason into synthesized
ModelCalls (#2061), v2->v3 nests the flat agent identity and top-level
runtime into AgentInfo (#2106), v3->v4 wraps float rewards as
Reward(score, weight=1) preserving reward sums (#2119), v4->v5 as
before. Steps copy what they mutate, so validating the same dict twice
is stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: drop trace record migrations, reset TRACE_VERSION to 1

The tightened schema restarts the version counter; pre-existing records
are not loadable and old eval runs cannot be resumed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: migrate the standalone agent example and env docs off removed trace APIs

* chore: drop the seat term from new comments, restore trimmed timing docstrings

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
eligotts added a commit that referenced this pull request Aug 6, 2026
Main already records these on ModelCall (#2061). They were reintroduced
by merge conflict resolution and are unused on the node.

Co-authored-by: Cursor <cursoragent@cursor.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.

4 participants