Back to DevLog

Making Foreman Interruptible and Fixing Verbose Voice Replies

2 min read

I needed to fix two problems that came up during live use of Foreman's voice mode.

Barge-In During Speech

The TTS was blocking in sd.wait(), so I couldn't interrupt long replies. I wrote a new _wait_or_interrupt() function in tools/speak.py that polls every 50ms while audio plays. Ctrl+space stops playback and returns "interrupted", Esc stops and returns "exit", normal completion returns None. The keyboard library is imported lazily with try/except so if it's unavailable the code falls back to sd.wait() exactly as before.

I checked the keys separately (ctrl + space as two is_pressed() calls, not the combo string) to match tools/listen.py.

Short Spoken Replies

The router was sending back long markdown-formatted text that took 15 seconds to read aloud. I fixed this at both ends. I wrote strip_for_speech() in tools/router.py that removes asterisks, backticks, and bullet markers, turns each line into its own sentence, drops trailing list punctuation, and caps output at two sentences. The full text still prints to the screen, but only the stripped version goes to TTS.

I also added an instruction to the router system prompt: "Replies will be read aloud. Answer in at most two short plain sentences. No markdown, no bullet points, no offers to help further."

I applied the same stripping to describe_capabilities so it speaks only the first two sentences while still printing all four.

Verification

I tested headless before going live. The strip function handled markdown correctly and left clean input unchanged. The fallback path worked when keyboard import failed. Live test confirmed barge-in cut speech immediately and the weather denial came back as two plain sentences. Esc mid-speech exited cleanly.

All changes went into commit 563bca3 and synced to both main and dev.

Share this post