API & CLI reference
Recorder (manual API)
The framework-agnostic core — the TRL adapter is a thin layer over this, and custom loops call it directly. No torch required.
from rlprobe import Recorder
rec = Recorder(
"runs/my-run",
algorithm="grpo", # "grpo" | "ppo" | "rloo" | ... (manifest metadata)
run_id=None, # default: generated
model_name="my-org/my-model",
trainer_config={...}, # dumped verbatim into the manifest
world_size=1, rank=0, # distributed semantics — see recording format
capture_env=True, # git sha, package versions, GPU info
sampling=None, # SamplingConfig(top_k=2, bottom_k=2, random_k=4)
queue_size=8192, # bounded queue; full = drop + count, never block
flush_interval_s=5.0, # write part files at least this often
steps_per_flush=200, # ... or after this many buffered steps
install_handlers=True, # atexit + excepthook + SIGTERM flush
)
| method | behavior |
|---|---|
log_step(step, **metrics) |
Record per-step scalars. Known names map to StepRecord fields; unknown numeric kwargs land in extra. Only enqueues — validation happens on the writer thread, so the call costs microseconds and never raises on bad values (a bad_record event is written instead). |
log_samples(step, samples, select=True) |
Record per-sample forensics (dicts or SampleRecords). select=True applies the top/bottom-k + random sampling policy. The recorder takes ownership of the dicts — don't mutate them afterwards. |
log_event(kind, step=None, message="", **data) |
Record a sparse event; written to disk immediately. |
flush(timeout=5.0) |
Ask the writer thread to persist everything buffered; True when done. Needed only when something else reads the run dir mid-run (live diagnosis does this for you). |
close() |
Drain, flush, compact part files into steps.parquet. Idempotent; also runs via context-manager exit and the installed exit handlers. |
dropped |
Count of records dropped because the queue was full. |
active |
False on non-recording ranks (the no-op recorder). |
TRL integration
from rlprobe.integrations.trl import attach
callback = attach(trainer, "runs/my-run") # record everything
callback = attach(trainer, "runs/my-run", halt=True) # + live guardian
attach(trainer, run_dir, **kwargs) constructs an RlprobeCallback, registers it on
the trainer, and returns it. Keyword arguments:
| kwarg | default | meaning |
|---|---|---|
halt |
None |
True = default HaltPolicy; or pass a custom HaltPolicy; None/False = never halt |
on_finding |
None |
callable invoked with each new Finding as it is first detected (works without halting) |
diagnose_every |
25 | run live diagnosis every N steps (flushes, re-reads from disk, evaluates detectors) |
doctor_config |
None |
per-detector threshold overrides (same shape as doctor.toml) |
| anything else | forwarded to Recorder (e.g. sampling=, record_all_ranks=) |
After a halt: callback.halted_by holds the triggering Finding, and the recording
contains an auto_halt event. Requires trl>=1.7,<1.8 (the per-sample shim reads a
private-but-stable buffer; if a future TRL removes it, sample capture degrades
gracefully with a shim_unavailable event and scalars are unaffected).
HaltPolicy
from rlprobe.doctor.live import HaltPolicy
HaltPolicy(
min_severity="critical", # "info" | "warn" | "critical"
min_confidence=0.7,
signatures=None, # None = any; or frozenset({"KL_BLOWUP", ...})
)
Conservative by default: only critical findings at ≥70% confidence — a warn-level hunch must never burn a checkpoint.
Doctor (Python API)
All read-side; none of it imports torch.
from rlprobe.doctor import diagnose
findings = diagnose("runs/my-run", config_path="doctor.toml") # list[Finding], ranked
| function | purpose |
|---|---|
diagnose(run_dir, config_path=None, config=None) |
full catalog over a recording → ranked list[Finding] |
diagnose_view(view, config=None) |
same, over an in-memory RunView (live mode uses this) |
RunView.load(run_dir) |
recording → numpy series (view.series("kl_mean"), view.component(name), view.samples) |
RunWatcher(run_dir, sinks=[...]) |
incremental live tailing; each tick() returns only new findings |
webhook_sink(url) |
Slack-compatible alert sink for RunWatcher |
render_report(run_dir, out_path=None) |
self-contained HTML post-mortem → Path |
diff_runs(run_a, run_b, z_thresh=4.0, sustain=5) |
config delta + metric divergence points + per-prompt gaps → RunDiff |
query_samples(run, sort="reward", step_from=..., step_to=..., k=10) |
rank captured samples by reward / advantage / kl / length / logprob |
spike_drivers(run, step, window=5, k=8) |
the samples most responsible for the update around step — ranked by advantage, the actual policy-gradient weight |
A Finding is a pydantic model: signature_id, severity, confidence,
first_step, explanation, evidence, suggested_actions, links_to_samples.
Storage layer
rlprobe.storage — RunReader (typed steps() / samples() / events(),
merges rank dirs), RunWriter (synchronous writer the Recorder wraps),
list_runs(root), load_manifest(run_dir).
CLI
Every command works on a plain run directory; nothing needs a server or the training environment.
| command | purpose | notable flags |
|---|---|---|
rlprobe ls ROOT |
list recorded runs | |
rlprobe doctor RUN |
ranked diagnosis; exit 1 on critical (CI-gate ready) | --json, --config doctor.toml |
rlprobe watch RUN |
live-tail a (possibly not-yet-existing) run; report new findings; exit 1 if critical seen | --poll 3.0, --webhook URL, --max-ticks N |
rlprobe report RUN |
self-contained HTML post-mortem | --out FILE, --config |
rlprobe show RUN --step N |
metrics + captured samples around a step | --window 5, --max-samples 10 |
rlprobe samples RUN |
forensic sample query | --sort reward\|advantage\|kl\|length\|logprob, --around N --window K or --from A --to B, --asc, -k 10 |
rlprobe diff A B |
config delta + divergence points + per-prompt gaps | --json, --z-thresh 4.0, --sustain 5 |
rlprobe synth DIR |
generate a synthetic healthy/pathological recording | --pathology NAME, --n-steps 500, --seed 0 |
Exit codes are part of the contract: doctor and watch return 1 when a critical
finding exists, so both drop straight into CI gates and shell automation.