Skip to navigation

nemo_gym.rollouts.rollout_health

View as Markdown

Deterministic post-run rollout quality verification workflow.

Checks operate only on persisted rollout records and their canonical ng_trajectory evidence. They return evidence; this module derives verdicts and writes reports.

Module Contents

Classes

NameDescription
CheckInputPersisted or derived evidence required to evaluate a check.
CheckScope-
CheckSpecStable, self-describing health-check contract.
CheckSubject-
FindingEvidence emitted by a check. Checks never emit verdicts.
HealthCheckResult-
RolloutDigest-
_AggregationSourcesThe shards gym eval aggregate merged into a rollout file, in its order, each as its path, size and SHA-256,
_FailuresThe rollouts that failed and never landed, and the rollouts the run owed.

Functions

NameDescription
_aggregated_shardsThe shards gym eval aggregate merged into run_dir/rollout_name, in its order; None if it merged none.
_context_window_exceededFrom the row’s own step_usage (written by scicode_agent): a sub-step recorded as context_window_exceeded.
_flagA config flag as written: a bool is known, anything else (absent, interpolated, malformed) is unknown.
_health_artifact_digestsBind gate facts to saved rollout inputs, not to external captures or observation completeness.
_health_check_collected_rolloutsWrite the quality summary for whatever a run collected; a run that never started collection has none.
_index_jsonl-
_jsonl_rowsStream a JSONL file’s objects; an inventory can be hundreds of MB.
_judge_runAdd the run-level check to the accounting: its coverage, and a reason and an unhealthy verdict per finding.
_landed_inlanded keys of one input, keyed as _read_failures keys that input read alone.
_last_failed_attemptsThe selected failed attempt of every rollout that never landed.
_limit_text-
_mark_duplicate_identitiesFlag physical records of one input that claim the same logical rollout identity.
_mlflow_configuredA names-only fact about the saved config snapshot: whether its three MLflow name fields are concrete.
_observed_upstream_recoveryCount only observed failed attempts and explicitly completed operations; never certify completeness.
_persisted_limitsThe limits the run’s earlier summary was judged by; none (no limit) when it has no readable summary.
_process_pool_chunksizeKeep several schedulable chunks per worker without unbounded IPC batches.
_read_failuresRead every input’s failures sidecar and inventory.
_read_record-
_reduce-
_remove_health_reports-
_report_sourcesThe rollouts files a report was computed from, relative to the folder it is written in
_resolve_rollout_path-
_response_populationThe call ids of the rollout’s verified successful raw model responses selected for evaluation, and of the
_rollout_accountingProject managed runs from selected history; preserve raw accounting for legacy runs.
_run_excessive_failuresThe run_excessive_failures check: one finding per group of failed rollouts that reaches its limit.
_run_health_lineRun health: failed rollouts against the configured limits, and the coverage reasons.
_sha256-
_sort_key-
_task_findings-
_unique_task_repeatsCollapse duplicate persisted records for task-level repeat semantics.
_with_captured_callsFailed rollouts’ rows, each with the model calls captured under its rollout id, read with the collector’s
_worker-
_write_reports-
capture_dirs_for_healthThe run’s model-call capture directories for run_health_checks; none when capture is off or misconfigured,
evaluate_recordEvaluate every rollout-level check on one rollout record.
format_health_report-
health_check_run_dirRecheck a run directory. The run’s own persisted limits judge it, so the verdict of the same artifacts
normalize_ignored_checksNormalize and validate check IDs supplied by library, CLI, or Hydra config.
run_health_checksCheck selected completed attempts while retaining run-wide failure and timeout evidence.
run_provenanceFacts about how the run was configured, read from the resolved config saved beside the rollouts.
write_aggregation_sourcesRecord the shards merged_fpath is merged from, for a recheck of its directory to read.
write_disabled_summaryWrite the quality summary of a run with disable_health_check set, so a disabled check is recorded as
write_provisional_summaryWrite the run accounting of a stopped run fast: rollouts.jsonl is not read, and the per-record checks

Data

CHECK_REGISTRY

MLFLOW_TRACKING_REQUIRED_KEY

Verdict

_MLFLOW_REQUIRED_NAMES

_PROCESS_POOL_CHUNKS_PER_WORKER

