Skip to content

feat(ros2): migrate ROS2 tools from subprocess CLI to rclpy native API - #88

Open
lijzijie wants to merge 1 commit into
nasa-jpl:mainfrom
lijzijie:feat/ros2-native-api-demo
Open

feat(ros2): migrate ROS2 tools from subprocess CLI to rclpy native API#88
lijzijie wants to merge 1 commit into
nasa-jpl:mainfrom
lijzijie:feat/ros2-native-api-demo

Conversation

@lijzijie

@lijzijie lijzijie commented Jul 6, 2026

Copy link
Copy Markdown

PR Description

feat(ros2): migrate ROS2 tools from subprocess CLI to rclpy native API

Branch: feat/ros2-native-api-demo
Files Changed: 5 files, +624 / -51 lines


1. What is the problem?

All ROS2 tools in ROSA (ros2_node_list, ros2_topic_list, ros2_service_list, ros2_node_info, ros2_topic_info, ros2_service_info) query the ROS2 graph by forking a subprocess to execute the ros2 CLI tool:

# Current implementation:
def ros2_topic_list():
    cmd = "ros2 topic list"
    output = subprocess.check_output(cmd, shell=True).decode()

Each invocation triggers the following expensive chain:

  1. fork() a child process
  2. Load the Python interpreter in the child
  3. Initialize the full rclpy + DDS stack from scratch
  4. Perform DDS peer discovery over multicast
  5. Query the graph, serialize output to text
  6. Parse the text output in the parent process

This is in stark contrast to the ROS1 implementation, where tools like rostopic_list() directly call the native Python API rostopic.get_topic_list() — a simple XML-RPC query to the ROS Master that returns instantly.

Additionally, the current ROS2 toolset is missing ros2_topic_pub — the ability to publish messages to topics. This means the agent cannot control any ROS2 robot's motion, a critical gap compared to the ROS1 turtle_agent which has full publish capabilities.

2. Why does this need to be fixed?

  • Performance: Each subprocess call takes 300–500ms due to process creation + DDS rediscovery overhead. For an LLM agent that chains multiple tool calls per query, this adds seconds of cumulative latency that directly degrades the user experience and robot responsiveness.
  • Asymmetry with ROS1: The ROS1 tools use native Python APIs (rospy) while ROS2 tools use subprocess CLI wrappers. This architectural inconsistency makes maintenance harder and provides a worse experience for the growing ROS2 user base.
  • Fragility: The subprocess approach depends on parsing CLI text output, which may change format across ROS2 distributions (Humble, Iron, Jazzy, Rolling).
  • Missing capability: Without ros2_topic_pub, ROSA cannot send geometry_msgs/Twist commands to control robot motion in ROS2 — a fundamental capability gap.
  • ROS1 EOL: ROS1 Noetic reached End-of-Life in May 2025. ROS2 is now the primary platform, making first-class ROS2 support critical for ROSA's future.

3. How is it fixed?

This PR introduces a hybrid native/fallback architecture:

Singleton Node pattern:

class ROSANode(Node):
    """Singleton ROS2 node shared across all tool invocations."""
    _instance = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            if not rclpy.ok():
                rclpy.init()
            cls._instance = cls("rosa_agent_node")
        return cls._instance

Automatic fallback:
Each tool first attempts the native rclpy path. If rclpy is unavailable (e.g., in CI/testing environments), it transparently falls back to the secure subprocess path:

@tool
def ros2_topic_list(pattern=None, blacklist=None):
    node = ROSANode.get_instance()
    if node is not None:
        try:
            topics = node.get_topic_names_and_types()
            return {"topics": [name for name, _ in topics]}
        except Exception:
            pass
    # Fallback to subprocess
    return get_entities("ros2 topic list", ...)

New ros2_topic_pub tool:
Adds the ability to publish messages to ROS2 topics using native rclpy publishers. Supports rate (Hz) and duration (seconds) parameters to handle continuous publishing (e.g. for smooth robot movement commands like cmd_vel).

Security hardening (included):
All subprocess fallback paths use shell=False with List[str] arguments and _validate_ros_arg() input sanitization.

4. What are the benefits?

Aspect Before (subprocess) After (native rclpy)
Tool call latency ~380ms per call ~0.8ms per call (450x faster)
Process overhead Fork + exec per call Zero — in-process memory read
DDS discovery Rediscovered every call Maintained by background singleton
Topic publishing ❌ Not available ros2_topic_pub tool added (with rate/duration control)
CLI format dependency Parses text output (fragile) Uses typed Python API (robust)
Backward compatibility N/A ✅ Auto-fallback to subprocess
Security shell=True (vulnerable) shell=False + input validation

5. How to reproduce and verify the improvement

To verify the performance improvement, we have developed a set of benchmark and demo scripts.
Since these scripts are only for evaluation purposes and not intended for the main repository, they are provided separately in the attached ros2_native_api_benchmarks.zip archive.

Please download the attached zip file, extract it to the root of this repository, and follow the instructions in the included README_BENCHMARK.md to observe the ~450x latency reduction and test the new ros2_topic_pub capability in a standalone ROS2 Turtlesim Docker environment.

6. How to verify no existing functionality is broken

# Inside the Docker container:
python3 -m pytest tests/test_rosa/tools/test_ros2.py -v

All existing tests pass without modification because:

  • The native path is only used when rclpy is available and the singleton node initializes successfully.
  • If either condition fails, the tool transparently falls back to the subprocess path (which is what the existing tests exercise via mocking).
  • No function signatures, return types, or public APIs have changed.

The original demo.sh and ROS1 TurtleSim demo are completely untouched.


Summary of Changes

File Change
src/rosa/tools/ros2.py Hybrid native/fallback architecture, ROSANode singleton, ros2_topic_pub (with rate & duration support)

@lijzijie

lijzijie commented Jul 6, 2026

Copy link
Copy Markdown
Author

Here are the benchmark and demo scripts to verify the performance improvements locally.

I have attached a zip file ros2_native_api_benchmarks.zip. Please download and extract it to the root directory of this repository, then follow the instructions in the included README_BENCHMARK.md to run the standalone ROS2 Turtlesim Docker environment.

Sample Benchmark Results

For reference, running the latency benchmark yields the following significant improvements:

=== 1. Subprocess CLI (ros2 topic list) ===
  Average Latency:   380.50 ms

=== 2. Recreate Node (rclpy.Node per call) ===
  Average Latency:    45.20 ms

=== 3. Shared Singleton Node (Direct memory read) ===
  Average Latency:     0.52 ms

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.

1 participant