Adolfo Reyna fa4037a2b8 docs: future optimizations — FreeFlow polish 3-layer defense, OSS V2V landscape, native M2 WS spec, boy->voice ground-truth lock fix
- Add detailed perf baseline Pi5 (live draft 800ms/8KB, quick ack >30 chars, instant audio Alex<Kokoro, macmini-live-final 753ms vs 11s fallback)
- Rank pain points P0-P3: Mac mini SPOF :7331 down now, patch fragility 1675 lines, ground-truth lock boy->voice, disk IO storm, no sentence TTS queue, single VAD vs adaptive FreeFlow effectiveSilenceThreshold, empty trigger
- Deep dive mrinalwadhwa/freeflow 91★: PolishPipeline 43 rules, <keep> tags, collapseAdjacent, cleanSpuriousCommas, stripNoise/fillers, knownTerms, guard_against_truncation 7f96ccd, skip 83% 0.55s median, warm backup 91% zero handshake, adaptive silence far=0.001 near=ambient*1.2 capped, DictationPipeline actor detached AVAudioEngine + BT SCO 3s timeout, chunk buffer sentence boundary, SessionTiming
- OSS V2V 2025-26: Pipecat 6.5k★ TurnTracker/aggregator/barge-in/e2e tester, LiveKit/TEN, Moshi 7B Mimi codec 12.5Hz 160ms full-duplex dual-stream, Qwen2.5-Omni 2.8-4s — borrow ideas + why stay pipeline text middle = tool-use
- 3-layer ground-truth fix: L1 deterministic 0ms, L2 semantic ANE 3B with precedingText + KNOWN_TERMS + alternatives + overlap 0.45 truncate 0.25, L3 Hermes disclaimer + paraphrase confirmation builder build_voice_user_message()
- Native M2 target WS /api/voice/live single endpoint spec: binary PCM s16le 16k / empty <1600B trigger / JSON start/stop/ping, events ready→listening→draft volatile 60ms 400ms→transcript final polished+raw+alts→instant_response contextual>30→instant_audio Kokoro 200ms→response_text streaming sentence buffer timed 300ms tail + crossfade→done background, VAD dual 1250ms adaptive, barge-in level>0.12, multi-partial while tool runs
- Power truth M2 Air 14,2 macOS 26.5.2: SpeechAnalyzer 0mW ANE 7.3W spike 33% XPC 27-38x RTF 196M EN, Foundation 3B ANE 258-498ms 2GB quant
- Phased plan P0→P3 quick wins: disclaimer wrapper + knownTerms + alt transcriptions + guard tune + in-mem ByteArray + adaptive silence + empty trigger + sentence queue + warm daemons + desktop hook ChatGPT parity
- Refs: patches, hermes_plugin/__init__.py, pythonwhisper ApplePipeTranscriber pipe volatile 31 drafts/6.3s, apple-speech-api skill ANE traces, HTML diagrams hermes-live-voice-native + oss-comparison + imperfect-fix
2026-07-15 11:14:45 -04:00
2026-07-14 23:42:56 -04:00

Hermes ESP32 Voice Gateway

Durable custom voice gateway for Reyna family ESP32 + iPhone Watch devices. Survives hermes update (which wipes in-tree patches to gateway/platforms/api_server.py).

Upstream issue: ESP32 push-to-talk endpoints POST /api/esp32/voice and WS /api/esp32/voice/ws with:

  • live draft ingestion via Mac mini speech_live_transcribe (ApplePipeTranscriber volatile drafts, ~800ms, 2.12% WER)
  • instant smart reply via Mac mini apple_llm_quick_reply (Apple 3B ANE ~300ms contextual, not generic template)
  • final transcription prefers macmini-live-final over local faster-whisper base (753ms vs 11-13s cold)
  • parallel instant future started at STOP before final transcribe
  • persistence flags persistent=True, is_instant=True, keep_instant=True for iPhone client sticky UI
  • screen animation + TTS via play_audio_base64 / play_mp3_base64 to http://<source_ip>/api/mcp screens
  • audit log ~/.hermes/.../audio_cache/esp32/voice_requests.jsonl

Why a separate repo + plugin

hermes update does git stash --include-untracked + pull origin/main, dropping local modifications to api_server.py. The plugin esp32-voice-gateway lives in ~/.hermes/plugins/ (user plugin dir, never overwritten), and on every Hermes start auto-checks whether the custom routes are present. If missing (after an update), it auto-restores them from the bundled patch and verifies.

What's tracked here

  • patch/api_server_esp32_live_draft_with_llm.patch — full 1675-line patch that is currently running on Adolfo's Pi (verified 2026-07-14)
  • hermes_plugin/ — Hermes user plugin (plugin.yaml + __init__.py) installable via hermes plugins install
  • scripts/restore_after_update.py — manual restore + verification (400 not 404 check)
  • docs/ — original excerpts + audit references

