• a disabled-by-default managed interface plugin that observes bounded Run, plugin-event, and scene history without owning simulation authority; and
  • InteractiveDebugSession, a local coordinator over an operator-owned FastSimApplication for pause, resume, single-step, guarded reset/stop, conditional pause breakpoints, inspection, and RGB screenshot requests.

The split follows FastSim's public capability boundary. A managed plugin is not granted the live Runtime, backend handles, lifecycle-control commands, or simulation.query. It can request coordinated Run completion through run.lifecycle; it cannot pause or capture a camera frame. The application owner has the public methods needed for those operations. The package never reaches through private composition objects and contains no grasp/place or other task semantics.

Installation

Python 3.11 and 3.12 are supported:

bash
python -m pip install "fastsim>=0.1.0a15,<0.2" \
  "fastsim-plugin-interactive-debug==0.1.3"

From source:

bash
python -m pip install /path/to/fastsim-next
python -m pip install \
  /path/to/fastsim-plugins/packages/fastsim-plugin-interactive-debug

The distribution publishes both managed-plugin entry points:

  • fastsim.components / fastsim.interactive-debug
  • fastsim.plugins / fastsim.interactive-debug

Safe default: no activation

Adding the component alone is safe and inert:

yaml
plugins:
  debug:
    use: plugin://fastsim/interactive-debug

The component default is enabled: false. The managed instance is still created and follows normal plugin lifecycle bookkeeping, but it performs no service lookup, scene query, subscription, publication, callback registration, or background-task creation. It registers no physics-tick hook, so the disabled steady state adds no per-tick work. Enabling it is an explicit Run configuration choice.

Managed observer

Enable observation and use explicit empty bindings when the debugger should see the full scene rather than only implicitly bound robot/articulation entities:

yaml
plugins:
  debug:
    use: plugin://fastsim/interactive-debug
    bindings: {}
    config:
      enabled: true
      watches:
        arm_joint_0:
          path: /scene/entities/robot/articulation/state/positions/0
      breakpoints:
        joint_limit:
          watch: arm_joint_0
          operator: ge
          expected: 1.2
          action: notify
          enabled: true

Omitting a plugin instance's bindings lets FastSim derive all eligible robot/articulation aliases. An explicit {} leaves the public scene-query grant unscoped, which is necessary for general object inspection. Deployment access policy can still narrow or deny the optional services.

The managed watch document has these roots:

text
/run
/scene/run_id
/scene/generation
/scene/tick
/scene/sim_time_s
/scene/entities/<entity-id>/descriptor
/scene/entities/<entity-id>/state
/scene/entities/<entity-id>/links/<link-id>
/scene/entities/<entity-id>/articulation/descriptor
/scene/entities/<entity-id>/articulation/state
/scene/attachments

Paths are RFC 6901 JSON Pointers. / is encoded as ~1, ~ as ~0, and tuple indexes are decimal. They are data lookups, never Python expressions. Operators are eq, ne, gt, ge, lt, le, contains, changed, and exists.

Managed breakpoints support action: notify. A hit is retained in bounded history and published as the namespaced plugin topic breakpoint.hit. Configuring an enabled action: pause fails during prepare() with CAPABILITY_UNAVAILABLE; it is never silently downgraded. Use the application session below for automatic pause.

The managed object exposes:

python
plugin.status
plugin.event_history
plugin.state_history
plugin.inspect_entity("box")
plugin.inspect_object("box")
plugin.inspect_articulation("robot")
plugin.inspect_robot("robot")
plugin.request_run_stop(confirm=True)

The final call uses RunCompletionRequest(outcome="cancelled") and the public run.lifecycle service. It is the only plugin-side terminal action. pause(), resume(), single_step(), reset(), and request_screenshot() all raise a stable DebugCapabilityUnavailableError.

Watches create their own smallest scene demand. A watch below /scene/entities/<id>/... reads only that entity, multiple entity watches are combined into one query, and stamp-only watches such as /scene/tick consume the delta envelope without rebuilding a catalog. You do not need to set streams.scene: true to use watches. That switch is reserved for users who explicitly want a complete scene document in every retained scene-history sample, so its cost intentionally scales with scene size. The static catalog is reused until generation, catalog revision, or catalog digest changes.

Application-owned interactive session

Use InteractiveDebugSession when the operator owns a public asynchronous FastSimApplication:

python
import asyncio
import fastsim
from fastsim_plugin_interactive_debug import InteractiveDebugSession


