Asset Core Docs

Deterministic world-state engine documentation and API references.

Decision Gate docs

Robotics Cell: Conveyor Sorting with Deterministic State

A single robot tracks items on a moving conveyor, reconciles sensor updates, and performs pick/place into grid bins with deterministic replay.

If you are reading this cold, this is a deterministic pick-and-place workflow with sensor correction and conflict recovery. Every step is an auditable commit with metadata and idempotency keys so you can replay or diagnose runs without guesswork.

The Problem: When Sensors and Planners Disagree

In production robotics, your vision system reports one position, your encoder says another, and your planner assumes a third. When a pick fails, you have three conflicting logs and no single source of truth. Was it:

  • Sensor drift during the conveyor move?
  • A stale position in the motion planner?
  • A race condition between the vision update and the pick command?

Without deterministic state, debugging becomes archaeology. With AssetCore, every state change is a timestamped, auditable commit. Sensor corrections, planner updates, and execution commands all flow through one authoritative log. When something fails, you replay the exact sequence and see what happened - no guesswork.

This scenario demonstrates production-grade patterns: continuous conveyor tracking, vision-based sensor reconciliation, conflict detection with automatic replanning, and deterministic replay for debugging.

Why this matters

Robotics stacks fail when planners, sensors, and execution disagree about ground truth. Asset Core makes state authoritative by treating every change as a deterministic, replayable transaction.

System model

  • Conveyor: continuous 1D placements with fixed-point coordinates.
  • Bins: grid container that enforces occupancy rules.
  • Gripper: slot container so picks are deterministic slot moves.
  • Robot pose: continuous 2D container for position and rotation.
  • Classes and shapes: registered so collision and placement rules are enforced.
  • Units: mm (fixed_point_scale=1000).

Workflow at a glance

  1. Setup - Register classes/shapes and create containers.
  2. Seed - Create robot pose, inbound item, and blocked bin.
  3. Conveyor Advance - Move item based on conveyor speed and encoder ticks.
  4. Conveyor Reconcile - Apply sensor correction to item position.
  5. Robot Move - Update the robot pose in continuous 2D.
  6. Pick - Move item from conveyor into the gripper.
  7. Place Blocked - Blocked bin returns POSITION_OCCUPIED.
  8. Grid Free - Find the next available grid anchor.
  9. Place - Place item into free grid anchor.
  10. Place Replay - Replay the placement to confirm idempotency.

System Design: What We Chose and Why

This scenario makes specific design choices that reflect AssetCore’s deterministic state model. Here’s what we optimized for and what we traded off.

Choice: Fixed-Point Coordinates

The Choice: All positions use fixed-point integers (fixed_point_scale=1000 means 1 unit = 1mm).

  • Why: Floating-point arithmetic is non-deterministic across CPUs - replay could diverge due to rounding differences. Fixed-point guarantees byte-identical replay.
  • Trade-off: You must choose precision upfront (1000 = 0.001mm resolution). For most robotics, millimeter precision is sufficient.
  • When it matters: Time-travel debugging, disaster recovery, compliance audits all depend on deterministic replay.

Choice: Sensor Reconciliation as Separate Commits

The Choice: Step 3 (encoder update) and Step 4 (vision correction) are separate commits.

  • Why: Each state change is an explicit decision with its own metadata. If vision confidence is low, you can query which corrections were applied and replay without them.
  • Trade-off: Two commits instead of one (minor latency overhead, typically <1ms per commit).
  • When it matters: Debugging sensor drift, validating vision system accuracy, proving compliance.

Choice: Conflict Detection Before State Change

The Choice: Step 7 detects POSITION_OCCUPIED at validation time (L2), before any state mutation.

  • Why: No partial commits, no cleanup, no “compensating transactions.” Validation failures are cheap (no I/O), and state stays clean.
  • Trade-off: Planner must query for free positions (Step 8) after conflict. Optimistic locking would fail less often but would require rollback logic.
  • When it matters: Multi-robot cells with concurrent picks/places. AssetCore’s single-writer per namespace guarantees conflicts are detected deterministically.

Choice: Idempotency Keys for Network Resilience

The Choice: Idempotency keys are optional but, when present, deduplicate retries based on a canonical hash of the full request payload.

  • Why: Same key + same payload returns the cached response; same key + different payload is rejected. This prevents duplicate motion commits under retry.
  • Trade-off: The authoritative service must generate or derive stable keys per interaction and must not forward untrusted client keys.
  • When it matters: At-least-once delivery, retries across flaky links, queue workers, and multi-region failover.

