Catbot Grammar Bug Fixed
- •Catbot wake-name bug traced to Vosk grammar limits, not only microphone or listening-window settings
- •Listening pause changed from .5 seconds to 1.5 seconds before deeper speech-recognition fix
- •Second recognizer added about 200 MB RAM and CPU rose from 4.3 to 8.0 ms per block
Anna Villarreal reported on August 1, 2026, that Catbot, her desktop pet voice assistant, misheard its wake name because its speech recognizer lacked a usable path for the made-up word “Catbot.” The bug made Catbot interrupt her, argue that she was saying names such as “cat bought,” and launch long correction dialogues instead of responding normally. Villarreal first changed the listening window from .5 seconds to 1.5 seconds so Catbot would wait through her natural pauses before speaking.
Hardware also affected recognition. Villarreal found that a webcam microphone picked up room noise, while a close-range omni directional headset microphone helped Catbot understand her speech more reliably. The central failure remained after the microphone change: Catbot still rejected its own name because the speech model was choosing likely words from its available grammar rather than recognizing the invented token “Catbot.”
Villarreal traced the problem to Vosk model support for runtime graphs. The preferred larger model, vosk-model-en-us-0.22, ignored training graphs because its decoding graph was pre-composed into one frozen file. The model that supported custom grammar was vosk-model-en-us-0.22-lgraph. Villarreal described the fix as limiting output choices rather than adding true custom commands, using the grammar to guide what the decoder was allowed to choose.
Catbot uses Kaldi, which decodes speech against a finite state transducer, HCLG (a graph for speech decoding), built from H, C, L and G layers. The G layer is the grammar or language model, assigning a cost to possible word sequences. Villarreal wrote that the total cost combines acoustic cost, meaning how well phones match audio frames, and language cost, meaning how likely the sentence is. Lower negative log probability is better, so the decoder picks the lowest-cost sequence.
The article gave “ice cream” and “I scream” as an example of why acoustics alone cannot solve recognition. The language model does not hear sound; it scores word sequences, while the decoder minimizes the sum of acoustic and language costs. Villarreal compared the alignment problem to Helen Keller and Anne Sullivan, saying an intermediary interface must bridge raw audio waves and symbolic language tokens. She called the computer science version multimodal alignment (bridging different data types).
The bug appeared clearly when Claude generated phrase outputs for “Catbot, engage.” The recognizer returned “kappa engage” with conf 114.1 and “cappa engage” with conf 112.1. Villarreal called this a vocabulary trap: because “Catbot” had no path through G, the decoder selected the closest available sound-alike phrase. As a temporary workaround, she configured Catbot to respond to “CB,” because those English letters were already available.
The code fix added a second grammar-constrained recognizer in Catbot_voice.py. The recognizer used VOSK_DYNAMIC set to vosk-model-en-us-0.22-lgraph, checked for graph/Gr.fst, built phrase lists from WAKE_NAME_FORMS, GRAMMAR_TAILS and GRAMMAR_BARE, filtered phrases with model.vosk_model_find_word, and added “[unk]” so unrelated speech could escape the command grammar. The grammar included variants such as “cat bot,” “cat pot,” “cat bought,” “cat bottom,” “catfight,” “combat,” “cabot,” “kat bot” and “cap pot,” plus commands including “engage,” “wake up,” “go to sleep,” “stop listening,” “open terminal,” “close terminal,” “approve” and “skip.”
The second recognizer runs alongside the main recognizer instead of replacing it, so the full model still handles conversation. Villarreal measured the added cost at about 200 MB of RAM and roughly double decode CPU, from 4.3 to 8.0 ms per 62 ms block. She said it still ran at 0.13x realtime and added no latency, with CATBOT_GRAMMAR=0 available as a switch if that stopped being true.