Commit Graph
4 Commits
Author SHA1 Message Date
aharrison-fullerandClaude Opus 5 128c174672 Fix daemon startup when --user changes, and document service accounts
launchd opens StandardOutPath/StandardErrorPath as the service user. Re-running
install.sh with a different --user leaves those files owned by the previous
one, so the job fails to start -- and since it never gets far enough to write
anything, the log gives no clue why. Chown them before bootstrapping.

README now documents the full dedicated-service-account setup, including the
two things that are easy to get wrong: macOS home directories are drwxr-x--- so
the checkout has to live outside one, and the HuggingFace cache follows HOME so
it has to move with the service user.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 03:52:36 +01:00
aharrison-fullerandClaude Opus 5 0889d2bf02 Run the daemon as the service account's own group, not staff
install.sh hardcoded GroupName staff in the plist. For the default case
(installing as yourself) that is a no-op, but it silently undermines the
--user flag: a dedicated service account would still run with group staff and
inherit read access to every staff-group path, including other users' home
directories. Derive the group from the user instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 03:48:08 +01:00
aharrison-fullerandClaude Opus 5 c846bc245c Wait for bootout to complete before bootstrapping the daemon
launchctl bootout returns before the job is actually gone. Bootstrapping into
that gap fails with "Bootstrap failed: 5: Input/output error" and, because the
script runs under set -e, exits with nothing loaded at all -- taking
speech-to-text down rather than reinstalling it.

Poll until the label disappears, and fail with an actionable message if it
does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 03:06:31 +01:00
aharrison-fullerandClaude Opus 5 4286e88344 Wyoming speech-to-text server for Home Assistant using NVIDIA Parakeet
Loads parakeet-mlx in-process (no HTTP hop) and serves it over the Wyoming
protocol. On an M4 Mac mini this transcribes typical voice commands in ~110ms
versus ~1150ms for a whisper.cpp large-v3 setup, with identical accuracy on a
ten-command benchmark.

Two behaviours matter beyond speed: silence returns an empty string rather
than whisper's "Thank you." hallucination, and there is no decoder context
carried between requests.

Notable implementation details, all covered by mutation-checked regression
tests:

- MLX streams are thread-local, so the model is loaded and evaluated on a
  single dedicated worker thread. Splitting those raises
  "There is no Stream(cpu, 1) in current thread".
- parakeet_mlx.load_audio() shells out to ffmpeg, which is unnecessary here
  since Wyoming delivers 16kHz mono PCM. The mel is built directly via
  get_logmel(), whose input must be float32 -- it views the complex STFT
  output as the input dtype, so anything narrower doubles the mel bin count.
- Wyoming's run loop has no except clause, so an exception escaping
  handle_event closes the connection without sending a Transcript and Home
  Assistant waits indefinitely. Failures are caught and returned as an empty
  transcript instead.

Defaults to parakeet-tdt-0.6b-v2 rather than the newer multilingual v3
because v2 emits digits ("21 degrees") where v3 spells numbers out, and
Home Assistant's local intent matching expects digits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 03:04:48 +01:00