What We Don’t Handle (and Why)

  • Motion planning: AssetCore records pose updates, but does not compute trajectories. Integrate with your motion planner (MoveIt, OMPL, custom) and commit the planned poses.
  • Real-time control: AssetCore is for state authority, not 1kHz control loops. Use it to coordinate high-level plans and record execution outcomes.
  • Sensor fusion: AssetCore stores sensor corrections (Step 4), but does not compute them. Your vision system provides the correction; AssetCore makes it authoritative and replayable.

These are deliberate scope boundaries: AssetCore provides deterministic state management and leaves domain-specific algorithms (motion planning, sensor fusion) to specialized tools.

Idempotency semantics

Idempotency keys are optional and only control retry de-duplication. When a key is present, Asset Core hashes the full request payload and uses (namespace, key, hash) to decide:

  • Same key + same payload: return the cached response (no re-execution).
  • Same key + different payload: reject with 409 Conflict.
  • Failed validation does not reserve the key; reuse after correction is valid.
  • No key: request executes normally, retries re-run. In robotics deployments, the orchestration service generates or derives keys and should not forward untrusted client keys.

How to read the walkthrough

  • Validation reads/streams are listed separately after the steps.
  • Single-operation steps use action helpers for readability.
  • Multi-operation steps stay as commit calls to preserve atomicity.
  • Rust HTTP always uses the commit endpoint with one or more operations.

Scenario artifacts

Latest verification

  • Run ID: 2026-01-19T18-43-00Z-robotics_cell
  • Status: Passed
  • Integrity root hash: 53624642c9af8f6caf0e4138541c948e0f5bd09b95d480e5e9e233d7416c3d32
  • Checks:
    • {'kind': 'structural_invariants'}: passed
    • {'kind': 'world_seq_monotonic'}: passed
    • {'kind': 'global_seq_monotonic'}: passed
    • {'kind': 'replay_idempotent'}: passed
    • {'kind': 'commit_log_integrity', 'expected_chain_hash': 'db3c030f7451ac6b5c4abfe5af7f08f3df463f6991cd19adb0beeb643781f455', 'expected_prev_chain_hash': '402bf8dde049d865bc48a2af2d9d5d9ab1e996b6da0dad5bb56118adccd5c1a2', 'expected_start_offset': 1, 'expected_end_offset': 7, 'expected_entry_count': 7, 'expected_last_global_seq': 23}: passed
    • {'kind': 'runpack_integrity'}: passed

Runpack downloads

Scenario snapshot

{
  "scenario_id": "robotics_cell",
  "version": "0.1.0",
  "namespace_id": 1,
  "containers": {
    "conveyor": "container-31001",
    "grid": "container-31002",
    "gripper": "container-31003",
    "robot_pose": "container-31004"
  },
  "classes": {
    "item": "class-41001",
    "robot_pose": "class-41002"
  },
  "units": {
    "fixed_point_scale": 1000,
    "length_unit": "mm",
    "time_unit": "ms"
  }
}

Failure branch

POSITION_OCCUPIED -> grid_free -> retry with same idempotency key

Audit trail (optional)

The transcript captures the exact request and response pairs emitted during the run. Here is a compact excerpt you can use to validate determinism and metadata propagation:

{
  "kind": "commit",
  "name": "conveyor-reconcile",
  "request": {
    "path": "/v1/write/namespaces/1/commit",
    "idempotency_key": "robotics-cell-reconcile",
    "actor_id": "robotics-cell",
    "metadata": {
      "job_id": "job-robotics-001",
      "sensor_ts_ms": 1725000001250,
      "step": "conveyor-reconcile",
      "trace_id": "trace-robotics-001",
      "vision_confidence": 0.78
    },
    "operations": [
      "MoveInstance"
    ]
  },
  "response": {
    "commit_id": "00000000000000000000000000000002",
    "world_seq_start": 15,
    "world_seq_end": 16,
    "event_count": 2
  }
}

Preflight note

  • Action calls submit commits by default.
  • Set ActionOptions(preflight=True) or call assetcore_commit_preflight for validation.

SDK setup

