Skip to content

feat: store raw score and weight on trace rewards - #2119

Merged
mikasenghaas merged 2 commits into
mainfrom
feat/trace-reward-score-weight
Jul 23, 2026
Merged

feat: store raw score and weight on trace rewards#2119
mikasenghaas merged 2 commits into
mainfrom
feat/trace-reward-score-weight

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

  • Trace.rewards values are now a Reward model carrying the raw score and its weight, instead of the pre-multiplied weight * score float — records keep raw scores readable and the weighted sum stays a derived view (Reward.value = score * weight, Trace.reward = sum of values).
  • record_reward stores the pair; TRACE_VERSION bumped 3 → 4; Reward exported from verifiers.v1.
  • Platform uploads flatten sub-reward keys and aggregate per-name means over the raw score (matching v0's per-function outputs); the headline reward / avg_reward remain the weighted sum.
  • The agentic-judge env's task_weight rescale now scales each reward's weight in place of mutating the product, so the solver's raw task scores stay visible on the trace.
  • Dashboard breakdown shows raw-score means; legacy bridge and the proposer-solver taskset read the new shape; e2e/judge test assertions updated.

Breaking

  • Trace record schema: rewards changes from dict[str, float] to dict[str, {score, weight}]; TRACE_VERSION 3 → 4. Old v3 records no longer parse through Trace/WireTrace, so --resume/replay of runs written before this change fails on the rewards field (no coercion shim by design). Migration: re-run the eval, or rewrite old records' reward values as {"score": <value>, "weight": 1.0}.
  • Consumers reading trace.rewards values as floats must switch to .score (raw) or .value (weighted contribution).

Verification

Ran uv run eval reverse_text_v1 --no-push before and after:

  • Before: rewards: {"lcs": 1.0} (version 3)
  • After: rewards: {"lcs": {"score": 1.0, "weight": 1.0}} (version 4), headline reward=1.000 unchanged; WireTrace.model_validate round-trips the new record.

pytest tests/v1 -m "not e2e", ruff, and pre-commit all pass.

🤖 Generated with Claude Code


Note

Medium Risk
Breaking trace schema and any code treating trace.rewards values as floats; headline reward/avg_reward behavior is preserved but resume/replay of v3 records fails without migration.

Overview
Trace rewards now persist as Reward objects (score + weight) instead of pre-weighted floats. Trace.reward is derived as the sum of score * weight; record_reward writes the pair unchanged. TRACE_VERSION bumps 3 → 4 — serialized traces with the old dict[str, float] shape no longer validate without migration.

Downstream readers are updated to use .score (or .weight where composition matters): eval dashboard reward breakdowns, platform push flattening and per-name means, legacy v0 bridge, proposer-solver solve rate, and agentic-judge task_weight (scales each reward’s weight in place instead of multiplying stored values). Tests assert the new shape, including separate raw score vs weight when env score weights apply.

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

Note

Store raw score and weight on trace rewards as Reward objects

  • Introduces a Reward model in verifiers/v1/trace.py with score, weight, and a computed value (score * weight) field, replacing the previous dict[str, float] type for Trace.rewards.
  • Bumps TRACE_VERSION from 3 to 4 to reflect the schema change.
  • Updates all call sites (push.py, legacy.py, agentic_judge/env.py, dashboard eval.py, and proposer_solver_v1) to access .score or .weight rather than treating reward values as raw floats.
  • Exports Reward from the verifiers.v1 package.
  • Behavioral Change: Any code reading trace.rewards[name] as a float will break; callers must now use .score, .weight, or .value.

Macroscope summarized 18ea5a1.

Trace.rewards entries are now Reward(score, weight) instead of the
pre-multiplied product, so records keep raw scores readable and the
weighted sum stays a derived view (trace.reward). Bumps TRACE_VERSION
to 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas requested a review from hallerite July 23, 2026 22:06
@macroscopeapp

macroscopeapp Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new Reward data model and changes the trace schema (version 3→4), replacing dict[str, float] with dict[str, Reward]. Breaking schema changes and new data models warrant human review to validate migration impact.

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

@mikasenghaas
mikasenghaas requested review from eligotts and xeophon July 23, 2026 22:06
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/trace.py


TRACE_VERSION = 3
TRACE_VERSION = 4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lol since when do we have this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i will reset this to 1 and only use it once we have trace api lol

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

but eventually it will be important and we have to think abt versioning

@mikasenghaas
mikasenghaas merged commit de5ffa2 into main Jul 23, 2026
11 of 12 checks passed
@hallerite
hallerite deleted the feat/trace-reward-score-weight branch July 23, 2026 22:31
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 Jul 31, 2026
…fload

Picks up 35 main commits: the v1 structure cleanup (#2146) that retires
StrictBaseModel and renames _NODE_DUMP_EXCLUDE -> EXCLUDE_FIELDS, the
ruff 0.16 / ty tooling bump (#2147, #2148), runtime-on-agent + agent
config stamped on the trace (#2106), Reward score/weight records (#2119),
MCP tools for Codex (#2140), and assorted v1 fixes.

Resolutions:
- trace.py: main's restructure subsumes this branch's block wholesale —
  EXCLUDE_FIELDS already carries multi_modal_data, and TRACE_VERSION=1 is
  main's deliberate reset (this branch never touched it). Took main.
- graph.py: kept the raw-mm sidecar validators and
  previous_multi_modal_data; adopted main's plain BaseModel now that
  StrictBaseModel is gone.
- clients/train.py: kept the is_multimodal import — still used by the
  bridge path that threads previous_multi_modal_data.

Ruff 0.16 flagged two spots this branch added that main's cleanup pass
never saw: a constant getattr in the ingress offload walker, and the
blind except at the prepare_request_body boundary (annotated noqa, per
main's own convention at rollout boundaries).
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.

2 participants