AWS Builder Center
Reachy Mini x Strands Agent on Jetson Thor - Part 8: 80 Emotion Moves, by Sentiment and Voice Prefix

Reachy Mini x Strands Agent on Jetson Thor - Part 8: 80 Emotion Moves, by Sentiment and Voice Prefix

Part 8 of the series: expressive reactions — the agent plays one of ~80 pre-choreographed moves from the Reachy Mini emotions library, matched to the sentiment of a spoken request or an MQTT message, with the valid move names injected into the prompt so it picks one in a single shot.

Series: Reachy Mini x Strands Agent on Jetson Thor (14 articles)

  1. …
  2. 8
    Reachy Mini x Strands Agent on Jetson Thor - Part 8: 80 Emotion Moves, by Sentiment and Voice Prefix This article
Principal AWS Consultant at Datacom - Cloud, IoT, Robotics and GenAI

What would I like to build?

Emotion moves: a voice prefix or MQTT message sentiment routes through the agent, which picks one validated move from the Reachy Mini emotions library and plays it
This is Part 8 of the series. The robot can move with primitive gestures , but those are deliberate, literal motions. For expression — reacting to good news, bad news, a greeting, a joke — I want something richer: a library of ~80 pre-choreographed emotion moves that the agent picks from to match the mood.
The moves themselves ship with the Reachy Mini Python SDK as a recorded-move library. The interesting part is how the agent chooses one. Rather than make it run a discovery call first, the valid move names are injected straight into the system prompt for the wake, so the agent picks a real, validated move name in a single shot — and play_emotion still validates the choice as a safety net.
Two triggers funnel into the same place: a spoken "play emotion, ..." prefix and an MQTT message's sentiment. Both wrap the sentence into one instruction — read its sentiment, then play the single best-matching move — so voice and MQTT behave identically.

Goals

  • Give the agent ~80 pre-choreographed emotion moves from the Reachy Mini SDK's recorded-move library
  • Let it pick one move matched to the sentiment of a request or message (praise → success1, bad news → sad1, greeting → welcoming1)
  • Avoid a discovery round-trip by injecting the valid move names into the system prompt
  • Validate the chosen name against the real list, returning the valid set to retry on a miss
  • Support two triggers — a spoken "play emotion" prefix and an MQTT message field — through one shared path

The Overall System

The moves load once at startup; their names go into the prompt; a trigger's sentiment is wrapped into an instruction; the agent picks a move; and play_emotion validates and plays it.
System Components:
  1. Recorded-move library — pollen-robotics/reachy-mini-emotions-library, loaded once into EMOTION_NAMES
  2. list_emotion_moves / play_emotion — the two Strands Agent tools wrapping the library
  3. Prompt injection — handle_wake appends the valid names so the agent picks one in a single shot
  4. _emotion_request — wraps a sentiment sentence into a play-one-move instruction
  5. Routers — _route_voice_request (the "play emotion" prefix) and the MQTT message field

Sequence Diagram

Follow "great job team, we hit the target!" — wrapped as a sentiment, matched to success1, validated, and played.
Sequence diagram for reachy-mini-emotion-moves

Architecture

ToolPurpose
list_emotion_moves()Returns the comma-separated list of available move names
play_emotion(name)Validates name against EMOTION_NAMES, then plays it via mini.play_move(...); an invalid name returns the valid list to retry

How it works

The recorded-move library

The pre-choreographed moves come from the Reachy Mini Python SDK recorded-move library. _load_emotions() loads it once and caches it, reading the names into EMOTION_NAMES:
1
2
3
from reachy_mini.motion.recorded_move import RecordedMoves
_emotions = RecordedMoves("pollen-robotics/reachy-mini-emotions-library")
EMOTION_NAMES = sorted(_emotions.list_moves())
The library is warmed at startup so the names are ready before the first wake; the log line [emotions] loaded {len(EMOTION_NAMES)} pre-choreographed moves. reports the count (~80). Names include success1, proud1, sad1, welcoming1, laughing1, curious1.

Injecting move names into the system prompt