from assetcore_sdk import AssetCoreClient
from assetcore_sdk.actions import ActionOptions
from assetcore_sdk.operations import (
    AddInstance,
    CreateContainer,
    MoveInstance,
    RegisterClass,
    RegisterClassContinuousShape1d,
    RegisterClassContinuousShape2d,
    RegisterClassShape,
)

# Snippets assume an async context (e.g., inside async def main()).
write_client = AssetCoreClient(
    base_url="http://localhost:8080",
    api_key="WRITE_API_KEY",
)
read_client = AssetCoreClient(
    base_url="http://localhost:8081",
    api_key="READ_API_KEY",
)

Walkthrough

Step 1: Setup

Register classes/shapes and create containers.

Why this step matters: Robotics workflows need container geometry and class shapes defined before any placement or motion can be validated.

Preconditions:

  • None. This is the bootstrap commit.

What this commit creates:

  • Conveyor (continuous 1D), bins (grid), gripper (slots), robot pose (continuous 2D).
  • Item and robot classes with grid and continuous shapes.

Expected state change:

  • Containers and shapes are registered so future MoveInstance operations can be validated.

Operations

  • CreateContainer (x4) - Creates a container (structured memory region) with the requested kind.
  • RegisterClass (x2) - Registers a class definition so future operations can reference it.
  • RegisterClassShape - Registers a grid shape footprint for a class or class variant.
  • RegisterClassContinuousShape1d - Registers a continuous 1D span for a class or class variant.
  • RegisterClassContinuousShape2d - Registers a continuous 2D rectangle for a class or class variant.
await write_client.commit_operations(
    [
        CreateContainer(
            kind={
                "bucket_cell_size": 1000,
                "max_x": 39999,
                "min_x": 0,
                "quantization_inv": 1000,
                "type": "continuous_line_1d",
            },
            policies=None,
            external_id="container-31001",
            owner_external_id="owner-77",
        ),
        CreateContainer(
            kind={"capacity": 16, "grid_width": 4, "type": "grid"},
            policies=None,
            external_id="container-31002",
            owner_external_id="owner-77",
        ),
        CreateContainer(
            kind={"count": 2, "type": "slots"},
            policies=None,
            external_id="container-31003",
            owner_external_id="owner-77",
        ),
        CreateContainer(
            kind={
                "bucket_cell_size": 1000,
                "max_x": 9999,
                "max_y": 9999,
                "min_x": 0,
                "min_y": 0,
                "quantization_inv": 1000,
                "type": "continuous_grid_2d",
            },
            policies=None,
            external_id="container-31004",
            owner_external_id="owner-77",
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-41001",
                "flags": 0,
                "name": "robotics-item-41001",
            },
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-41002",
                "flags": 0,
                "name": "robotics-robot-41002",
            },
        ),
        RegisterClassShape(
            request={"class_id": "class-41001", "shape": {"height": 1, "width": 1}},
        ),
        RegisterClassContinuousShape1d(
            request={"class_id": "class-41001", "span": {"length": 2000}},
        ),
        RegisterClassContinuousShape2d(
            request={
                "class_id": "class-41002",
                "rect": {"height": 1000, "width": 1000},
            },
        ),
    ],
    namespace_id=1,
    idempotency_key="robotics-cell-setup",
    actor_id="robotics-cell",
    metadata={
        "job_id": "job-robotics-001",
        "step": "setup",
        "trace_id": "trace-robotics-001",
    },
)

Step 2: Seed

Create robot pose, inbound item, and blocked bin.

Why this step matters: Seeding gives the workflow concrete instances to move: a robot pose, an inbound item, and a deliberately blocked bin.

Preconditions:

  • Setup commit has created containers and registered classes/shapes.

What to notice in the code:

  • Robot pose is placed in continuous 2D.
  • Inbound item is placed on the conveyor line.
  • Blocked bin item occupies the target grid cell.

Expected state change:

  • Robot, inbound item, and blocker instances exist at their starting locations.

Operations

  • AddInstance (x3) - Mints a new instance and places it at a target location.