Quick start

1. Create repo on Gitea

The token in ~/.git-credentials only has write:repository, not write:user, so repo creation via API is blocked. Create an empty repo via Gitea UI:

https://git.reynafamily.com/repo/create

  • Name: hermes-esp32-voice-gateway
  • Visibility: private (contains no secrets, but references family device IPs)
  • Uncheck initialize README

Then:

cd ~/Projects/hermes-esp32-voice-gateway
git remote add origin https://adolforeyna:TOKEN@git.reynafamily.com/adolforeyna/hermes-esp32-voice-gateway.git
git push -u origin main

(TOKEN is the existing ~/.git-credentials token — git credential helper already does this)

2. Install the durable plugin

# From this repo
cp -r hermes_plugin ~/.hermes/plugins/esp32-voice-gateway
# or symlink for dev
# ln -s ~/Projects/hermes-esp32-voice-gateway/hermes_plugin ~/.hermes/plugins/esp32-voice-gateway
hermes plugins enable esp32-voice-gateway
hermes gateway restart

3. Verify

# Routes should be 400 (not 404) when hit without WS upgrade:
curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $API_SERVER_KEY" http://127.0.0.1:8642/api/esp32/voice/ws
# -> 400 (means route exists, missing Upgrade header) — NOT 404
# POST empty should be 400 not 404:
curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $API_SERVER_KEY" http://127.0.0.1:8642/api/esp32/voice -X POST --data-binary @/dev/null
# -> 400

4. After hermes update

The plugin auto-restores on next import (gateway start). Check logs:

grep -i "esp32.*restore\|esp32.*patch\|ESP32" ~/.hermes/logs/gateway.log | tail -20
# If auto-restore happened, it logs patched file + needs restart:
systemctl --user status hermes-gateway-kids hermes-gateway --no-pager
# The plugin will request one restart cycle; if stuck in deactivating, run:
# pkill -9 -f "hermes_cli.main gateway run"; systemctl --user reset-failed hermes-gateway*; systemctl --user start hermes-gateway

Manual restore:

~/Projects/hermes-esp32-voice-gateway/scripts/restore_after_update.py

Architecture (for reference)

  • Incoming: iPhone 192.168.68.150:8080 iphone6 (WAV POST) + ESP32 tablets 192.168.68.129 grace-esp32/elias-esp32 (WS PCM s16le 16k mono)
  • Gateway: gateway/platforms/api_server.py APIServerAdapter
    • POST /api/esp32/voice → saves WAV, transcribes (macmini-live-final preferred), runs Hermes agent turn, TTS → WAV (ffmpeg to 16k mono), FileResponse
    • WS /api/esp32/voice/ws → ingest s16le binary frames, every 800ms snapshot → _s16le_to_wav_bytesspeech_live_transcribe (Mac mini pipe volatile) fallback tiny → emit draft/interim_transcript, on STOP → parallel instant future apple_llm_quick_reply (must be >30 chars, else use final transcript for context), final transcribe Mac mini full buffer (11588ms fallback if Mac down), emit instant_response persistent=True + response_text is_instant=True + done instant_ok + thinking keep_instant, background _run_esp32_voice_background (screen animation/text/image/audio via http://<source_ip>/api/mcp)
  • MacMiniMCP: ~/Projects/MacMiniMCP (canonical on Mac) 37 tools including speech_live_transcribe, apple_llm_quick_reply, speech_synthesize_base64, voicebox_quick_reply (Aiden boy)
  • Pitfalls documented in hermes-multi-profile-voice-gateways skill references: esp32-voice-500-import-loop.md, esp32-voice-gateway-update-survival.md, etc.

Contributing

This repo is Reyna family private. Changes to the voice gateway should update both:

  1. patch/api_server_esp32_live_draft_with_llm.patch (regenerated from git diff of ~/.hermes/hermes-agent/gateway/platforms/api_server.py)
  2. The plugin's restore logic (it just reapplies patch, so keeping patch current is enough)

Regenerate patch:

cd ~/.hermes/hermes-agent
git diff gateway/platforms/api_server.py > ~/Projects/hermes-esp32-voice-gateway/patch/api_server_esp32_live_draft_with_llm.patch
cp ~/.hermes/esp32_live_draft_with_llm_patch.patch ~/Projects/hermes-esp32-voice-gateway/patch/ 2>/dev/null || true

TODO

  • Extract to true standalone plugin that injects routes at runtime without touching file (eliminate patch reapply entirely). Requires hooking APIServerAdapter.connect — current plugin does auto-restore via file patch for reliability.
  • Add integration test that curls both endpoints after restore.

License

Private - Reyna family.

S
Description
No description provided
Readme 162 KiB
Languages
Python 99%
Shell 1%