Files
talk/docs/spec.md
T
m3ta-chironandClaude Fable 5.1 2e2bc27acf docs: talk-Skill mit Opt-in-Etikette, AGENTS-Snippet, README Installation/Release/Toast
skills/talk/SKILL.md: wann sprechen (nur auf Trigger in der Session oder
Standing-Order in der AGENTS.md, session-sticky bis Widerruf), wann nicht,
was eine gute Ansage ist (Status · nächster Schritt · wofür der Nutzer
gebraucht wird; Beispiele gut/schlecht), Umgang mit muted/discarded/
--now/Prioritäten, Pitfalls. AGENTS-snippet.md: der Block für Projekt-
AGENTS.md.

README: Installation (Release-Asset mit Prüfsumme, Nix-Flake, go install),
Skill-Abschnitt, Toast als echte Desktop-Benachrichtigung, Release-Ablauf
und nix build in „Entwicklung". Spec-Nachtrag: Release ohne GoReleaser,
Notifications ohne beeep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 10:37:46 +02:00

14 KiB
Raw Blame History

Spec: talk 2.0 — Cross-Platform Voice Notifications für KI-Agenten

Status: ready-for-agent Created: 2026-10-18 Source: Grilling-Session 2026-10-18 (shared understanding, alle Äste besucht)


Problem Statement

KI-Agenten (Coding-Agenten wie opencode/Claude Code, Firmen-Chats, Hintergrund-Jobs) können mich heute nur visuell erreichen — Terminal-Output, Toast, push notification. Wenn ich weg vom Bildschirm bin (Kaffee, anderes Fenster, Meeting am zweiten Monitor), verpasse ich, dass ein Build fertig ist, ein Agent auf meine Freigabe wartet oder ein Job gecrasht ist.

Der bestehende talk/crunch-Stack löst das prinzipiell, ist aber unbrauchbar als Basis: bash + mpv + notify-send + agenix = Linux-Desktop-only, ElevenLabs-only (Key pro Host, in der Praxis nur auf einem bestätigt funktionierend), kein natives Tool-Interface für Agenten (nur Shell-Umwege), keine Queue (Audio überlappt), flüchtiger Mute-Toggle, und null Geschichte auf dem Windows-Fleet der Firma.

Solution

talk 2.0: Ein cross-platform Werkzeug (Windows/macOS/Linux, amd64+arm64) in Go als statische Binaries — talk (CLI mit Bubble-Tea-TUI) und talkd (Daemon mit Queue). Jeder KI-Agent, der ein Programm ausführen kann, kann damit sprechen: talk "Build fertig". Lokale MCP-Clients bekommen zusätzlich einen nativen MCP-Endpoint auf localhost. Die TTS-Qualität skaliert mit verfügbaren Keys über eine Vier-Stufen-Fallback-Kette bis hinunter zu kostenlosen/offline-Stimmen — Stimme funktioniert also überall ab Minute eins. Sprechen ist striktes Opt-in durch den Nutzer; der Daemon erzwingt Rate-Limit, Mute und Ruhezeiten als Backstop unter allem Prompt-Verhalten.

Verteilung: Gitea-Releases nach agent-lib-Muster (Asset-Schema talk_<os>_<arch> + sha256); Firma: interne Spiegelung + Fleet-Role (Ansible, Program Files + PATH); privat: Nix-Flake. Skill lebt im Repo und rollt in der Firma über az-agent-defaults/agent-lib aus.

