Record & Replay
HORUS provides a record/replay system for capturing node execution and replaying it with tick-perfect determinism. This enables debugging workflows including time travel, mixed replay, and comparing behavior between runs.
Overview
The record/replay system supports:
- Full recording: Capture entire system execution
- Tick-perfect replay: Reproduce exact behavior deterministically
- Time travel: Jump to any recorded tick
- Mixed replay: Combine recorded nodes with live execution
- Playback control: Speed adjustment, tick ranges
Enabling Recording
Via Builder API
Enable recording through builder methods:
use horus::prelude::*;
// Enable recording via builder API
let mut scheduler = Scheduler::new()
.with_recording();
with_recording() takes no session name. The session is read from the HORUS_RECORD_SESSION environment variable, or auto-generated as recording_<secs>.<millis>_p<pid> when that variable is unset. That name is what every later command expects — horus record info <session>, Scheduler::delete_recording("<session>") — so set the variable if you need a predictable one.
Via CLI
# Record during a run
horus run --record my_session my_project
--record simply sets HORUS_RECORD_SESSION, so a plain Scheduler::new() starts recording under that session too — the .with_recording() call is not required when running under the CLI.
When recording is enabled, the scheduler captures each node's inputs, outputs and timing — but how much it captures depends on where the node runs:
| Node | Inputs | Outputs | Timing |
|---|---|---|---|
| Main-loop (BestEffort) | Yes | Yes | Yes |
RT (.rate(), .budget(), .deadline()) | Yes | No | Yes |
| Compute / Event / AsyncIo | No | No | Yes |
Output capture lives on the main-thread execution path only; the RT executor does not
implement it. Because .rate() alone promotes a node to RT, the nodes people most often
want to record produce snapshots with empty outputs — enough to replay into, not enough
to compare against.
If you need recorded outputs, keep the node on the main loop (no execution class) for the recording run, or compare against the topics themselves rather than the snapshot.
Replaying Recordings
Full Replay
Replay an entire recorded session. Note that replay does not stop when the recording is
exhausted — the clock clamps at the last timestamp and the scheduler keeps ticking — so bound
it with --stop-tick, run_for(..), or Ctrl-C:
use horus::prelude::*;
use std::path::PathBuf;
// Load and replay an entire scheduler recording
let mut scheduler = Scheduler::replay_from(
PathBuf::from("/home/you/.local/share/horus/recordings/crash/scheduler@abc123.horus")
)?;
scheduler.run()?;
Time Travel
Jump to specific tick ranges during replay:
// Start at a specific tick
let mut scheduler = Scheduler::replay_from(path)?
.start_at_tick(1500);
// Stop at a specific tick
let mut scheduler = Scheduler::replay_from(path)?
.stop_at_tick(2000);
// Adjust playback speed (0.01 to 100.0)
let mut scheduler = Scheduler::replay_from(path)?
.with_replay_speed(0.5); // Half speed
Mixed Replay
Combine recorded nodes with live execution for what-if testing:
use horus::prelude::*;
use std::path::PathBuf;
let mut scheduler = Scheduler::new();
// Add replay nodes from recordings
scheduler.add_replay(
PathBuf::from("recordings/Lidar@001.horus"),
0, // priority
)?;
// Add live nodes alongside
scheduler.add(live_controller).order(1).done();
scheduler.run()?;
Output Overrides
Override specific outputs during replay:
let mut scheduler = Scheduler::replay_from(path)?
.with_override("sensor_node", "temperature", 25.0f32.to_le_bytes().to_vec());
CLI Commands
Record and replay from the command line:
# Start recording during a run
horus run --record my_session my_project
# List recording sessions
horus record list
horus record list --long # Show file sizes and tick counts
# Show details of a session
horus record info my_session
# Replay a recording
horus record replay my_session
horus record replay my_session --start-tick 1000 --stop-tick 2000
horus record replay my_session --speed 0.5
# Compare two recording sessions
horus record diff session1 session2
horus record diff session1 session2 --limit 50
# Export to JSON or CSV
horus record export my_session --output data.json --format json
horus record export my_session --output data.csv --format csv
# Inject recorded nodes into a new run
horus record inject my_session --nodes camera_node,lidar_node
horus record inject my_session --all --loop
# Delete a recording session
horus record delete my_session
horus record delete my_session --force
Managing Recordings
// List all recording sessions
let sessions = Scheduler::list_recordings()?;
// Delete a recording session
Scheduler::delete_recording("old_session")?;
See Also
- Deterministic Execution - Reproducible node execution
- BlackBox Flight Recorder - Flight recorder for post-mortem debugging
- Scheduler Configuration - SchedulerConfig and node configuration