Add closed-loop atomic-action execution runner - #449
Conversation
1794d02 to
00456c8
Compare
3e13cce to
172c68c
Compare
00456c8 to
0bbeb2d
Compare
Greptile SummaryThe PR adds a controller-independent closed-loop runner, simulation adapter, acknowledgement and safe-stop lifecycle, late-bound PickUp targets, and supporting documentation and tests.
Confidence Score: 4/5The PR is not yet safe to merge because single-waypoint trajectories can still undergo terminal tracking validation before the controller has had a meaningful settling interval. A one-waypoint trajectory retains dt[:, 0] as its terminal hold; when that standard initial interval is zero, the runner waits only minimum_cycle_time before checking terminal tracking error, so an in-flight controller can trigger unnecessary replanning and exhaust recovery. Files Needing Attention: embodichain/lab/sim/atomic_actions/execution.py, embodichain/lab/sim/atomic_actions/runner.py
|
| Filename | Overview |
|---|---|
| embodichain/lab/sim/atomic_actions/runner.py | Adds non-blocking scheduling, controller acknowledgement handling, effect-verification boundaries, and cancel-then-hold safety behavior. |
| embodichain/lab/sim/atomic_actions/execution.py | Corrects multi-waypoint interval placement, but the previously reported single-waypoint zero-settling behavior remains. |
| embodichain/lab/sim/atomic_actions/sim_adapter.py | Adds simulation-backed observation, command, hold, cancellation, and deterministic clock integration. |
| embodichain/lab/sim/atomic_actions/primitives/pick_up.py | Extends grasp goals to resolve late-bound scene poses and register dynamic recovery dependencies. |
| tests/sim/atomic_actions/test_runner.py | Adds coverage for runner scheduling, acknowledgement, cancellation, verification, and batched behavior. |
Sequence Diagram
sequenceDiagram
participant App
participant Runner as ExecutionRunner
participant Adapter as Observation/Command/Clock
participant Session as ExecutionSession
App->>Runner: step() / run_until_blocked()
Runner->>Adapter: observe(task_state)
Adapter-->>Runner: PlanningContext
Runner->>Session: tick(context, effect_success)
Session-->>Runner: ExecutionTick + JointCommand
Runner->>Adapter: send(command)
Adapter-->>Runner: acknowledgement
Runner->>Adapter: sleep(hold_duration)
alt failure or cancellation
Runner->>Adapter: cancel()
Runner->>Adapter: hold(observed_position)
end
Reviews (6): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
This PR adds a closed-loop execution layer around typed atomic-action ExecutionSessions by introducing a controller/simulation-facing ExecutionRunner plus a simulation adapter that implements observation, command dispatch, and deterministic clock advancement. It completes the runtime loop implied by the typed planning/recovery contracts (stacked on #448) and documents a tracking-error recovery tutorial.
Changes:
- Introduces
ExecutionRunner(+ config, ports, acknowledgements, cancellation, and effect-verification blocking/resume behavior) as the session’s outer execution lifecycle. - Adds
SimulationExecutionAdapterimplementingObservationProvider,CommandSink, andExecutionClockfor deterministic headless simulation stepping. - Adds tests, tutorial script, and documentation/API/agent-context updates covering runner usage and tracking-error recovery.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/sim/atomic_actions/test_sim_adapter.py | New unit tests validating simulation adapter observation, dispatch semantics, stable ID correlation, and timing. |
| tests/sim/atomic_actions/test_runner.py | New unit tests validating non-blocking scheduling, acknowledgements, safe-stop behavior, replanning on tracking error, and effect verification boundaries. |
| scripts/tutorials/atomic_action/tracking_error_recovery.py | New runnable tutorial demonstrating injected tracking error, replanning, and completion in headless simulation. |
| embodichain/lab/sim/atomic_actions/sim_adapter.py | Adds simulation adapter implementing observation/command/clock ports for ExecutionRunner. |
| embodichain/lab/sim/atomic_actions/runner.py | Adds ExecutionRunner and related protocols/types for closed-loop scheduling and controller interaction. |
| embodichain/lab/sim/atomic_actions/execution.py | Exposes ExecutionSession.latest_context for runner safe-hold behavior and failure fallback. |
| embodichain/lab/sim/atomic_actions/init.py | Exports new runner and adapter APIs as part of the public atomic-actions module surface. |
| docs/source/tutorial/atomic_actions.rst | Updates tutorial docs to use ExecutionRunner + adapter and adds the new recovery example entry point. |
| docs/source/overview/sim/atomic_actions/index.md | Updates overview to include runner boundary and clarifies responsibility split between session/runner/adapters. |
| docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst | Adds API reference entries for runner, ports, adapter, and related enums/types. |
| agent_context/topics/atomic-actions/atomic-actions.md | Updates agent context to include runner/ports responsibilities and usage patterns. |
| agent_context/MAP.yaml | Updates topic keywords and file references to include runner and sim adapter. |
| .agents/skills/add-atomic-action/SKILL.md | Updates development skill guidance to reference runner/adapter as the execution integration path. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (2)
embodichain/lab/sim/atomic_actions/runner.py:674
ExecutionRunner._dispatch()usesassert command is not Noneto validate required inputs. Assertions are stripped withpython -O, which would turn these into potentialNonedispatches (and harder-to-diagnose failures). Prefer explicit checks that raise an exception (which you already convert into a rejected acknowledgement).
if operation is CommandOperation.SEND:
assert command is not None
acknowledgement = self._command_sink.send(
command,
timeout=self.cfg.command_timeout,
)
elif operation is CommandOperation.HOLD:
assert command is not None
acknowledgement = self._command_sink.hold(
embodichain/lab/sim/atomic_actions/sim_adapter.py:191
SimulationExecutionAdapter.send()returns early whenactive_maskis all-false, so it does not write the provided (hold) targets even though the docstring says inactive rows are written with active rows to avoid stale tracking. This can leave the controller/sim tracking an old target if a caller usessend()for a pure-hold command.
self._validate_command(command)
if not command.active_mask.any():
return CommandAcknowledgement.accepted_ack("No active rows.")
|
Want your agent to iterate on Greptile's feedback? Start a greploop in Codex and it will work through the open comments and keep going until this PR reviews clean. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Suppressed comments (1)
embodichain/lab/sim/atomic_actions/runner.py:407
- ExecutionRunner.step() returns/records the PlanningContext produced by ObservationProvider before ExecutionSession.tick() replaces its task state with the session’s verified TaskState. This can make RunnerStep.context (and the stored effect-verification context) inconsistent with tick.task_state if an ObservationProvider doesn’t faithfully echo the provided task_state.
Since ExecutionSession.tick() always overwrites context.task (see execution.py:310-315), it’s safer for the runner to switch to session.latest_context after tick() and use that for _last_context/_update_effect_boundary and the returned RunnerStep.
try:
tick = self._session.tick(context, effect_success=effect_success)
except Exception as exc:
return self._fail(
f"Execution session failed: {type(exc).__name__}: {exc}",
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (1)
embodichain/lab/sim/atomic_actions/runner.py:595
- In
run_until_blocked(), the sleep duration is taken fromresult.wait_durationcomputed before callingon_step. Ifon_stepis slow or (as inmoving_target_recovery.py) advances the injected clock,self._clock.sleep(result.wait_duration)can oversleep and delay the next due cycle. Recompute the remaining wait from the current clock time right before sleeping so callback time/clock advancement is accounted for.
if result.wait_duration > 0.0:
try:
self._clock.sleep(result.wait_duration)
except Exception as exc:
…2a-execution-runner
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (1)
embodichain/lab/sim/atomic_actions/execution.py:642
hold_durationis computed fromdt[:, waypoint_index + 1], which meansTimedTrajectory.dt[:, 0]is now effectively ignored (waypoint 0 is always dispatched immediately). BecauseTimedTrajectory.durationmust equaldt.sum(dim=1), non-zerodt[:, 0]would make the execution schedule inconsistent with the trajectory’s own timing metadata. Consider explicitly validatingdt[:, 0] == 0(or documenting/enforcing it elsewhere) to avoid silent timing drift for callers that provide customdttensors.
next_waypoint_index = min(
waypoint_index + 1,
phase.trajectory.waypoint_count - 1,
)
hold_duration = phase.trajectory.dt[:, next_waypoint_index]
Description
This PR adds a closed-loop execution layer for typed atomic-action plans. It introduces a non-blocking
ExecutionRunner, explicit observation/command/clock ports, command acknowledgement and cancellation semantics, trajectory timing propagation, safe hold behavior, and a simulation adapter that preserves batched environment correlation.The runner closes the gap between planning/recovery contracts and real simulation execution.
GraspGoal.grasp_xposnow accepts a late-boundSceneEntityPose, allowing one executingPickUpaction to track a versioned grasp target. The moving-target tutorial spawns a cube in front of the robot, moves it sideways during PickUp, observesdynamic_goal_changed, replans the complete approach/close/lift trajectory, and physically lifts the relocated cube.Review follow-ups correct waypoint arrival scheduling, fall back to
Robot.get_proprioception()["qf"]when direct effort observation is unavailable, forward pure-hold batches to the simulator, and replace dispatch assertions with explicit validation errors.Fixes: N/A (no linked issue).
Type of change
Validation
black --check --diff --color ./— 892 files unchangedpytest -q tests/sim/atomic_actions— 178 passed, 1 skipped, 3 deselecteddynamic_goal_changed, replanned the active PickUp, acquired the moved cube, and lifted it by 0.135 mScreenshots
Not applicable; the tutorial runs headlessly and can record its simulation video.
Checklist
black .command to format the code base.