feat(v1): episode-level derived aggregates and typed EnvInfo - #2187
Merged
Conversation
Episode gains derived, non-serialized aggregates mirroring Trace's property pattern (usage, num_input/output/total_tokens, num_turns), env becomes a typed EnvInfo carrying the env id, error is renamed to last_error to match Trace, and Trace/Episode relax from StrictBaseModel to plain BaseModel so additive schema evolution no longer breaks older readers. The dashboard's duplicated _tokens helper is replaced by the Trace properties it re-implemented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
ApprovabilityVerdict: Needs human review This PR changes the serialization schema ( You can customize Macroscope's approvability policy. Learn more. |
Extras are ignored everywhere instead of rejected: the whole record tree (messages, usage, task data, timing, calls, nodes, judge/rubric types) tolerates additive schema evolution. WireTaskData keeps extra="allow" to round-trip unknown task fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Timing.generation -> Timing.agent (AgentSpan, still split into model and harness), Trace.split_generation -> split_agent_time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 515b820. Configure here.
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>
hallerite
previously approved these changes
Jul 31, 2026
Migrates #2144's new records (Artifact, CollectHook) off the removed StrictBaseModel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hallerite
approved these changes
Jul 31, 2026
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
Episodegains derived, non-serialized aggregates mirroringTrace's property pattern:usage(agent-call usage summed viaUsage.aggregate; judge/off-graph usage stays ontrace.extra_usage),num_input_tokens/num_output_tokens/num_total_tokens,num_turns, andby_agent(traces grouped by agent name, e.g. n solvers). Consumers holding a validatedWireEpisode(the serve wire, prime-rl) get them for free — properties stay off the wire and disk.Episode.envis now a typedEnvInfocarrying the env id, instead of a bare string.Episode.erroris renamed tolast_error, matchingTrace.last_error; short docstrings added to all Episode fields and properties.StrictBaseModelis removed: the whole record tree (Trace,Episode, messages, usage, task data, timing, calls, nodes, judge/rubric types) parses non-strict — extras are ignored instead of rejected, so additive schema evolution no longer breaks older readers.WireTaskDatakeepsextra="allow"to round-trip unknown task fields.The dashboard's
_tokenshelper is deleted in favor of theTraceproperties it re-implemented (num_input_tokens/num_output_tokens/num_branches/usage).uv.locksyncs the per-packageexclude-newercutoffs to the UTC timestamps pyproject pinned in chore: timezone-stable uv.lock and trace record cleanups #2172 (the lock was never re-locked, so everyuv run --lockedhook failed).The
Timing.generationspan is renamedTiming.agent(GenerationSpan→AgentSpan, still split intomodelandharnesstime), andTrace.split_generationbecomessplit_agent_time.Breaking
Timing.generation→Timing.agent(GenerationSpan→AgentSpan,Trace.split_generation→split_agent_time): persisted traces carrytiming.agent; older records withtiming.generationlose that span on re-read (extras are ignored).Episode.envisEnvInfoinstead ofstr: episodes persist as"env": {"id": ...}; existingtraces.jsonlfiles with stringenvno longer parse for resume/replay. Readepisode.env.idfor the id;Episode.of(trace, env=...)still takes the id string.Episode.error→Episode.last_error.vf.StrictBaseModelis gone (removed from the public API); subclasspydantic.BaseModelinstead. No models reject unknown fields anymore: extras are ignored on validation (and dropped on rewrite paths like resume) rather than raising.Verification
uv run pytest tests/green;ruff,pre-commit run --all-files, and thetypre-push hook pass (re-run after the non-strict sweep).uv run eval reverse_text_v1 -n 2 --richrenders the usage/time rows and per-trace tokens correctly, pushes traces, and the persisted episode round-trips throughread_episodeswithenv.idand working aggregates.🤖 Generated with Claude Code
Note
High Risk
Persisted episodes and traces change shape (
envobject,timing.agent), and relaxing validation breaks strict readers and oldtraces.jsonlresume unless migrated; timing rename drops legacygenerationspans on re-read.Overview
Episode now stores
envasEnvInfo({"id": ...}on disk) instead of a plain string, exposeslast_error(waserror), and adds derived properties—usage, token/turn totals, andby_agent—summed from traces without extra wire fields.Trace timing renames
generation→agent(GenerationSpan→AgentSpan,split_generation→split_agent_time). Rollout, debug, legacy v0 mapping, and the eval dashboard follow the new span name and read token counts fromTraceproperties instead of a local_tokenshelper.StrictBaseModelis removed across traces, episodes, messages, tasks, judges, and artifacts; models use plainpydantic.BaseModelso unknown fields are ignored rather than rejected. Public exports dropStrictBaseModeland exportAgentSpaninstead ofGenerationSpan.Replay and e2e expectations use
episode.env.idand the new serializedenvshape.Reviewed by Cursor Bugbot for commit c9ee978. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add episode-level derived aggregates and structured
EnvInfotoEpisodeEpisodenow stores a structuredEnvInfoobject (with anidfield) instead of a plain string forenv, and exposes new aggregate properties:num_input_tokens,num_output_tokens,num_total_tokens,num_turns,usage, andby_agent.GenerationSpanis renamed toAgentSpanandTiming.generationbecomesTiming.agentthroughout traces, the dashboard, debug/replay CLI, and the v0-to-v1 legacy bridge.StrictBaseModelis removed across all v1 models; all Pydantic models now useBaseModelwithextra='ignore'semantics, accepting unknown fields instead of raising validation errors.errorproperty onEpisodeis replaced bylast_error; the dashboard and replay CLI are updated accordingly.agentinstead ofgenerationfor timing fields and a nestedenvobject instead of a string — existing consumers of these fields will break.Macroscope summarized c9ee978.