Replay & Causality API
Every symbol here is exported from the omniloop top-level package and appears
in omniloop.__all__.
from omniloop import ( DeterministicReplay, InputReplayer, ReplayReport, FunctionalTrainingLoop, cause_of, effects_of, load_events, find_divergence, fnv1a64, find_repo_root, export_tuned_config_from_journal,)Deterministic replay
Section titled “Deterministic replay”TrainingLoop.replay(journal_path, source, tunable=(), ...)
Section titled “TrainingLoop.replay(journal_path, source, tunable=(), ...)”Re-executes a recorded run, sourcing mutations from the journal instead of live
IPC and verifying a state hash per tick. Returns a TrainingLoop that behaves
like the original, so the loop body needs no changes.
from omniloop import TrainingLoop
loop = TrainingLoop.replay("run.omni", cfg, tunable=["learning_rate"])for _ in range(steps): with loop.tick(): train_step(cfg)
report = loop.replay_report()print(report.ticks_verified, report.first_divergence)Determinism guarantee. Replay feeds back recorded inputs — mutations, IPC commands, and any seeds the loop recorded — and verifies the state hash at each tick that has one. It does not control sources of nondeterminism outside the journal: unseeded RNG, wall-clock reads, thread scheduling, GPU kernel non-associativity, or filesystem state. A divergence therefore means “this run did not reproduce”, not necessarily “OmniLoop replayed it wrong”; the first divergent tick is where to look.
What the hash covers. The state hash is taken over the telemetry frame with
provenance removed: every __sys_* key (pid, script path, interpreter, argv,
journal path), the captured __source_files__, and the volatile status fields
(timestamp, thread_state, is_safety_tripped, error_message,
watch_trip, __tick_duration_ns__). Those describe the invocation, not the
computation, and necessarily differ between a recording and its replay — hashing
them would make every replay diverge at its first checkpoint no matter what the
loop computed. Everything else in the frame, including your metrics, tunables,
and __exec_active__, is part of the hash.
InputReplayer
Section titled “InputReplayer”The Rust-backed index underneath TrainingLoop.replay(). Pre-indexes inputs,
hash checkpoints, and telemetry by tick for O(1) lookup, so replaying a long
journal does not re-scan it per tick.
from omniloop import InputReplayer
replayer = InputReplayer("run.omni")print(replayer.tick_count())verification = replayer.verify_tick(42, current_state_hash)verify_tick returns a TickVerification, one of:
| Variant | Meaning |
|---|---|
Match |
The recorded and recomputed hashes agree |
Diverged { expected, actual } |
They disagree — this is the divergence point |
NoCheckpoint |
This tick carries no recorded hash; nothing to compare |
DeterministicReplay
Section titled “DeterministicReplay”A thin wrapper over InputReplayer for driving a replay without
TrainingLoop — the raw-primitives path.
from omniloop import DeterministicReplay
replay = DeterministicReplay("run.omni", output_path="replayed.omni")Pass output_path to record the replayed run as its own journal, which is what
you then compare against the original with find_divergence.
ReplayReport
Section titled “ReplayReport”Summary returned at the end of a replay.
| Field | Meaning |
|---|---|
total_ticks |
Ticks present in the journal |
ticks_verified |
Ticks that carried a hash and were checked |
first_divergence |
Tick of the first mismatch, or None |
ticks_verified is usually lower than total_ticks: hashes are checkpoints,
not per-tick records.
Divergence
Section titled “Divergence”find_divergence(journal_a, journal_b)
Section titled “find_divergence(journal_a, journal_b)”Returns the first tick at which two journals disagree, or None when every
shared checkpoint matches. Checkpoints present in only one journal are ignored,
so two runs recorded at different hash cadences still compare cleanly.
from omniloop import find_divergence
tick = find_divergence("sim.omni", "real.omni")if tick is None: print("sim and real agree at every shared checkpoint")else: print(f"first divergence at tick {tick}")fnv1a64(data)
Section titled “fnv1a64(data)”The 64-bit FNV-1a hash used for state hashing. Exposed so external tooling can reproduce a state hash without linking the core. The canonical hash excludes volatile keys (timestamps) by construction, so it is stable across runs.
Causality
Section titled “Causality”Every mutation batch is assigned one server-generated UUID v4 correlation id, threaded through the IPC ring, the registry, and into journal records. Both directions of the causal chain are queryable.
cause_of(journal_path, tick)
Section titled “cause_of(journal_path, tick)”What caused the state at this tick — the mutation or control records whose correlation id leads here.
effects_of(journal_path, correlation_id)
Section titled “effects_of(journal_path, correlation_id)”What this change led to — every record downstream of that correlation id.
from omniloop import cause_of, effects_of
for record in cause_of("run.omni", tick=880): print(record["kind"], record["summary"])
for record in effects_of("run.omni", "6f1c…"): print(record["tick"], record["summary"])The dashboard’s Event Console exposes both as one click per event.
load_events(journal_path)
Section titled “load_events(journal_path)”Returns the full typed event stream (mutations, control, lifecycle, input) from a journal, without a native rebuild. Use it when you want the records as plain Python rather than a causal query.
Config recovery
Section titled “Config recovery”export_tuned_config_from_journal(journal_path, out_path=None)
Section titled “export_tuned_config_from_journal(journal_path, out_path=None)”Reconstructs the final tuned values from a journal’s mutation records alone —
the offline counterpart to loop.export_tuned_config(). Use it to recover a
hand-tuned configuration from a run whose process is long gone.
from omniloop import export_tuned_config_from_journal
values = export_tuned_config_from_journal("run.omni", "tuned.yaml")omniloop inspect run.omni prints the same values without writing code.
Functional / JAX loops
Section titled “Functional / JAX loops”FunctionalTrainingLoop
Section titled “FunctionalTrainingLoop”For loops whose state is an immutable pytree, where mutating a config object in place is not an option.
from omniloop import FunctionalTrainingLoop
loop = FunctionalTrainingLoop(params, tunable=["learning_rate"])
@jax.jitdef step(params, batch): ...
for batch in data: params = loop.apply_mutations(params) # returns an updated pytree params = step(params, batch) loop.log(loss=float(loss))apply_mutations returns a new structure with pending edits applied, safe to
use inside a jit-compiled step.
Utilities
Section titled “Utilities”find_repo_root(start=None)
Section titled “find_repo_root(start=None)”Locates an OmniLoop repository checkout by walking up from start (or the
current directory), honouring OMNILOOP_REPO_ROOT. Used by omniloop up to
find the dashboard; exported because integrations sometimes need the same
answer. Returns None when there is no checkout — a pip-only install is a
normal case, not an error.