I've been working on getting robots to run structured spoken conversations: a clinic intake (name, age, reason for the visit, allergies), explaining something and checking the person understood it, a quiz, walking someone through an exercise. Wiring an LLM straight into the speech loop gives you a nice demo quickly, but in my tests it was hard to rely on. It drifted off task, re-asked things it already had, or never decided the conversation was over. The same task could play out quite differently from one run to the next, and everything hinged on getting the initial prompt right, so moving to a new task or domain was never just a matter of changing the description.
DialoStack is a set of ROS 2 packages where the dialogue flow is plain Python (states, turn limits, confirmation, cancellation) and the LLM is only used to interpret what the user said and to phrase the next reply. It started as my bachelor's thesis (presented at WAF 2026) and I'm keeping it going as an MIT project. v1.4.0 is out today and adds ROS 2 Lyrical next to Jazzy.
You define the frame, the robot fills it
Every dialogue is a goal on one action, /dialog/execute_task. For slot filling you describe the task and pass the frame the robot has to fill:
{"slots": [
{"name": "drink", "type": "str", "canonical_values": ["coffee", "tea"]},
{"name": "size", "type": "str", "canonical_values": ["small", "large"]},
{"name": "sugar_spoons", "type": "int", "minimum": 0, "maximum": 5},
{"name": "age", "type": "int", "minimum": 0, "maximum": 120},
{"name": "senior_discount", "type": "bool",
"condition_slot": "age", "condition_minimum": 65}
]}
Slots are typed, can have a closed set of values or numeric bounds, and can depend on other slots (e.g. senior_discount is only asked when age is 65 or more). Validation happens in two places:
- In code. If the model extracts a value that doesn't fit the type, the allowed values or the range, it's dropped and the robot asks again, offering the valid options.
- With the person. Before the action returns, the robot reads the data back and the user confirms it or corrects whatever is wrong ("actually, make it a large").
What you get in result.final_frame_json has the same slot names, types and allowed values every time, so whatever acts on it can rely on it. If you don't pass a schema, the engine generates one from the task description. That's meant for prototyping, try the dialogue, iterate, then pin the schema down. You can also resume from a half-filled frame or chain dialogues.
Other strategies, same action
- Explanation: explains a topic, asks a comprehension question, answers follow-ups
- Quiz: questions from a bank, grades free-form spoken answers
- Procedure: guides through steps, and the user can ask to repeat, go back or skip
- Roleplay: plays a character (an interviewer, say) and scores the user against a rubric at the end
Web GUI
A Frame Builder to design slot schemas, quiz banks, procedure steps and roleplay scenes and keep them in a library. A live monitor where you watch the frame fill turn by turn, with STT/LLM/TTS timings for each turn. Plus a prompt editor and configuration profiles.
Speech, models, robots
- faster-whisper + Silero VAD for STT, streaming Piper TTS, and barge-in (the user can interrupt the robot)
- Gemini, OpenAI, Claude or Ollama behind the same interface. With Ollama no data leaves the machine, which matters for settings like the clinic one
- Robot-agnostic core: audio over /audio_in and /audio_out, or the browser tab as microphone and speaker, so you don't need a robot to try it. The only embodiment layer so far is for NAO (gestures, eye LEDs) built for my robot
- A BehaviorTree.CPP leaf node, so a dialogue can be one step of a larger mission
Try it (via Docker)
docker run -d --name dialostack -p 5173:5173 -p 9090:9090 ghcr.io/aquintan4/dialostack
Open http://localhost:5173 and, in Configuration > LLM, pick the provider you prefer: paste a Gemini, OpenAI or Claude API key (Gemini's free tier is enough to try it), or point it at an Ollama model running on your machine if you'd rather keep everything local. Then press Start engine and talk.
Dialostack has run in demos on a real NAO, including a clinic intake scenario, and in testing at the university, but I don't know of anyone using it with real users yet. That's what I'm after now. I posted it on ROS Discourse a few weeks ago and the feedback there turned into features (the numeric bounds above came from that thread). If you work on robots that talk to people: what's missing for your use case, or what would stop you from trying it?
Usefull links:
- Repo (MIT license)
- Project page with demos
- Discourse thread
- Paper (WAF 2026, p. 20)