Skip to content

[Real-world active learning] PR 4: in-process RealRobot + babyrobot submodule - #93

Merged
amburger66 merged 3 commits into
masterfrom
real-robot-in-process-bridge
Jul 27, 2026
Merged

[Real-world active learning] PR 4: in-process RealRobot + babyrobot submodule#93
amburger66 merged 3 commits into
masterfrom
real-robot-in-process-bridge

Conversation

@amburger66

@amburger66 amburger66 commented Jul 27, 2026

Copy link
Copy Markdown

Plan PR 4 of claude_plans/BRIDGE_INTERFACE_PLAN.md (in BabyRobotPredicator).

Cross-link: this is the predicators half of BabyRobotPredicator#49 — PR 2: RealRobot in-process interface, which added the RealRobot library this PR calls. Per CONTRIBUTING.md, the two are one change split across repos.

What

Replaces the two-process JSON socket bridge with an in-process library call. No behavior change on the arm — still whole-episode, still open-loop.

  • Submodule. BabyRobotPredicator is added as submodules/BabyRobotPredicator, pinned at 3565015 (its plan PRs 0–2: pyproject.toml, the message dataclasses, and RealRobot).
  • real_robot_bridge.py. RealRobotBridgeClient and the entire socket/JSON transport are deleted — no sockets, no serialization, no length-prefix framing, no remote-code-execution surface, because there is no longer a wire. What remains is:
    • make_real_robot(), a factory that imports babyrobot inside the function body and constructs the RealRobot with dry / perception from config;
    • _split_actions(actions, layout), now driven by a GripperJointLayout (four numbers) rather than reaching into a SingleArmPyBulletRobot;
    • split_actions_by_option(actions), which groups a buffered episode into per-option chunks. Unused by this PR — it is what the real-world wrapper (plan PR 6) needs to ask for one observation per option boundary — but it lands and is tested here alongside its sibling.
  • pybullet_domino_real.py. Holds a RealRobot instead of a socket client and calls it directly. Same buffering, same whole-episode flush, same homing before the first option.
  • Settings. real_robot_bridge_host / _port are dropped (there is no socket to address); real_robot_dry and real_robot_perception are added.

babyrobot stays optional

It is a private repo, so predicators' CI cannot clone it. That constraint is enforced, not assumed:

  • not in install_requires; installed separately from the submodule path;
  • no module under predicators/ imports babyrobot at module level — a test asserts this on the parse tree, so it holds whether or not babyrobot happens to be installed;
  • mypy.ini gets a [mypy-babyrobot.*] stanza covering both the present and absent cases;
  • asking for real-robot execution without it raises one clear error naming the submodule and the two install commands, and only when the factory is actually called;
  • README.md documents the optional install.

Tooling scoped away from submodules/

run_autoformat.sh reformatted 40+ files inside the submodule on its first run — dirtying another repo's working tree with this repo's style config. run_autoformat.sh, scripts/run_checks.sh (which the README points at as the one-line local check), conftest.py, .coveragerc and mypy.ini are all scoped to skip submodules/ now.

The submodule lives at submodules/, not third_party/, to keep it distinct from the pre-existing predicators/third_party/ — that holds vendored source (fast_downward_translator, ikfast) imported through the predicators namespace, whereas this is a pointer to a live external repo installed as its own distribution and imported as babyrobot.

Not in this PR

Per the plan these are PRs 5–7 and are deliberately absent: observation conversion (state_from_observation / task_from_observation), option-boundary observation, twin resynchronization, and RealWorldEnv.

How this was tested

  • tests/pybullet_helpers64 passed.
  • tests/pybullet_helpers/test_real_robot_bridge.py collects 15 tests in both configurations (so a silent mass-skip cannot pass for green): 15 pass with the submodule installed, 7 pass / 8 skip without it. import predicators.envs works in both.
  • mypy . --config-file mypy.ini — clean, 661 source files.
  • Full pytest . --pylint -m pylint gate — the changed files rate 10.00/10; yapf --diff clean.
  • Dry-robot smoke check (no hardware): a Pick chunk and a Place chunk shipped as separate step calls produce exactly one close at the hand. This is the bug that forced whole-episode shipping — the split still re-emits the leading close on the Place, and RealRobot's session-wide gripper dedup drops it, so the release is not dropped.

Not validated on hardware. The Franka was not moved; every check above ran with RealRobot(dry=True) or against a stub. The arm smoke test is human-run at the rig (plan verification step 9).

Note for reviewers

.gitmodules uses the SSH URL (git@github.com:BasisResearch/BabyRobotPredicator.git), matching how the repo is cloned today. Say the word if an HTTPS URL suits more people.

🤖 Generated with Claude Code

…ng helpers

Plan PR 4 of BRIDGE_INTERFACE_PLAN.md. Replaces the two-process JSON socket
bridge with an in-process library call. **No behavior change on the arm**:
still whole-episode, still open-loop.

- Add BabyRobotPredicator as the private submodule third_party/BabyRobotPredicator,
  pinned at 3565015 (its plan PRs 0-2: pyproject, message dataclasses, RealRobot).
- real_robot_bridge.py: delete RealRobotBridgeClient and the whole socket/JSON
  transport. What remains is a make_real_robot() factory that imports babyrobot
  lazily, and the action-splitting helpers -- _split_actions (now driven by a
  GripperJointLayout rather than a SingleArmPyBulletRobot) plus
  split_actions_by_option, which the real-world wrapper will use to chunk by
  option boundary.