await write_client.commit_operations(
    [
        AddInstance(
            class_id="class-41002",
            client_tag="robot-arm-1",
            key=None,
            location={
                "container_id": "container-31004",
                "coord": {"x": 2000, "y": 3000},
                "kind": "continuous_2d",
                "rotation_millideg": 0,
            },
        ),
        AddInstance(
            class_id="class-41001",
            client_tag="inbound-item",
            key=None,
            location={
                "container_id": "container-31001",
                "coord": {"x": 4000},
                "kind": "continuous_1d",
            },
        ),
        AddInstance(
            class_id="class-41001",
            client_tag="blocked-bin",
            key=None,
            location={
                "container_id": "container-31002",
                "kind": "grid_cell",
                "position": 6,
                "rotation": None,
            },
        ),
    ],
    namespace_id=1,
    idempotency_key="robotics-cell-seed",
    actor_id="robotics-cell",
    metadata={
        "job_id": "job-robotics-001",
        "sensor_ts_ms": 1725000000000,
        "step": "seed",
        "trace_id": "trace-robotics-001",
        "vision_confidence": 0.82,
    },
)

Step 3: Conveyor Advance

Move item based on conveyor speed and encoder ticks.

Why this step matters: The conveyor belt moves items based on encoder ticks. In a non-deterministic system, you’d trust the encoder’s tick count and hope it matches reality. AssetCore records the exact position as a commit - timestamped, with encoder metadata - so if a pick fails later, you can replay from this point and see whether the encoder position was correct or if sensor drift occurred.

Preconditions:

  • The inbound item exists on the conveyor from the seed commit.

What AssetCore provides:

  • Position updates are atomic commits (no partial moves)
  • Metadata captures encoder ticks and speed for forensic analysis
  • Idempotency key ensures replaying this commit produces the same state

What to notice in the code:

  • encoder_ticks: 1825 and speed_mm_s: 450 in metadata - captured for audit
  • Continuous 1D coordinate x: 10000 (10mm with fixed_point_scale=1000)
  • Idempotency key robotics-cell-advance prevents duplicate moves on retry

Expected state change:

  • The inbound item moves to the new conveyor coordinate.

Operations

  • MoveInstance - Moves an existing instance to a new location.
await write_client.actions.move_instance(
    instance="inst-2",
    to={
        "container_id": "container-31001",
        "coord": {"x": 10000},
        "kind": "continuous_1d",
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-advance",
        actor_id="robotics-cell",
        metadata={
            "encoder_ticks": 1825,
            "job_id": "job-robotics-001",
            "speed_mm_s": 450,
            "step": "conveyor-advance",
            "trace_id": "trace-robotics-001",
        },
    ),
)

Step 4: Conveyor Reconcile

Apply sensor correction to item position.

Why this step matters: Vision system detects actual position differs from encoder estimate (10000mm encoder vs 11250mm vision). AssetCore applies the correction as a separate commit with vision_confidence: 0.78 metadata. If you replay from this point, you see the exact sensor correction that was applied.

Preconditions:

  • The inbound item remains on the conveyor after the advance step.

What AssetCore provides:

  • Sensor corrections are first-class commits with metadata
  • Low-confidence corrections are recorded for later analysis
  • Replay shows exact delta between encoder and vision estimates

What to notice in the code:

  • Delta of 1250mm (12.5% drift from encoder estimate)
  • Vision confidence 0.78 captured in metadata
  • Separate commit allows querying “which corrections were applied”

Expected state change:

  • The inbound item moves to the corrected coordinate.

Operations

  • MoveInstance - Moves an existing instance to a new location.
await write_client.actions.move_instance(
    instance="inst-2",
    to={
        "container_id": "container-31001",
        "coord": {"x": 11250},
        "kind": "continuous_1d",
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-reconcile",
        actor_id="robotics-cell",
        metadata={
            "job_id": "job-robotics-001",
            "sensor_ts_ms": 1725000001250,
            "step": "conveyor-reconcile",
            "trace_id": "trace-robotics-001",
            "vision_confidence": 0.78,
        },
    ),
)

Step 5: Robot Move

Update the robot pose in continuous 2D.

Why this step matters: Robot pose updates are authoritative state changes, not planner suggestions. Recording them as commits makes motion audit-ready.

Preconditions:

  • The robot instance exists in the pose container from the seed commit.

What to notice in the code:

  • MoveInstance targets a continuous 2D container with fixed-point coordinates.

Expected state change:

  • The robot pose updates to the new X/Y coordinate.

Operations

  • MoveInstance - Moves an existing instance to a new location.