User Stories

  1. Als Nutzer will ich talk "Text" aufrufen können, damit ich sofort weiß, wie die Schnittstelle funktioniert, ohne Doku zu lesen.
  2. Als Nutzer will ich, dass der Aufruf non-blocking zurückkehrt, damit mein Skript/Agent nicht auf das Audio wartet.
  3. Als Skript-Autor will ich talk "Text" --wait, damit Cron-Jobs erst nach der Ansage weiterlaufen bzw. enden.
  4. Als Nutzer will ich talk wrap -- <befehl> nutzen können, damit mir Dauer + Erfolg/Misserfolg eines beliebigen Befehls angesagt werden.
  5. Als KI-Agent will ich das Programm mit Argument oder via stdin füttern können, damit ich es aus jeder Umgebung heraus aufrufen kann.
  6. Als KI-Agent (MCP-fähig) will ich einen lokalen MCP-Endpoint mit einem speak-Tool nutzen können, damit ich Voice nativ als Tool habe statt Shell-Umwege.
  7. Als Browser-KI-Chat (später) will ich POST /speak auf localhost mit CORS-Erlaubnis rufen können, damit eine Web-UI (z. B. LibreChat) mich ansprechen kann, ohne dass talk dafür geändert werden muss.
  8. Als Nutzer mit ElevenLabs-Key will ich die Premium-Stimme bekommen, damit Ansagen so klingen wie gewohnt.
  9. Als Nutzer mit nur einem OpenAI-Key will ich OpenAI-TTS als Provider nutzen können, damit ich keinen zweiten Account brauche.
  10. Als Nutzer ohne jeden Key will ich automatisch Edge-TTS (kostenlos) bekommen, damit Stimme out-of-the-box funktioniert.
  11. Als Offline-Nutzer will ich auf OS-eingebaute TTS-Stimmen (say/SAPI/espeak) zurückfallen, damit auch ohne Netz/Key gesprochen wird.
  12. Als Nutzer will ich, dass die Provider-Kette automatisch durchfällt (Key ungültig, Kontingent leer, Netz weg), damit nie stumm bleibt, wenn nicht sein muss.
  13. Als Nutzer will ich die Sprache des Texts automatisch erkannt bekommen (de/en), damit englische Agent-Antworten nicht mit deutscher Stimme verhackstückt werden.
  14. Als Nutzer will ich pro Provider und Sprache eine Stimme in der config.toml setzen, damit meine gewohnte Stimme überall gleich klingt.
  15. Als Nutzer will ich talk setup als geführten Wizard (Stimmen probehören, Key testen, Config schreiben), damit ich ohne Terminal-Erfahrung in 2 Minuten eingerichtet bin.
  16. Als Nutzer will ich talk status als Live-Dashboard (Queue, Mute, aktiver Provider, Version), damit ich immer weiß, was das Tool gerade tut.
  17. Als Nutzer will ich talk voices als interaktive Liste mit Vorhör-Preview, damit ich Stimmen ohne IDs auswendig zu lernen vergleichen kann.
  18. Als Nutzer will ich talk mute / talk unmute als persistenten Schalter, damit alle Agenten und Skripte still sind, bis ich es erlaube.
  19. Als Nutzer will ich konfigurierbare Ruhezeiten (quiet_hours = "18:00-08:00"), damit 23:47-Uhr-Cron-Jobs mich nicht wecken.
  20. Als Nutzer will ich talk cancel, damit eine laufende oder geplante Ansage sofort stoppt.
  21. Als Nutzer will ich --priority, damit Dringendes die Queue vorn überspringt und Gedrängel bei Rate-Limit verworfen werden darf.
  22. Als Nutzer will ich, dass sich Audio-Nachrichten nie überlappen (Queue serialisiert), damit bei drei gleichzeitigen Agenten alles verständlich bleibt.
  23. Als Nutzer will ich ein Rate-Limit als Daemon-Gesetz (Standard ~1 Ansage/30 s), damit kein Agent-Spam möglich ist, egal was im Prompt steht.
  24. Als Agent-nutzender Nutzer will ich Opt-in-Etikette im Skill dokumentiert: Agent spricht nur auf meine Anweisung oder Standing-Order in der AGENTS.md, session-sticky bis Widerruf, damit keine Agentin von sich aus plappert.
  25. Als Agent-nutzender Nutzer will ich, dass die Ansage nützlich ist (Status / nächster Schritt / wofür ich gebraucht werde), nicht die vorgelesene Langantwort, damit 10 Sekunden Ansage reichen.
  26. Als Fleet-Administrator will ich statische Binaries von Gitea-Releases versionpinnt + sha256-verifiziert ausrollen (agent-lib/officecli-Muster), damit das Ansible-Rollout deterministisch ist.
  27. Als Fleet-Administrator will ich Keys user-spezifisch als Env-Variablen (TALK_*) ausrollen nach unserem AZ_FLEET-Muster, damit kein Secret in Config-Dateien landet.
  28. Als Fleet-Administrator will ich talk doctor als Diagnose-Output, damit „kein Ton bei Kollege X" in 5 Sekunden selbsterklärend ist.
  29. Als Fleet-Benutzer ohne Adminrechte will ich, dass talk ohne Runtime-Abhängigkeiten (kein ffmpeg, kein Player, kein Python) läuft, damit es auf jedem Firmenrechner einfach funktioniert.
  30. Als Nutzer auf einem Rechner ohne laufenden Daemon will ich einen Degraded Mode (Lock-File, sequenziell), damit talk auch ohne talkd nie kaputt ist.
  31. Als Privatnutzer mit NixOS-Hosts will ich einen Flake-Output im Repo, damit meine Hosts talk wie gewohnt deklarativ beziehen.
  32. Als OS-Nutzer will ich eine Desktop-Notification parallel zum Audio mit Abbrechen-Button, wo das OS es hergibt, damit ich stummschalten kann, ohne ein Terminal zu öffnen.
  33. Als Nicht-Terminal-Nutzer will ich, dass TUI-Befehle ohne TTY auf plain Output zurückfallen, damit Pipes und Automatisierung niemals brechen.
  34. Als Projekt-Betreuer will ich PR-getriebene Tests + Release-Matrix in Gitea Actions, damit jeder Tag automatisch konsistent gebaute Artefakte für alle Plattformen produziert.

