real robot: a fixed-plan explorer, and a record of every look - #115
Merged
Conversation
Three pieces for exercising the online loop on hardware, where the cost is wall-clock rather than compute. fixed_plan explorer. The planning explorers cost minutes per episode, so a full cycle is expensive to run for the sake of testing the loop around it. This one replays a plan file every episode and costs nothing, so a run that goes wrong is the loop's fault and not the planner's. It reads replay_plan's format, which means a plan dumped by probe_real_scene --dump-plan and already verified through replay_plan can be handed straight to the loop. Grounded per episode, because a human reset rebuilds the task and hands back fresh Objects. Every look is logged, not only the ones over tolerance. A run whose looks all behaved previously said nothing at all, so there was no way to distinguish "perception was healthy" from "nobody checked". The line carries the per-object breakdown, which is what tells a single knocked domino apart from a table-height offset shared by all of them -- the max alone cannot. Looks can be dumped to JSON (real_robot_observation_dump_dir, off by default). Each file records what was perceived beside what the twin predicted, so a session can be re-examined offline without the robot. Write failures are logged and swallowed: losing a dump must not take the arm down mid-episode. The learner is untouched.
Stage 6 proper is exp_domino_real.yaml -- live cameras, a live arm, a human reset per episode, a planning explorer costing minutes an episode. That is what to run once the loop is trusted, and the wrong thing to debug the loop with. This keeps the loop shape (explore, learn, test) and makes one pass survivable: the fixed_plan explorer replays a verified plan instead of planning, nothing moves, no camera opens, and every look is dumped. It runs with the robot powered down. It does not replace the learner, which is separately expensive; the header says so.
Wait counts consecutive settled steps in its own memory, and its option policy already consults it once per step. OptionBoundaryBuffer consulted it a second time, so Wait judged the scene settled in a third of the steps it really takes. The check now restores the option's memory, making it a pure query.
Wait ends once the scene holds still for several consecutive steps. Writing perception into the twin moves objects without the scene having moved, so Wait counted every look as motion, zeroed its tally, and never saw the scene settle -- the episode then ran to the step cap instead of ending when the plan did, spending a look every few steps on the way. rebaseline_quiescence re-seeds the baseline and keeps the tally, so the jolt is skipped rather than counted.
The old name described the mechanism, so a reader had to already know what a quiescence baseline was to guess what the call did. The new one names the event instead: the state was set from outside rather than moved into, which is the thing the caller actually knows.
amburger66
marked this pull request as ready for review
August 5, 2026 18:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Stage 6 (real active learning) is the first stage where the loop runs for hours, and its cost is wall-clock rather than compute: every episode pays for a planning explorer, and every human reset pays for a person. Testing the loop shouldn't require paying for the planner. Separately, a run today leaves almost no record of what perception actually saw.
1. A
fixed_planexplorerThe explorer is already a plug point (
explorer:in the approach config). The shipped cheap ones —random_options,random_nsrts,no_explore— are all random, so they can't reproduce a specific failure.fixed_planreplays one plan file every episode. It readsreplay_plan.py's format, so a plan produced byprobe_real_scene --dump-plan, watched in simulation and already shipped to the arm throughreplay_plan, can be handed straight to the online loop without retyping anything.Grounded per episode rather than once, because a human reset rebuilds the episode's task and hands back fresh
Objectinstances.2. Every look is logged, not just the ones over tolerance
TwinCorrector.absorbonly logged when divergence exceededreal_robot_divergence_atol. A run whose looks all behaved therefore said nothing — indistinguishable from nobody having looked.It now logs every look, with the per-object breakdown. That distinction matters:
_max_position_divergenceanswers "how bad", which is what the tolerance is checked against, but only "which object" tells a single knocked domino apart from a table-height offset shared by all of them.3. Looks can be dumped to JSON
real_robot_observation_dump_dir(empty = off) writes one file per look, recording what was perceived beside what the twin predicted, plus the per-object distances. That makes a session re-examinable offline, without the robot and without the cameras.Write failures are logged and swallowed — losing a dump must never take the arm down mid-episode.
4. Two bugs the rehearsal found, and the fix for each
Running the rehearsal is what surfaced these. Both concern
Wait, whoseterminalis stateful: it counts consecutive settled steps inoption.memory, and its own option policy consults it once per step.OptionBoundaryBufferwas consulting it a second time. Two callers were driving one tally, soWaitjudged the scene settled in a third of the steps it really takes. The boundary check now snapshots and restores the option's memory, making it a pure query.A twin correction was being counted as the scene moving. Writing perception into the twin replaces object poses without anything having moved. Since a correction only happens when a chunk ships — i.e. exactly at the boundary — it zeroed the tally at precisely the moment it was about to pay off, every cycle.
Waittherefore never terminated, the episode ran to the 500-step cap instead of ending when the plan did, and it burned a look every few steps on the way.note_external_state_changekeeps the tally and moves the comparison point past the jump.Measured on the rehearsal, same config and same plan, before → after:
Wait5. A rehearsal config
predicatorv3/stage6_rehearsal_domino_real.yamlruns the whole loop shape — explore, learn, test, reset — with the arm powered down and the cameras unplugged:real_robot_dryso nothing moves,scene_fileperception so no camera opens, and thefixed_planexplorer so an episode costs nothing and does the same thing every time.It does not replace the learner, which is separately expensive; that is called out in the config's own header.
Testing
pytest tests/pybullet_helpers/ tests/explorers/test_fixed_plan_explorer.py tests/envs/test_real_robot_execution.py tests/test_skill_factories.py— 163 passed. Full suite (excluding the network-dependentapproaches/agent_sdkdirs) 855 passed. pylint (repo rcfile), yapf, isort, docformatter and mypy all clean.New tests cover: the plan grammar including the
#header those files carry and the-> {...}subgoal tail; the explorer replaying the file rather than searching; a missing path failing loudly rather than quietly exploring some other way; per-object divergence naming and ordering; a dumped look recording both sides of the comparison; dumping staying off by default; the boundary check leaving a statefulterminaluntouched; and a settle tally surviving an external state change.Exercised end to end through the rehearsal, twice. Not exercised on hardware — that is Stage 6 itself.
Scope
The learner is deliberately untouched. Swapping the explorer removes the exploration cost, but
agent_po_predicate_invention_alstill invents predicates, which is separately expensive. A cheap stand-in there would beagent_sim_learningwithagent_sim_learn_oracle_sim_programandagent_sim_learn_oracle_sim_paramsboth on — handing the learner the oracle artifacts instead of synthesising them — but that is a separate change and unverified.One thing this PR does not contain: the rehearsal also surfaced a constant 4 mm z error in captured scenes, traced to the pose-estimation scripts defaulting to a table height that was superseded when it was measured. That fix belongs to BabyRobotPredicator and is on its
domino-table-z-4mmbranch.