Perception-planning loop
New observations update belief and memory, so the planner reasons over the latest task state instead of a stale prompt snapshot.
A compositional programming model and runtime for closed-loop robot agents whose modules run on different clocks.
Robot systems will continue to draw on different model libraries, middleware, runtimes, and hardware interfaces. As these systems become more modular, they need a common way to describe what each module computes, how modules connect, and when their computations happen.
Deep learning found such a boundary in the computation graph: layers remain reusable while runtimes map the graph to devices. A robot agent needs more from its graph. Its modules are stateful, run on different clocks, and close feedback loops while the world keeps moving.
Retriever represents an agent as a temporal computation graph. A stateful Flow is the unit of composition; its Clock says when it runs; each edge’s Sync policy says which upstream history it consumes. This graph is the stable program. It can be compared and analyzed, stepped locally for debugging, or executed asynchronously across supported backends without changing its temporal meaning.
Our work develops four connected pieces: the continuous-time formulation behind the graph, the Python programming model used to author it, the runtime that executes it, and Retriever-0, a real-robot pipeline that puts the full stack together.
This page follows those pieces from behavior to implementation. The demonstrations show what the agent does; the pipeline and code show how it is composed; the interactive timeline shows how one graph unfolds across mismatched clocks.
Robotics is unlikely to settle on one planner, policy library, middleware, or hardware stack. Different frameworks can still converge on a shared graph vocabulary for modules, topology, clocks, and data handoff. That makes systems easier to compare, modules easier to replace, and execution traces easier to inspect. It also gives coding agents a concrete program to edit and debug.
The need grows rather than shrinks as embodied reasoning models improve. The current generation is explicitly designed to orchestrate lower-level policies as tools, track progress toward completion, and reason about the next step while the robot is still moving. Those are coordination properties, and they have to hold in a real program: something must decide when the reasoner is called, which observations it sees, whether its last plan is still valid, and what the controller does in the meantime. A stronger reasoner with variable latency widens the rate gap it has to be coordinated across. Retriever is the layer where those decisions are written down and checked, so a better model can be dropped in as one Flow rather than rebuilt into the glue.
Retriever-0 takes goals in natural language, plans with a VLM, keeps a belief of the scene across episodes, and executes learned bimanual skills in one closed-loop program. In Episode 1, the agent searches four drawers for black pepper. In Episode 2, it remembers the top-right drawer and goes there directly. The bag task exercises bimanual retrieval and distribution with a different composition of Flows.
Concretely, the running agent does five things at once, and each is a declared part of the program rather than a behaviour of the scheduler:
SkillCmd for the low-level policy.Take out any one of these and the task degrades in a specific way: without belief the agent re-searches drawers forever, without replanning it stalls once the plan stops matching what it knows.
Control may tick every few milliseconds while a VLM takes several seconds. Between them, perception, memory, planning, and learned skills update as the world changes. The behavior depends on which data crosses each rate boundary and when. Most robot stacks spread those decisions across callbacks, queues, and scheduler behavior. Retriever makes them part of the program.
A Retriever agent is a graph of reusable temporal modules. A Flow owns local state, a Clock decides when it runs, and a Sync policy on each edge selects which upstream history it consumes. These declarations keep a slow planner, a learned skill, and a fast controller independent without pushing timing logic into callbacks.
Retriever-0 is a demonstration agent for long-horizon manipulation under partial observability. It closes the perception–reasoning–action loop by combining camera observations, belief memory, VLM planning, VLA skills, execution monitoring, and high-rate control. As the task unfolds, the agent jointly updates its memory, plan, and action without interrupting execution.
Retriever-0 closes the loop across perception, belief memory, planning, skill execution, and control.
Read the graph through three elements:
Together, the blocks, clocks, and labeled edges form a complete, decomposable description of the agent’s temporal computation graph.
Feedback is part of Retriever-0’s graph. New observations change the belief used by planning. The execution monitor reads plan chunks and skill progress to decide when to replan or switch skills, while the controller keeps running.
New observations update belief and memory, so the planner reasons over the latest task state instead of a stale prompt snapshot.
The execution monitor uses plan chunks and progress predictions to trigger replanning or skill switching while execution continues.
Slow planning, medium-rate skill inference, and high-rate control run together because clocks and sync policies define the timing contract.
In Python, these loops are ordinary Pipeline edges declared with the same Flow, Clock, and Sync vocabulary.
The same graph fits in a short Python program. Flows define computation, clocks define run conditions, sync policies define what crosses each edge, and a Pipeline composes the pieces.
Each task class implements the core Flow API. The surrounding program assigns clocks, wires edges, and chooses sync policies. step() and run() execute the same graph.
# 1) Define Flows (what computes) and clocks (when they run)top_cam = CameraSource(id=0) @Rate(hz=30)wrist_cam = CameraSource(id=1) @Rate(hz=30)belief = BeliefMemoryFlow() @Trigger("inspection_done")monitor = ExecutionMonitorFlow() @Trigger("belief_updated", "progress_prediction")planner = VLMPlanFlow("gemini") @Trigger("replan")vla = VLASkillFlow("pi05") @Rate(hz=2)robot = ControllerFlow(id=0) @Rate(hz=200)
# 2) Compose the graph and declare sync policies on edgespipe = Pipeline("Closed-loop Agent")with pipe: wrist_cam.then(vla, sync=Latest()) \ .then(robot, sync=Chunking())
top_cam.then(belief, sync=Latest()) \ .then(planner, sync=Latest()) \ .then(monitor, sync=Latest()) \ .then(vla, sync=Latest())
# 3) Debug locally or deploy asynchronouslypipe.step(dt=0.1)pipe.run(backend="dora")Drag across the timeline to follow 15 seconds of execution. Each row is a Flow running on its own clock. Arrows show time spent computing, and blocks show outputs that remain valid until a replacement arrives.
Dots mark Flow starts. Dashed links connect causes to effects. They do not mark regular global ticks.
The graph, code, and timeline are three views of the same temporal contract. Retriever can step the graph locally or run it asynchronously without changing what the program means.
Each Flow samples inputs by timestamp. Given the same recorded history and a fixed order for equal timestamps, replay produces the same behavior regardless of live scheduling. The trace shows exactly what each Flow consumed, so a run can be inspected during debugging or reused as training data.
The technical blog covers the full formulation, synchronization semantics, runtime mapping, and proof sketch.
We thank Sebastian Castro, Toby Huang, Siyuan Huang, Haoyan Lin, Shoukang Yu, and Liyun Zhang for helpful technical discussions and contributions to implementation and experimentation. We thank Hao Zhou and Jason Wu for their support. Also, we thank Will Shen, Wenlong Huang, Leslie Kaelbling, Jeannette Bohg, and other colleagues for helpful discussions and feedback.