Implementation Decisions

  • Sprache/Laufzeit: Go; statische Binaries für win/mac/linux × amd64/arm64. Keine Runtime-Abhängigkeiten.
  • Binaries: talk (CLI) + talkd (Daemon). Repo: code.m3ta.dev/m3tam3re/talk, MIT-Lizenz.
  • Daemon: talkd besitzt die Queue (Serialisierung, Prioritäten, Rate-Limit, Verwerfen bei Gedrängel), den persistenten Mute-State, die Ruhezeiten-Fenster (lokale Zeit, still verwerfen oder toast-only, konfigurierbar), die HTTP-API (POST /speak auf 127.0.0.1, CORS-Domains konfigurierbar) und einen MCP-Endpoint (Streamable HTTP, localhost).
  • Degraded Mode: ohne laufenden talkd fällt das CLI auf Lock-File-Sequential zurück; nichts bricht still.
  • Audio: embedded Playback (oto) — kein mpv/ffmpeg nötig. Audio-Sink wählbar inkl. null (CI, Server, doctor).
  • Notifications: best effort über beeep (notify-send / macOS Notification Center / Windows Toast); Cancel-Button, wo das OS ihn hergibt.
  • Provider-Kette V1 (Auto-Fallback in dieser Reihenfolge, überschreibbar per Config/Flag): ElevenLabs → OpenAI TTS → Edge-TTS (frei, kein Key) → OS built-in (macOS say / Windows SAPI / Linux espeak).
  • Keys/Config: Env-first (TALK_ELEVENLABS_KEY, TALK_OPENAI_KEY, …) plus optionale config.toml im platform-nativen Config-Dir (~/.config/talk / ~/Library/Application Support/talk / %APPDATA%\talk). Provider-Endpunkte konfigurierbar (Testbarkeit, Firmenserver später). Per-User-Keys in V1; zentraler Firmenserver/Gateway bewusst später.
  • Sprache/Stimmen: de/en-Heuristik (reine Funktion, kein ML); voice.<provider>.<lang>-Mapping in der config.toml; persönliche ElevenLabs-Stimme (KXxZ…) als privater Default, neutrale Defaults im Repo.
  • CLI-Verhalten: non-blocking Default; Flags --wait, --now (Queue-Überprung für Dringendes), --priority, --voice, --lang, --provider. Befehle: say, wrap, mute, unmute, status, cancel, voices, test, doctor, setup.
  • TUI (Bubble Tea): interaktiv = setup (Wizard), status (Live-Dashboard), voices (Picker mit Preview); plain & scriptbar = say, wrap, mute, cancel, test, doctor; kein TTY → immer plain.
  • Etikette (Skill-Policy): striktes Opt-in — Sprechen nur auf Nutzer-Trigger des Skills oder Standing-Order in der AGENTS.md; session-sticky bis Widerruf (Prompt-Level, kein Daemon-Session-State); Ansageinhalt = Status/nächster Schritt/wofür der Nutzer gebraucht wird, nie die Langantwort. Daemon-Backstop: Rate-Limit + Mute + Ruhezeiten gelten unabhängig vom Prompt.
  • Verteilung: GoReleaser → Gitea-Releases, Asset-Schema talk_<os>_<arch> + sha256 (agent-lib-Vertrag); Firma spiegelt intern (pin_mirror-Muster) + Fleet-Role nach agent-lib/officecli-Vorbild (Program Files + Machine-PATH, admin-geschützte ProgramData-Config); Skill-Rollout Firma via az-agent-defaults → agent-lib Three-State-Sync; privat via flake.nix.
  • CI: Gitea Actions — PR → Tests, Tag → Release-Matrix.
  • Legacy-Abriss: alte voice-notify/crunch-jobs-Skills (AGENTS-Repo) werden Redirect-Stubs; nixpkgs-PRs #24/#7 werden als superseded geschlossen; m3ta-nixpkgs talk/crunch nach Migration deprecatet; ElevenLabs-Key (exponiert in altem Prototyp-Skript) wird rotiert, danach das Skript gelöscht.
  • Naming: Name bleibt talk (akzeptierte Kollision mit historischem Unix-talk; say vermieden wegen macOS).

