Skip to content

refactor(rtps): the participant lives below both callers that publish through it - #3864

Merged
cagataycali merged 2 commits into
strands-labs:mainfrom
cagataycali:refactor/rtps-participant-below-tools
Sep 19, 2026
Merged

cagataycali merged 2 commits into
strands-labs:mainfrom
cagataycali:refactor/rtps-participant-below-tools

Conversation

@cagataycali

Copy link
Copy Markdown
Member

RtpsRobot opened a DDS writer by importing the use_rtps @tool - the last drivers|mesh -> tools inversion that was a transport rather than a verb package. The participant (one DomainParticipant, writers/readers cached per (topic, type), the IDL sample builder, the action dispatch) moves to strands_robots/rtps/participant.py, beside the mangling and IDL bundle it is built on. The tool keeps what an agent envelope owns: the numeric-option domains, the operator gate, the docstring a model reads.

before                                   after
mesh.rtps_robot -> tools.use_rtps        mesh.rtps_robot -> rtps.participant
                  (drivers|mesh -> tools)              (same layer)
measure before after
upward runtime edges 4 3
drivers|mesh -> tools 4 3
runtime cycles 0 0
tools/use_rtps.py 329 138
package LOC 202,471 202,610
test LOC 553,659 553,670

+139 package lines are the new module's own contract: why it sits there, the GATE_TOOL label, and rtps_action's required gate argument. Serves #3818 0.8-0.9 (layered DAG, zero cycles as a test).

Safety is unchanged and now has one owner. rtps_action takes the operator gate as a required keyword argument - a caller cannot publish to a blocklisted surface by forgetting one - and both callers pass the same GATE_TOOL label, so one cmd_vel incident files one interrupt id and one audit source whichever surface asked.

Tests. Pre-fix control (new test files on a clean main worktree): 3 failed / 19 passed, and the cross-transport gate suite cannot import the participant at all; after: 22 / 33 passed. New pins - a rtps.participant row in the layer-contract table (placement, nothing read above its layer, callers in two layers), the required-gate signature, and a robot publish prompting byte-identically to the tool's. scripts/check_whole_tree_graders.py 6196 passed / 77 skipped; tests/mesh tests/tools tests/rtps + the root graders 10048 passed / 24 skipped; ruff + mypy clean on 2,205 files.

… through it

RtpsRobot reached a DDS writer by importing the use_rtps @tool, the last
drivers|mesh -> tools inversion that was a transport rather than a verb
package. The participant - one DomainParticipant, writers and readers cached
per (topic, type), the IDL sample builder and the action dispatch - moves to
strands_robots/rtps/participant.py, beside the mangling and the IDL bundle it
is built on. The tool keeps what an agent envelope owns: the numeric-option
domains, the operator gate and the docstring a model reads.

rtps_action takes the operator gate as a required keyword argument, so a caller
cannot publish to a blocklisted surface by forgetting one, and both callers key
it with the same GATE_TOOL label - one interrupt id and one audit source for a
command that reaches the same physical topic either way.
@cagataycali
cagataycali enabled auto-merge (squash) September 18, 2026 17:42

@yinsong1986 yinsong1986 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Moves the DDS mechanics (shared DomainParticipant, per-(topic, type) writer/reader caches, IDL sample builder, action dispatch) out of the use_rtps @tool into strands_robots/rtps/participant.py, so RtpsRobot publishes through the participant directly instead of importing an agent tool from the layer above — eliminating the last drivers|mesh -> tools transport inversion. The safety contract is preserved and strengthened: rtps_action takes the operator gate as a required keyword-only argument (no caller can silently omit it), both callers key it with the same GATE_TOOL label so one blocklisted topic files one interrupt id and one audit row, the gate is consulted after the backend probe and before the process-wide lock, and topic/type validation via ros_topic_error/dds_type_name now sits in the participant where both callers hit it. The moved code is byte-equivalent to what lived in the tool; behavior visible to callers is unchanged.

What's good

  • The required-gate signature plus the never_gated singleton makes the un-gated-caller failure mode unrepresentable, and tests/test_every_ros_transport_consults_the_command_gate.py pins both the signature (keyword-only, no default) and byte-identical operator prompts across the tool and robot surfaces.
  • The layer contract table in tests/test_import_layers_are_a_dag.py gains a rtps.participant row with an equality assertion, so moving the participant back up into tools fails a test rather than silently restoring the inversion.
  • The obsolete mesh.rtps_robot -> tools.use_rtps exception is removed from scripts/check_import_layers.py rather than left as a vestige.
  • Test seams were migrated consistently (_backend, time.sleep, rtps_action patch points all repointed to the participant module) and the pre-fix control run documented in the description shows the new pins fail on main.

@cagataycali
cagataycali merged commit 75bb0b3 into strands-labs:main Sep 19, 2026
6 of 9 checks passed
cagataycali pushed a commit to cagataycali/robots that referenced this pull request Sep 19, 2026
strands-labs#3864 landed the sibling RTPS cut on main. Both changes coexist: the layer
roster carries a row per shared transport, the cross-transport gate suite
becomes one table with a row per transport that has a second caller, and the
recorded-call comparisons drop the gate, which is a fresh closure per call and
never on the wire.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants