Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 125 additions & 24 deletions .agents/skills/add-atomic-action/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ Inspect only the files relevant to the requested skill:
|---|---|
| Base action and descriptors | `embodichain/lab/sim/atomic_actions/core.py` |
| Goals and dynamic pose references | `embodichain/lab/sim/atomic_actions/goals.py` |
| Role-to-resource binding | `embodichain/lab/sim/atomic_actions/bindings.py` |
| Skill endpoint requirements | `embodichain/lab/sim/atomic_actions/requirements.py` |
| Resolved endpoint bindings and targets | `embodichain/lab/sim/atomic_actions/bindings.py` |
| Invocation, options, and resolved request | `embodichain/lab/sim/atomic_actions/invocation.py` |
| Control-part semantic commands | `embodichain/lab/sim/atomic_actions/control.py` |
| Invocation policies | `embodichain/lab/sim/atomic_actions/policies.py` |
| Robot/task/scene state | `embodichain/lab/sim/atomic_actions/state.py` |
| Dynamic scene provider contract | `embodichain/lab/sim/atomic_actions/scene.py` |
| Effects and plans | `embodichain/lab/sim/atomic_actions/effects.py`, `plans.py` |
| Runtime command frames and payloads | `embodichain/lab/sim/atomic_actions/runtime_commands.py` |
| Endpoint command transports | `embodichain/lab/sim/atomic_actions/transports.py` |
| Trajectory helpers | `embodichain/lab/sim/atomic_actions/trajectory_ops.py` |
| Engine-owned planning resources | `embodichain/lab/sim/atomic_actions/runtime.py` |
| Declarative robot resources and adapters | `embodichain/lab/sim/skills/profiles.py` |
| Reference implementations | `embodichain/lab/sim/atomic_actions/primitives/` |
| Static compiler and execution session | `engine.py`, `execution.py` |
| Controller-facing execution ports | `runner.py`, `sim_adapter.py` |
Expand Down Expand Up @@ -92,24 +96,33 @@ class PushOptions(ActionOptions):
push_distance: float = 0.05
```

Do not put arm/hand names, hand qpos, or named robot postures in options. Bind
participants with `ActionBinding`. Register embodiment-specific commands such
as `open`, `grasp`, or `ready` on `ControlPartCommandProfile`; use
Do not put arm/hand names, hand qpos, or named robot postures in options.
Declare robot-independent participant slots and endpoints with
`SkillBindingContract`; the engine or a bound robot skill profile produces the
engine-owned `ActionBinding`. Register embodiment-specific commands such as
`open`, `grasp`, or `ready` on `ControlPartCommandProfile`; use
`ActionControlOverrides` only for one invocation revision.

## 3. Implement the planner

Inherit `AtomicAction[PushGoal, PushOptions]` directly. Declare stable metadata and resolve
resources from semantic binding roles.
Inherit `AtomicAction[PushGoal, PushOptions]` directly. Declare stable metadata
and an explicit, robot-independent endpoint contract. Every concrete action
class must declare `binding_contract` in its own class body; use
`SkillBindingContract()` for a skill that consumes no robot resource.

```python
from typing import ClassVar

from embodichain.lab.sim.atomic_actions import (
ResolvedActionRequest,
ActionPlan,
AtomicAction,
CARTESIAN_POSE_CAPABILITY,
JointPositionTarget,
PlanningContext,
ResolvedActionRequest,
SkillBindingContract,
SkillEndpointRequirement,
SkillResourceSlot,
StateDelta,
)
from embodichain.lab.sim.atomic_actions.trajectory_ops import (
Expand All @@ -122,7 +135,19 @@ class Push(AtomicAction[PushGoal, PushOptions]):
skill_id: ClassVar[str] = "push"
GoalType: ClassVar[type] = PushGoal
OptionsType: ClassVar[type] = PushOptions
manipulator_roles: ClassVar[tuple[str, ...]] = ("primary",)
binding_contract: ClassVar[SkillBindingContract] = SkillBindingContract(
slots=(
SkillResourceSlot(
slot_id="primary",
endpoints=(
SkillEndpointRequirement(
endpoint_id="motion",
capabilities=frozenset({CARTESIAN_POSE_CAPABILITY}),
),
),
),
),
)

def __init__(self, default_options: PushOptions | None = None) -> None:
super().__init__(default_options)
Expand All @@ -133,11 +158,13 @@ class Push(AtomicAction[PushGoal, PushOptions]):
context: PlanningContext,
) -> ActionPlan:
goal = self.require_goal(request)
options = request.skill_options
manipulator = request.binding.manipulator("primary")
control_part = manipulator.name
joint_ids = list(manipulator.joint_ids)
motion_target = request.binding.endpoint(
"primary", "motion"
).require_target(JointPositionTarget)
control_part = motion_target.control_part
joint_ids = list(motion_target.joint_ids)
start_qpos = context.robot.qpos[:, joint_ids]
target_poses = goal.contact_pose

# Build planner states and generate controlled-joint motion using
# request.motion_policy. Embed it into full robot DoF.
Expand Down Expand Up @@ -168,6 +195,11 @@ Follow these invariants:

- Let the engine supply `self.robot` and `self.motion_generator`; use
`_on_bind()` only for robot/device-dependent setup.
- Keep slot and endpoint IDs semantic and robot-independent. Declare all-of
capabilities, required typed commands, and disjointness constraints in the
`SkillBindingContract`; do not infer resources from endpoint names.
- Resolve an endpoint with `request.binding.endpoint(slot_id, endpoint_id)` and
call `require_target(ExpectedTarget)` before using target-specific fields.
- Import pure target-shaping, interpolation, pose-translation, and full-robot
embedding helpers directly from `atomic_actions.trajectory_ops`; keep
stateful planning inside `MotionGenerator`.
Expand All @@ -176,8 +208,8 @@ Follow these invariants:
`plan()` method; the latter injects the latest dynamic obstacle poses into a
copied planner policy.
- Plan from `context.robot.qpos`, never an implicit live robot start state.
- Return full-robot `(B, N, robot.dof)` motion as a tensor or
`TimedTrajectory` with matching `env_ids`.
- For joint-backed motion, return full-robot `(B, N, robot.dof)` motion as a
tensor or `TimedTrajectory` with matching `env_ids` through `build_plan()`.
- Preserve row-local planner success. `build_plan()` normalizes the mask and
replaces unsuccessful trajectory rows with the context's observed qpos.
- Preserve backend timing/derivatives when available.
Expand All @@ -196,7 +228,60 @@ Follow these invariants:
`collision_entity_ids`; supported planners receive those entity poses through
the framework-owned `plan()` entry point.

## 4. Register and invoke
## 4. Emit generic runtime commands when needed

Use `build_command_plan()` when a skill targets a mobile base, whole-body
controller, tool, or another non-joint transport. Build immutable endpoint
commands; keep live controller and device handles in the transport:

```python
target = request.binding.endpoint("primary", "tool").require_target(ToolTarget)
frames = tuple(
RuntimeCommandFrame(
commands=(EndpointCommand(target=target, payload=ToolPayload(value)),),
active_mask=torch.ones(
context.batch_size,
dtype=torch.bool,
device=context.robot.qpos.device,
),
env_ids=context.env_ids,
hold_duration=torch.full(
(context.batch_size,),
request.motion_policy.control_dt,
device=context.robot.qpos.device,
),
)
for value in command_values
)
return self.build_command_plan(
request,
context,
success=success,
commands=TimedCommandSequence(frames=frames, env_ids=context.env_ids),
)
```

For a new transport kind:

1. Define an immutable `RuntimeEndpointTarget` and `RuntimeCommandPayload` with
the same stable `transport_id`; both must return independently owned
snapshots. Payloads also expose `batch_size` and `device`. If target-specific
addressing or safe hold depends on fields beyond the exact target type,
`transport_id`, and `target_id`, override `address_fingerprint` to include
those immutable fields; frames, replans, and revisions preserve it.
2. If declarative robot profiles select it, define a `ResourceEndpoint` and an
exact-type `ResourceEndpointAdapter` that returns `EndpointResolution` with
the runtime target and physical claim metadata.
3. Implement `EndpointCommandTransport.send()`, `hold()`, and `cancel()`, then
register it in `EndpointCommandRouter` used as the `ExecutionRunner` command
sink. The router validates payload types before dispatch.

The default command-plan feedback mode is timed and `joint_trajectory` is
optional. Use joint-position feedback only when a matching full-robot
`joint_trajectory` is supplied. Test target/payload snapshot ownership, frame
batch/device consistency, routing, acknowledgement, hold, and cancel behavior.

## 5. Register and invoke

Register an instance by its class-level `skill_id`:

Expand All @@ -213,10 +298,14 @@ register_action(Push)
Construct a grounded invocation explicitly:

```python
binding = engine.bind_control_parts(
"push",
{"primary": {"motion": "left_arm"}},
)
invocation = ActionInvocation(
skill_id="push",
goal=PushGoal(contact_pose),
binding=ActionBinding(manipulators={"primary": "left_arm"}),
binding=binding,
motion_policy=MotionPolicy(sample_count=60),
recovery_policy=RecoveryPolicy(max_replans=2),
)
Expand All @@ -228,26 +317,33 @@ For dynamic scene updates or online error recovery, create a session with
through `ExecutionRunner`. Use non-blocking `runner.step()` in an existing event
loop or `runner.run_until_blocked()` in a simple application.

## 5. Export and document
`engine.bind_control_parts()` is the explicit direct-core path for joint-backed
control parts. When a `RobotSkillProfile` is installed, prefer
`engine.skill_profile.resolve("push", selections).action_binding` so capability,
command, resource-claim, and custom-adapter validation remain declarative.

## 6. Export and document

Export the goal, options, and action from:

1. `embodichain/lab/sim/atomic_actions/primitives/__init__.py`
2. `embodichain/lab/sim/atomic_actions/__init__.py`

Add the stable skill ID, goal, roles, and effect to
Add the stable skill ID, goal, binding slots/endpoints, and effect to
`docs/source/overview/sim/atomic_actions/builtin_actions.md`. Update API docs for
new public classes. Do not create a compatibility re-export module or a closed
built-in-goal union.

## 6. Test behavior
## 7. Test behavior

Add pure pytest tests under `tests/sim/atomic_actions/`. Cover:

- descriptor `skill_id`, `GoalType`, and required roles;
- invalid goal and missing binding rejection;
- descriptor `skill_id`, `GoalType`, and explicit binding contract;
- invalid goal, wrong binding owner, and missing/extra endpoint rejection;
- per-environment planning success/failure masks;
- full-robot trajectory shape, `env_ids`, timing, and failed-row hold behavior;
- generic command target/payload ownership, frame batch/device consistency, and
optional `joint_trajectory` behavior when the skill emits command frames;
- side-effect-free context handling;
- masked `StateDelta` application for task effects;
- `SceneEntityPose` replanning when the action accepts a dynamic goal;
Expand All @@ -264,9 +360,12 @@ then use the `pre-commit-check` skill before committing.
|---|---|
| Inherit another action | Inherit `AtomicAction` directly; compose helpers. |
| Add one generic target with many optional fields | Define a narrow action-owned goal. |
| Put hardware names in the goal | Bind semantic roles through `ActionBinding`. |
| Put arm/hand control-part names in skill options | Use `ActionBinding` as their only source. |
| Bind a joint, link, TCP frame, or arbitrary name | Every binding value must be a key in `RobotCfg.control_parts`. |
| Put hardware names in the goal | Declare semantic slots/endpoints and resolve an engine-owned binding. |
| Put arm/hand control-part names in skill options | Read typed runtime targets from bound endpoints. |
| Declare legacy role tuples on the action | Declare a class-local `SkillBindingContract`. |
| Use role-specific binding accessors | Use `binding.endpoint(...).require_target(...)`. |
| Construct a binding from role dictionaries | Use a bound skill profile, or `engine.bind_control_parts()` for the direct joint path. |
| Pass an arbitrary joint/link/TCP name to the direct path | `bind_control_parts()` values must be keys in `RobotCfg.control_parts`; add an endpoint adapter for another resource kind. |
| Put hand qpos or named robot postures in skill options | Register semantic commands on the concrete control-part profile. |
| Put planner/recovery knobs in skill options | Move them to invocation policies. |
| Pass a motion generator to each action | Pass it once to `AtomicActionEngine`; construct actions from default options only. |
Expand All @@ -277,4 +376,6 @@ then use the `pre-commit-check` skill before committing.
| Mutate held state after planning | Declare a `StateDelta`. |
| Treat `plan_success` as physical success | Verify effects during execution. |
| Step the simulator from the action | Emit plans; connect execution through `ExecutionRunner`. |
| Put live controller handles in targets or payloads | Keep immutable addressing/data in values and own handles in the transport. |
| Force a non-joint endpoint into a fake trajectory | Emit typed frames with `build_command_plan()` and install its transport. |
| Override public `plan()` | Implement `_plan()` so scene binding cannot be bypassed. |
23 changes: 22 additions & 1 deletion agent_context/MAP.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ topics:
- physical YAML obstacle name
- sphere derived obstacle names
- empty collision mesh
- CollisionWorldInfo
- collision_world_info
- dynamic_collision_entity_ids
- collision_world_entity_ids
- collision_world_batch_mode
Expand Down Expand Up @@ -602,7 +604,9 @@ topics:
- held_objects
- HeldObjectState
- ActionBinding
- ActionBindingRoute
- EndpointBinding
- RuntimeEndpointTarget
- JointPositionTarget
- SkillBindingContract
- SkillResourceSlot
- SkillEndpointRequirement
Expand Down Expand Up @@ -648,6 +652,20 @@ topics:
- ControlPartCommandProfile
- ActionControlOverrides
- JointPositionCommand
- RuntimeCommandPayload
- JointPositionPayload
- EndpointCommand
- RuntimeCommandFrame
- TimedCommandSequence
- EndpointCommandTransport
- EndpointCommandRouter
- endpoint transport
- transport_id
- target_id
- safe stop
- cancel then hold
- ActionPlan.commands
- joint_trajectory
- invocation revision
- MotionPolicy
- MotionPolicy.strategy
Expand All @@ -662,6 +680,7 @@ topics:
- build_pose_plan_states
- build_joint_plan_states
- engine.register
- engine.make_invocation
- BUILTIN_ACTION_TYPES
- load_builtins
- engine.plan
Expand All @@ -680,6 +699,8 @@ topics:
- embodichain/lab/sim/atomic_actions/policies.py
- embodichain/lab/sim/atomic_actions/requirements.py
- embodichain/lab/sim/atomic_actions/runtime.py
- embodichain/lab/sim/atomic_actions/runtime_commands.py
- embodichain/lab/sim/atomic_actions/transports.py
- embodichain/lab/sim/atomic_actions/state.py
- embodichain/lab/sim/atomic_actions/plans.py
- embodichain/lab/sim/atomic_actions/execution.py
Expand Down
Loading
Loading