Rather than make the agent call list_emotion_moves first, handle_wake appends the cached names to the system prompt for that wake, so the agent picks a valid move in a single shot with no discovery round-trip:
1
2
3
sys_prompt = SYSTEM_PROMPT
if EMOTION_NAMES:
sys_prompt += "\n\nValid play_emotion move names: " + ", ".join(EMOTION_NAMES) + "."
play_emotion still validates the chosen name. If loading the library failed at startup, the injection is skipped and the agent falls back to the list_emotion_moves tool.

Two triggers: voice prefix and MQTT message

Both triggers funnel through _emotion_request(sentiment), which wraps a sentence into an instruction telling the agent to read its sentiment and call play_emotion with the single best-matching move name.
The spoken trigger is EMOTION_PREFIX (default "play emotion", env-overridable). _route_voice_request checks whether the transcription starts with the prefix; if so it strips the prefix and routes the rest as a sentiment, otherwise the request passes through unchanged so the voice path stays generic:
1
2
3
4
if EMOTION_PREFIX and stripped.lower().startswith(EMOTION_PREFIX):
sentiment = stripped[len(EMOTION_PREFIX):].lstrip(" ,.:;-").strip()
return _emotion_request(sentiment or stripped)
return text
The MQTT trigger is an AWS IoT Core payload with a message field. When a payload carries a non-empty "message", the handler routes its text through the same _emotion_request, so voice and MQTT behave identically:
1
2
if str(payload.get("message", "")).strip():
return _emotion_request(str(payload["message"]).strip())

Technical Challenges & Solutions

Challenge 1: Picking a real move without a discovery call

Problem: The agent can only call play_emotion with a name that actually exists. Making it call list_emotion_moves first on every emotional reaction adds a model round-trip and latency.
Solution: Inject the cached EMOTION_NAMES directly into the system prompt for the wake. The agent sees the full valid set up front and picks one in a single shot — no discovery call — while play_emotion still validates as a backstop.

Challenge 2: A hallucinated or misspelled move name

Problem: Even with names in the prompt, an LLM might invent or misspell a move, which would fail to play.
Solution: play_emotion validates the chosen name against EMOTION_NAMES. An invalid name doesn't crash — it returns the valid list, so the agent can immediately retry with a correct one.

Challenge 3: One path for two triggers

Problem: Voice and MQTT arrive in different shapes (a prefixed transcription vs. a JSON message field), but should produce the same expressive behaviour.
Solution: Both normalise to a sentiment sentence and go through the single _emotion_request wrapper, so the agent receives an identical instruction regardless of source — voice and MQTT are guaranteed to behave the same.

Getting Started

Trigger an emotion

1
2
3
4
5
6
7
# MQTT: the agent reads the sentiment and plays a matching move
./send_mqtt.sh '{"message":"great job team, we hit the target!"}' # -> success1
./send_mqtt.sh '{"message":"unfortunately the build failed again"}' # -> sad1

# Voice: start the assistant, say the wake word, then the prefix
./reachy_assistant.sh
# "Hey Reachy ... play emotion, I am so happy"
Praise maps to success1/proud1, bad news to sad1, a greeting to welcoming1, a joke to laughing1 — the agent reads the mood and plays one matching move.

What's Next

In Part 9 - Idle Presence: Humans vs. Cats , the robot starts noticing who's around while it rests — periodically asking Cosmos who it sees and routing the observation through species-specific tools for people and cats, all fully local and $0.

Summary

This post covered the emotion-move layer:
  • ~80 pre-choreographed moves from the Reachy Mini SDK recorded-move library (pollen-robotics/reachy-mini-emotions-library), loaded once into EMOTION_NAMES
  • Sentiment matching — the agent picks one move to fit the mood (praise → success1, bad news → sad1, greeting → welcoming1)
  • Prompt injection — the valid move names are appended to the system prompt, so the agent picks one in a single shot with no discovery round-trip
  • Validation — play_emotion checks the name against the real list and returns the valid set on a miss
  • Two triggers, one path — a spoken "play emotion" prefix and an MQTT message field both normalise through _emotion_request, so voice and MQTT behave identicallyMore in this series
  • Previous - Part 7: A Second Trigger - AWS IoT Core MQTT 
  • Next - Part 9: Idle Presence - Humans vs Cats 

Series: Reachy Mini x Strands Agent on Jetson Thor (14 articles)

  1. …
  2. 8
    Reachy Mini x Strands Agent on Jetson Thor - Part 8: 80 Emotion Moves, by Sentiment and Voice Prefix This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article