feat(gateway): voice command service

Recognize simple device commands in the transcript and act on them without
a Tatlock round-trip. commands.match() maps volume up/down, mute/unmute,
"set volume to N", and "goes to eleven"/max to a "command" message sent
straight to the device; the utterance never reaches the LLM. Matching is
deliberately precise so real requests ("set an alarm for a quarter to
eleven") are not hijacked. Adds the "command" message to the device
protocol in docs/architecture.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKPbR6DY2JygHbyLjxm7Uu
This commit is contained in:
2026-07-15 21:29:00 +02:00
co-authored by Claude Opus 4.8
parent cdb23bda05
commit 6b7fbb60c9
5 changed files with 176 additions and 1 deletions
+9
View File
@@ -266,8 +266,17 @@ gateway → device: {"type": "reply_text", "text": "..."}
gateway → device: {"type": "audio_start", "sample_rate": 16000}
gateway → device: <binary PCM frames> (may arrive sentence-by-sentence; play as a stream)
gateway → device: {"type": "audio_end"}
gateway → device: {"type": "command", "action": "volume_up"} (LLM-bypass; see below)
```
`command` (gateway → device) is an **alternative to the reply path**: when the
gateway recognizes a simple device command in the transcript (volume/mute), it
sends a `command` instead of calling Tatlock — no `reply_text`/audio — then returns
to `idle`. Actions: `volume_up`, `volume_down`, `mute`, `unmute`, and `volume_set`
with an extra `"level"` field (011, the on-device volume scale). Matched by the
gateway's `commands.py`; applied on the device in `gw_client.c``face.c`.
Planned additions (documented before implemented, here first):
- `reply_delta` (gateway → device): incremental reply text for on-screen streaming while