Step, Debug, and Replay (Notebook)
The notebook companion to Debug and Visualize:
pipe.step(dt=...) advances a graph one tick at a time in your own Python
process, so a normal debugger stops inside Flow.step(). Here we step a small
graph, read back a StepResult, inspect the validated IR, and record a run so it
can be replayed deterministically. Every output block below is captured from a
real run — nothing here needs a backend, camera, or robot.
⬇ Download .ipynb▶ Open in Colab
A graph to debug
Section titled “A graph to debug”Three tiny Flows: a Counter source on a clock, a Doubler that fires on each
arrival, and a Sink that keeps the last value it saw. This is an ordinary
Retriever graph — the only difference is that we’ll drive it with step()
instead of handing it to a backend.
Output
pipe.step(dt=...) advances one tick
Section titled “pipe.step(dt=...) advances one tick”step(dt=0.1) runs one sample -> step -> publish pass over the whole graph and
advances a logical clock by dt. It returns synchronously, so a breakpoint in
any Flow.step() stops here in this process — no remote debugger to attach.
Output
Takeaway: every tick advances logical time by dt and executes every node whose clock fires. Because the stepper is single-process and synchronous, the debugger treats step() like any other function call.
Read back a StepResult
Section titled “Read back a StepResult”Each step() returns a StepResult — a frozen record of what that tick did:
.executed (node ids that ran), .outputs (each node’s returned object), and
.inputs (what each node consumed). No printing inside a Flow required; you read
the tick’s effects straight off the result.
Output
Takeaway: Counter emitted value=1, Doubler turned it into value=2, and Sink returned None but stashed the value in its own state — exactly what a breakpoint would have shown you mid-tick.
pipe.validate() gives you the IR
Section titled “pipe.validate() gives you the IR”Before debugging runtime behavior, check the graph you actually built.
pipe.validate() compiles the authored graph to its Intermediate Representation:
typed nodes, edges (with sync adapter and queue size), and the execution
topology. This is the same IR the backends and pipe.visualize() consume.
Output
Takeaway: if the IR is wrong — a missing edge, an unexpected adapter, the wrong execution order — fix the wiring first. Do not start by debugging backend scheduling, cameras, or multiprocessing.
Record a run, then replay it
Section titled “Record a run, then replay it”pipe.record(node, path, steps=...) steps the graph and saves a node’s output
stream to disk. A .pkl.gz artifact is pure gzip + pickle — no extra
dependencies — so it round-trips anywhere retriever-core runs.
Output
pipe.replay(node, path=...) swaps that node for an in-process replay source that
re-emits the recorded values in order. The rest of the graph is untouched, so
downstream Flows re-run against a fixed input — no live source needed, and every
run is identical.
Output
Takeaway: the replayed source reproduces the recorded values exactly, and Doubler doubles each one deterministically. That is the debug loop: inspect the IR, step in-process with a debugger, then record once and replay many times to turn a timing-sensitive run into a stable artifact you can share. Next: Debug and Visualize extends this to perception, Rerun, and MCAP recordings.
This page is generated from notebooks/src/step_debug_replay.py (jupytext py:percent source). Grab the runnable notebook and run every cell yourself.
