Custom policy support: almond_axol.policy SDK + run-policy policy_type custom - #325
shawnpatel wants to merge 1 commit into
Conversation
…_type custom Run Policy can now drive the arms from a model that isn't a LeRobot checkpoint. The new almond_axol.policy SDK (numpy + websockets only) lets a user subclass Policy (setup/reset/infer) or pass a plain function to serve(); run-policy with policy_type custom connects to it over a small documented WebSocket protocol, streams joint state + RGB frames + task, and executes the returned action chunks through the same aggregation, execution shaping, contact watchdog, episode control and rollout recording as LeRobot policies. - run_policy: AxolRobotClient's transport seams factored out; a custom client subclass does hello/reset/observation round trips, checks the declared action layout and fps, and stamps chunk rows from the observation timestep. policy_path is optional for custom (forwarded to the server). - serve/introspect: select options come from Literal annotations, so run-policy offers custom while collect-dagger does not. - Control panel: policy path is required only for LeRobot policy types; a note explains the custom server setup. - Docs: operations/custom-policy guide and api/policy reference (incl. wire protocol); run-policy pages updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9c037dc. Configure here.
| ) | ||
| self.fatal_error = exc | ||
| self.shutdown_event.set() | ||
| return |
There was a problem hiding this comment.
Slow inference aborts episode teardown
High Severity
The custom receiver stays inside PolicyClient.infer until the policy replies (up to 60s), and it still starts that wait for a leftover mailbox observation after shutdown. Episode teardown only joins workers for 5s, then closes the socket and flags a fatal error, so Save/Discard/timeout discards the episode and ends the run whenever inference is still in flight.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 9c037dc. Configure here.


Summary
Run Policy previously only ran LeRobot checkpoints. This PR adds support for running your own model: the robot sends joints and camera frames, and your code returns action chunks. It works from both the SDK and the control panel.
SDK:
almond_axol.policy. It needs only numpy and websockets, not LeRobot or torch.obs -> chunkfunction also works.action_namesandfps. The robot refuses to start on a mismatch.PolicyServerruns in the background (for tests and notebooks).PolicyClientis the robot side and can also exercise a server without a robot.run-policy. New
--policy_type custom, which connects to your server at--server_host/--server_port(default127.0.0.1:8765). No LeRobot server is started for it, and the checkpoint fps check is skipped.policy_pathbecomes optional for custom runs and is passed to your server unchanged.Only the connection to the policy is new. The existing client's gRPC-specific parts (open, reset, and installing a chunk) are split into methods, and a subclass swaps in the WebSocket client. The robot sends one observation at a time and waits for its chunk. That chunk then goes through the same code as before: chunk alignment and ensembling, the execution filter, the contact watchdog, episode control, and dataset recording.
Control panel.
Literaltype annotation.customtherefore appears for run-policy but not for collect-dagger, which loads LeRobot in-process.video_backendalso becomes a dropdown.Docs. New
operations/custom-policy(how-to) andapi/policy(reference, including the wire protocol). The run-policy pages, the control-panel guide, the README, and the module tree are updated.websockets>=13,<16is now a direct dependency. It was already installed throughuvicorn[standard].Not in this PR
axol policy.checkcommand for validating a policy server without a robot.127.0.0.1or firewalling the port.Test plan
uv run pytest: 1591 passed, coverage 57%. The newtests/test_custom_policy.pycovers:setuprefusing a sessionPolicyServerwith a fake robot: chunk truncation and timestep stamping, action-layout and fps mismatches, failures that stop the run, and an unreachable servertests/test_docs_examples.pychecks the new API page's tables and examples against the code.ruff check,ruff format --checknpm test,npm run lint,npm run format:check,npm run build🤖 Generated with Claude Code
Note
Medium Risk
New real-time robot control path over unauthenticated WebSocket; failures are guarded by action-schema and fps checks, but misconfigured or hostile policy servers could send bad actions if the port is exposed.
Overview
Adds custom policy support so Run Policy can drive the robot from a non-LeRobot model via a new
almond_axol.policySDK and WebSocket wire protocol (hello/reset/observation→actions).axol run-policy --policy_type customconnects to an operator-hosted policy server (almond_axol.policy.serve) at--server_host/--server_port(default127.0.0.1:8765) instead of spawning or using LeRobot’s gRPCPolicyServer.policy_pathis optional for custom runs and forwarded verbatim; LeRobot checkpoint fps checks are skipped (the server may declare fps in handshake).AxolCustomPolicyClientreuses the same chunk aggregation, execution filtering, and episode flow as LeRobot by swapping only transport.CLI/config: new
RunPolicyType(customonly on run-policy, not collect-dagger).websocketsis a direct base dependency. Control panel derivespolicy_typedropdown fromLiteralannotations, makespolicy_pathrequired only for LeRobot types, and shows a custom-policy hint. Docs/README cover the SDK, operations guide, and API reference; tests cover protocol, server/client sessions, and the robot-side client.Reviewed by Cursor Bugbot for commit 9c037dc. Configure here.