Skip to content
Closed
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
51 changes: 47 additions & 4 deletions agent_context/topics/atomic-actions/atomic-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,13 @@ command-only `RuntimeEndpointTarget`. This keeps motion, mobile, whole-body,
articulation, and custom controller transports extensible without treating a
control part as symbolic state identity.

`HeldObjectState` is verified symbolic knowledge only. Neither it nor the
standard effect runtime creates simulator joints, managed attachments,
kinematic parents, frozen bodies, or pose overrides. Physical grasp retention
therefore depends on the configured controller, collision geometry, materials,
contact solver, and rigid-body parameters. A command-state evidence value is
only accepted controller intent and never physical contact proof by itself.

Providers emit raw `PoseRelationEvidenceBatch`, `BinaryEffectEvidenceBatch`,
`ScalarEffectEvidenceBatch`, or `JointStateEvidenceBatch` values with stable
environment IDs, per-row validity/acquisition diagnostics, timestamps, and
Expand All @@ -491,6 +498,17 @@ Cause events (`ACTION_PLANNING_FAILED`, `EFFECT_VERIFICATION_FAILED`, and
`EFFECT_VERIFICATION_TIMEOUT`) are distinct from the `ACTION_RETRY` recovery
event. `SESSION_COMPLETED` and `SESSION_FAILED` are distinct terminal events.

Effect verification is currently a terminal action boundary. There is no
in-flight physical-invariant monitor for the held-object relation during Pick
lift or HandOver transfer/release/delivery. A slip can therefore be detected at
the terminal monitor but cannot interrupt the trajectory at the frame where it
occurs. On a failed HandOver, the success-only `StateDelta` is not committed,
but an already verified source-held relation also is not reconciled from
failure evidence; blindly retrying after both grippers lost the object can use
stale symbolic state. Pure-dynamics recovery needs a typed, phase-aware
in-flight guard plus failure-outcome reconciliation rather than a simulator-side
attachment.

Recovery replans reuse the current immutable `ResolvedActionRequest`, including
its owned goal snapshot. Mutable goal values are copied, while simulator-backed
`BatchEntity` handles retain their runtime identity. To change a goal, option,
Expand Down Expand Up @@ -693,7 +711,13 @@ profile catalog rather than duplicate robot data across tasks.
The Open Drawer vertical slice has completed its supported-simulation physical
run and reached the configured drawer joint target. Repeated cube pick/place has
completed one physical Pick/Place/settle/validator cycle; the full three-cycle
run remains in threshold calibration.
run remains in threshold calibration. The dual-UR5/PGI HandOver slice has
completed three consecutive supported-simulation Pick/transfer/settle/validator
runs using contact dynamics only. Its calibrated profile drives only the PGI
master joints, keeps mimic-child drives disabled, uses a 0.011 close target with
stiffness 2000, damping 50, and maximum effort 140, models the can at 0.33 kg,
and uses 200 motion samples. The default 0.05-rad tracking gate and bounded
replanning remain active.

When no explicit contact or constraint callback is installed, simulation grasp
and release evidence combines the live object-to-endpoint pose relation with
Expand All @@ -707,9 +731,28 @@ contact by itself.

`DynamicSettleMonitor` is shared by reset events and the Expert Program
`wait_stable` post-policy. It owns threshold, cadence, consecutive-check,
settled, and timeout state but never steps simulation. The demo policy yields
full-qpos holds through the normal environment step path. Segment validators
remain a separate dataset/task boundary.
settled, and timeout state but never steps simulation. Eligible rows reuse live
target qpos so a contact-blocked position gripper retains closure preload;
initially inactive rows use fresh measured-qpos holds. Early-settled eligible
rows keep their targets until the active cohort terminates. Every action still
passes through the normal environment-step path, and segment validators remain
a separate dataset/task boundary.

The standard simulation factory lowers both `MotionPolicy.control_dt` and
`ExecutionRunnerCfg.minimum_cycle_time` to the authoritative Gym `step_dt`.
When `hold_during_effect_verification=False`, runner polling emits no
observed-position HOLD; the bridge advances physics by replaying the last
accepted environment action. HandOver also sets `hold_on_completion=False`, so
its subsequent `wait_stable` policy continues the existing targets rather than
neutralizing the gripper at its contact-displaced qpos. Cancellation and
failure still perform cancel followed by an observed-position safe hold.

