Skip to content

Repository files navigation

speechd

Native, private-by-default speech inference service for Apple Silicon macOS.

The server binds to loopback and is intended to be exposed only through authenticated Tailscale Serve. This repository contains the open server; the macOS client is maintained separately.

Production readiness is fail-closed until the FluidAudio Cohere INT8 CoreML bundle and the S1 helper are installed. Development mode exists only for protocol and integration tests.

Build

Requirements: Apple Silicon macOS 14+, Swift 6, and Tailscale 1.52+ for Serve. Build with:

swift build -c release
swift test

The repeatable local harness is:

swift build -c release
scripts/validate-contracts.sh
scripts/benchmark.sh 3

Copy config.example.toml to ~/.config/speechd/config.toml, stage the FluidAudio Cohere bundle, and configure the S1 helper path. .env.example documents daemon overrides only; keep real values and tokens in a private environment. Model weights and helper binaries are never committed.

CI/CD

Every push and pull request runs .github/workflows/ci.yml on macOS 14. CI runs the test suite, builds release binaries, validates API contracts, and exercises installer, Tailscale Serve, and uninstall plans in dry-run mode.

CI does not install launchd services, publish Tailscale routes, or download model and helper artifacts. Production deployment remains explicit: pull the reviewed commit, run scripts/install.sh, then run scripts/check-installed.sh on the deployment machine.

Local development

swift run speechd --development --port 8787
curl http://127.0.0.1:8787/healthz

Development mode uses deterministic test adapters and disables auth only when explicitly requested by the --development flag. It is not a production inference mode.

Production setup

  1. Stage the FluidAudio Cohere INT8 CoreML bundle:
scripts/download-models.sh

This downloads only the pinned q8 CoreML assets required by FluidAudio into the configured model directory. The model card is CC-BY-NC-4.0; review that license before deployment. Download the pinned superwhisper/s1-mini-GGUF Q4_K_M artifact separately and configure its local helper. The legacy Python Cohere bridge is not supported or required.

  1. Configure models.cohere_model_directory and inference.s1_helper. Cohere runs in-process through FluidAudio; S1 remains a local JSON-lines helper.

  2. Create a server token:

swift run -c release speechd-admin token create laptop

Save the printed plaintext token. It cannot be recovered later.

  1. Install launchd:
scripts/install.sh

scripts/install.sh waits for /healthz and /readyz to return 200 (600 seconds by default to cover the one-time CoreML/ANE compile); it fails with diagnostics if the Cohere bundle or S1 helper is unavailable. Set SPEECHD_STARTUP_TIMEOUT_SECONDS to adjust the bounded wait. The installer records the source commit, SHA-256 hashes for both installed binaries, and the launchd PID in an installation manifest. Run scripts/check-installed.sh after updates; it fails if the checkout is dirty, the commit changed, either binary changed, or launchd is running a different process. Every source update therefore requires a fresh install before the service is considered aligned.

  1. Configure private HTTPS through Tailscale Serve:
scripts/configure-tailscale.sh

The resulting endpoint is https://<machine>.<tailnet>/v1/... and WebSocket clients use wss://<machine>.<tailnet>/v1/audio/stream. Funnel is never configured. The script publishes HTTPS on port 443 by default and proxies to the loopback daemon port. Set SPEECHD_TAILSCALE_HTTPS_PORT only when a nonstandard Serve port is required.

The macOS client is intentionally not included in this public repository. It uses the same authenticated HTTP and WebSocket contracts; other clients can integrate directly.

OpenAI-compatible request

curl -H "Authorization: Bearer $SPEECHD_TOKEN" \
  -F file=@speech.wav -F model=cohere-s1 \
  https://speechbox.example.ts.net/v1/audio/transcriptions

Use cohere-raw for ASR-only output. Supported uploads are 16-bit PCM WAV (mono/stereo, 16 kHz) and raw PCM16.

Native API

POST /api/v1/transcribe accepts audio_base64, optional sample_rate, channels, language, model, and S1 cleanup options (styling, structure, context). See openapi.yaml and /openapi.json.

WebSocket

Connect with a bearer token, send masked binary PCM16 mono 16 kHz frames (20–40 ms recommended), then send:

{"type":"input_audio.commit"}

The server emits session.started, VAD events, transcription.started, and one transcription.completed event. It never emits fake partial transcripts. Any conforming WebSocket client can exercise this path.

Privacy and operations

Audio is temporary and deleted after completion/cancel/disconnect. Transcripts are not logged or retained. /healthz reports process health; /readyz requires the FluidAudio Cohere bundle, S1 helper, and inference subsystem. /api/v1/status provides non-content metrics. See SECURITY.md, ARCHITECTURE.md, and BENCHMARKS.md.

Update

Pull the new source, reinstall, then verify alignment. Existing configuration, tokens, and model directories are preserved:

git pull --ff-only
scripts/install.sh
scripts/check-installed.sh

Troubleshooting

  • /healthz returning 200 means the daemon is alive.
  • /readyz returning 503 means a required model/helper is missing or unavailable; inspect the launchd stderr log and configured paths.
  • 401 means the bearer token is missing, invalid, or revoked.
  • Confirm the backend remains loopback-bound and Tailscale Serve is configured without Funnel.
launchctl kickstart -k gui/$(id -u)/com.speechd
launchctl bootout gui/$(id -u)/com.speechd
scripts/uninstall.sh   # keeps config and models

About

Private-by-default native macOS speech inference server

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages