sl-controls 033979aa47 feat(social): personality system — SOUL.md persona, mood engine, relationship DB (Issue #84)
New packages:
- saltybot_social_msgs: PersonalityState.msg + QueryMood.srv custom interfaces
- saltybot_social_personality: full personality node

Features:
- SOUL.md YAML/Markdown persona file: name, humor_level (0-10), sass_level (0-10),
  base_mood, per-tier greeting templates, mood prefix strings
- Hot-reload: SoulWatcher polls SOUL.md every reload_interval seconds, applies
  changes live without restarting the node
- Per-person relationship memory in SQLite: score, interaction_count,
  first/last_seen, learned preferences (JSON), full interaction log
- Mood engine (pure functions): happy | curious | annoyed | playful
  driven by relationship score, interaction count, recent event window (120s)
- Greeting personalisation: stranger | regular | favorite tiers
  keyed on interaction count thresholds from SOUL.md
- Publishes /social/personality/state (PersonalityState) at publish_rate Hz
- /social/personality/query_mood (QueryMood) service for on-demand mood query
- Full ROS2 dynamic reconfigure: soul_file, db_path, reload_interval, publish_rate
- 52 unit tests, no ROS2 runtime required

ROS2 interfaces:
  Sub: /social/person_detected  (std_msgs/String JSON)
  Pub: /social/personality/state (saltybot_social_msgs/PersonalityState)
  Srv: /social/personality/query_mood (saltybot_social_msgs/QueryMood)
2026-03-01 23:32:58 -05:00

100 lines
3.4 KiB
Python

"""
personality.launch.py — Launch the saltybot personality node.
Usage
-----
# Defaults (bundled SOUL.md, ~/.ros/saltybot_personality.db):
ros2 launch saltybot_social_personality personality.launch.py
# Custom persona file:
ros2 launch saltybot_social_personality personality.launch.py \\
soul_file:=/home/robot/my_persona/SOUL.md
# Custom DB + faster reload:
ros2 launch saltybot_social_personality personality.launch.py \\
db_path:=/data/saltybot.db reload_interval:=2.0
# Use a params file:
ros2 launch saltybot_social_personality personality.launch.py \\
params_file:=/my/personality_params.yaml
Dynamic reconfigure (no restart required)
-----------------------------------------
ros2 param set /personality_node soul_file /new/SOUL.md
ros2 param set /personality_node publish_rate 5.0
"""
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, OpaqueFunction
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def _launch_personality(context, *args, **kwargs):
pkg_share = get_package_share_directory("saltybot_social_personality")
params_file = LaunchConfiguration("params_file").perform(context)
soul_file = LaunchConfiguration("soul_file").perform(context)
db_path = LaunchConfiguration("db_path").perform(context)
# Default soul_file to bundled config if not specified
if not soul_file:
soul_file = os.path.join(pkg_share, "config", "SOUL.md")
# Expand ~ in db_path
if db_path:
db_path = os.path.expanduser(db_path)
inline_params = {
"soul_file": soul_file,
"db_path": db_path or os.path.expanduser("~/.ros/saltybot_personality.db"),
"reload_interval": float(LaunchConfiguration("reload_interval").perform(context)),
"publish_rate": float(LaunchConfiguration("publish_rate").perform(context)),
}
node_params = [params_file, inline_params] if params_file else [inline_params]
return [Node(
package = "saltybot_social_personality",
executable = "personality_node",
name = "personality_node",
output = "screen",
parameters = node_params,
)]
def generate_launch_description():
pkg_share = get_package_share_directory("saltybot_social_personality")
default_params = os.path.join(pkg_share, "config", "personality_params.yaml")
return LaunchDescription([
DeclareLaunchArgument(
"params_file",
default_value=default_params,
description="Full path to personality_params.yaml (base config)"),
DeclareLaunchArgument(
"soul_file",
default_value="",
description="Path to SOUL.md persona file (empty = bundled default)"),
DeclareLaunchArgument(
"db_path",
default_value="~/.ros/saltybot_personality.db",
description="SQLite relationship memory database path"),
DeclareLaunchArgument(
"reload_interval",
default_value="5.0",
description="SOUL.md hot-reload polling interval (s)"),
DeclareLaunchArgument(
"publish_rate",
default_value="2.0",
description="Personality state publish rate (Hz)"),
OpaqueFunction(function=_launch_personality),
])