_PROCESS_POOL_MAX_CHUNKSIZE

_RUN_FAILURES_CHECK

logger

API

class nemo_gym.rollouts.rollout_health.CheckInput

Bases: enum.Enum

Persisted or derived evidence required to evaluate a check.

AGENT_TURNS
= 'agent_turns'
BOUND_CALLS
= 'bound_calls'
OBSERVED_MODEL_CALLS
= 'observed_model_calls'
OWNED_MODEL_CALLS
= 'owned_model_calls'
RECORD
= 'record'
REPEAT_DIGESTS
= 'repeat_digests'
REPEAT_VERDICTS
= 'repeat_verdicts'
RUN_ACCOUNTING
= 'run_accounting'
TRAJECTORY
= 'trajectory'
class nemo_gym.rollouts.rollout_health.CheckScope

Bases: enum.Enum

ROLLOUT
= 'rollout'
RUN
= 'run'
TASK
= 'task'
class nemo_gym.rollouts.rollout_health.CheckSpec()

Bases: BaseModel

Stable, self-describing health-check contract.

evaluation_scope
CheckScope
id
str
model_config
= ConfigDict(frozen=True)
reads
frozenset[CheckInput]
subject
CheckSubject
nemo_gym.rollouts.rollout_health.CheckSpec._require_one_call_binding_view(
classmethod
class nemo_gym.rollouts.rollout_health.CheckSubject

Bases: enum.Enum

AGENT_TURN
= 'agent_turn'
CHECK_EXECUTION
= 'check_execution'
MODEL_CALL
= 'model_call'
RECORD
= 'record'
ROLLOUT
= 'rollout'
RUN
= 'run'
TASK
= 'task'
TRAJECTORY_CAPTURE
= 'trajectory_capture'
class nemo_gym.rollouts.rollout_health.Finding()

Bases: BaseModel

Evidence emitted by a check. Checks never emit verdicts.

check
str
detail
dict[str, Any] = Field(default_factory=dict)
locator
dict[str, int | str] | None = None
subject
dict[str, int | str]
class nemo_gym.rollouts.rollout_health.HealthCheckResult()

Bases: BaseModel

rollouts
list[RolloutDigest]
summary
dict[str, Any]
summary_path
Path
verdicts_path
Path
class nemo_gym.rollouts.rollout_health.RolloutDigest()

Bases: BaseModel

capture_completion_tokens
int = 0
capture_observed
bool
capture_prompt_tokens
int = 0
context_window_exceeded
bool | None = None
duplicated_calls
int = 0
ended_on_error
bool = False
errors_by_status
dict[str, int] = Field(default_factory=dict)
findings
list[Finding]
model_call_errors
int = 0
model_call_errors_usage_unknown
int = 0
model_calls
int = 0
policy_calls_observed
bool = False
responses
dict[str, list[str]] | None = None
rollout_id
str
rollout_index
int | str
scored
bool = False
stage_index
int | str | None = None
successful_model_calls
int = 0
task_index
int | str
timing
RolloutTiming | None = None
transcript_completion_tokens
int = 0
transcript_prompt_tokens
int = 0
unobserved
list[str]
upstream_recovery_observations
dict[str, int] | None = None
verdict
Verdict
verification_skipped
bool | None = None
class nemo_gym.rollouts.rollout_health._AggregationSources()

Bases: BaseModel

The shards gym eval aggregate merged into a rollout file, in its order, each as its path, size and SHA-256, and that file’s size and SHA-256.

merged_bytes
int
merged_sha256
str
shards
list[tuple[str, int, str]]
class nemo_gym.rollouts.rollout_health._Failures(
rows: dict[tuple[typing.Any, ...], dict[str, typing.Any]],
owed: set[tuple[typing.Any, ...]] | None,
any_sidecar: bool
)
Dataclass

The rollouts that failed and never landed, and the rollouts the run owed.

any_sidecar
bool
owed
set[tuple[Any, ...]] | None
rows
dict[tuple[Any, ...], dict[str, Any]]
nemo_gym.rollouts.rollout_health._aggregated_shards(
run_dir: pathlib.Path,
rollout_name: str = 'rollouts.jsonl'
) -> list[pathlib.Path] | None

The shards gym eval aggregate merged into run_dir/rollout_name, in its order; None if it merged none.

The merged rows have none of the shards’ failures or inventories, so a recheck reads the shards, as the aggregation did. A merged file that is gone, or not byte for byte the one the list was written for, is not theirs; a shard that is gone cannot be read, and one not byte for byte the one merged holds other rows. Each raises rather than check rows other than the merged ones, or without their failures.

nemo_gym.rollouts.rollout_health._context_window_exceeded(
record: collections.abc.Mapping[str, typing.Any]
) -> bool | None

From the row’s own step_usage (written by scicode_agent): a sub-step recorded as context_window_exceeded.

scicode_agent sets that status from a best-effort error-text match or model metadata, so it is a signal of a context-window failure, not proof that the input was clipped.

nemo_gym.rollouts.rollout_health._flag(
value: typing.Any
) -> bool | None

A config flag as written: a bool is known, anything else (absent, interpolated, malformed) is unknown.

nemo_gym.rollouts.rollout_health._health_artifact_digests(
paths: collections.abc.Sequence[pathlib.Path]
) -> list[dict[str, str | None]]

Bind gate facts to saved rollout inputs, not to external captures or observation completeness.

nemo_gym.rollouts.rollout_health._health_check_collected_rollouts(
output_fpath: pathlib.Path,
config: typing.Any,
landed_keys: set | None = None,
coverage: dict | None = None,
terminated: bool = False,
model_call_capture_dirs: collections.abc.Sequence[pathlib.Path] = (),
disabled: bool = False
) -> None

Write the quality summary for whatever a run collected; a run that never started collection has none.

After SIGTERM, Slurm kills the job once KillWait (120 s on some clusters) is over, and the full check can take minutes on a large run. So a stopped run with landed_keys gets the provisional summary: the run accounting only, without reading the rollouts. With disabled, the summary only records that (write_disabled_summary). A check that cannot run writes nothing, so saved evidence stays as it is.

nemo_gym.rollouts.rollout_health._index_jsonl(
paths: collections.abc.Sequence[pathlib.Path],
histories: collections.abc.Mapping[pathlib.Path, nemo_gym.rollouts.rollout_store.RolloutStore | None] | None = None
nemo_gym.rollouts.rollout_health._jsonl_rows(
path: pathlib.Path
) -> collections.abc.Iterator[dict[str, typing.Any]]

Stream a JSONL file’s objects; an inventory can be hundreds of MB.

nemo_gym.rollouts.rollout_health._judge_run(
artifacts: dict[str, typing.Any],
coverage: dict[str, int]
) -> None

Add the run-level check to the accounting: its coverage, and a reason and an unhealthy verdict per finding.

nemo_gym.rollouts.rollout_health._landed_in(
landed: set[tuple[typing.Any, ...]],
source_index: int
) -> set[tuple[typing.Any, ...]]

landed keys of one input, keyed as _read_failures keys that input read alone.

nemo_gym.rollouts.rollout_health._last_failed_attempts(
landed: set[tuple[typing.Any, ...]],
paths: collections.abc.Sequence[pathlib.Path],
histories: collections.abc.Mapping[pathlib.Path, nemo_gym.rollouts.rollout_store.RolloutStore | None] | None = None
) -> list[dict[str, typing.Any]]

The selected failed attempt of every rollout that never landed.

A run with a manifest selects it from its run history, as its accounting does; a legacy output’s sidecar row that stands last is it.

nemo_gym.rollouts.rollout_health._limit_text(
value: float | None
) -> str
nemo_gym.rollouts.rollout_health._mark_duplicate_identities(
ignored_checks: frozenset[str]
) -> None

Flag physical records of one input that claim the same logical rollout identity.

Each input numbers its rollouts on its own, as the run accounting counts them: runs merged from different environment servers can reuse task indices.

nemo_gym.rollouts.rollout_health._mlflow_configured(
config: collections.abc.Mapping[str, typing.Any]
) -> bool | None

A names-only fact about the saved config snapshot: whether its three MLflow name fields are concrete.

True when all three are concrete strings, False when any is absent, null or empty, None when a value is interpolated or not a string. It is not MLFlowConfig.is_available (the token, which the snapshot may mask, is not judged) and says nothing about whether an exporter was activated, whether tracking was required or disabled, or whether anything was delivered: overrides, resumes and post-run exports can differ from the snapshot. No value is copied.

nemo_gym.rollouts.rollout_health._observed_upstream_recovery(
calls: list[dict[str, typing.Any]]
) -> dict[str, int] | None

Count only observed failed attempts and explicitly completed operations; never certify completeness.

nemo_gym.rollouts.rollout_health._persisted_limits(
run_dir: pathlib.Path

The limits the run’s earlier summary was judged by; none (no limit) when it has no readable summary.

nemo_gym.rollouts.rollout_health._process_pool_chunksize(
item_count: int,
workers: int
) -> int

Keep several schedulable chunks per worker without unbounded IPC batches.

nemo_gym.rollouts.rollout_health._read_failures(
landed: set[tuple[typing.Any, ...]],
paths: collections.abc.Sequence[pathlib.Path]

Read every input’s failures sidecar and inventory.

The last attempt of a rollout stands, and a rollout that later landed is not failed. With an inventory, only owed rollouts count: an unreadable record or a sidecar row outside the inventory is not one.

nemo_gym.rollouts.rollout_health._read_record(
) -> tuple[dict[str, typing.Any], str | None]
nemo_gym.rollouts.rollout_health._reduce(
ignored_checks: frozenset[str]
) -> dict[str, typing.Any]
nemo_gym.rollouts.rollout_health._remove_health_reports(
output_fpath: pathlib.Path
) -> None
nemo_gym.rollouts.rollout_health._report_sources(
paths: collections.abc.Sequence[pathlib.Path],
report_dir: pathlib.Path
) -> list[str]

The rollouts files a report was computed from, relative to the folder it is written in (run.<QUALITY_SUMMARY_SOURCES_KEY>): the reports keep one name per folder whatever the rollouts file is called, so a reader with several runs’ rollouts in one folder can tell whose they are. Only the directories are resolved, as _aggregated_shards does, so a shard keeps its own name.

nemo_gym.rollouts.rollout_health._resolve_rollout_path(
run_dir: pathlib.Path,
rollout_file: str | pathlib.Path | None
) -> pathlib.Path
nemo_gym.rollouts.rollout_health._response_population(
scope_complete: bool,
bindings: typing.Any
) -> dict[str, list[str]] | None

The call ids of the rollout’s verified successful raw model responses selected for evaluation, and of the faulted ones among them. IDs only: no response text is kept.

Selected means bound to an agent turn of a complete call scope. A bound call that failed is a trial matter and is left out. Every other bound call must carry an explicit 2xx/3xx status, a raw response and a call id, or the population is unknown. A fault is judged from the raw output itself: a length-limited finish that is not a length refusal (truncated), or an output with nothing usable (_response_has_usable_output: empty, reasoning only or malformed). Token counts alone are never a fault.

nemo_gym.rollouts.rollout_health._rollout_accounting(
landed: set[tuple[typing.Any, ...]],
paths: collections.abc.Sequence[pathlib.Path],
duplicated: int,
run_coverage: collections.abc.Sequence[collections.abc.Mapping[str, typing.Any] | None] | None = None,
scored: set[tuple[typing.Any, ...]] | None = None
) -> dict[str, typing.Any]

Project managed runs from selected history; preserve raw accounting for legacy runs.

Managed missing means reserved attempts with no outcome. Never-dispatched work and intentional omissions have their own categories. Final failures include exhausted budgets as well as terminal outcomes. A stopped collector supplies its in-memory coverage so this path never reloads large rollout records on SIGTERM. In a legacy sidecar, a record_omitted_rollouts row records no result: an interrupted attempt is the rollout’s latest execution and leaves it missing, and a row never started is skipped. scored holds the landed keys whose records are all scored. It is None when the records were not read, which leaves a legacy input’s scored count unknown.

nemo_gym.rollouts.rollout_health._run_excessive_failures(
artifacts: dict[str, typing.Any],
ignored_checks: frozenset[str]
) -> tuple[list[nemo_gym.rollouts.health.types.Finding], dict[str, int]]

The run_excessive_failures check: one finding per group of failed rollouts that reaches its limit.

It reads only the run accounting, so a stopped run can evaluate it without the rollouts. With no limit configured, or no inventory to count expected rollouts against, it is unobserved.

nemo_gym.rollouts.rollout_health._run_health_line(
artifacts: dict[str, typing.Any]
) -> str

Run health: failed rollouts against the configured limits, and the coverage reasons.

nemo_gym.rollouts.rollout_health._sha256(
fpath: pathlib.Path
) -> str
nemo_gym.rollouts.rollout_health._sort_key(
) -> tuple[tuple[int, typing.Any], ...]
nemo_gym.rollouts.rollout_health._task_findings(
grouped: dict[int | str, list[nemo_gym.rollouts.health.types._TaskRepeat]],
ignored_checks: frozenset[str]
) -> tuple[dict[int | str, list[nemo_gym.rollouts.health.types.Finding]], dict[str, dict[str, int]]]
nemo_gym.rollouts.rollout_health._unique_task_repeats(

Collapse duplicate persisted records for task-level repeat semantics.

nemo_gym.rollouts.rollout_health._with_captured_calls(
rows: collections.abc.Iterable[dict[str, typing.Any]],
capture_dirs: collections.abc.Sequence[pathlib.Path]
) -> collections.abc.Iterator[dict[str, typing.Any]]

Failed rollouts’ rows, each with the model calls captured under its rollout id, read with the collector’s key and reader.

Collection joins captures only into a rollout that produced a result, so a row that failed without one (an episode cut by its deadline, say) has none, although the model server captured its calls. Each capture dir is listed once, never created. A row comes back as it is, its time unmeasured, when it carries its own capture (so no call counts twice), when its capture is missing or partial (a write still claimed, a failed write, an unreadable record), or when its identity or its capture cannot be read.

nemo_gym.rollouts.rollout_health._write_reports(
summary: dict[str, typing.Any],
output_dir: pathlib.Path
) -> tuple[pathlib.Path, pathlib.Path]
nemo_gym.rollouts.rollout_health.capture_dirs_for_health(
global_config_dict: typing.Any
) -> list[pathlib.Path]

The run’s model-call capture directories for run_health_checks; none when capture is off or misconfigured, so a capture setting never stops the health check.

nemo_gym.rollouts.rollout_health.evaluate_record(
record: dict[str, typing.Any],
ignored_checks: frozenset[str],
parse_error: str | None = None

Evaluate every rollout-level check on one rollout record.

The one code path behind both the post-run report (which reads record from a JSONL line) and live emission (which passes the in-memory result, so line is None).

nemo_gym.rollouts.rollout_health.format_health_report(
) -> str
nemo_gym.rollouts.rollout_health.health_check_run_dir(
run_dir: str | pathlib.Path,
rollout_file: str | pathlib.Path | None = None,
workers: int | None = None,
ignored_checks: collections.abc.Sequence[str] = (),
json_output: bool = False,
model_call_capture_dir: str | pathlib.Path | None = None

Recheck a run directory. The run’s own persisted limits judge it, so the verdict of the same artifacts does not change. Each limit set explicitly on limits overrides only that persisted limit and is recorded as an override; set to None it removes the limit. A limit not set keeps its persisted value.

A directory gym eval aggregate merged shards into is rechecked from those shards, with the failures and inventories beside them, as the aggregation checked them. An explicit rollout_file is checked alone.

model_call_capture_dir is the run’s model_call_capture_dir; with it, failed rollouts’ captured calls count in the timeouts section’s time split.

nemo_gym.rollouts.rollout_health.normalize_ignored_checks(
checks: collections.abc.Sequence[str] | str | None
) -> tuple[str, ...]

Normalize and validate check IDs supplied by library, CLI, or Hydra config.

nemo_gym.rollouts.rollout_health.run_health_checks(
rollout_paths: pathlib.Path | collections.abc.Sequence[pathlib.Path],
output_dir: pathlib.Path | None = None,
workers: int | None = None,
ignored_checks: collections.abc.Sequence[str] = (),
limits_source: nemo_gym.rollouts.health.types.LimitsSource | collections.abc.Mapping[str, nemo_gym.rollouts.health.types.LimitsSource] = 'config',
run_coverage: collections.abc.Sequence[collections.abc.Mapping[str, typing.Any] | None] | None = None,
source_histories: collections.abc.Mapping[pathlib.Path, nemo_gym.rollouts.rollout_store.RolloutStore | None] | None = None,
model_call_capture_dirs: collections.abc.Sequence[pathlib.Path] = ()

Check selected completed attempts while retaining run-wide failure and timeout evidence.

Workers read selected byte ranges from the original histories, which own selection and identity. Aggregation supplies its scoring snapshots so health cannot select a later attempt while retaining older coverage counts. Standalone calls load the current histories; explicit None entries denote loose inputs. History-map keys accept the same relative paths and aliases as rollout_paths. Loose JSONL and explicit failure sidecars retain their line-by-line behavior.

nemo_gym.rollouts.rollout_health.run_provenance(
rollout_path: pathlib.Path
) -> dict[str, typing.Any]

Facts about how the run was configured, read from the resolved config saved beside the rollouts.

Only values the config states are reported: route_failures_to_sidecar and each agent’s skip_verification are booleans when written as booleans and None otherwise, never a default. Each top-level server entry reports its kind (responses_api_agents, resources_servers or environment_servers), its implementation key and, for an agent, the resources server it names. No values beyond these names and flags are copied.

nemo_gym.rollouts.rollout_health.write_aggregation_sources(
merged_fpath: pathlib.Path,
shards: collections.abc.Sequence[tuple[pathlib.Path, int, str]],
merged_contents_fpath: pathlib.Path
) -> None

Record the shards merged_fpath is merged from, for a recheck of its directory to read.

The merged file holds only the shards’ rows: their failure sidecars and inventories stay next to them. shards holds each shard’s path with the size and SHA-256 of what the merge read from it, and merged_contents_fpath the rows about to take merged_fpath’s place; the record names both by size and SHA-256, so it never vouches for other contents. Each shard is recorded relative to the record’s directory, so the shards and the merged file can move together, and under its own file name: a shard reached through a file link keeps the sidecars beside the link, as the aggregation read them.

nemo_gym.rollouts.rollout_health.write_disabled_summary(
rollout_paths: collections.abc.Sequence[pathlib.Path],
output_dir: pathlib.Path
) -> pathlib.Path | None

Write the quality summary of a run with disable_health_check set, so a disabled check is recorded as state: disabled rather than by an absent summary. It counts and judges nothing: the counts and healthy are null.

A report written at the end of a run must not stop it, so a write error is logged and returns None.

nemo_gym.rollouts.rollout_health.write_provisional_summary(
rollout_path: pathlib.Path,
landed: set[tuple[typing.Any, typing.Any]],
ignored_checks: collections.abc.Sequence[str] = (),
run_coverage: collections.abc.Mapping[str, typing.Any] | None = None
) -> pathlib.Path

Write the run accounting of a stopped run fast: rollouts.jsonl is not read, and the per-record checks are skipped.

landed is the (task index, rollout index) of every persisted record, as rollout collection holds it. Duplicates come from the per-record pass, so healthy is false when the accounting already has a reason and null otherwise. gym eval health-check on the run dir writes the full summary.

nemo_gym.rollouts.health.checks.CHECK_REGISTRY: tuple[CheckSpec, ...] = (CheckSpec(id='check_execution_error', evaluation_scope=(CheckScope.ROLLOUT), su...
nemo_gym.rollouts.rollout_health.MLFLOW_TRACKING_REQUIRED_KEY = 'mlflow_tracking_required'
nemo_gym.rollouts.health.types.Verdict = Literal['healthy', 'unhealthy', 'unobserved']
nemo_gym.rollouts.rollout_health._MLFLOW_REQUIRED_NAMES = ('mlflow_tracking_uri', 'mlflow_experiment_name', 'mlflow_run_name')
nemo_gym.rollouts.rollout_health._PROCESS_POOL_CHUNKS_PER_WORKER = 4
nemo_gym.rollouts.rollout_health._PROCESS_POOL_MAX_CHUNKSIZE = 128
nemo_gym.rollouts.rollout_health._RUN_FAILURES_CHECK = 'run_excessive_failures'
nemo_gym.rollouts.rollout_health.logger = logging.getLogger(__name__)