- a disabled-by-default managed
interfaceplugin that observes bounded Run, plugin-event, and scene history without owning simulation authority; and InteractiveDebugSession, a local coordinator over an operator-ownedFastSimApplicationfor 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:
python -m pip install "fastsim>=0.1.0a15,<0.2" \
"fastsim-plugin-interactive-debug==0.1.3"
From source:
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-debugfastsim.plugins / fastsim.interactive-debug
Safe default: no activation
Adding the component alone is safe and inert:
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:
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:
/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:
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:
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: robotentities 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 requiresplanning_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:
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.