Skip to content

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.

Terminal window
omniloop --version # SDK version, native core build, and Python
omniloop --help # every subcommand

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.


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.

Terminal window
omniloop up # server + dashboard
omniloop up --no-dashboard # server only
omniloop up --session exp-a # isolate this stack from other runs
omniloop 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:

Terminal window
$ 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=viGk883W1zVzlOlyxajSf5KQnp3IHFVr

The 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”:

Terminal window
omniloop up --session arm_left --watch arm_right,estimator

Each 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.

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.


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.

Terminal window
omniloop demo # run until Ctrl+C
omniloop demo --steps 500 # stop after 500 iterations
omniloop demo --interval 0.01 # 100 Hz instead of the default 20 Hz
omniloop 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.


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.

Terminal window
omniloop status
omniloop status --session exp-a
omniloop status --server http://127.0.0.1:8000
omniloop 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.

The in-depth version: watches the channel, queries the server, and names the fault.

Terminal window
omniloop doctor
omniloop 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.

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.

Terminal window
omniloop clean --dry-run # show what would go
omniloop clean --session exp-a # one session only
omniloop 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.


The one-command summary: format version, record counts by kind, tick range, recorded span, events, and the final tuned parameter values.

Terminal window
omniloop inspect run.omni
omniloop 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.

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.

Terminal window
omniloop verify run.omni
omniloop 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.

Steps through a recorded run tick by tick: what was mutated, what the loop did, and the metrics around it.

Terminal window
omniloop replay run.omni --events-only # just the things that happened
omniloop replay run.omni --tick 4350 # start at a tick
omniloop 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

Finds the first tick where two runs’ state hashes disagree.

Terminal window
omniloop diff recorded.omni replayed.omni

Exits 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.

The causal chain. Telemetry is excluded: this is what was done, not the metrics it moved.

Terminal window
omniloop why run.omni --tick 4350 # what led here
omniloop why run.omni --correlation-id corr-7 # what one mutation caused

Exactly 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.

Converts a journal to MCAP (for Foxglove, Rerun, and the ROS 2 ecosystem) or to newline-delimited JSON.

Terminal window
omniloop export-mcap run.omni # -> run.mcap
omniloop export-mcap run.omni --format jsonl # -> run.jsonl
omniloop export-mcap run.omni -o /tmp/out.mcap

MCAP 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.


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.

Terminal window
omniloop mcp # stdio, read-only (observe mode)
omniloop mcp --allow-control # + mutation, halt/step, watchpoints, bounds
omniloop mcp --mode journal # journal tools only; touches no shared memory
omniloop 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.


Terminal window
omniloop completion bash > /etc/bash_completion.d/omniloop
omniloop completion zsh > "${fpath[1]}/_omniloop"
omniloop completion fish > ~/.config/fish/completions/omniloop.fish

Generated from the live parser, so new subcommands and flags appear automatically.