Back to DevLog

Making Foreman Interruptible: Barge-In During Speech

2 min read

Voice mode blocked on long replies. I needed to interrupt speech and start recording immediately.

The Polling Solution

I replaced sd.wait() with a new _wait_or_interrupt() 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. If it's unavailable, speech falls back to sd.wait() exactly as before. Interruption is additive and never the reason TTS breaks.

Short Spoken, Full Printed

I added strip_for_speech() in the router. It removes markdown formatting, turns each line into a sentence, drops trailing list punctuation, and caps at two sentences. The full reply prints to the terminal, but only the stripped version speaks.

I updated the router system prompt to request replies in at most two plain sentences with no markdown.

Wiring the Exit Signal

A local speak() wrapper in foreman.py turns the "exit" return into "Leaving voice mode." and unwinds the loop. Both execute() and run_command() now return that signal so Esc during speech exits cleanly.

Live Test

I verified headless first. Dry-runs on markdown text showed formatting stripped and output capped at two sentences. The keyboard import fallback called sd.wait() as expected.

The live test passed. Barge-in cut speech and started recording immediately. The weather denial spoke as two plain sentences instead of the full markdown block. Esc during speech exited cleanly.

Share this post