async def main() -> None:
    simulation = fastsim.app("run.yaml", planning_reads=True)
    async with simulation:
        await simulation.prepare()
        debug = InteractiveDebugSession(simulation)
        debug.add_watch(
            "arm_joint_0",
            "/scene/entities/robot/articulation/state/positions/0",
        )
        debug.add_breakpoint(
            "joint_limit",
            watch="arm_joint_0",
            operator="ge",
            expected=1.2,
            action="pause",
        )
        await debug.attach()
        await debug.start_monitoring(interval_s=0.1)
        await simulation.start()

        status = debug.status
        robot = await debug.inspect_robot("robot")
        capture = await debug.request_screenshot("overview_camera")

        await debug.pause()
        await debug.step(5)
        await debug.resume()
        await debug.close()


asyncio.run(main())

planning_reads=True is required by FastSim for the application scene client. The session uses only snapshot(), scene.catalog/state, plugin_events(), the public lifecycle methods, and camera_rgb().

The application session reads no scene data when it has no scene watches. Entity watches use selective queries and cache the static catalog. Pass full_scene_history=True only when complete scene documents are needed in every retained debug sample.

reset() and stop() require confirm=True. Reset replaces the simulation generation; stop tears down the Run. close() stops only the debugger's monitor and never closes or stops the application it was given. Pause breakpoints are edge-triggered and evaluated at public snapshot boundaries. step(count) stops early after a breakpoint hit and is capped at 10,000 steps per call.

The application-session watch document uses /application and /scene roots. For example, /application/runtime/tick reads the Runtime tick and the managed scene paths above continue to work under /scene.

Histories and status

Both surfaces return immutable DebugStatus, DebugHistoryRecord, WatchResult, and BreakpointHit values. Default managed limits are:

History Capacity Maximum configurable
lifecycle/plugin/scene events 256 65,536
coherent scene states 128 16,384
breakpoint hits 128 16,384

Oldest records are evicted; history is never unbounded. Subscription queues are also bounded. Status reports available capabilities, exact limitations, queue consumer state, current generation/tick/time, watch results, hit history, and a bounded last error.

Inspection and screenshots

Inspection is assembled from public SceneCatalogSnapshot and SceneStateSnapshot values:

  • objects use their generic entity descriptor, pose, and twist;
  • articulations add ordered joint descriptors, positions, velocities, and SI units; and
  • robots are kind: robot entities with the same generic articulation contract.

There is no backend-native Robot handle in the result.

request_screenshot(entity_id) calls the application's public camera_rgb(). It returns immutable RGB24 bytes and metadata: camera entity, width, height, Run/generation/tick/time, byte length, media type, and SHA-256. It does not capture a desktop or viewport and does not encode PNG. If the backend or entity does not provide an RGB camera, FastSim's typed query failure is propagated.

Exact limitations

  • Managed plugins cannot pause, resume, step, reset, or directly stop Runtime.
  • Managed plugins cannot call on-demand camera capture. A configured RGB observation is not treated as a newer screenshot while paused.
  • Application breakpoints are polling/snapshot-boundary checks, not atomic inside a physics tick. A state can cross a condition between polls.
  • Scene inspection requires scene.query; application inspection additionally requires planning_reads=True.
  • Only configured scene-camera RGB24 is supported. Desktop, viewport, depth, segmentation, PNG, and video capture are outside this release.
  • Watches cannot execute code, call methods, or mutate state.
  • No control trajectory, grasp/place, attachment, planner, or task action is synthesized by this package.
  • Live backend, GPU, camera-device, and GUI validation depends on the selected FastSim deployment and is not claimed by unit tests.

Development verification

From this package directory, with the a13 source first on PYTHONPATH:

bash
FASTSIM_SRC=/path/to/fastsim-next/src
PYTHONPATH="$FASTSIM_SRC:src" python -m pytest -q
python -m ruff check src tests
PYTHONPATH="$FASTSIM_SRC:src" python -m mypy src/fastsim_plugin_interactive_debug
python -m build --no-isolation

Tests cover disabled zero activation and A/B tick-loop performance, strict configuration, non-executable watch evaluation, breakpoint edges, bounded histories and queues, service denial, managed lifecycle cleanup, coordinated completion, a reference FastSimApplication lifecycle vertical, application safety gates, object/robot/generic-articulation inspection, scene-camera RGB screenshot metadata, manifest/digest consistency, entry-point metadata, and wheel/sdist contents.