await write_client.actions.move_instance(
    instance="inst-1",
    to={
        "container_id": "container-31004",
        "coord": {"x": 3500, "y": 4000},
        "kind": "continuous_2d",
        "rotation_millideg": 0,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-robot-move",
        actor_id="robotics-cell",
        metadata={
            "job_id": "job-robotics-001",
            "step": "robot-move",
            "trace_id": "trace-robotics-001",
        },
    ),
)

Step 6: Pick

Move item from conveyor into the gripper.

Why this step matters: Picking is a deterministic move from conveyor to gripper. There is no hidden state - the item leaves one container and enters another.

Preconditions:

  • The inbound item is on the conveyor.
  • The target gripper slot is empty.

What to notice in the code:

  • MoveInstance targets a slot container (gripper).

Expected state change:

  • The item leaves the conveyor container and occupies the gripper slot.

Operations

  • MoveInstance - Moves an existing instance to a new location.
await write_client.actions.move_instance(
    instance="inst-2",
    to={"container_id": "container-31003", "kind": "slot", "slot_index": 1},
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-pick",
        actor_id="robotics-cell",
        metadata={
            "job_id": "job-robotics-001",
            "step": "pick",
            "trace_id": "trace-robotics-001",
        },
    ),
)

Step 7: Place Blocked (Expected error)

Blocked bin returns POSITION_OCCUPIED.

Why this step matters: Bin position 6 is occupied. AssetCore returns POSITION_OCCUPIED error before any state change. No partial commit, no cleanup required. The conflict detection happens at validation time (L2), so your state stays clean.

Preconditions:

  • The item is in the gripper slot.
  • The target grid cell is occupied by the blocker.

What AssetCore provides:

  • Conflicts detected before state mutation (cheap validation)
  • Error response includes exact conflict details (which position, which item)
  • Idempotency key preserved for retry with corrected position

What to notice in the code:

  • Error is deterministic (not a race condition)
  • retryable: false means client must change the request (query for free position)
  • Same idempotency key is reused after replanning

Expected state change:

  • No state changes are applied; this is a deterministic validation failure.

Operations

  • MoveInstance - Moves an existing instance to a new location.

Expected error

{
  "code": "POSITION_OCCUPIED",
  "detail": "Position 6 in container 2 is occupied by item at anchor 6",
  "hint": "Select an unoccupied position or move the existing item first.",
  "retryable": false,
  "status": 409,
  "title": "ConflictError",
  "type": "urn:assetcore:error:POSITION_OCCUPIED"
}
await write_client.actions.move_instance(
    instance="inst-2",
    to={
        "container_id": "container-31002",
        "kind": "grid_cell",
        "position": 6,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-place",
        actor_id="robotics-cell",
        metadata={
            "job_id": "job-robotics-001",
            "step": "place-attempt",
            "trace_id": "trace-robotics-001",
        },
    ),
)

Step 8: Grid Free

Find the next available grid anchor.

Why this step matters: After detecting the conflict, the planner queries for free grid positions. This read operation sees the current projection - no locks, no blocking. AssetCore’s read scalability means planners can continuously query for available positions without impacting write throughput.

Preconditions:

  • A placement attempt failed due to an occupied grid cell.

What AssetCore provides:

  • Read queries never block writes (CQRS architecture)
  • Grid-free query returns first available position deterministically
  • Freshness lag is bounded and measurable (see response metadata)

What to notice in the code:

  • Query parameters: height=1, width=1 (item footprint)
  • Response returns position 1 (first free anchor in row-major order)
  • No transaction required (read-only query)

Expected state change:

  • None. This is a read-only planning query.
await read_client.get_container_grid_free(
    container_id="container-31002",
    namespace_id=1,
    height=1,
    width=1,
)

Step 9: Place

Place item into free grid anchor.

Why this step matters: Replanned placement commits the corrected anchor in one atomic move from the gripper to the grid.

Preconditions:

  • The item remains in the gripper.
  • The free anchor returned by grid_free is still available.

What to notice in the code:

  • MoveInstance targets the free grid cell returned by the read step.

Expected state change:

  • The item leaves the gripper and occupies the free grid cell.

Operations

  • MoveInstance - Moves an existing instance to a new location.
await write_client.actions.move_instance(
    instance="inst-2",
    to={
        "container_id": "container-31002",
        "kind": "grid_cell",
        "position": 1,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-place",
        actor_id="robotics-cell",
        metadata={
            "job_id": "job-robotics-001",
            "replan_reason": "grid_free",
            "step": "place",
            "trace_id": "trace-robotics-001",
        },
    ),
)

