Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b705a04
feat(v1): name an env, and the cohort an episode was planned in
mikasenghaas Aug 4, 2026
c2765a1
chore(v1): trim the new docstrings, make EnvInfo.name optional
mikasenghaas Aug 4, 2026
507c4fe
fix(v1): a resumed run rejoins the group it left
mikasenghaas Aug 4, 2026
98cdf64
revert(v1): don't mint groups in the eval CLI
mikasenghaas Aug 4, 2026
50f2297
feat(v1): the train run records the policy it came from, and Episode.…
mikasenghaas Aug 4, 2026
d3be61d
feat(v1): an eval run records the policy version it measures
mikasenghaas Aug 4, 2026
c12e77b
feat(v1): online eval belongs to the training run
mikasenghaas Aug 4, 2026
70490aa
chore(v1): export EnvInfo
mikasenghaas Aug 4, 2026
8c318d0
feat(v1): a run records the policy span, and derives staleness from it
mikasenghaas Aug 4, 2026
def0d5a
chore(v1): drop Episode.to_record, plainer wording on the policy span
mikasenghaas Aug 4, 2026
54eef2c
docs(v1): say what off_policy_steps measures for each kind
mikasenghaas Aug 4, 2026
260b6f1
feat(v1)!: what an episode is to its run is a nested, discriminated m…
mikasenghaas Aug 4, 2026
66dccbf
chore(v1): drop the GroupInfo docstring
mikasenghaas Aug 4, 2026
9576a5a
fix(v1): an eval's off-policy is its drift, not its distance from a step
mikasenghaas Aug 4, 2026
eddc781
refactor(v1): the run types live with the episode they describe
mikasenghaas Aug 4, 2026
a79e674
chore(v1): docstrings in the file's own register
mikasenghaas Aug 4, 2026
9313f4b
refactor(v1)!: the policy span belongs to the episode's metadata
mikasenghaas Aug 5, 2026
3a38df5
revert(v1): drop GroupInfo from the episode
mikasenghaas Aug 5, 2026
306a95f
fix(v1): keep raw node tensors out of the written episode
mikasenghaas Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions verifiers/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@
from verifiers.v1.configs.taskset import TasksetConfig
from verifiers.v1.env import Env
from verifiers.v1.envs.single_agent import SingleAgentEnv, SingleAgentEnvConfig
from verifiers.v1.episode import Episode, WireEpisode
from verifiers.v1.episode import (
EnvInfo,
Episode,
EvalMetadata,
EvalRunInfo,
Metadata,
PolicySpan,
RunInfo,
TrainMetadata,
TrainRunInfo,
WireEpisode,
)
from verifiers.v1.errors import (
EnvError,
HarnessError,
Expand Down Expand Up @@ -75,16 +86,13 @@
AgentSpan,
Branch,
Error,
EvalRunInfo,
ModelCall,
Reward,
RunInfo,
TimeSpan,
TimeSplit,
Timing,
Trace,
TraceTask,
TrainRunInfo,
VersionInfo,
WireTrace,
)
Expand Down Expand Up @@ -194,11 +202,16 @@
"WireTrace",
"Reward",
"Episode",
"EnvInfo",
"WireEpisode",
"TRACE_VERSION",
"AgentInfo",
"RunInfo",
"EvalRunInfo",
"EvalMetadata",
"Metadata",
"TrainMetadata",
"PolicySpan",
"ModelCall",
"TrainRunInfo",
"VersionInfo",
Expand Down
3 changes: 1 addition & 2 deletions verifiers/v1/cli/eval/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.cli.eval import EvalConfig
from verifiers.v1.env import Env, RunSlot
from verifiers.v1.episode import Episode
from verifiers.v1.episode import Episode, EvalRunInfo
from verifiers.v1.taskset import SEED
from verifiers.v1.trace import EvalRunInfo

logger = logging.getLogger(__name__)

Expand Down
9 changes: 6 additions & 3 deletions verifiers/v1/cli/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from verifiers.v1.configs.cli.eval import EvalConfig
from verifiers.v1.episode import Episode, WireEpisode
from verifiers.v1.trace import Trace
from verifiers.v1.trace import EXCLUDE_FIELDS, Trace
from verifiers.v1.utils.aio import run_shielded
from verifiers.v1.utils.install import env_name

Expand Down Expand Up @@ -74,9 +74,12 @@ def save_config(config: BaseModel, results_dir: Path) -> None:


