Robot skill profiles#
A RobotSkillProfile describes how robot-independent atomic-skill
requirements map onto one robot embodiment. Configure the robot’s resources,
semantic commands, default choices, and policy presets once; task code can then
select skill-local participants instead of constructing an ActionBinding from
robot-specific control-part names.
The model is deliberately generic. It does not define global arm and tool
fields. Each atomic skill publishes its own participant slots and endpoint
requirements, while a robot resource may expose any endpoints appropriate to
that embodiment: manipulation motion and grasping, a mobile base, a torso, or a
whole-body controller.
Configured ownership#
Componentized deployments build the in-memory profile from three explicit owners:
configs/components/embodiments/<embodiment>.yamlowns the simulation robot, sensor suite, and optionalskill_profileresources, endpoints, command presets, and embodiment-specific services;task-local
task_program/integration.yamlowns semantic defaults, action options, effect-monitor mappings, and task-specific services; andconfigs/components/execution_policies/<policy>.yamlowns motion, tracking, recovery, runner, and effect-assurance settings.
The runnable task.<embodiment>.yaml selects these components. Composition
checks their embodiment contracts and produces the exact
RobotSkillProfile and SkillPolicyPreset values described
below. The reusable physical env.yaml owns none of this Task Program metadata,
so changing environments does not copy the robot profile and changing a
compatible embodiment does not rewrite the source program.
Contracts on the two sides#
An atomic action owns a
SkillBindingContract:
a
SkillResourceSlotnames each skill-local participant, such asprimary,source, ordestination;a
SkillEndpointRequirementdeclares the all-of capabilities and typed semantic commands needed from that participant;DisjointSlotEndpointsdeclares endpoint views that must not share physical channels within one participant; coupled whole-body views may overlap when the skill does not declare this constraint; andDisjointResourceSlotsrequires multi-participant skills to select physically disjoint resources.
The robot side supplies RobotResource values. A resource exposes named
ResourceEndpoint values and may contain other resources through
members. Members form a directed acyclic graph and describe the physical
claim; endpoint capabilities are always explicit and are never inherited or
inferred from names. ControlPartEndpoint is the built-in joint-backed
endpoint type, not the resource schema itself.
skill contract robot profile
slot primary resource left_participant
+-- endpoint motion <--------------> +-- endpoint motion -> left_arm
`-- endpoint grasp <--------------> `-- endpoint grasp -> left_hand
capabilities + commands + members/physical claim
Binding the profile to an engine resolves each endpoint through a registered
ResourceEndpointAdapter and validates physical claims, known
solver-backed kinematics capabilities, command types and dimensions, complete
defaults, policy presets, and installed skill contracts. The resulting
BoundRobotSkillProfile exposes only installed, agent-visible skills
with at least one valid resource assignment.
Endpoint and resource declarations are snapshotted when owned by a resource,
profile, or resolved binding. Custom endpoint types whose payloads cannot be
deep-copied must override ResourceEndpoint.snapshot() and return a new
value of the same exact type.
Configure a manipulation participant#
The following profile groups two physical leaves into one participant. The
motion and grasp endpoint names come from the built-in manipulation
contracts; they are local protocol names, not global robot-resource categories.
import torch
from embodichain.lab.sim.atomic_actions import (
BATCH_INVERSE_KINEMATICS_CAPABILITY,
CARTESIAN_POSE_CAPABILITY,
FORWARD_KINEMATICS_CAPABILITY,
GRASP_CAPABILITY,
ControlPartCommandProfile,
ExecutionRunnerCfg,
MotionPolicy,
PickUpOptions,
)
from embodichain.lab.task_program.semantics import (
ControlPartEndpoint,
EffectAssurance,
ResourceBinding,
RobotResource,
RobotSkillProfile,
SkillPolicyPreset,
)
left_motion_capabilities = frozenset(
{
CARTESIAN_POSE_CAPABILITY,
FORWARD_KINEMATICS_CAPABILITY,
BATCH_INVERSE_KINEMATICS_CAPABILITY,
}
)
profile = RobotSkillProfile(
profile_id="example_robot",
resources={
# Physical leaves own disjoint robot joints.
"left_arm_leaf": RobotResource(
resource_id="left_arm_leaf",
endpoints={"control": ControlPartEndpoint("left_arm")},
),
"left_hand_leaf": RobotResource(
resource_id="left_hand_leaf",
endpoints={"control": ControlPartEndpoint("left_hand")},
),
# A skill selects this participant as one indivisible resource.
"left_participant": RobotResource(
resource_id="left_participant",
endpoints={
"motion": ControlPartEndpoint(
"left_arm",
capabilities=left_motion_capabilities,
),
"grasp": ControlPartEndpoint(
"left_hand",
capabilities=frozenset({GRASP_CAPABILITY}),
),
},
members=("left_arm_leaf", "left_hand_leaf"),
),
},
command_profiles={
"left_hand": ControlPartCommandProfile.joint_positions(
open=torch.tensor([0.04, 0.04]),
grasp=torch.tensor([0.0, 0.0]),
),
},
defaults={
"pick_up": ResourceBinding(
resources={"primary": "left_participant"},
),
},
presets={
"default": SkillPolicyPreset(
preset_id="default",
action_option_templates={"pick": PickUpOptions()},
effect_assurance=EffectAssurance.PROJECTED,
motion_policy=MotionPolicy(strategy="ik_interp"),
runner_cfg=ExecutionRunnerCfg(command_timeout=2.0),
),
},
default_preset="default",
)
Set SkillPolicyPreset.required_planner only when a preset depends on one
planner backend, typically because it carries backend-specific typed planning
options. Profile binding checks that requirement against the engine’s configured
backend and fails early on a mismatch. Leave it as None for portable presets.
A SkillPolicyPreset owns independently snapshotted motion, tracking,
recovery, workflow-recovery, runner, action-option, and effect contracts. Task
Program integration selects a preset in this order: an integration-wide
runtime_preset, the profile’s skill_presets[atomic_skill_id], then
default_preset. At execution time, the environment adapter uses the selected
preset’s transport timeouts, minimum cycle time, and completion-hold behavior.
Standard immutable registrations do not accept an unrelated task-side runner
override.
Configure semantic action behavior with the preset#
SkillPolicyPreset.action_option_templates is the required typed behavior
table for semantic calls that can select the preset. Each key is the exact
semantic call ID (pick, place, hand_over, or a registered call ID), and
each value is the target action’s exact frozen ActionOptions dataclass.
Static linking rejects a missing entry or a value of the wrong exact type
before simulation starts.
The preset owns independent snapshots of every template. Semantic lowering may replace only compiler-owned dynamic values—for example Pick’s downstream target poses—while reusable distances, directions, waypoint counts, and other behavior remain configuration. A registered semantic lowerer builds the goal but cannot return replacement options. Planner choice, sample count, tracking, recovery, runner policy, and effect monitors remain in their dedicated preset fields.
Select semantic grounding providers#
Some semantic calls require embodiment knowledge that does not belong in the
agent-facing call or the atomic action. The built-in semantic HandOver is the
canonical example: the robot profile selects a named provider that supplies a
safe middle and default final object target for that embodiment. An explicit
semantic HandOver.final_target overrides the provider’s final target.
profile = RobotSkillProfile(
profile_id="dual_arm_robot",
resources=dual_arm_resources,
command_profiles=hand_command_profiles,
defaults=dual_arm_skill_defaults,
presets={"default": default_preset},
default_preset="default",
grounding_providers={"hand_over": "center_workspace_handover"},
)
grounding_providers maps a semantic call ID to a provider ID. The selected
ID must match one provider declared by the immutable Task Program simulation
registration; missing or unknown providers fail during program analysis, before
observation, planning, or controller work. The provider is executable
integration code and therefore is owned by that registration rather than stored
inside the declarative profile.
Every ControlPartEndpoint.control_part must be a key in
robot.control_parts. A composite endpoint may reuse a member’s control part,
but all joints controlled directly by the composite must already be covered by
its members. Two physical leaf resources may not claim the same joint; model a
shared physical part once and reference that leaf from multiple composites.
command_profiles are generic IDs selected by endpoint adapters; the built-in
control-part adapter defaults the ID to its control_part, and the engine
installs those profiles into the current action core automatically.
One-dimensional joint-position commands are broadcast across environments.
Their last dimension must equal the resolved endpoint’s degree of freedom. Use
invocation-level command overrides for object- or environment-specific values.
Safe preset and dynamic collision worlds#
When the authoritative scene registry declares dynamic collision entities and
safe is reachable through the integration-wide, per-skill, or
profile-default preset selection, semantic integration validates that path
conservatively during binding. The safe preset must use motion_gen, and the
active motion generator must explicitly support dynamic collision worlds;
otherwise binding fails before provider observation, planning, or command
emission.
A linked call receives an effective immutable preset snapshot with
DynamicCollisionMode.REQUIRED; the source profile preset is not mutated.
Other presets, and scenes without dynamic collision entities, retain their
configured collision mode.
Select effect assurance and monitors with the preset#
A SkillPolicyPreset must select one EffectAssurance. There is
no default:
EffectAssurance.VERIFIEDadvances semantic state from measured evidence.effect_monitorsmaps semantic call IDs to versionedEffectMonitorRefvalues. Every curated Pick, Place, or HandOver call used by the preset requires a monitor.EffectAssurance.PROJECTEDadvances the action plan’s expected symbolic effect after command completion and forbids monitor entries. It is intended for trajectory demonstrations, not physical task acceptance.
Monitor parameters are bounded declarative values; executable objects, tensors, cyclic containers, and non-finite numbers are rejected. A manifest also rejects entries whose semantic ID is absent from its call catalog, and Task Program validates the exact monitor ID/revision before live grounding.
Task Program creates a fresh monitor for every verified grounded call. Pick
expects one attached destination relation, place one detached source relation,
and handover both source-detached and destination-attached relations in the
same observation. The monitor compares fresh backend evidence with owned
object-to-endpoint baselines; it never treats the planned StateDelta or
current TaskState as proof that the physical effect occurred. Invalid or
missing per-environment evidence remains unresolved. Consecutive-sample state
survives request-mask shrinkage within one attempt and resets when recovery
installs a new attempt.
Note
The monitor contract is backend-neutral. Simulation, hardware perception, or
controller feedback supplies typed pose-relation evidence. The Task Program
adapter that connects that evidence to ExecutionRunner is separate from the
profile and monitor configuration.
Bind, discover, and resolve#
Construct the atomic engine from the profile’s lowered control-part commands, install any custom actions, and then bind the profile in the semantic layer:
from embodichain.lab.sim.atomic_actions import AtomicActionEngine
engine = AtomicActionEngine(
motion_generator,
control_profiles=profile.action_control_profiles(),
)
bound = profile.bind(engine)
# This is the embodiment-filtered semantic catalog, not every installed action.
assert "pick_up" in bound.skills
resolved = bound.resolve("pick_up")
assert resolved.resource_ids == {"primary": "left_participant"}
binding = resolved.action_binding
preset = bound.preset(skill_id="pick_up")
The atomic engine never imports or stores RobotSkillProfile. The returned
BoundRobotSkillProfile is owned by the semantic integration and checks
the engine’s catalog revision whenever it performs discovery or resolution.
BoundRobotSkillProfile.resolve() returns a ResolvedSkillBinding
containing the selected logical resources, their adapter-resolved endpoints,
their combined ResourceClaim, and an engine-owned generic
ActionBinding. A
Task Program lowering uses that binding and the selected preset when constructing
an invocation; profile resolution does not plan or execute the action itself.
If exactly one assignment is valid, resolution selects it. If several remain, the caller must provide enough skill-local selections or the profile must define a complete per-skill default:
left = bound.resolve("pick_up", selections={"primary": "left_participant"})
candidates = bound.candidates("pick_up")
Incomplete defaults are rejected when the profile is bound. Without an
unambiguous choice, resolution raises AmbiguousSkillBindingError rather
than selecting a resource by declaration order. An unsupported selection raises
UnsupportedSkillError with endpoint, capability, command, or claim
rejection details.
engine.actions remains the direct-core implementation registry.
engine.skills is the installed semantic catalog before embodiment filtering,
and bound.skills is the profile-supported catalog. Registering or replacing an
action invalidates the bound profile; bind it again before discovery or
resolution.
BoundRobotSkillProfile.source_profile identifies the exact immutable
profile used for the binding. The bound view also snapshots the engine’s
monotonic Atomic Skill catalog revision. A later agent-visible action
registration or replacement makes discovery, preset selection, and resolution
fail until the profile and semantic integration are rebound; an equal public
descriptor does not make a different implementation owner safe to reuse.
Extend the graph beyond manipulation#
Resource and capability identifiers are open strings. A joint-driven mobile robot can model a base and a whole-body controller without changing the profile schema:
base = RobotResource(
resource_id="base",
endpoints={
"motion": ControlPartEndpoint(
"base",
capabilities=frozenset({"motion.planar_pose"}),
)
},
)
torso = RobotResource(
resource_id="torso",
endpoints={"motion": ControlPartEndpoint("torso")},
)
whole_body = RobotResource(
resource_id="whole_body",
endpoints={
"motion": ControlPartEndpoint(
"full_body",
capabilities=frozenset({"motion.whole_body"}),
)
},
members=("base", "torso", "left_arm_leaf", "right_arm_leaf"),
)
Here base, torso, and full_body must be real, non-empty robot control
parts, and the full_body joint set must be covered by the listed members. A
future locomotion or whole-body skill can require the corresponding endpoint
and capability in its own binding contract. Existing built-in actions do not
consume these example capabilities.
Non-joint controllers add one endpoint declaration type and one adapter. The
adapter returns EndpointResolution with a typed immutable
RuntimeEndpointTarget, an optional
command-profile key, joint IDs when applicable, and adapter-defined claim
tokens. The generic graph, matching, command, default, and conflict code does
not change. For example, a twist controller can return a target addressed to a
base_velocity transport and claim_tokens={"controller:base"} with no joint
IDs. Exclusive endpoints must provide joint IDs or claim tokens; a read-only or
otherwise shareable virtual endpoint must opt into exclusive=False
explicitly.
Adapters are registered by exact endpoint type. The built-in
ControlPartEndpointAdapter cannot be overridden; define a distinct
endpoint subtype and adapter when controller semantics differ. An adapter may
set requires_command_profile=True when a missing generic command-profile ID
must make profile binding fail immediately.
On the standard Task Program path, endpoint adapters and their ordered Gym
transports are declared by SimulationTaskProgramRegistration. Adapter
classes publish their endpoint type, runtime target types, transport IDs, and
versioned tracking/evidence routes; encoder classes publish their transport ID
and exact target/payload types. Registration rejects missing, unused,
duplicate, or conflicting declarations, and live profile binding checks the
resolved routes against the fingerprinted declarations. Stateful adapters,
transports, grounding providers, and safety factories must be frozen dataclasses
whose configuration is recursively immutable.
The standard factory currently accepts built-in closed-loop routes only for
ControlPartEndpoint. Custom endpoint adapters must declare empty tracking and
effect-evidence routes and therefore support timed/open-loop completion. Custom
mobile or whole-body closed-loop tracking needs a registration-owned provider
factory; it cannot be supplied later as a task-side callback.
A resolved action binding is keyed only by the skill-local
(slot_id, endpoint_id) pair. A reusable non-joint capability supplies a
matching RuntimeCommandPayload, a
shared atomic skill that emits
RuntimeCommandFrame values, and an
EndpointCommandTransport registered
with EndpointCommandRouter. The
core binding, session, runner, and router do not need controller-specific
changes. Once that shared capability exists, new tasks and robot variants reuse
it through profile and task configuration rather than task-specific motion
code.
Important
ResourceClaim combines leaf IDs, concrete joint IDs, and adapter claim tokens.
It and explicit disjoint constraints detect physical overlap for binding and
future scheduling work. They do not enable parallel action execution. The
runtime does not merge concurrent endpoint-command streams. Joint-backed plans
may retain a full-robot trajectory for feedback and offline compilation, but
runtime dispatch is scoped to the endpoints in each command frame.
See Atomic actions for the direct atomic-action core, Embodied Task Program for declarative semantic contracts, Embodied Task Program for semantic task execution, and Scene registry for canonical scene identity and snapshots.