Testing Decisions

  • Guter Test testet ausschließlich extern beobachtbares Verhalten an der Schnittstelle, nie Implementationsdetails.
  • Primäre Seam (eine): die Prozessgrenze von talkd — Daemon + CLI als Black Box, mit zwei über die Config injizierbaren Fakes: (1) Provider-Endpunkte zeigen im Test auf einen Fake-HTTP-Server mit Audio-Bytes; (2) audio.sink = "null" schaltet echtes Playback ab. An dieser Seam werden getestet: Queue-Serialisierung/Priorität/Rate-Limit/Verwerfen, Mute-Persistenz, Ruhezeiten, Fallback-Kette (Fake-Provider 500 → nächster Provider), non-blocking + --wait, Degraded Mode, HTTP-/speak-Vertrag inkl. CORS, MCP-Endpoint-Grundvertrag, status/cancel/doctor-Output.
  • Zwei reine Neben-Seams (tischgesteuerte Unit-Tests, keine Mocks): Spracherkennung (de/en) und Config-Auflösung (Env + TOML-Merge).
  • Bewusst ungetestet (hardware-/OS-gebunden): echtes Audio-Rendering und OS-Toasts — abgedeckt durch talk doctor als manuelles Diagnosewerkzeug.
  • Vorläufer: keine — grüne Wiese. CI läuft von Ticket 1 an in Gitea Actions mit.

Out of Scope

  • LibreChat-Frontend-Anbindung (CORS-Tür wird gebaut, Integration selbst nicht)
  • Browser-Extension als Brücke
  • Zentraler Company-Gateway-Server (ein Key für alle)
  • Timer/Scheduling (crunch at|in|daily bleibt OS-nativ: cron/systemd/Task Scheduler)
  • Lokale Neural-Stimmen (Piper/Kokoro) — späterer Ausbau
  • Kalenderbewusste Ruhezeiten (nur statische Zeitfenster in V1)
  • Homebrew/Scoop/AUR-Feeds — später optional
  • Mehrnutzer-Maschinen (Ein-Nutzer-pro-Host wie im Fleet)

Further Notes

  • Pending (Nutzer): ElevenLabs-Key-Rotation im Dashboard VOR Löschung des alten Prototyp-Skripts.
  • Persönlicher Default-Voice: ElevenLabs KXxZd16DiBqt82nbarJx (aus altem Stack übernommen als privater Default, nicht Repo-Default).
  • Session-Stickiness der Etikette ist Prompt/Skill-Level — der Daemon kennt keine Agent-Sessions.
  • Offene Später-Entscheidung: LibreChat-Anbindung via Frontend-Patch vs. Browser-Extension — blockiert nichts, Tür steht mit CORS-fähiger HTTP-API.
  • Verwandte Firmen-ADRs: 0009 (agent-lib), 0012/0013 (Secrets-Env-Bridge/Machine-Env-Keys), 0016/0017 (user-scharfe Keys, MCP-Audience-Scoping) — talk folgt diesen Mustern.

Nachtrag 2026-10-24 — Entscheidungen nach Architektur-Review

Diese Punkte ersetzen die entsprechenden Aussagen oben (Grilling-Stand 2026-10-18):

  • Ein Binary statt talk + talkd. Der Daemon ist talk daemon run; talk say/talk wrap starten ihn beim ersten Sprechbefehl selbst im Hintergrund (daemon.autostart, Default an; talk daemon start|stop explizit). Der Degraded Mode mit Lock-Datei entfällt; ohne erreichbaren oder startbaren Daemon spricht talk direkt (unserialisiert, mit Hinweis).
  • CLI ist die Agenten-Schnittstelle. MCP bleibt als Tür bestehen, ist aber kein Fokus mehr; Browser-Zugriff ist serverseitig auf POST /speak von gelisteten Origins beschränkt (Origin-Guard, nicht nur CORS).
  • now:true bricht bewusst aus der Ruhezeit aus (bestätigt).
  • Zwei weitere Gesetze: queue.max_text (400 Zeichen, Kürzung an der Wortgrenze) und queue.max_age (2 min, veraltete Stau-Ansagen verfallen).
  • Zielbild Veröffentlichung: talk soll perspektivisch öffentlich nutzbar sein. Oberstes Kriterium ist Usability für Nicht-Terminal- Nutzer; die Bubble-Tea-TUI ist kein Selbstzweck — einfache, klare Befehle (doctor, setup, test, voices) haben Vorrang.
  • Release ohne GoReleaser: scripts/release.sh baut die sechs Targets mit go build; .gitea/workflows/release.yml legt bei Tag v* das Gitea-Release an. Asset-Vertrag unverändert (talk_<os>_<arch>[.exe] + checksums.txt). flake.nix liefert Paket, App, Overlay und devShell.
  • Notifications ohne beeep: Desktop-Toast über die Bordmittel des OS (notify-send / osascript / PowerShell), exec-basiert wie osbuiltin; zunächst nur für quiet_mode = "toast". Cancel-Button: später.