feat(v1)!: move run info from trace to episode - #2244
Merged
Conversation
Trace.run says which run a trace belongs to, but the episode is the thing that gets dispatched — and an episode that produced no traces has nowhere to say it at all. Episode gains the same consumer-stamped RunInfo, plus a record_run that stamps it and every trace it carries in one step, so the two cannot drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trace.run said which run a trace belonged to, but the episode is what a consumer dispatches — and an episode that produced no traces had nowhere to record it at all. The field and Trace.record_run move onto Episode, whose record_run sets the run and passes any extra info down to the traces, so a consumer stamps once instead of walking them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
record_run's extra metadata described the episode but was being copied onto each of its traces. It has a home now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
append_trace wraps a trace in an episode of its own, so the row on disk was not the record the caller got back: it carried no run, and a different env. Mint once, stamp, persist that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
marked this pull request as ready for review
August 4, 2026 17:25
Contributor
ApprovabilityVerdict: Needs human review This is an explicitly marked breaking change ( You can customize Macroscope's approvability policy. Learn more. |
hallerite
approved these changes
Aug 4, 2026
mikasenghaas
added a commit
that referenced
this pull request
Aug 4, 2026
They were written when the run was a field on Trace. #2244 moved the field to Episode; the types stayed behind, and Trace has not referred to one since. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
added a commit
that referenced
this pull request
Aug 6, 2026
This reverts commit f14b41c. Run identity returns to Trace.run / Trace.record_run. The episode-level home turned out to push consumers that keep per-trace records into episode reconstruction for no gain. Co-authored-by: Claude Fable 5 <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
Trace.runrecorded which run (eval or train) a trace belonged to. But the episode is what a consumer actually dispatches, and an episode that produced no traces — cancelled, or a task that raised before reaching the env — had nowhere to record its run at all.So the run moves up:
Episode.run: RunInfo | None, consumer-stamped, on the unit that was dispatched.Episode.record_run(run, **info)sets the run and passes any extrainfodown to each trace, so a consumer stamps once instead of walking them.Trace.runandTrace.record_runare removed.The field stays optional and unstamped by the library — verifiers doesn't own the notion of a run, consumers do. vf's own eval CLI now stamps at the episode level in all three places it used to loop over traces.
Breaking
Trace.runis gone — readEpisode.run. Traces persisted with arunfield still load (records parse non-strict), but the value is dropped.Trace.record_runis gone — callEpisode.record_run, which also forwards**infoto the traces. A consumer holding only loose traces can settrace.infodirectly.trace.info.Verification
uv run pytest tests/v1 -m "not e2e"green.ruff check/ruff format --check;tyreports nothing on the touched files.🤖 Generated with Claude Code
Note
Move run metadata from
TracetoEpisodein eval runnerrun: RunInfo | Noneandinfo: dictfields toEpisode, with arecord_run()method to set them, mirroring the API previously onTrace.run_evalandrun_eval_serverin runner.py to callepisode.record_run()once per episode instead of stamping each trace individually.runfield andrecord_run()method fromTraceentirely.Traceserialization no longer includes arunfield; any consumers reading run metadata from traces will need to read it from the parentEpisodeinstead.Macroscope summarized 1336d48.
Note
Medium Risk
Schema and API break for anything reading
Trace.run; persisted traces may drop the oldrunfield on load, but eval output shape stays episode-based.Overview
Breaking:
Trace.runandTrace.record_runare removed. Consumers should useEpisode.runandEpisode.record_run, so trace-less episodes (cancelled or failed before agents run) can still record which eval/train run they belonged to.Episodegains optionalrun, episode-levelinfo, andrecord_run(run, **info)that sets the run and merges metadata on the episode once instead of on every trace.The v1 eval runner stamps
EvalRunInfoon the completed episode in all paths (localon_complete, env-serverrun_unit, and legacyrun_groupwhere each trace is wrapped withEpisode.ofthenappend_episodeinstead of per-traceappend_trace).Reviewed by Cursor Bugbot for commit 1336d48. Bugbot is set up for automated code reviews on this repo. Configure here.