CLI Reference
The omniloop command is installed with the SDK (pip install omniloop). It
does three jobs: it starts the visualizer stack, it tells you why the channel
between your loop and the dashboard is not working, and it reads .omni
journals after the fact.
omniloop --version # SDK version, native core build, and Pythonomniloop --help # every subcommandExit codes
Section titled “Exit codes”Every command is scriptable. The codes are uniform:
| Code | Meaning |
|---|---|
0 |
Healthy, or the operation completed |
1 |
A fault was found, or the input could not be used at all |
2 |
Partial: the journal was damaged and only a prefix was recovered |
3 |
Diverged: diff found the two runs disagreeing (a finding, not an error) |
Commands that produce structured output accept --json.
Running the stack
Section titled “Running the stack”omniloop up
Section titled “omniloop up”Starts the telemetry server and the dashboard together, streams both logs with
a prefix, and shuts both down on Ctrl+C. Works from a plain pip install — the
wheel carries the server and a prebuilt dashboard.
omniloop up # server + dashboardomniloop up --no-dashboard # server onlyomniloop up --session exp-a # isolate this stack from other runsomniloop up --token # require auth; generates and prints a token| Flag | Effect |
|---|---|
--session ID |
Sets OMNILOOP_SESSION_ID for the child processes |
--watch IDS |
Also watch these comma-separated sessions (OMNILOOP_SESSIONS) |
--no-dashboard |
Start only the telemetry server |
--token [TOKEN] |
Require clients to authenticate (OMNILOOP_AUTH_TOKEN). Give the flag alone to generate one |
Authentication is off by default, which is what a local single-user tool should be — but the Origin allow-list accepts any loopback origin, so with auth off any page served on any localhost port can mutate parameters and halt the loop. On a shared bench machine, or one running someone else’s dev server, that is worth one flag:
$ omniloop up --token [ok] dashboard http://127.0.0.1:8000/?token=viGk883W1zVzlOlyxajSf5KQnp3IHFVr
[note] authentication is ON — clients must present this token: OMNILOOP_AUTH_TOKEN=viGk883W1zVzlOlyxajSf5KQnp3IHFVrThe printed dashboard URL carries the token, so enabling auth does not lock out
the operator who enabled it. Unauthenticated clients are handed no capabilities
and are closed with 4401 on their first command.
--watch is what turns a relay from “one loop” into “one robot”:
omniloop up --session arm_left --watch arm_right,estimatorEach node keeps its own segment pair and its own single publisher — that is what makes a telemetry read cheap and tear-free — and the relay fans them into one dashboard on one time axis. A node that has not started yet is logged and skipped rather than fatal. Commands stay per-loop: a connection drives the session it selected, never “whichever published last”.
It prints the URLs it just started, and with --session, the exact line to run
your loop with in the other terminal — the two have to agree on the session
or they read different shared-memory segments.
up preflights both ports. If something already holds 127.0.0.1:8000 it says
so and stops, rather than letting uvicorn fail seconds later behind a wall of
child output.
Which dashboard you get
Section titled “Which dashboard you get”| Situation | What up serves |
Where |
|---|---|---|
pip install omniloop, any directory |
the dashboard bundled in the wheel | http://127.0.0.1:8000 |
A repository checkout with npm install done |
the Vite dev server, so edits hot-reload | http://localhost:5173 |
A checkout without Node or node_modules |
the bundled dashboard, and says why it is not the dev server | http://127.0.0.1:8000 |
| Neither a bundle nor Node | nothing — the server runs headless and up names the build command |
— |
Set OMNILOOP_DASHBOARD_DIR to serve a bundle from a specific directory
instead of the packaged one.
omniloop demo
Section titled “omniloop demo”Runs a bundled mock training loop for the dashboard to drive. It ships in the
wheel, so a fresh pip install has something to point at without cloning the
repository.
omniloop demo # run until Ctrl+Comniloop demo --steps 500 # stop after 500 iterationsomniloop demo --interval 0.01 # 100 Hz instead of the default 20 Hzomniloop demo --journal demo.omni # record it for replay| Flag | Effect |
|---|---|
--steps N |
Stop after N iterations |
--interval S |
Seconds per iteration (default 0.05) |
--journal PATH |
Record the run to an .omni journal |
It exposes learning_rate, entropy_coef, batch_size and exploration as
live controls. learning_rate carries a limits= envelope narrower than its
slider, so dragging past 5e-3 demonstrates
limit enforcement and the clamp badge.
Diagnostics
Section titled “Diagnostics”omniloop status
Section titled “omniloop status”A two-line health snapshot. Exits 0 only when a loop is publishing and the
server is up, so it gates a script that is about to talk to a running stack.
omniloop statusomniloop status --session exp-aomniloop status --server http://127.0.0.1:8000omniloop status --json| Flag | Default | Effect |
|---|---|---|
--session ID |
env | Session to check |
--server URL |
http://127.0.0.1:8000 |
Telemetry server base URL |
--json |
off | Machine-readable health snapshot |
The channel is reported as one of three states:
| State | Meaning |
|---|---|
live |
A loop is publishing now, with its rate |
stale |
A frame is retained in shared memory but nothing is publishing — the loop exited or crashed |
absent |
Nothing has ever published on this session |
stale is its own state because shared memory keeps the last frame written for
as long as the segment exists. A single successful read proves the session was
used, never that anything is running.
omniloop doctor
Section titled “omniloop doctor”The in-depth version: watches the channel, queries the server, and names the fault.
omniloop doctoromniloop doctor --json --watch-seconds 2 # for CI| Flag | Default | Effect |
|---|---|---|
--session ID |
env | Session to probe |
--server URL |
http://127.0.0.1:8000 |
Telemetry server base URL |
--watch-seconds N |
1.0 |
How long to observe. Must be > 0; use ≥ 0.25 for a conclusive live/stale verdict |
--json |
off | Machine-readable findings |
Fault codes, all of which appear in faults[] under --json:
| Code | Meaning |
|---|---|
stale_segment |
A retained frame, but no publisher |
no_telemetry |
Nothing has ever published on this session |
publisher_collision |
Two or more PIDs publishing into one session |
shm_unavailable |
The shared-memory channel could not be opened |
server_unreachable |
Nothing listening at --server |
server_error |
Server reachable, query failed |
session_mismatch |
Server reads a different session than you |
ring_corruption |
Non-zero command-ring corruption counters |
The reported rate is the loop’s publish rate, not the probe’s. doctor
samples at 20 Hz, so on a faster loop it sees only a fraction of the frames; it
reconstructs the true rate from the engine’s telemetry_skipped counter and
reports what it personally sampled separately. See
omniloop doctor for the counter semantics.
omniloop clean
Section titled “omniloop clean”Purges stale shared-memory segments left behind by processes that died hard —
/dev/shm on Linux, the shared_memory-rs backing files under %TEMP% on
Windows.
omniloop clean --dry-run # show what would goomniloop clean --session exp-a # one session onlyomniloop clean # everything stale| Flag | Effect |
|---|---|
--session ID |
Only purge that session (default: all) |
--dry-run |
List what would be removed, remove nothing |
--force |
Also remove segments that look live |
Segments written to in the last minute are skipped by default. Removing one out from under a running loop corrupts its command ring, and “all sessions” on a shared machine otherwise includes your colleague’s run.
Windows leaks too. The kernel does reference-count the named section, but
each segment is also backed by a file in %TEMP%\shared_memory-rs, unlinked only
when its owner drops cleanly. A killed loop leaves the file and its last frame
behind, at ~1 MB per segment and two segments per session — which doctor then
reports as a stale segment holding a frozen frame. On one developer machine this
had accumulated 552 files / 552 MB over ten days. clean enumerates and removes
them like any other platform.
On macOS POSIX objects persist until reboot but are not exposed as files, so
there is nothing to enumerate — clean says so rather than claiming success.
Reading journals
Section titled “Reading journals”omniloop inspect
Section titled “omniloop inspect”The one-command summary: format version, record counts by kind, tick range, recorded span, events, and the final tuned parameter values.
omniloop inspect run.omniomniloop inspect run.omni --json--json carries intact (boolean) and integrity — gate CI on intact, so a
summary of a truncated journal is not mistaken for a short run.
omniloop verify
Section titled “omniloop verify”Verifies the tamper-evident SHA-256 hash chain of an .omni journal file, checking that no record was altered, removed, re-timed, or re-ordered.
omniloop verify run.omniomniloop verify run.omni --json| Flag | Effect |
|---|---|
--json |
Output verification verdict, checkpoint count, record count, and break details as JSON |
Exits 0 if the hash chain is fully consistent and untampered, 1 if the chain failed verification or could not be opened.
omniloop replay
Section titled “omniloop replay”Steps through a recorded run tick by tick: what was mutated, what the loop did, and the metrics around it.
omniloop replay run.omni --events-only # just the things that happenedomniloop replay run.omni --tick 4350 # start at a tickomniloop replay run.omni --limit 50| Flag | Effect |
|---|---|
--tick N |
Start the timeline at this tick |
--limit N |
Show at most N records (the whole file is still integrity-checked) |
--events-only |
Mutations, control, lifecycle and input only — hide periodic telemetry |
--json |
Emit the timeline as JSON |
--verify |
Explain how to run a true deterministic replay |
omniloop diff
Section titled “omniloop diff”Finds the first tick where two runs’ state hashes disagree.
omniloop diff recorded.omni replayed.omniExits 3 on a divergence, 0 when every shared checkpoint matched. If the two
journals share no hash checkpoints the pass is vacuous, and diff says so
rather than reporting a clean result — record with hash_every= to get them.
omniloop why
Section titled “omniloop why”The causal chain. Telemetry is excluded: this is what was done, not the metrics it moved.
omniloop why run.omni --tick 4350 # what led hereomniloop why run.omni --correlation-id corr-7 # what one mutation causedExactly one of --tick or --correlation-id is required. The --tick view
suggests a correlation id it actually found, so following a mutation forward is
one copy-paste away.
omniloop export-mcap
Section titled “omniloop export-mcap”Converts a journal to MCAP (for Foxglove, Rerun, and the ROS 2 ecosystem) or to newline-delimited JSON.
omniloop export-mcap run.omni # -> run.mcapomniloop export-mcap run.omni --format jsonl # -> run.jsonlomniloop export-mcap run.omni -o /tmp/out.mcapMCAP export needs the extra: pip install 'omniloop[mcap]'.
A damaged journal exports its surviving prefix and exits 2; the partial file
is kept, and a salvaged MCAP is finished properly so standard readers open it.
AI agents
Section titled “AI agents”omniloop mcp
Section titled “omniloop mcp”Runs the MCP server so an AI coding agent can drive the control plane: read the halt context behind a frozen loop, tune a parameter live, and verify the fix reproduces.
omniloop mcp # stdio, read-only (observe mode)omniloop mcp --allow-control # + mutation, halt/step, watchpoints, boundsomniloop mcp --mode journal # journal tools only; touches no shared memoryomniloop mcp --list-tools # print the surface and exit, starting nothing| Flag | Default | Effect |
|---|---|---|
--mode |
observe |
Server operating mode (observe, control, journal) |
--allow-control |
off | Shorthand for --mode control to grant live parameter tuning and halt control |
--server-url URL |
ws://127.0.0.1:8000/ws |
Relay server WebSocket URL for live control |
--timeout SECS |
5.0 |
Timeout in seconds for relay operations |
--list-tools |
off | Print exposed MCP tools, resources, and prompts, then exit |
Needs the extra: pip install 'omniloop[mcp]'.
This command speaks JSON-RPC over stdin/stdout and is launched by an MCP host,
not run by hand — it says so and points at --list-tools if stdout is a
terminal. --allow-control grants write access to a loop that may be driving
hardware, which is why it is opt-in.
Shell completion
Section titled “Shell completion”omniloop completion bash > /etc/bash_completion.d/omniloopomniloop completion zsh > "${fpath[1]}/_omniloop"omniloop completion fish > ~/.config/fish/completions/omniloop.fishGenerated from the live parser, so new subcommands and flags appear automatically.