def write_episode(results_dir: Path, episode: Episode) -> None:
"""Serialize and append one rollout episode in the worker thread."""
"""Serialize and append one rollout episode in the worker thread. Raw per-node tensors stay
out of the record — they are the trainer's, and numpy bytes do not round-trip json."""
# Preserve fields declared by typed Trace subclasses nested in the episode.
data = type_adapter(type(episode)).dump_json(episode, exclude_none=True)
data = type_adapter(type(episode)).dump_json(
episode, exclude_none=True, exclude={"traces": {"__all__": EXCLUDE_FIELDS}}
)
with (results_dir / TRACES_FILE).open("ab") as f:
f.write(data + b"\n")

Expand Down
77 changes: 75 additions & 2 deletions verifiers/v1/episode.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""The episode — one run's traces plus their shared standing, whole."""

import uuid
from typing import Any, Generic
from typing import Annotated, Any, Generic, Literal

from pydantic import BaseModel, Field

from verifiers.v1.configs.agent import WireAgentConfig
from verifiers.v1.state import State, StateT
from verifiers.v1.task import DataT, WireTaskData
from verifiers.v1.trace import AgentConfigT, Error, RunInfo, Trace
from verifiers.v1.trace import AgentConfigT, Error, Trace
from verifiers.v1.types import Usage


Expand All @@ -18,6 +18,79 @@ class EnvInfo(BaseModel):
id: str = ""
"""`EnvConfig.env_id`, e.g. `agentic-judge+gsm8k-v1`."""

name: str | None = None
"""What the caller knows this env by, when that differs from `id`."""


class EvalRunInfo(BaseModel):
"""A standalone eval: a model measured against nothing that is training."""

type: Literal["eval"] = "eval"
id: str


class PolicySpan(BaseModel):
"""Live policy versions with a derived, non-serialized drift in updates."""

start: int = 0
end: int = 0

@property
def drift(self) -> int:
return max(0, self.end - self.start)


class Metadata(BaseModel):
"""What one episode is to the training run it belongs to."""

policy: PolicySpan | None = None
"""`None` when it was not generated from the live policy, as with a frozen sampler."""


class TrainMetadata(Metadata):
"""An episode a training run trains on."""

type: Literal["train"] = "train"
step: int | None = None
"""The batch window it landed in, which is not known until it does."""

@property
def off_policy_steps(self) -> int | None:
"""Versions behind the policy in training, queue time included."""
if self.policy is None or self.step is None:
return None
return max(0, (self.step - 1) - self.policy.start)


class EvalMetadata(Metadata):
"""An episode a training run measures itself with."""

type: Literal["eval"] = "eval"
step: int
"""The step whose eval produced it, known when it is dispatched."""

@property
def off_policy_steps(self) -> int | None:
"""Versions the policy moved under it. Nothing trains on an eval, so it can only be
off-policy by drifting — and its `step` is fixed at dispatch, so it cannot say that."""
return self.policy.drift if self.policy else None


EpisodeMetadata = Annotated[TrainMetadata | EvalMetadata, Field(discriminator="type")]


class TrainRunInfo(BaseModel):
"""A training run: one id over the episodes it trains on and the ones it evaluates itself with,
which `metadata` tells apart."""

type: Literal["train"] = "train"
id: str
metadata: EpisodeMetadata = Field(default_factory=TrainMetadata)


RunInfo = Annotated[EvalRunInfo | TrainRunInfo, Field(discriminator="type")]
"""The run an episode belongs to, discriminated on `type`."""


class Episode(BaseModel, Generic[DataT, StateT, AgentConfigT]):
"""The artifact Env.run produces. Contains multiple agents' traces."""
Expand Down
20 changes: 1 addition & 19 deletions verifiers/v1/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import traceback
import uuid
from collections.abc import Callable, Mapping
from typing import TYPE_CHECKING, Annotated, Any, Generic, Literal
from typing import TYPE_CHECKING, Any, Generic

import numpy as np
from pydantic import BaseModel, Field, PrivateAttr
Expand Down Expand Up @@ -132,24 +132,6 @@ def value(self) -> float:
return self.score * self.weight


class EvalRunInfo(BaseModel):
type: Literal["eval"] = "eval"

id: str
step: int | None = None


class TrainRunInfo(BaseModel):
type: Literal["train"] = "train"

id: str
step: int | None = None


RunInfo = Annotated[EvalRunInfo | TrainRunInfo, Field(discriminator="type")]
"""The run a trace belongs to, discriminated on `type`."""


class ModelCall(BaseModel):
"""A model call, automatically recorded at intercept time."""

Expand Down
Loading