This staged B behavior solves the validated joint-position HandOver path but is
not a generic continuation contract for mobile-base or whole-body transports.
Those endpoints need a typed transport-owned continuation command rather than a
joint-qpos latch. `wait_stable` also runs only after terminal effect verification
and symbolic-state commit, so its timeout is a post-policy failure and does not
trigger atomic-action recovery.

Runtime and demo results expose deterministic JSON-safe metadata. Call traces
include invocation identity, masks, command counts, execution/recovery events,
Expand Down
86 changes: 79 additions & 7 deletions docs/design/declarative_expert_program_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
tests are explicitly enabled. Open Drawer has completed its
supported-simulation physical run; repeated cube pick/place has completed one
Pick/Place/settle/validator cycle, while the full three-cycle run remains in
threshold calibration.
- Baseline: `main@bcccb787e8f9165e9c8acf6f39f165ba6ac752a4`
threshold calibration. Dual-UR5/PGI HandOver has completed three consecutive
supported-simulation Pick/transfer/settle/validator runs using contact
dynamics only.
- Baseline: `main@bcccb787dcafdafd7b944ba210e5e85f9cd1d0cb`
- Last updated: 2026-08-11
- Related issues: [#471](https://github.com/DexForce/EmbodiChain/issues/471),
[#474](https://github.com/DexForce/EmbodiChain/issues/474)
Expand Down Expand Up @@ -586,6 +588,58 @@ handover, and articulation-joint progress. Hardware can implement the same
contract with perception, force, or controller feedback. Custom monitors stay
an advanced extension point.

Grasp and handover must remain real dynamics outcomes. A simulation effect
monitor is observational: it must not create a fixed joint, managed attachment,
kinematic parent, frozen body, or pose override to make a held-object relation
persist. Grasp retention comes from embodiment-owned drive settings, collision
geometry, material/contact parameters, solver settings, and the commands sent
through the normal controller path. An accepted semantic ``grasp`` command is
controller intent, not physical proof.

``HeldObjectState`` records a relation only after live physical evidence has
passed the selected monitor. The target contract must treat contradictory
evidence, including object-to-endpoint slip, as a real effect failure, invalidate
the affected row's assumed relation, and enter bounded recovery instead of
repairing the scene. The runtime now exposes the active named motion phase,
observes phase-scoped held-object invariants from fresh physical evidence, and
applies removal-only ``StateDelta`` reconciliation to failed rows before any
retry or recovery hand-off. ``Pick`` can use the existing bounded action retry;
``Place`` and ``HandOver`` currently emit a typed ``RECOVERY_REQUIRED`` boundary
because replaying the same invocation after its required relation was removed
would be invalid. A workflow-level re-acquisition policy, blocking acquisition
gates, per-expectation terminal failure reconciliation, and fail-closed
reconciliation for evidence that remains unresolved at the action deadline
remain explicit design decisions rather than implicit scene repair. For
handover, success transfers the verified relation from source to destination
while the destination remains physically closed. Releasing the destination is
a separate ``Place`` or ``Release`` semantic call.

The first pure-dynamics rollout uses the staged **B** continuation policy. The
standard simulation factory lowers both trajectory ``control_dt`` and runner
``minimum_cycle_time`` to the authoritative Gym step. A persistent
joint-position task may disable observed-position holds during terminal effect
verification and on successful completion; bridge wait steps then replay the
last accepted environment action, and a following ``wait_stable`` policy keeps
eligible rows on their live drive targets. Cancellation and failure retain the
normal cancel-then-observed-position safe stop. This split preserves physical
gripper preload without converting a success continuation into a universal
safe-state policy.

The validated dual-UR5/PGI slice drives only each PGI master joint (the mimic
child drive is disabled), uses a ``0.011`` close target with
``stiffness=2000``, ``damping=50``, and ``max_effort=140``, models the can at
``0.33 kg``, and executes a 200-sample motion policy. These values are task and
embodiment calibration, not effect-monitor success shortcuts: the normal
``0.05 rad`` tracking gate, bounded replanning, physical effect evidence, and
settling thresholds remain enabled.

The B policy is the complete continuation scope of this refactor. A generic
mobile-base or whole-body continuation abstraction is deliberately excluded
from the implementation plan and acceptance checklist. If a later transport
requires persistence beyond its normal command contract, it should be proposed
and validated independently instead of becoming a blocker for the declarative
expert-program rollout.

## 8. Expert Program configuration

### 8.1 Version 1 schema
Expand Down Expand Up @@ -1165,10 +1219,16 @@ The backend-neutral typed state expectations, evidence addresses and sources,
pose/binary/scalar/joint evidence clauses, versioned monitor registry,
profile-owned monitor selection, grounded Pick/Place/HandOver/articulation
effects, row-local composite hysteresis kernel, canonical `SkillRuntime`, and
production simulation evidence ports are wired end to end. Physical simulation
acceptance is partial: Open Drawer and one cube Pick/Place/settle/validator
cycle have completed, while the full repeated-cube run and embodiment-owned
HandOver pose integration remain validation work.
production simulation evidence ports are wired end to end. Phase-scoped
held-object guard requests, live evidence collection, row-local symbolic
invalidation, bounded Pick retry, and typed external-recovery hand-off are also
implemented. Physical simulation acceptance is partial: Open Drawer and one
cube Pick/Place/settle/validator cycle have completed. The embodiment-owned
dual-UR5/PGI HandOver slice now completes Pick, transfer, terminal
physical-effect verification, settling, and target validation through real
contact dynamics; blocking acquisition gates, workflow-level re-acquisition,
per-expectation terminal reconciliation, fault-injection coverage, and the full
repeated-cube run remain validation or design work.

Deliverables:

Expand Down Expand Up @@ -1416,7 +1476,19 @@ The design is complete when all of the following hold:
goals without caller duplication.
- [x] `Place` is object-centric and consumes verified held-object state.
- [ ] Built-in grasp, release, handover, and supported articulation effect
monitors work in simulation.
monitors work in simulation. The dual-UR5/PGI HandOver vertical slice is
physically validated; remaining skill/embodiment coverage keeps this
aggregate item open.
- [ ] Grasp and handover simulation gates retain objects through configured
drive/contact dynamics only; no monitor or runtime path creates a
synthetic attachment, freezes the object, or overrides its pose.
- [ ] Physical held-object loss is observed as effect failure, invalidates the
affected symbolic relation, and exercises bounded recovery rather than
being hidden by a simulator-side attachment. The phase-aware observation,
row-local invalidation, bounded Pick retry, and typed recovery boundary
are implemented; blocking acquisition, per-expectation terminal
reconciliation, workflow-level re-acquisition, and real-simulation fault
injection remain open.
- [x] Repeated sub-threshold motion eventually publishes the correct scene
revision.
- [x] Custom actions have a documented and tested intentional hard-break
Expand Down
17 changes: 17 additions & 0 deletions docs/source/overview/sim/atomic_actions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,23 @@ and resets the history. Evidence exactly at the deadline is valid, while a due
observation after the deadline is handled by session timeout without invoking
the verifier.

The curated semantic runtime also installs phase-scoped, negative
held-object guards for named trajectory segments. Before a due command is
dispatched, `ExecutionRunner` passes a fresh observation and the current
`HeldObjectGuardRequest` to its synchronous guard verifier. Each request has a
single-use verification ID, the active waypoint/segment identity, and the
action-owned symbolic key/object identities that may be invalidated. A
contradictory result must name that canonical object and carry a removal-only
`StateDelta`; `ExecutionSession` applies that delta to only the failed rows
before retrying or emitting `RECOVERY_REQUIRED`.
Unavailable or unresolved evidence does not count as a physical contradiction,
and the guard verifier is not invoked after the authoritative action deadline.

The current guard is observational and negative; a blocking positive
acquisition gate, outcome-aware terminal reconciliation, and workflow-level
re-acquisition remain separate policies. Neither the monitor nor runtime
creates a simulator attachment, freezes an object, or overrides its pose.

## Action Agent integration

An MLLM should not construct `ActionInvocation` by copying arbitrary JSON into
Expand Down
2 changes: 2 additions & 0 deletions embodichain/lab/gym/envs/expert_program/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
SimulationPlanningObservationProvider,
create_simulation_expert_program_adapter,
)
from .simulation_handover import ConfiguredHandOverPoseProvider
from .simulation_policies import (
SimulationSegmentPolicyPort,
default_simulation_settle_presets,
Expand Down Expand Up @@ -183,6 +184,7 @@
"ControlPartCommandPreset",
"ControlPartEndpointBinding",
"ControlPartResourceBinding",
"ConfiguredHandOverPoseProvider",
"CyclicPoseTargetCfg",
"DeclarativeCfgValue",
"DemoBridgeError",
Expand Down
17 changes: 10 additions & 7 deletions embodichain/lab/gym/envs/expert_program/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,22 +1092,25 @@ def validate_bound_endpoint_extensions(
)


def _profile_with_control_dt(
def _profile_with_step_dt(
profile: RobotSkillProfile,
*,
control_dt: float,
step_dt: float,
) -> RobotSkillProfile:
"""Return the registration profile aligned to one Gym control cadence."""
"""Return the registration profile aligned to one Gym runtime cadence."""
return replace(
profile,
presets={
preset_id: SkillPolicyPreset(
preset_id=preset.preset_id,
schema_version=preset.schema_version,
motion_policy=replace(preset.motion_policy, control_dt=control_dt),
motion_policy=replace(preset.motion_policy, control_dt=step_dt),
tracking_policy=preset.tracking_policy,
recovery_policy=preset.recovery_policy,
runner_cfg=preset.runner_cfg,
runner_cfg=replace(
preset.runner_cfg,
minimum_cycle_time=step_dt,
),
effect_monitors=preset.effect_monitors,
action_option_templates=preset.action_option_templates,
)
Expand Down Expand Up @@ -1476,9 +1479,9 @@ def validate_robot_profile(
self.assert_unchanged()
if type(profile) is not RobotSkillProfile:
raise TypeError("profile must be exactly RobotSkillProfile.")
expected = _profile_with_control_dt(
expected = _profile_with_step_dt(
self.catalog.robot_profile,
control_dt=step_dt,
step_dt=step_dt,
)
if _canonical_json(profile) != _canonical_json(expected):
raise IntegrationFingerprintMismatch(
Expand Down
19 changes: 12 additions & 7 deletions embodichain/lab/gym/envs/expert_program/simulation_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,10 +718,11 @@ class SimulationExpertProgramFactory(ExpertProgramEnvironmentFactory):
translation_threshold: Material scene translation threshold.
rotation_threshold: Material scene rotation threshold.

Every profile policy is rebuilt with ``control_dt == step_dt``. The Gym
cadence is authoritative because commands cannot be emitted between
environment steps; silently retaining a preset's unrelated fallback
cadence would make trajectory timing unrepresentable at the bridge.
Every profile policy is rebuilt with ``control_dt == step_dt`` and
``minimum_cycle_time == step_dt``. The Gym cadence is authoritative because
commands and fresh feedback cannot be produced between environment steps;
silently retaining a preset's unrelated fallback cadence would make runtime
timing unrepresentable at the bridge.
"""

def __init__(
Expand Down Expand Up @@ -872,7 +873,7 @@ def create_scene_registry(self) -> SceneRegistry:
return registry

def create_robot_skill_profile(self) -> RobotSkillProfile:
"""Build a profile whose every motion policy uses the Gym cadence."""
"""Build a profile whose motion and runner policies use Gym cadence."""
profile = self._robot_profile_binding.build(self._robot)
aligned_presets = {
preset_id: SkillPolicyPreset(
Expand All @@ -884,7 +885,10 @@ def create_robot_skill_profile(self) -> RobotSkillProfile:
),
tracking_policy=preset.tracking_policy,
recovery_policy=preset.recovery_policy,
runner_cfg=preset.runner_cfg,
runner_cfg=replace(
preset.runner_cfg,
minimum_cycle_time=self._step_dt,
),
effect_monitors=preset.effect_monitors,
action_option_templates=preset.action_option_templates,
)
Expand All @@ -893,9 +897,10 @@ def create_robot_skill_profile(self) -> RobotSkillProfile:
aligned = replace(profile, presets=aligned_presets)
if any(
preset.motion_policy.control_dt != self._step_dt
or preset.runner_cfg.minimum_cycle_time != self._step_dt
for preset in aligned.presets.values()
):
raise AssertionError("Profile motion policies were not cadence-aligned.")
raise AssertionError("Profile runtime policies were not cadence-aligned.")
self._registration.validate_robot_profile(
aligned,
step_dt=self._step_dt,
Expand Down
Loading
Loading