Step 10: Place Replay

Replay the placement to confirm idempotency.

Why this step matters: Replaying the exact same commit (same idempotency key) returns success without duplicate effects. This is how AssetCore guarantees idempotency - replaying the log always produces the same state. In a distributed system, this means network retries are safe.

Preconditions:

  • The placement commit has already succeeded.

What AssetCore provides:

  • Idempotency at the commit level (not just HTTP)
  • Replay produces same commit_id and world_seq as original
  • Safe to retry any operation without state corruption

What to notice in the code:

  • Identical payload to the successful placement (same key, same operations)
  • Response confirms no new events generated (idempotent)
  • This is how disaster recovery works: replay the entire log

Expected state change:

  • None. The replay returns the cached commit.

Operations

  • MoveInstance - Moves an existing instance to a new location.
await write_client.actions.move_instance(
    instance="inst-2",
    to={
        "container_id": "container-31002",
        "kind": "grid_cell",
        "position": 1,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="robotics-cell-place",
        actor_id="robotics-cell",
        metadata={
            "job_id": "job-robotics-001",
            "replan_reason": "grid_free",
            "step": "place",
            "trace_id": "trace-robotics-001",
        },
    ),
)

Validation

These reads and streams confirm the final state after the steps without mutating anything.

Read 1: Read Conveyor

Verify conveyor placements are empty.

await read_client.get_container_continuous_placements_1d(
    container_id="container-31001",
    namespace_id=1,
)

Read 2: Read Slots

Verify gripper slots are empty.

await read_client.get_container_slots(
    container_id="container-31003",
    namespace_id=1,
)

Read 3: Read Grid

Verify grid placements contain blocker and item.

await read_client.get_container_grid(
    container_id="container-31002",
    namespace_id=1,
)

Read 4: Read Robot

Verify robot pose placement.

await read_client.get_container_continuous_placements_2d(
    container_id="container-31004",
    namespace_id=1,
)

Stream 5: Stream Commit

Read the placement commit from SSE.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "assetcore_read_stream",
  "params": {
    "from_world_seq": 22,
    "limit": 1,
    "namespace_id": 1
  }
}

What This Proves: Production-Grade Robotics with Deterministic Guarantees

This scenario demonstrates the complexity of production pick-and-place workflows: continuous position tracking, sensor reconciliation, conflict detection, and automatic replanning.

Proof Point 1: Every State Change is Auditable

Each commit carries metadata: job_id, trace_id, sensor_ts_ms, vision_confidence. When a pick fails, you do not guess - you query the commit log and see:

  • Exact encoder position at conveyor advance
  • Vision correction applied (delta: 1250mm, confidence: 0.78)
  • Which grid position was blocked
  • New grid position selected after replanning

Proof Point 2: Conflict Detection Without Locking

Step 7 shows POSITION_OCCUPIED detection at validation time. No distributed locks, no coordination overhead. AssetCore’s single-writer architecture makes conflicts deterministic: they are detected before any state change, and the error response tells you exactly what to fix.

Proof Point 3: Idempotent Replay for Debugging

Step 10 proves idempotency: replaying the exact same commit (same idempotency key, same operations) succeeds without side effects. This is how you debug production failures:

  1. Export the commit log from the failure window
  2. Replay locally to reproduce exact state
  3. Inspect intermediate state at any commit
  4. Fix the issue and verify with replay

Business Value: From “It Failed” to “Here’s Why”

Traditional robotics stacks give you three conflicting logs (vision, planner, execution) and force you to reconstruct what happened. AssetCore gives you one authoritative log where every state change is timestamped, attributed (actor_id), and replayable.

For commissioning: Replay production runs to validate cell behavior

For debugging: Time-travel to the exact commit where failure occurred

For compliance: Export immutable audit trail with sensor metadata

For optimization: Analyze commit metadata to identify bottlenecks

This is what world-class tooling enables: deterministic state management that lets you focus on robotics domain logic instead of fighting state reconciliation bugs.

Next Steps

  • Try it locally: Clone the repo and run the scenario to execute this workflow
  • Extend it: Add multi-robot coordination by sharding across namespaces
  • Integrate it: See SDK Reference for production integration patterns
  • Ask us: Book a technical deep-dive to discuss your robotics use case