Install any skill in seconds. Free to start, no credit card required.
Get Started Free →ROS2 Managed (Lifecycle) Node implementation with Clean Architecture (Python & C++)
.claude/skills/harunkurtdev-ros2-lifecycle-nodes/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-24 | ✗→✓ | ▲ Improved | -16% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 30% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 9% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 28% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 50% | 0% |
+---------+
| UNKNOWN |
+----+----+
|
create()
v
+------+------+
| UNCONFIGURED|<----+
+------+------+ |
| |
configure() cleanup()
v |
+------+------+ |
| INACTIVE |------+
+------+------+
|
activate()
v
+------+------+
| ACTIVE |
+------+------+
|
deactivate()
v
+------+------+
| INACTIVE |
+-------------+See previous Python example.
cpp// infrastructure/ros2/nodes/managed_node.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <rclcpp_lifecycle/lifecycle_node.hpp> #include <rclcpp_lifecycle/lifecycle_publisher.hpp> #include <std_msgs/msg/string.hpp> namespace infrastructure::ros2::nodes { using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn; class ManagedNode : public rclcpp_lifecycle::LifecycleNode { public: explicit ManagedNode(const std::string& node_name, bool intra_process_comms = false) : rclcpp_lifecycle::LifecycleNode(node_name, rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms)) {} CallbackReturn on_configure(const rclcpp_lifecycle::State &) override { RCLCPP_INFO(get_logger(), "Configuring..."); pub_ = this->create_publisher<std_msgs::msg::String>("topic", 10); return CallbackReturn::SUCCESS; } CallbackReturn on_activate(const rclcpp_lifecycle::State &) override { RCLCPP_INFO(get_logger(), "Activating..."); pub_->on_activate(); // Start timers, etc. return CallbackReturn::SUCCESS; } CallbackReturn on_deactivate(const rclcpp_lifecycle::State &) override { RCLCPP_INFO(get_logger(), "Deactivating..."); pub_->on_deactivate(); return CallbackReturn::SUCCESS; } CallbackReturn on_cleanup(const rclcpp_lifecycle::State &) override { RCLCPP_INFO(get_logger(), "Cleaning up..."); pub_.reset(); return CallbackReturn::SUCCESS; } CallbackReturn on_shutdown(const rclcpp_lifecycle::State &) override { RCLCPP_INFO(get_logger(), "Shutting down..."); return CallbackReturn::SUCCESS; } private: rclcpp_lifecycle::LifecyclePublisher<std_msgs::msg::String>::SharedPtr pub_; }; } // namespace
Allows controlling the state of another node using the lifeycle_msgs services.
cpp// infrastructure/ros2/lifecycle/lifecycle_client.hpp #include <rclcpp/rclcpp.hpp> #include <lifecycle_msgs/srv/change_state.hpp> #include <lifecycle_msgs/srv/get_state.hpp> class LifecycleClient { public: LifecycleClient(rclcpp::Node::SharedPtr node, const std::string& target_node) { client_ = node->create_client<lifecycle_msgs::srv::ChangeState>( target_node + "/change_state"); } bool change_state(std::uint8_t transition_id) { auto request = std::make_shared<lifecycle_msgs::srv::ChangeState::Request>(); request->transition.id = transition_id; // ... async call logic ... return true; } private: rclcpp::Client<lifecycle_msgs::srv::ChangeState>::SharedPtr client_; };
on_configure and release them in on_cleanup.on_error callback to handle failures during transitions.LifecycleNode in launch files to automatically manage states.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 9,890 | 7,366 | -26% | 1 | 1 | 0% | 1,840 | 2,401 | +30% | 0 | 0 | — |
case-01 | fail→fail | 11,787 | 13,219 | +12% | 1 | 1 | 0% | 2,351 | 3,710 | +58% | 0 | 0 | — |
case-02 | pass→pass | 18,101 | 15,125 | -16% | 1 | 1 | 0% | 3,970 | 4,345 | +9% | 0 | 0 | — |
case-03 | pass→pass | 11,733 | 9,334 | -20% | 1 | 1 | 0% | 2,230 | 2,849 | +28% | 0 | 0 | — |
case-04 | pass→pass | 7,442 | 6,774 | -9% | 1 | 1 | 0% | 1,476 | 2,217 | +50% | 0 | 0 | — |
case-06 | pass→pass | 11,454 | 10,208 | -11% | 1 | 1 | 0% | 2,222 | 2,933 | +32% | 0 | 0 | — |
case-07 | pass→pass | 8,272 | 6,299 | -24% | 1 | 1 | 0% | 1,521 | 2,203 | +45% | 0 | 0 | — |
case-08 | pass→pass | 16,991 | 16,419 | -3% | 1 | 1 | 0% | 3,001 | 4,071 | +36% | 0 | 0 | — |
case-09 | pass→pass | 4,383 | 3,085 | -30% | 1 | 1 | 0% | 762 | 1,525 | +100% | 0 | 0 | — |
case-10 | pass→pass | 4,667 | 3,437 | -26% | 1 | 1 | 0% | 862 | 1,691 | +96% | 0 | 0 | — |
case-11 | pass→pass | 7,248 | 5,995 | -17% | 1 | 1 | 0% | 1,298 | 2,241 | +73% | 0 | 0 | — |
case-12 | pass→pass | 3,562 | 3,042 | -15% | 1 | 1 | 0% | 684 | 1,639 | +140% | 0 | 0 | — |
case-13 | pass→pass | 4,476 | 3,913 | -13% | 1 | 1 | 0% | 875 | 1,737 | +99% | 0 | 0 | — |
case-14 | pass→pass | 9,764 | 4,927 | -50% | 1 | 1 | 0% | 1,974 | 1,918 | -3% | 0 | 0 | — |
case-15 | pass→pass | 3,810 | 2,823 | -26% | 1 | 1 | 0% | 622 | 1,509 | +143% | 0 | 0 | — |
case-16 | pass→pass | 5,363 | 3,970 | -26% | 1 | 1 | 0% | 889 | 1,718 | +93% | 0 | 0 | — |
case-17 | pass→pass | 3,803 | 3,275 | -14% | 1 | 1 | 0% | 561 | 1,565 | +179% | 0 | 0 | — |
case-18 | pass→pass | 3,554 | 3,211 | -10% | 1 | 1 | 0% | 570 | 1,581 | +177% | 0 | 0 | — |
case-19 | pass→pass | 4,500 | 3,383 | -25% | 1 | 1 | 0% | 797 | 1,667 | +109% | 0 | 0 | — |
case-20 | pass→pass | 7,653 | 5,776 | -25% | 1 | 1 | 0% | 1,462 | 2,095 | +43% | 0 | 0 | — |
case-21 | pass→pass | 6,474 | 4,713 | -27% | 1 | 1 | 0% | 997 | 1,710 | +72% | 0 | 0 | — |
case-22 | pass→pass | 9,534 | 5,373 | -44% | 1 | 1 | 0% | 1,561 | 1,880 | +20% | 0 | 0 | — |
case-23 | pass→pass | 5,177 | 2,982 | -42% | 1 | 1 | 0% | 837 | 1,496 | +79% | 0 | 0 | — |
case-24 | fail→pass | 16,167 | 6,538 | -60% | 1 | 1 | 0% | 2,665 | 2,228 | -16% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +4 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.