Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when adding a new simulation atomic action or motion primitive to EmbodiChain's AtomicActionEngine.
.claude/skills/dexforce-add-atomic-action/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 188% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 96% | 0% |
Add an action-owned goal and a side-effect-free AtomicAction._plan() implementation. The inherited public plan() entry point binds the current collision scene before calling the skill hook. The engine owns all motion-planning resources; action constructors accept only optional typed default options. Keep task-graph/MLLM logic, simulator stepping, controller I/O, and physical-effect commits outside the action.
Inspect only the files relevant to the requested skill:
| Purpose | Path | |---|---| | Base action and descriptors | embodichain/lab/sim/atomic_actions/core.py | | Goals and dynamic pose references | embodichain/lab/sim/atomic_actions/goals.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/task_program/semantics/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 |
The public contract is:
pythonplan = engine.plan(invocation: ActionInvocation[Goal], context: PlanningContext)
Use engine.plan_action(action, invocation, context) for a configured action that is intentionally not in the stable skill registry. Never pass a motion generator to an action constructor.
Do not add compatibility code for ActionTarget, WorldState, ActionResult, execute(), or AtomicActionEngine.run().
Place a frozen action-owned dataclass beside the action. Add a stable goal_kind: ClassVar[str]. Do not inherit a marker base merely for dispatch; declare the accepted type on the action instead.
pythonfrom dataclasses import dataclass from typing import ClassVar import torch @dataclass(frozen=True, slots=True, eq=False) class PushGoal: goal_kind: ClassVar[str] = "push" contact_pose: torch.Tensor
Keep only semantic intent in the goal. Do not include arm/hand names, planner options, retry counts, live state, or a generic optional field bag. Use SceneEntityPose for a pose that must be resolved again when the scene moves. Use ObjectActionGoal only when the shared semantics field is genuinely required.
Define a frozen ActionOptions subclass only when skill behavior may vary by invocation. Examples include distances, grasp constraints, and segment split counts. If no such behavior exists, use the base ActionOptions.
Do not put strategy, planner choice, sample count, control period, velocity limits, collision policy, or recovery thresholds in skill options; those belong to MotionPolicy or RecoveryPolicy.
pythonfrom dataclasses import dataclass from embodichain.lab.sim.atomic_actions import ActionOptions @dataclass(frozen=True, slots=True, eq=False) class PushOptions(ActionOptions): push_distance: float = 0.05
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.
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.
pythonfrom typing import ClassVar from embodichain.lab.sim.atomic_actions import ( ActionPlan, AtomicAction, CARTESIAN_POSE_CAPABILITY, JointPositionTarget, PlanningContext, ResolvedActionRequest, SkillBindingContract, SkillEndpointRequirement, SkillResourceSlot, StateDelta, ) from embodichain.lab.sim.atomic_actions.trajectory_ops import ( build_pose_plan_states, to_full_robot_trajectory, ) class Push(AtomicAction[PushGoal, PushOptions]): skill_id: ClassVar[str] = "push" GoalType: ClassVar[type] = PushGoal OptionsType: ClassVar[type] = PushOptions 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) def _plan( self, request: ResolvedActionRequest[PushGoal, PushOptions], context: PlanningContext, ) -> ActionPlan: goal = self.require_goal(request) 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. result = self.motion_generator.generate( build_pose_plan_states(target_poses), options=request.motion_policy.to_motion_gen_options( start_qpos=start_qpos, control_part=control_part, ), ) success, trajectory = to_full_robot_trajectory( result, base_qpos=context.robot.qpos, joint_ids=joint_ids, env_ids=context.env_ids, control_dt=request.motion_policy.control_dt, ) return self.build_plan( request, context, success=success, trajectory=trajectory, expected_effects=StateDelta(), )
Follow these invariants:
self.robot and self.motion_generator; use_on_bind() only for robot/device-dependent setup.
capabilities, required typed commands, and disjointness constraints in the SkillBindingContract; do not infer resources from endpoint names.
request.binding.endpoint(slot_id, endpoint_id) andcall require_target(ExpectedTarget) before using target-specific fields.
embedding helpers directly from atomic_actions.trajectory_ops; keep stateful planning inside MotionGenerator.
require_goal() before planning._plan() rather than overriding the framework-owned publicplan() method; the latter injects the latest dynamic obstacle poses into a copied planner policy.
context.robot.qpos, never an implicit live robot start state.(B, N, robot.dof) motion as atensor or TimedTrajectory with matching env_ids through build_plan().
build_plan() normalizes the mask andreplaces unsuccessful trajectory rows with the context's observed qpos.
segment_lengths mapping withthe actual returned waypoint counts. Segments are inspection metadata, not independent planning or recovery boundaries.
failed_plan(request, context, message=...) for an expected softplanning failure.
effect occurred.
StateDelta; the execution runtimeapplies them only after verification.
scene_dependencies indirectly by using SceneEntityPose in the goal;build_plan() records them for dynamic invalidation.
SceneProvider declarescollision_entity_ids; supported planners receive those entity poses through the framework-owned plan() entry point.
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:
pythontarget = 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:
RuntimeEndpointTarget and RuntimeCommandPayload withthe 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.
ResourceEndpoint and anexact-type ResourceEndpointAdapter that returns EndpointResolution with the runtime target and physical claim metadata.
EndpointCommandTransport.send(), hold(), and cancel(), thenregister 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.
Register an instance by its class-level skill_id:
pythonengine.register(Push())
Use the global registry only for discoverable third-party classes:
pythonregister_action(Push)
Construct a grounded invocation explicitly:
pythonbinding = engine.bind_control_parts( "push", {"primary": {"motion": "left_arm"}}, ) invocation = ActionInvocation( skill_id="push", goal=PushGoal(contact_pose), binding=binding, motion_policy=MotionPolicy(sample_count=60), recovery_policy=RecoveryPolicy(max_replans=2), ) compiled = engine.compile((invocation,))
For dynamic scene updates or online error recovery, create a session with engine.start(...), then connect it to observation, command, and clock ports through ExecutionRunner. Use non-blocking runner.step() in an existing event loop or runner.run_until_blocked() in a simple application.
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.
Export the goal, options, and action from:
embodichain/lab/sim/atomic_actions/primitives/__init__.pyembodichain/lab/sim/atomic_actions/__init__.pyAdd 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.
An Atomic Skill is not automatically a Task Program Semantic Call. If the user also requests high-level declarative exposure, finish and test the Atomic Skill contract first, then invoke $add-semantic-call. Prefer a registered Semantic Call extension unless the concept is intentionally promoted to a stable built-in language primitive.
If the new skill requires new logical resources, endpoints, capabilities, or commands on a reusable robot, update them through $add-embodiment-component; do not encode task or profile metadata in the Atomic Action.
Add pure pytest tests under tests/sim/atomic_actions/. Cover:
skill_id, GoalType, and explicit binding contract;env_ids, timing, and failed-row hold behavior;optional joint_trajectory behavior when the skill emits command frames;
StateDelta application for task effects;SceneEntityPose replanning when the action accepts a dynamic goal;planner;
Run focused tests, format changed Python files with the pinned Black version, then use the pre-commit-check skill before committing.
| Mistake | Required correction | |---|---| | 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 | 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. | | Read robot.get_qpos() inside plan() | Use context.robot.qpos. | | Return an arm-only tensor | Embed into full robot DoF. | | Collapse or manually mask batched planner success | Return the row-local mask; let build_plan() hold unsuccessful rows. | | Model one action as independently recoverable phases | Return one trajectory with optional named segment metadata. | | 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. |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-08 | fail→pass | 20,716 | 11,709 | -43% | 1 | 1 | 0% | 2,481 | 5,413 | +118% | 0 | 0 | — |
case-01 | fail→fail | 49,681 | 15,878 | -68% | 1 | 1 | 0% | 6,873 | 4,804 | -30% | 0 | 0 | — |
case-02 | fail→fail | 24,404 | 18,013 | -26% | 1 | 1 | 0% | 4,104 | 4,814 | +17% | 0 | 0 | — |
case-03 | fail→fail | 61,653 | 15,644 | -75% | 1 | 1 | 0% | 7,323 | 4,442 | -39% | 0 | 0 | — |
case-04 | fail→pass | 26,727 | 11,148 | -58% | 1 | 1 | 0% | 2,646 | 5,279 | +100% | 0 | 0 | — |
case-05 | fail→pass | 21,000 | 10,942 | -48% | 1 | 1 | 0% | 3,099 | 5,413 | +75% | 0 | 0 | — |
case-06 | fail→pass | 21,562 | 14,728 | -32% | 1 | 1 | 0% | 2,212 | 6,369 | +188% | 0 | 0 | — |
case-07 | fail→pass | 22,315 | 24,603 | +10% | 1 | 1 | 0% | 3,332 | 6,531 | +96% | 0 | 0 | — |
case-09 | fail→pass | 17,703 | 3,443 | -81% | 1 | 1 | 0% | 1,911 | 4,873 | +155% | 0 | 0 | — |
case-10 | fail→pass | 19,781 | 13,786 | -30% | 1 | 1 | 0% | 2,612 | 5,359 | +105% | 0 | 0 | — |
case-11 | fail→pass | 16,930 | 5,320 | -69% | 1 | 1 | 0% | 1,802 | 5,195 | +188% | 0 | 0 | — |
case-12 | fail→fail | 36,972 | 21,955 | -41% | 1 | 1 | 0% | 1,901 | 4,838 | +154% | 0 | 0 | — |
case-13 | fail→pass | 21,950 | 6,759 | -69% | 1 | 1 | 0% | 2,627 | 5,375 | +105% | 0 | 0 | — |
case-14 | fail→fail | 22,466 | 10,353 | -54% | 1 | 1 | 0% | 2,780 | 4,459 | +60% | 0 | 0 | — |
case-15 | fail→pass | 10,865 | 10,135 | -7% | 1 | 1 | 0% | 1,847 | 5,070 | +174% | 0 | 0 | — |
case-16 | fail→pass | 21,363 | 6,177 | -71% | 1 | 1 | 0% | 2,809 | 5,284 | +88% | 0 | 0 | — |
case-17 | fail→pass | 21,005 | 9,361 | -55% | 1 | 1 | 0% | 2,267 | 4,932 | +118% | 0 | 0 | — |
case-18 | fail→pass | 23,986 | 6,789 | -72% | 1 | 1 | 0% | 3,158 | 5,440 | +72% | 0 | 0 | — |
case-19 | fail→pass | 6,536 | 3,168 | -52% | 1 | 1 | 0% | 1,073 | 4,758 | +343% | 0 | 0 | — |
case-20 | pass→pass | 25,890 | 19,456 | -25% | 1 | 1 | 0% | 2,918 | 7,945 | +172% | 0 | 0 | — |
case-21 | pass→pass | 25,289 | 26,407 | +4% | 1 | 1 | 0% | 4,584 | 8,859 | +93% | 0 | 0 | — |
case-22 | pass→fail | 30,225 | 15,449 | -49% | 1 | 1 | 0% | 4,739 | 4,514 | -5% | 0 | 0 | — |
case-23 | fail→fail | 24,310 | 11,469 | -53% | 1 | 1 | 0% | 3,731 | 4,646 | +25% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 17 counted toward the lift figure. The other 6 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +57 percentage points is the difference between those two pass rates over the 17 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/27/2026 | +59% |
| gemini-3.6-flash | verified | 8/22/2026 | +59% |
Other measured skills in the registry, with their headline benchmark lift.