- pybullet_domino_real.py: holds a RealRobot instead of a socket client and
  calls it directly. Same buffering, same whole-episode flush, same homing.
- Settings: drop real_robot_bridge_host/port (there is no socket); add
  real_robot_dry and real_robot_perception.

babyrobot stays OPTIONAL: it is not in install_requires, no module under
predicators/ imports it at module level, mypy gets a [mypy-babyrobot.*] stanza,
and asking for real-robot execution without it raises one error naming the
submodule and the install command. `import predicators.envs` and `pip install -e .`
both work on a checkout that cannot clone it -- which is what CI is.

Also scopes run_autoformat.sh, conftest.py, .coveragerc and mypy.ini away from
third_party/, so this repo's tooling never rewrites a submodule's working tree.

Deliberately NOT in this PR (they are plan PRs 5-7): observation conversion,
option-boundary observation, twin resynchronization, RealWorldEnv.

Tested: tests/pybullet_helpers 64 passed; the new test file collects 15 in both
configurations -- 15 pass with the submodule installed, 7 pass / 8 skip without
it. mypy clean (661 files); pylint 10.00/10 on the changed files; yapf clean.
Dry-robot smoke check: a Pick chunk and a Place chunk shipped separately produce
exactly one `close` at the hand, so the release is not dropped.
@amburger66 amburger66 self-assigned this Jul 27, 2026
@amburger66
amburger66 marked this pull request as ready for review July 27, 2026 18:49
Comment and prose edits only, no code change. Strips a trailing space on
settings.py:555 that tripped yapf and pylint's trailing-whitespace check.
@amburger66
amburger66 requested a review from yichao-liang July 27, 2026 19:12
@yichao-liang

Copy link
Copy Markdown
Collaborator

Thanks for the great work!
One comment: there is already a third_party directory inside the predicators directory here. Would it make sense to install the submodule there?

@amburger66

amburger66 commented Jul 27, 2026

Copy link
Copy Markdown
Author

Thanks for the great work! One comment: there is already a third_party directory inside the predicators directory here. Would it make sense to install the submodule there?

@yichao-liang Thanks for reviewing! Right, I had the same thought so I asked claude --

predicators/third_party/ is vendored source — 42 files checked into this repo as ordinary files (fast_downward_translator, ikfast),
imported through the predicators namespace: from predicators.third_party.fast_downward_translator.translate import ...
(predicators/utils.py:71).

third_party/BabyRobotPredicator is a pointer to a live external repo, installed as its own pip distribution from its own
pyproject.toml and imported as babyrobot — never as predicators.third_party.babyrobot. Its location on disk doesn't determine its
import path; pip install -e third_party/BabyRobotPredicator does.

Reasons that distinction pushed it to the top level:

1. predicators/ is the packaged directory. setup.py runs find_packages(include=["predicators", "predicators.*"]) with
include_package_data=True. Dropping a second repo's entire tree — its .git, scenes, models, its own tests — inside the directory that
pip install -e . treats as the package root invites it into predicators' own distribution. The existing vendored dirs are small,
deliberately-chosen source files, not a whole checkout.
2. Nesting would imply a namespace relationship that doesn't exist. Everything currently under predicators/third_party/ is reachable
as predicators.third_party.*. babyrobot isn't, and shouldn't be — the shared message classes are the contract precisely because both
repos import the same babyrobot.realrobot.messages.

I think this makes sense, but maybe we want to name the folder something else (like submodules) to disambiguate. Thoughts?

@yichao-liang

Copy link
Copy Markdown
Collaborator

I see! submodules sounds like a better name, let’s use that!

The submodule lived at third_party/BabyRobotPredicator, which collided with the
pre-existing predicators/third_party/ -- a different thing entirely: vendored
source (fast_downward_translator, ikfast) imported through the predicators
namespace. submodules/ says what this one is: a pointer to a live external repo,
installed as its own distribution and imported as `babyrobot`.

- git mv third_party submodules, and rename the .gitmodules section so the
  submodule's name matches its path. Still pinned at 3565015.
- Retarget the exclusions: conftest.py, .coveragerc, mypy.ini, run_autoformat.sh.
- Update the paths in the README, the missing-babyrobot error message, and the
  test that asserts that message names the submodule.

Two fixes that fall out of this:

- mypy's exclude regex said `third_party`, which also matched the vendored
  predicators/third_party and silently dropped it from the check. Narrowing it
  to `submodules` restores master's coverage: mypy now reports 699 files against
  master's 698, the difference being this branch's one new test file.
- scripts/run_checks.sh -- which the README points at as the one-line way to run
  the checks locally -- had the same submodule-dirtying bug run_autoformat.sh
  did: `docformatter -i -r .` and `isort .` walk the whole tree. Both now skip
  submodules/.

Verified with the submodule both installed and absent: yapf/isort/docformatter
clean, mypy clean, pylint 10.00/10 on changed files, tests/pybullet_helpers
64 passed with babyrobot installed from the new path and 56 passed / 8 skipped
without it.
@amburger66
amburger66 enabled auto-merge (squash) July 27, 2026 20:00
@amburger66
amburger66 merged commit 207aec9 into master Jul 27, 2026
14 checks passed
@amburger66
amburger66 deleted the real-robot-in-process-bridge branch July 27, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants