Zwei Ursachen:
- pending_queue-Bubbles haben (noch) keine clientMsgId → der Reconnect-
History-Sync erkannte sie nicht als lokal-only und verwarf sie, waehrend
projectQueues den Eintrag behielt → 'N in Warteschlange' fror ein ohne
sichtbare Nachricht. Jetzt bleiben pending_queue-Bubbles beim Sync erhalten.
- Watchdog: haengt ein Kontext >15s auf 'running', obwohl der Brain ihn NICHT
als busy meldet (Antwort beim Abbruch verloren), schaltet die Queue jetzt
selbst weiter (dequeue oder idle) statt fuer immer zu blockieren.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Trigger-Zeit (Issue 4): fires_at wird beim Anlegen IMMER auf UTC normalisiert
(naiv = lokale Wanduhrzeit Europe/Berlin → UTC, aware → astimezone). Passt zur
UTC-Ausfuehrung in background.py → feuert zur gemeinten Ortszeit, zeitzonen-
portabel. Tool-Desc + Prompt: fires_at = lokale Wanduhrzeit ohne Zone schreiben,
Server rechnet um. Behebt den 2h-Versatz.
- project_summary (Issue 2): liest jetzt auch den Hauptchat (name='Hauptchat')
und die Beschreibung sagt ARIA, es fuer 'schau mal in Projekt X / Hauptchat
rein' / 'hol dir die Infos aus ...' zu nutzen.
- Privatkopie (Issue 3): IDENTITY_ANCHOR-Regel — YouTube-Downloads fuer Stefans
PRIVATgebrauch sind eine Privatkopie (§53 UrhG), nicht reflexartig ablehnen/
moralisieren; Urteil bleibt fuer Weiterverbreitung/kommerziell.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App-Features + Completed-Log aktualisiert; veraltetes Barge-In-Cancel-Verhalten
durch das neue Anstellen-statt-Abbrechen ersetzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Queue pro Projekt (anstellen statt abbrechen) + [[AWAIT]]-Rueckfrage-Loop,
Pro-Projekt-Textfeld-Entwuerfe, TTS-Abspiel-Queue (back-to-back sprechen
nacheinander), Voice bricht nicht mehr ab. App + Diagnostic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher war die serialisierte Sprachausgabe zweier fast gleichzeitig fertiger
Antworten Timing-Glueck: PcmStreamPlayer.start() ruft stopInternal() (flush+
release), eine neue Antwort haette die laufende also abgeschnitten, sobald ihr
Audio waehrend der Wiedergabe der ersten ankam.
Jetzt echte Abspiel-Queue im audioService: kommt eine neue HOERBARE Antwort
waehrend eine andere noch hoerbar spielt (pcmAudiblePlaying bis
PcmPlaybackFinished, nicht nur bis Stream-Ende), werden ihre PCM-Chunks
gepuffert und erst nach dem Drain der laufenden nachgespielt. Bei wartender
Antwort meldet PcmPlaybackFinished NICHT 'fertig' (kein Wake-Word-Re-Arm).
Harter Stop/Barge-In/Mute verwirft die Queue. Race gegen gleichzeitige Chunks
einer dritten Antwort geschlossen (Flags vor await gesetzt). onPcmCached meldet
den WAV-Pfad nachgespielter Antworten fuer Mund-Button-Replay.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnostic bekommt denselben Queue-Automaten wie die App: anstellen statt
abbrechen, awaiting_reply pausiert die Queue (naechste Eingabe beantwortet die
Rueckfrage), Queue-Banner mit loeschbaren Eintraegen ueber dem Eingabefeld,
und Pro-Kontext-Textfeld-Entwuerfe (Feldinhalt bleibt beim Kontextwechsel
erhalten). rvs_chat-Handler liest awaiting_reply aus der Bridge-Payload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zweite Nachricht waehrend ARIA arbeitet wird jetzt ANGESTELLT statt den
laufenden Task abzubrechen. Stellt ARIA eine blockierende Rueckfrage, pausiert
die Queue und die naechste Eingabe beantwortet sie — bis eine finale Antwort
kommt, dann laeuft der naechste Queue-Eintrag (pro Projekt unabhaengig).
- Brain: ARIA deklariert Rueckfragen per unsichtbarem [[AWAIT]]-Marker
(wie speak/converse; kein '?'-Raten). _extract_await_marker strippt ihn,
chat() gibt 5-Tupel (+awaiting_reply), System-Prompt erklaert den Marker.
- Bridge: awaiting_reply aus Brain-Response in die chat-Broadcast-Payload.
- App: app-lokale Queue + Zustandsautomat (idle/running/awaiting_reply) pro
Projekt; Send-Flow von Abbruch auf Anstellen; Stop-Button (cancelRequest)
schaltet die Queue weiter; sichtbare pending_queue-Bubbles (tippen loescht);
Rueckfrage-/Queue-Banner ueber dem Eingabefeld. Voice bricht nicht mehr ab
(haltet nur TTS, serialisiert im Brain-Lock); Text-Send erkennt Brain-busy
als Fallback. Pro-Projekt-Textfeld-Entwuerfe (Draft-Map + AsyncStorage).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
colorForScannerFrame existiert in react-native-camera-kit v13 nicht (No-Op) —
entfernt. Die Lib markiert zudem etliche optionale CameraScreen-Props faelschlich
als required (defaultProps fuellen sie zur Laufzeit); Props lokal als any
gespreadet, um die fehlerhaften .d.ts zu umgehen ohne Runtime-Verhalten zu
aendern. scanBarcode/onReadCode bleibt die korrekte v13-Barcode-API.
Projekt jetzt komplett tsc-clean (0 Fehler).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sendTextMessage stand vor interruptAriaIfBusy und sendPendingAttachments,
hatte beide aber im deps-Array → Temporal Dead Zone (TS2448/2454). Lief nur,
weil Babel const→var hebt (deps auf erstem Render undefined, Body laeuft erst
bei Interaktion). Deklaration hinter beide verschoben — Abhaengigkeit ist
einseitig, sendTextMessage wird nur in der JSX genutzt. tsc-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Live-chat-Payload trug keine files — Anhaenge kamen nur als separates
file_from_aria-Event (eigene, teils unsichtbare Bubble). An der Text-Nachricht
tauchte die Datei erst nach einem Seitenwechsel auf (Reload aus chat_backup,
das die files kennt).
- Bridge: files jetzt direkt in der chat-Payload (selbe Struktur wie Backup).
- App: chat-Handler haengt payload.files an die Text-Bubble (wie der Reload-Pfad)
und entfernt die redundante Solo-file_from_aria-Bubble mit passendem serverPath.
- App: file_from_aria legt keine Doppel-Bubble an, wenn die Datei schon an einer
Nachricht haengt (Event-Reihenfolge-unabhaengig).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude haengt manchmal reflexartig ein leeres <voice></voice> an (v.a. bei
Spotify-Antworten). clean_text_for_tts nahm dann group(1)='' → text='' → gar
keine Sprachausgabe. Das erklaerte, warum Playlist-Wechsel 'Fliegen' vorgelesen
wurde (Claude schrieb Inhalt ins Tag), 'Prodigy' aber stumm blieb (leeres Tag)
— reiner Claude-Output-Zufall, nicht Skill/Playlist-Name.
Fix: leeres/whitespace <voice> ignorieren und den normalen Anzeigetext lesen.
Deterministisch, unabhaengig von Claudes Tag-Reflex.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_LIVE_HINTS war die falsche Idee: aus offenem Freitext per Wortliste die
Absicht raten ist nie vollstaendig, jeder Miss = Halo (nur von per-Skill auf
per-Wort verschoben). Generisch heisst nicht 'groessere Regex', sondern: das
Modell entscheidet selbst ob es Grundwahrheit/ein Tool braucht (<<ESCALATE>>,
Prompt). Der 8B kann das noch nicht zuverlaessig → local bleibt per Einstellung
abschaltbar (aus, nicht raus); ein staerkeres lokales Modell uebernimmt spaeter
die Selbst-Erkennung. Absicherung bleibt der Output-Guard, nicht der Input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'lied' hatte eine Wortgrenze → 'lieder' (lied+er) matchte nicht, 'springe
zwei lieder weiter' rutschte an local vorbei. lied\w*/song\w* + spring\w*
...weiter ergaenzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B1b hatte dem 8B run_*/web_search-Tools gegeben. Das war die Wurzel von
BEIDEM: (1) ein 8B ist ein unzuverlaessiger Tool-Caller und erfindet lieber
eine plausible Antwort ('der Song ist X') statt das Tool zu rufen → halo;
(2) das zwang zu per-Skill-Guards (skaliert nicht).
Fix ohne eine einzige per-Skill-Zeile:
- Local: tools=[] — kann kein Skill-Ergebnis mehr faelschen.
- Router: _LIVE_HINTS schickt alles mit aktueller Grundwahrheit
(Wetter/News/Uhrzeit/Musik/Preise/Timer/…) VORAB an Claude, nicht erst
nach einem gescheiterten Local-Versuch (kein Doppel-Zahlen). Topic-Ebene,
nicht per-Skill — ein neuer Skill braucht hier keine Zeile.
- Prompt: _AWARENESS sagt local explizit, dass es tool-los ist und fuer
Live/Aktion eskaliert.
Arbeitsteilung: Fast-Path (Regex→Skill, deterministisch, 0 halo) fuer
Befehle · Local (tool-los) fuers Reden · Claude fuer alles mit Tool/Fakt/
Gedaechtnis/Urteil.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local erfand Live-Auskuenfte (aktueller Song, Restzeit, Skip-Titel) ohne
run_spotify zu rufen. Neuer Detektor _claims_live_media_state() + Guard
eskaliert solche Behauptungen ohne echten Skill-Call an Claude. Local-Prompt
gehaertet: nie Titel/Interpret/Restzeit ohne Tool-Ergebnis nennen.
Router: _FORCE_CLAUDE respektiert 'nimm Claude/Clodi' → nie lokal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ohne Ohr (manuelle Aufnahme) + Stop-Button: das echte STT-Endpoint (mit Text)
fuellt die Bubble, aber danach klappt ein zweites, LEERES stream_end-Endpoint
nach (Audio schon transkribiert). Der Empty-Handler loeschte die Bubble
bedingungslos per audioRequestId — auch die schon mit Text gefuellte.
Ergebnis: Bubble weg, obwohl ARIA an der Antwort arbeitet; erst nach
App-Neustart (aus chat_backup) wieder da.
Fix: leeres Endpoint entfernt nur noch den NOCH UNAUFGELOESTEN Platzhalter
(Text enthaelt 'Spracheingabe wird verarbeitet'), nicht eine bereits
aufgeloeste Bubble.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Skandal-Ursache: die skill_update-Anleitung sagt "erst skill_get lesen, dann
updaten" — aber skill_get existierte GAR NICHT als Tool. ARIA/Claude konnte
einen Skill also nie LESEN vor dem Aendern → Blind-Rewrite aus Beschreibung +
Gedaechtnis. (Claude sagte korrekt "skill_get steht mir nicht zur Verfuegung"
und hat den Spotify-Skill trotzdem komplett neu geschrieben.)
Neu: skill_get(name) liefert Manifest (args/fast_patterns/speak/converse) +
kompletten entry_code (echter Python) + README. skills.read_skill_source().
So sieht ARIA den Ist-Code und aendert gezielt statt blind zu ueberschreiben.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fast-Path hatte converse hart auf False — ein Fast-Path-Skill konnte damit nie
ein Dialog-Skill sein. Jetzt konsistent zu local/Claude: speak UND converse
kommen aus dem Skill-Manifest-Default und werden vom Skill-JSON-Output pro
Aufruf ueberschrieben. Default (kein Flag) bleibt false/false = stumm/stop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verallgemeinert die Ausgabesteuerung, dynamisch statt hardcoded:
- Zwei Flags: speak (vorlesen?) + converse (danach 30s weiterlauschen?).
Getrennt, weil 'vorlesen' und 'Dialog offen halten' verschiedene Dinge sind
('was laeuft gerade' → vorlesen JA, aber keine 30s).
- Statischer Manifest-Default (speak/converse) PLUS: der Skill kann beide im
JSON-Output PRO AUFRUF setzen und den Default ueberschreiben — so kann EIN
Skill gemischt sein (Spotify: 'next' stumm/stop, 'was laeuft' vorlesen/stop).
- chat() gibt jetzt (reply, answered_by, speak, converse) zurueck; gilt fuer
Fast-Path (converse immer False), local UND Claude (run_*-Skill setzt beide).
- Brain: _last_skill_flags aus Skill-stdout-JSON, _skill_response_flags mergt
Output > Manifest > False. main.py ChatOut.converse, background.py angepasst.
- Bridge: converse aus /chat gelesen + in Chat-Payload + _process_core_response.
- App: converseRef aus der Payload; onPlaybackFinished endet mit skipPassive
wenn converse=false (vorlesen ohne 30s).
- Skill-Bau-Anleitung (skill_create/update-Schema): speak+converse dokumentiert
MIT dem WARUM, damit ARIA sie beim Bauen sinnvoll setzt (nicht nur mechanisch).
- Prompt-Hardcode fuer Spotify-Faehigkeiten raus → Skills beschreiben sich selbst.
Bestehende Skills ohne Flags = false/false = stumm/stop (kein Verhaltenswechsel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- router.py: die run_spotify-Faehigkeiten NICHT mehr im Prompt aufzaehlen
(war selbst Hardcoding). Generisch: "run_*-Skills — was sie koennen steht in
IHRER Tool-Beschreibung, lies + nutze sie fuer alles Passende". Skills
beschreiben sich selbst (dynamisch, ARIA-authored). Anti-Halluzination bleibt.
- App: Stop-Button waehrend passivem 30s-Lauschen ('listening') beendet jetzt
sauber (exitPassiveListening) statt via leerem Endpoint den Passiv-Stream neu
zu starten — die 30s liefen sonst von vorne los.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local hat "Spotify: überspringe 3 Titel" / "Playlist X abspielen" geantwortet
OHNE run_spotify je zu rufen (Skill-Logs: kein Aufruf) — reine Fabrikation,
real passierte nichts. Zwei Ursachen behoben:
- Prompt: run_spotify-Beschreibung von "next/pause/weiter" auf JEDE
Musiksteuerung erweitert (Playlist, Gerät, überspringen mehrerer, Lautstärke)
+ harte Anti-Halluzinations-Regel (nie eine Aktion behaupten ohne Tool-Call).
- Guard: sagt local eine Steuerbefehl-Quittung ('Spotify: …', 'Playlist …
abspielen', 'überspringe … Titel') aber hat KEINEN run_*-Skill gerufen
(_local_ran_skill=False) → eskalieren, Claude ruft das Tool zuverlässig.
_looks_like_skill_action bewusst eng (Doppelpunkt-Praefix / spezifische Verben),
trifft normale Musik-Konversation nicht.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Reverse-Geocode lief nur async im Hintergrund — die ERSTE Nachricht an
einem Ort (v.a. direkt nach Bridge-Neustart, Cache leer) wurde gebaut BEVOR
der Name fertig war → Präfix ohne Ort → local suchte Wetter mit rohen
Koordinaten und fand nichts.
Neu: _ensure_place_name() awaitet den (laufenden oder frisch gestarteten)
Geocode kurz mit Timeout, bevor der Standort-Präfix gebaut wird. Gecacht →
nur die erste Nachricht an einem neuen Ort zahlt ~0,5s, danach instant.
Timeout/Fehler → Fallback auf reine Koordinaten (kein Hänger).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neben "nordwestwaerts" jetzt auch die genaue Bearing-Gradzahl (0-359) —
"unterwegs nordwestwaerts (304 Grad)". Die KI nutzt, was die Frage braucht
(Pannenhilfe → Ort/km, Luftfahrt/Navigation → exakte Peilung).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aus dem permanenten GPS wird die Bewegungsrichtung berechnet: Bearing zwischen
einem Anker-Punkt und der aktuellen Position, gemappt auf 8-Wind-Adverb
(nordwestwaerts …). Der Anker rueckt erst bei echter Bewegung (>25 m) nach,
sonst zaehlt GPS-Jitter im Stand nicht — Stehenbleiben behaelt die letzte
Richtung, Umdrehen liefert automatisch die neue.
Praefix jetzt z.B.:
[Stefans aktueller Standort: A 28 bei km 55,6, Oldenburg, Niedersachsen
(GPS 53.12, 8.22), unterwegs nordwestwaerts. ...]
Reiner Bridge-Fix (Haversine + Bearing lokal, kein Dienst noetig).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Erweitert den Reverse-Geocode: Nominatim zoom=18 → Straße + Hausnummer, PLZ,
Stadt, Bundesland ("Am Wunderburgpark 6, 26135 Oldenburg, Niedersachsen").
Bei Autobahn/Bundes-/Kreisstraße wird die Ref (A 28 / B 75 / K …, aus
extratags) vorangestellt + best-effort Kilometer via Overpass-Milestone
(highway=milestone, distance-Tag, naechster im Umkreis) → z.B.
"A 28 bei km 55,6, Oldenburg, Niedersachsen". Für Pannenhilfe & Co.
Cache-Raster von ~1km auf ~110m verfeinert (Straßen-Genauigkeit). Overpass
nur wenn's nach Straße/Autobahn aussieht (ref/Autobahn im Namen). Alles
best-effort mit stiller Fallback-Kette (Fehler → gröberer Ort → nur GPS).
NOMINATIM_URL + OVERPASS_URL env → self-hostbar.
Richtung ("Richtung Leer") bewusst weggelassen — nicht zuverlaessig aus
einem Punkt ableitbar (braucht Fahrtrichtung/Routing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Das lokale 8B kann Koordinaten nicht zuverlässig in eine Stadt uebersetzen
(riet "Berlin" fuer Oldenburg). Claude kann's, aber das kostet Tokens.
Loesung: die Bridge loest lat/lon EINMAL via Nominatim (OpenStreetMap, gratis,
keyless) auf und schreibt den Namen in den Praefix:
[Stefans aktueller Standort: Oldenburg, Niedersachsen (GPS 53.12, 8.22). ...]
So muss KEIN Modell mehr raten — local UND Claude lesen den Ort direkt ab,
Token-Ersparnis bleibt voll erhalten.
- _persist_location merkt sich den letzten Ort + triggert den Geocode async
(im Hintergrund, gecacht pro ~1km-Raster, nur bei Bewegung → Nominatim-
Rate-Limit locker eingehalten).
- _build_core_text nutzt frische ODER letzte bekannte Position (Praefix faellt
beim passiven Weiterreden ohne frisches GPS nicht mehr weg) + den Ortsnamen.
- NOMINATIM_URL env (Default oeffentlicher Dienst) → spaeter self-hostbar.
Reiner Bridge-Fix, kein APK/Brain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vorher gab Claude immer speak=true zurück — führte Claude einen speak=false-
Steuerbefehl aus (z.B. "spiele auf duffy desktop weiter"), wurde die Antwort
trotzdem vorgelesen + 30s. Jetzt konsistent zu Fast-Path/local: führt Claude
einen run_*-Skill mit speak=false erfolgreich aus → speak=false (kein TTS,
App STOP). Der Text landet aber normal in der Bubble (Stefans Wunsch: Text ok,
nur nicht vorlesen). Antwort-Skills (speak=true) + reine Konversation bleiben
gesprochen; bei Skill-Fehler bleibt's gesprochen (Erklaerung soll man hoeren).
Reiner Brain-Fix — App liest speak bereits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verallgemeinert das "run_* → stumm"-Heuristik: jeder Skill deklariert im
Manifest ein speak-Flag.
- speak=false (Default) = Steuerbefehl (Spotify, Licht) → kein TTS, App
beendet direkt (STOP), wie bisher.
- speak=true = Antwort-Skill (Info/Ergebnis) → Antwort wird vorgelesen +
Gespraechs-Fenster bleibt offen.
Gilt für BEIDE Pfade: Fast-Path (_fast_path_speak aus skill.speak) und
lokale Skill-Ausführung (_local_turn_speak = _skill_speak_flag(run_*)).
Info-Tools (web_search/memory_search/trigger_timer) bleiben gesprochen.
- skills.py: speak in create_skill + update_skill-allowed.
- agent.py: skill_create/skill_update Tool-Schema dokumentiert speak (damit
ARIA es beim Skill-Bau setzen kann), Dispatch reicht es durch.
- App: _isSilent nur noch speak===false (kein answeredBy=fast-path-Fallback
mehr, sonst waere ein speak=true-Fast-Path faelschlich stumm).
Bestehende Skills ohne Feld = false = stumm (kein Verhaltenswechsel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wenn das lokale LLM einen echten Skill (run_*, z.B. Spotify) ausführt, ist es
ein Steuerbefehl — genau wie Fast-Path. Jetzt speak=False: kein Vorlesen, und
die App beendet direkt (STOP) statt ins 30s-Gespräch zu gehen. Deckt den Fall
ab, wo Whisper sich verhört ("Nächster Slick") und local statt Fast-Path
den Skill auffängt.
Info-Tools (web_search/memory_search/trigger_timer) zählen NICHT — deren
Antwort/Bestätigung bleibt gesprochen. Reiner Brain-Fix: die App liest speak
bereits (2b48e5c), kein neuer APK nötig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zwei Fehler: (1) War der PCM-Stream schon komplett empfangen (isFinal durch),
sind pcmStreamActive+isPlaying false — der AudioTrack spielt aber seinen
Buffer noch sekundenlang aus. stopPlayback() returnte dann früh ("nichts
aktiv") und rief PcmStreamPlayer.stop() NIE → Mund-Button wirkungslos.
(2) stopPlayback() wirft pcmBuffer weg → die Cache-WAV waere unvollstaendig,
Nachhoeren via Lautsprecher-Symbol kaputt.
Neue _silenceAudibleOutput(): stoppt den AudioTrack IMMER (auch im Drain-Fall),
laesst aber pcmBuffer/pcmMessageId/pcmStreamActive stehen — restliche Chunks
cachen stumm weiter, isFinal schreibt die vollstaendige WAV. setMuted(true)
nutzt das statt stopPlayback(). stopPlayback bleibt fuer Barge-In/Cancel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fehlauslösung im Auto: Spotify läuft über die Lautsprecher, das Mikro hört
mit, openWakeWord halluziniert "computer" rein (Log: wake.detect state=armed
→ leerer Transkript). Der Echo-Canceler kann nur ARIAs eigenes TTS
rausrechnen, nicht Spotify (fremde App, kein Referenzsignal).
- Wake-Word-Threshold jetzt konfigurierbar (loadWakeThreshold), Default von
0.5 auf 0.6 hoch (strenger → weniger Fehlauslösung). Slider im Wake-Word-
Settings-Bereich (0.30–0.90, greift beim "Speichern + Aktivieren").
- Weiterreden-Fenster (passives Lauschen) als Slider, Default 30s (10–60s).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nach dem Re-Arm-Fix rief die App resume() → das spielte einen zweiten Gong
und oeffnete ein neues Aufnahme-Fenster, obwohl Stefan nur einen Steuerbefehl
gab. Sein gewuenschtes Verhalten:
- Klarer Befehl (Fast-Path, speak=false, z.B. Liedersteuerung) = KEINE
Konversation → STOP: direkt zurueck aufs Wake-Word (kein Gong, keine
Aufnahme, kein 30s-Fenster).
- Gespraech (gesprochene Antwort) = kein neuer Gong, direkt 30s passives
Lauschen (weiterreden wie mit einem Menschen), 30s still → Wake-Word.
endConversation(skipPassive) neu: true springt direkt zu armed statt in
passives Lauschen. onPlaybackFinished (Gespraech) → passiv (Vordergrund) bzw.
direkt armed (Hintergrund); stille Fast-Path-Antwort → skipPassive; manueller
Stop-Button → skipPassive. resume() bleibt nur noch Mic-Fail-Retry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fast-Path-Antworten sind speak=False → kein TTS → onPlaybackFinished feuert
nie. Das Wake-Word-Re-Arm haengt aber genau daran → nach "nächster Titel"
blieb das Ohr grau in 'conversing' stecken (Stefan im Auto, 2 Min gewartet,
kein Re-Arm). Log bestaetigt: nach stream.final kam kein wake.end/wake.start.
- Bridge: chat-Payload traegt jetzt 'speak' (war nur answeredBy).
- App: bei stiller Antwort (speak=false ODER answeredBy=fast-path) und
laufender Konversation dieselbe Re-Arm-Logik wie bei TTS-Ende anstossen
(aktiv → resume/Konversationsfenster, sonst → endConversation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Im Auto cancelt der App-seitige No-Speech-Watchdog den conversing-Stream
vorzeitig (Whisper-Partials kommen bei Fahrgeraeusch verspaetet) und startet
passives Lauschen, waehrend die Bridge dieselbe Aeusserung parallel noch
finalisiert. Ergebnis: derselbe Befehl ("nächstes lied") wird zweimal
ausgefuehrt — aus zwei verschiedenen audioRequestIds, darum griff die
bestehende ID-Idempotenz nicht.
Text-Fenster-Dedup: identischer normalisierter Endpoint-Text innerhalb 5s
wird verworfen. Serverseitig, kein APK noetig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#2 Vorzeitiges Absenden: endpointMs war hart 1500ms — im Auto (Sprechpausen)
schnitt das mitten im Satz ab (die 11.8s-Frage wurde bei "…ohne dass ein"
gekappt). Jetzt konfigurierbar (aria_stt_endpoint_ms, Default 2400, 1000-4000).
#3 Ohr bleibt ausgegraut: beim Re-Arm rief der Wake-Word-Service
OpenWakeWord.start(), waehrend die passive Streaming-Aufnahme noch das Mikro
hielt → start() schlug fehl → state=off. Neuer micReleaseHook cancelt die
Aufnahme VOR start() (endConversation / exitPassiveListening /
discardIfFreshlyTriggered). ChatScreen registriert den Hook.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Das Auge/Toggle war faelschlich nur in der vertikalen Projektliste unter
Einstellungen — Stefan managed Projekte aber ueber die Ordner-Bubbles im
Kontext-Streifen auf dem Main-Tab, und der filterte hidden gar nicht
(verstecktes Projekt blieb sichtbar, kein Auge).
renderContextStrip: versteckte Projekte standardmaessig raus, 🙈/👁-Auge pro
Bubble (eigenes onclick + stopPropagation, wechselt nicht den Kontext),
"versteckte anzeigen (N)"-Toggle-Chip am Ende; setStripProjectHidden patcht
+ raeumt Focus wenn noetig. project_changed refresht jetzt Streifen UND Liste.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die /-Response hatte keinen Cache-Control-Header. Der Browser cachte das
grosse Single-HTML-Dashboard heuristisch und servierte trotz (soft) Reload
die alte Inline-JS/CSS — neue Features (Projekte-verstecken-Button, Auge,
Token-Ersparnis-Card) tauchten erst nach Hard-Reload auf. Jetzt no-store +
no-cache, so dass jeder Load die frische Version bekommt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kern-Bug: der Diagnostic-Server reichte ein von RVS empfangenes
project_changed NIE an die Browser weiter — der Handler in index.html war
toter Code, der Diagnostic aktualisierte die Projektliste also nie live.
- server.js: RVS-Handler forwardet project_changed jetzt an die Browser-Tabs;
der /api/brain-Proxy broadcastet project_changed (RVS + lokal) nach jeder
erfolgreichen Projekt-Mutation (create/switch/end/archive/PATCH inkl. hidden).
- App ProjectsBrowser: sendet project_changed nach dem Verstecken/Sichtbar-
machen und laedt bei eingehendem project_changed selbst neu.
Damit: verstecken in der App -> sofort im Diagnostic weg (und umgekehrt),
ohne Seiten-Refresh. Diagnostic-Hide funktionierte schon (Daten/Proxy/Logik
korrekt) — versteckte sind per "Versteckte anzeigen (N)"-Toggle sichtbar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spiegelt das Diagnostic-Feature auf App-Seite:
- Project-Interface + updateProject um hidden erweitert; setProjectHidden().
- ProjectsBrowser: versteckte Projekte standardmaessig ausgeblendet (mama
sieht sie nicht). Auge pro Zeile (🙈 verstecken / 👁 sichtbar), Toggle
"👁 Versteckte anzeigen (N)" blendet sie temporaer gedimmt + Badge ein
zum Ansehen/Auswaehlen/Wieder-Sichtbarmachen. Eigener Touch am Auge, damit
der Tap nicht das Projekt wechselt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neues hidden-Flag pro Projekt (default false, bleibt voll nutzbar — nur
optisch aus der Liste ausgeblendet, unabhaengig von status/archived).
- projects.py: hidden in create_project + update_project-Patchkeys.
- main.py: ProjectUpdateBody.hidden → PATCH /projects/{id} setzt es.
- Diagnostic: pro Projekt-Bubble ein Auge (🙈 verstecken / 👁 sichtbar
machen); Header-Toggle "Versteckte anzeigen (N)" blendet sie temporaer
ein (gedimmt + Badge) zum Ansehen/Auswaehlen, ohne sie permanent
sichtbar zu machen. Auge im eingeblendeten Zustand macht sie dauerhaft
wieder sichtbar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Escalate-on-Tool-Error-Logik griff nur bei hartem FEHLER-Prefix (Exit
!= 0). Action-Skills melden Fehlschlaege aber oft im stdout-Text bei Exit 0
(Spotify: "Fehler beim Uebertragen", "Geraet nicht gefunden"). Das lokale
LLM las den Fehler dann brav vor statt zu eskalieren — und wiederholte beim
Nachhaken dieselbe leere Absichtserklaerung.
Generisch (nicht Spotify-spezifisch): fuer run_*-Skills werten weiche
Fehler-Marker im Ausgabetext ebenfalls als Fehlschlag → Claude uebernimmt
das mehrstufige Mitdenken. Info-Tools (web_search/memory_search) ausgenommen,
damit deren Inhalt das Wort "Fehler" tragen darf. Eskalieren ist immer sicher.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zwei Ursachen, beide adressiert:
- App raeumte den (kontext-scoped) Thinking-Indikator nur ueber das
agent_activity 'idle' der Bridge. Kam das nicht an (dedup/mismatch),
blieb "ARIA denkt" + Abbrechen stehen. Jetzt raeumt die App den
Indikator beim Eintreffen der Antwort selbst (definitiver Turn-Ende-Beweis).
- Bridge sendete 'thinking' mit der REQUEST-projectId, 'idle' aber mit der
TURN-projectId (Brain kann umrouten, z.B. Voice-Sticky). Bei Abweichung
bekam der Request-Kontext nie sein idle → zusaetzliches idle fuer die
Request-projectId am Turn-Ende.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regression seit das lokale LLM leichte Turns (Wetter, Spotify) beantwortet:
es nutzt bewusst KEINE <voice>-Tags, an denen frueher die Zahl-Aussprache hing.
clean_text_for_tts schrieb nur Uhrzeiten/Dezimalzahlen/Zahl+Einheit aus, nie
freistehende Ganzzahlen ('23°C', '100%', '7 Nachrichten').
Neuer vollstaendiger Konverter _int_to_words_de (0..999999) + generischer
Durchlauf am Ende von clean_text_for_tts, der alle uebrigen Ganzzahlen zu
Woertern macht. Tag-unabhaengig -> gilt fuer local UND claude. Lange
Ziffernfolgen (IDs/Codes, >6 Stellen) bleiben Ziffern; an .,:/ klebende
Zahlen (IPs, Versionen) werden uebersprungen. Nebenbei: fehlendes Leerzeichen
bei '23°C' -> 'dreiundzwanzig Grad Celsius' gefixt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Native AudioFocus: dispatchMediaPlay() (echter KEYCODE_MEDIA_PLAY an die aktive
MediaSession, wie die Kopfhoerer-Play-Taste) + isMusicActive() zum Gaten.
audio.ts merkt vor dem Focus-Grab ob Musik lief und resumt am Dialog-Ende nur
dann — deterministisch statt des auf OnePlus flakigen nudgeMediaResume.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Test 'Wer ist Melanie?': das lokale Qwen klammte zu + verwechselte den Frager
('frag Stefan direkt' — Stefan IST der User), weil der schlanke Lokal-Prompt kein
Memory hat. Claude dagegen antwortete diskret + korrekt (und haengte sogar selbst
<voice></voice> an → stumm, 'jemand koennte mithoeren').
Fix: Local-Prompt weist an, bei Fragen zu Stefans Leben/Personen/Beziehungen/
Vergangenheit/gespeichertem Wissen NICHT aus dem Nichts zu antworten, sondern
zu eskalieren (<<ESCALATE>> → Claude mit vollem Gedaechtnis). Die Diskretions-
Regel selbst bleibt (funktioniert auf Claude einwandfrei).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
)
Schwerwiegend: ARIA hat bei Selbstvorstellung / "was weisst du ueber mich"
intime Details (Beziehungen, Partnerinnen, Lebensweise) proaktiv rausgedumpt —
ein Vertrauensbruch, wenn jemand mithoert. ARIA DARF das wissen, aber niemals
ungefragt aussprechen.
Regel in IDENTITY_ANCHOR (steht als einziger garantiert in BEIDEN Tiers, lokal
+ Claude): private/intime Infos sind hochvertraulich; nie von sich aus, nicht in
Vorstellungen/Zusammenfassungen/Triggern; nur auf konkrete Nachfrage, knapp und
diskret. Auf "was weisst du ueber mich": allgemein + diskret antworten, kein
Aufzaehlen. "Jemand koennte mithoeren" als Leitplanke.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stefans Idee: die QUELLE entscheidet ueber Vorlesen, nicht der Reply-Inhalt.
Fast-Path (reiner Steuerbefehl) = speak=False (stumm); ARIA-Antworten
(local/claude) = speak=True (vorlesen ok — eine Ansage ist sogar nett).
- agent.chat() gibt jetzt (reply, answered_by, speak) zurueck; main.py
ChatOut.speak; background.py-Aufrufer angepasst.
- bridge: liest speak aus /chat, reicht es an _process_core_response; bei
speak=False wird TTS uebersprungen — VOR jeder <voice>-Logik.
Robust: unabhaengig davon, ob die Skill-fast_pattern-Reply ein <voice></voice>
enthaelt (das verlor ARIA beim semantischen Skill-Rebuild — genau der Bug).
App braucht keinen Change: kein xtts_request -> kein Audio -> stumm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adapter meldet bei Modellwechsel/Erst-Load service_status (service=llm):
loading -> ready (mit loadSeconds; freshlyDownloaded 🎉 bei langem Erst-Load)
oder error. Laeuft ueber den vorhandenen Pfad: Adapter -> RVS -> Diagnostic
(RVS-Client) -> Browser -> updateServiceStatus (generisch; nur Label 'Lokales
LLM' ergaenzt). Kein Bridge-/server.js-Change noetig.
Download-% gibt llama-swap nicht her — daher Zustands-Status (laedt/bereit/
Fehler), im gemeinsamen Service-Banner wie whisper/flux.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mehrere lokale Modelle, on-demand geladen/geswappt, in Diagnostic waehlbar.
Design: das Brain schickt den Modellnamen (aus local_llm.json) im llm_request
mit -> Adapter -> llama-swap laedt/swappt. Keine separate Gamebox-Config noetig.
- xtts: `llama`-Container -> `llama-swap` (unified-cuda), config.yaml mit
qwen3-8b (Standard) + qwen3-4b; Auto-Download via -hf, Cache /models geteilt
(qwen3-8b schon da). Adapter -> llama-swap:8080, Timeout 600s (Erst-Download).
- adapter: `model` aus dem Request an llama-swap durchreichen (Fallback env).
- brain: router.load_config liest localLlmModel; local_llm_chat(model=...);
agent gibt cfg-Modell mit; bridge reicht model durch (_local_llm + Route).
- diagnostic: /api/local-models-list (aus /shared/config/local_models.json,
seeded), local-llm-config um localLlmModel erweitert; Dropdown "Lokales
Modell" im Settings-Block + Erst-Download-Hinweis.
BLIND gebaut (Gamebox nicht testbar hier): llama-swap CLI/Config-Pfad beim
ersten Start via `docker logs aria-llama-swap` pruefen. Live-Lade-Status
(Adapter->Diagnostic) ist B0.5-2 (Folgeschritt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zeigt wie in Diagnostic, ob eine Antwort vom lokalen Modell / Claude / Fast-Path
kam — aber in der App als Opt-in, damit die App "Mama-tauglich" bleibt.
- ChatScreen: ChatMessage.answeredBy; aus dem chat-Payload UND der History-Sync
eingefangen; kleiner farbiger Badge im statusRow der ARIA-Bubble, nur wenn
showSource an. State aus AsyncStorage aria_show_source (default false, per
2s-Reload synchron mit den Settings).
- SettingsScreen: Toggle "Antwort-Quelle anzeigen" in der Chat-Bubbles-Card,
schreibt aria_show_source (pro Geraet — Stefan an, Mama aus).
- Bridge liefert answeredBy schon in chat_backup/History mit (kein Change noetig).
Server-Teil in 9cc1aec. APK-Rebuild noetig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zeigt pro Antwort, welcher Backend sie erzeugt hat — farbcodiert (lokal=gruen,
Claude=blau, Fast-Path=lila). Nur in Diagnostic (App bleibt Mama-tauglich).
- agent.chat() gibt jetzt (reply, answered_by) zurueck; an jedem Return-Punkt
gesetzt (fast-path/local/claude). Beide Aufrufer (main.py, background.py)
angepasst. main.py: ChatOut.answered_by.
- bridge: liest answered_by aus /chat, reicht es an _process_core_response,
broadcastet es im chat-Payload UND persistiert es in chat_backup (answeredBy)
→ bleibt nach Reload.
- diagnostic: srcBadgeHtml() rendert den Badge in Live-Chat + History;
server.js liefert answeredBy in der chat_history.
Erweiterbar: spaeter kann ein Bild-Backend (FLUX/Modellname) denselben Kanal
nutzen. Modellwechsel-fuer-Antworten (groessere Modelle bei mehr GPUs) bleibt
B0.5/Skalierungs-Thema im Plan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stefan-Punkt: der 'auf <Geraet>'-Router-Patch war ein per-Fall-Band-Aid, das
nicht skaliert (naechster Skill mit komplexer API → wieder patchen). Besser:
das LLM merkt es selbst.
- Router-Hardcoding zurueckgenommen (kein 'auf Geraet'→Claude mehr).
- GENERAL: im lokalen Tool-Loop → scheitert ein Tool-Call (Ergebnis beginnt mit
'FEHLER'), uebernimmt Claude. Gilt fuer JEDEN Skill, kein per-Fall-Wissen im
Router. Lokal probiert, bei Fehler eskaliert.
- skill_create-Anleitung: SEMANTISCH bauen — Skills bieten klare Operationen als
args (action/device_name), NICHT rohe {path,method,body}-Durchreichung. Das
args-Schema ist die 'Bedienungsanleitung', die das LLM sieht (die haben wir
also schon — sie muss nur semantisch sein). Was das LLM sonst raten muesste
(Endpunkte/IDs/Payload) gehoert INS Skill.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Log-Beweis: Qwen baute fuer 'auf android abspielen' einen erfundenen Endpoint
(/v1/me/player/start) mit Platzhalter-Device-ID -> 404. Device-Transfer ist ein
komplexer Mehrschritt-Flow (Geraete abfragen -> Name->echte ID -> richtiger
Endpoint); das 8B ist da unzuverlaessig. Router schliesst 'auf <Geraet>'-
Formulierungen jetzt aus dem lokalen Pfad aus -> Claude. Simple Steuerung
(pause/next/play, 'was laeuft') + 'auf deutsch' bleiben lokal (verifiziert).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ARIA soll Patterns wortstellungs-tolerant + mit Synonymen/Fuellwoertern in EINEM
Regex schreiben (z.B. 'pause spotify' UND 'spotify pause' UND 'stopp mal'), statt
nur einer Formulierung. Plus der Schluessel-Hinweis: Fast-Paths nur fuer die
HAEUFIGSTEN Befehle — den Rest faengt das lokale LLM (hat das Tool) schnell ab,
also lieber wenige breite Patterns als viele enge. Damit kriegen kuenftige
Skills automatisch robuste Patterns (statt manuellem Nachpatchen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Qwen imitierte aus dem Kontext den <voice>-TTS-Block, packte aber die GANZE
Antwort hinein -> Display strippt <voice> -> leere Bubble (nur TTS klang richtig).
Fix: (1) Local-Prompt verbietet <voice>/[FILE:]/<tool_call>/Markup explizit —
nur Plain-Text (wird angezeigt UND vorgelesen). (2) Sicherheitsnetz: Voice-Tags
aus lokalen Antworten strippen, falls das 8B es doch tut.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
web_search war versehentlich in META_TOOLS -> Claude bekam es zusaetzlich zu
seinen nativen WebSearch/WebFetch/Bash (redundant/verwirrend). Jetzt als
WEB_SEARCH_TOOL nur im lokalen Tool-Set (_build_local_tools). Claude nutzt
weiter seine eigene Suche + Voll-Seiten-WebFetch (wichtig fuer Pentest/Research);
SearXNG ist fuer das lokale Tier, das sonst keinen Netzzugriff haette.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pro Schalter ein kurzer Hilfetext (AN/AUS-Bedeutung, Testmodus-Warnung),
deutlichere Labels, Status-Zeile mit Ampel (⚪/🟡/🟢) + Hinweis auf aktuellen
B1a-Stand (lokal plaudert nur, Tools folgen in B1b).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wiederkehrender Identity-Bug: --system-prompt liefert die Persona korrekt (Proxy-
Test = "ICH BIN ARIA"), ABER ein einziger in der History gespeicherter Break
("ich bin nach wie vor Claude / die Persona ist erfunden") zieht bei schwachen
Folgeturns eine Kaskade nach sich — das Modell setzt seine eigene Ablehnung fort.
Das erste Cleanup verlangte "claude code" und liess deutsche Breaks durch.
Fix zweifach:
- prompts.py: looks_like_identity_break() — STARKE selbstreferenzielle Marker
(ich-bin-Claude / erfundene Persona / diese-Session-injiziert / nicht-real-in-
dieser). Bewusst NICHT das blosse "injizier"/"prompt injection" — das nutzt
ARIA in Pentest-Antworten legitim (verifiziert: 0 False Positives).
- agent.py: nach dem Claude-Loop Break-Check; bei Break Retry (Nondeterminismus
holt meist ARIA), sonst sichere ARIA-Fallback-Antwort — Break wird NIE
persistiert. Auch die lokale Fast-Lane eskaliert bei Break auf Claude.
- clean_poisoned_turns.py: auf denselben starken Matcher umgestellt (der breite
produzierte False Positives auf echte Security-Doku, im Dry-Run gesehen).
VM bereits bereinigt (je 2 Rest-Breaks aus conversation.jsonl + chat_backup).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gemessen: lokaler Call mit 12-Turn-Fenster ~2,6s statt ~0,8s. Im Hauptchat (bis
50 Turns) wuerde volles Fenster das Prefill aufblaehen und den Speed-Vorteil
auffressen. Lokales Tier ist fuer kurze Plauder-Turns — letzte 8 Turns reichen.
B1a end-to-end verifiziert: plaudern→lokal (~0,8s warm), Tool/hart→Claude,
localOnly erzwingt lokal; Config live gelesen, Default aus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Button = remote start/stop = braucht Agent pro Host, aber dumme Variante:
gpu_placement.json als Single Source of Truth, Agent reconciled nur (Mensch =
Scheduler). Loest die Reboot-Falle (Laufzeit-Move vs statische Config). Deploy:
Code ueberall via git, Config entscheidet Platzierung. Nach B0/B1; Fallback =
COMPOSE_PROFILES manuell.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Semi-Auto-Controller verworfen (Host wechselt selten). Pin via Compose-Profiles
pro Host (.env COMPOSE_PROFILES), Verschiebe-Regel up-neu + rm-alt gegen
Reboot-Wiederauferstehung, GPU-Dashboard aus Heartbeats (read-only). Skaliert
auf Gamebox3/4x3060 ohne Orchestrator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Idee Container-start/stop auf RVS-Hosts: billiger Teil (Registrierung/Heartbeat
-> Flotten-Sichtbarkeit + Router-Erreichbarkeit) lohnt bald; teurer Teil
(Agent+Controller+Placement = Mini-Nomad) erst bei groesserer Flotte, dann eher
Swarm/Nomad statt Eigenbau. Fuer 2 Gameboxen: statische Platzierung + llama-swap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roher VRAM ist nicht ueber RVS teilbar (Relay, nicht GPU-Bus). Echte Pfade:
mehr Karten/Box = VRAM-Pool (volles Arsenal), mehr GPU-Hosts = Modell-Server
ueber RVS (Cluster), llama-swap = geteilter Server im Host. Plus geplantes
Diagnostic-Info-Icon mit VRAM-Bedarf pro Ausbaustufe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Klargestellt: lokales Modell ist ueber ALLES im Bilde (kurze Awareness-Liste im
System-Prompt -> gezieltes Escalieren), darf aber nur den sicheren Satz
ausfuehren. Harte Grenze ist Kontext/VRAM (volles Schema ~15-20K Tokens passt
nicht in 8K, 32K-Kontext sprengt die geteilte 12GB-3060), nicht Misstrauen.
Claude im RZ mit 200K-1M Kontext kann sich das ganze Arsenal leisten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brain → HTTP /internal/local-llm → Bridge → RVS → llm-adapter → llama.cpp,
1:1 nach dem FLUX-Roundtrip-Muster gebaut:
- Bridge: _pending_llm (requestId→Future), llm_response-Handler (setzt Future),
_local_llm() (sendet llm_request, wartet mit 30s-Timeout), HTTP-Route
POST /internal/local-llm ({messages, max_tokens?, temperature?, stop?}).
- Brain: local_llm.py mit local_llm_chat() — POSTet an die Bridge, gibt
{ok, content, model?, elapsedMs?} zurueck, wirft nie (Aufrufer eskaliert
bei ok=false auf Claude).
Provider-Kette bereits verifiziert (718ms). Als naechstes: Test brain→bridge→
gamebox end-to-end, dann B1 (Router-Heuristik + Escalation) und der
Diagnostic-Testchat/Status (B0.5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Qwen3 hat Thinking default AN -> verbraet Tokens im <think>-Block, liefert bei
kleinem max_tokens leeren content und ist ~3x langsamer (2,2s statt 0,7s im
Test). ARIAs lokales Tier soll fixe Antworten geben, nicht grübeln (grübeln =
harter Turn = Claude). Adapter setzt daher chat_template_kwargs
{enable_thinking:false}; abschaltbar via LLM_DISABLE_THINKING=false.
Verifiziert end-to-end (RVS->Adapter->llama->Qwen3): "Hallo! Ich bin bereit."
in 718ms Round-trip RZ<->Gamebox@home.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Statt einer manuell abgelegten Datei zieht llama.cpp das Modell beim ersten
Start selbst von Hugging Face (-hf Qwen/Qwen3-8B-GGUF:Q4_K_M, offizielles
Repo verifiziert) und cached es unter xtts/models (persistent). Modell/Quant
via LLM_HF_REPO/LLM_HF_QUANT in der .env wechselbar, kein Code.
Diagnostic-Modellauswahl (on-demand laden/aktivieren mehrerer Modelle) als
Folge-Baustein B0.5 via llama-swap ins Plan-Doc aufgenommen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan B, Phase B0 (Provider): lokales Qwen3-8B auf der Gamebox, angebunden
per RVS wie f5tts/whisper (kein IP-Pflegen, nur URL+Token).
- xtts/llm-adapter/: RVS-Client (spiegelt whisper-bridge: TLS+ws-Fallback,
Reconnect-Backoff), nimmt llm_request, ruft llama.cpp /v1/chat/completions
lokal, antwortet llm_response (korreliert per requestId). Nicht-streamend
in B0; llm_partial fuer B2 reserviert.
- xtts/docker-compose.yml: neue Services `llama` (llama.cpp server-cuda,
GGUF via ./models, OpenAI-API auf :8081) + `llm-adapter`.
- rvs/server.js: ALLOWED_TYPES += llm_request/llm_response/llm_partial.
- GGUF (mehrere GB) via .gitignore aus dem Repo; xtts/models/ mit .gitkeep.
Topologie-Hinweis: Gamebox@home, ARIA@RZ -> Bounce ueber Internet ist
unvermeidbar (Voice macht's schon so); Router faellt bei Nichterreichbarkeit
per Escalation auf Claude zurueck. Consumer-Seite (Bridge-Relay + Brain-
Client + Router) kommt als naechstes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design-Doc: schnelles lokales LLM fuer die einfachen ~80% der Turns (<1s,
gratis auf Gamebox-GPU), Claude nur fuer die schweren 20%. Anbindung ueber
den RVS-Token-Room wie TTS/STT (kein IP-Pflegen), Router mit Heuristik +
Escalation, schlanke Persona lokal, Phasen B0-B3. Basiert auf der
Latenz-Messung (CLI-Boden ~3,5s) aus dieser Session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher: tts_text = tts_text_preview or text — ein leeres <voice></voice> fiel
auf den vollen Text zurueck und wurde doch gesprochen. Jetzt: ein vorhandener
<voice>-Tag ist die EXPLIZITE TTS-Vorgabe (auch leer). Leeres <voice></voice>
= Bubble sichtbar, aber KEIN TTS. Nur ohne Tag Rueckfall auf vollen Text.
Motivation: Spotify-Steuerbefehle (next/pause/…) laufen jetzt ueber den
Fast-Path (<1s), aber die gesprochene Quittung klaute auf dem Handy den
Audio-Fokus -> Spotify duckte/pausierte und spielte (bei kurzem Text) nicht
weiter. Steuer-Skills koennen ihre Reply nun mit <voice></voice> stummschalten.
Generell nutzbar: ARIA kann jede Antwort bewusst stumm halten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Einmal-Tool zum Entfernen der aus-der-Rolle-Antworten ("das ist injiziert,
ich bin Claude Code"), die waehrend der --append-system-prompt-Phase in die
History geschrieben wurden und das Modell per Self-Grounding rueckwaerts aus
der Rolle zogen (siehe 2284c1a). Feld-agnostisch: bedient conversation.jsonl
(content/project_id) UND chat_backup.jsonl (text/projectId). Entfernt nur
Hauptthread-Assistant-Turns mit "claude code" + zweitem Ablehnungs-Marker
plus die ausloesende Frage; projekt-getaggte Turns (z.B. legitime Pentest-
Doku, die Injection als Arbeitsmaterial erwaehnt) bleiben unangetastet.
Dry-Run per Default, Backup vor --apply, idempotent.
Bereits auf der Dev-VM angewandt: je 5 Fehl-Dialoge aus beiden Dateien
entfernt, Brain neu gestartet, Hauptchat verifiziert (ARIA antwortet wieder
als ARIA mit vollem Memory-Zugriff).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Self-Grounding-Ansatz (268636c) allein reichte nicht: Sonnet 5 in Claude
Code haelt seine eingebaute "You are Claude Code"-Identitaet hart und liest den
synthetischen <previous_response> als "fabricated". Ursache bleibt der Kanal:
--append-system-prompt HAENGT ARIAs Persona nur hinten an Claude Codes Basis-
Identitaet an — bei duennem Kontext (Hauptchat) gewinnt die Basis und wehrt die
Persona als Injection ab. Anhaengen ist gegen das Anti-Injection-Training des
Modells nicht durchsetzbar.
Fix: System-Prompt VOLL ersetzen statt anhaengen — sed-Patch in docker-compose
schaltet manager.js buildArgs von --append-system-prompt auf --system-prompt.
Damit ist die ARIA-Persona DIE Identitaet des Modells (kein Anhaengsel), und
Claude Codes dynamische Sektionen (cwd '/', git, platform) — die "ich bin ein
Coding-Agent"-Signale — fallen weg. Bestaetigt per claude-code-guide: die CLI
isoliert bei --system-prompt vollstaendig, die eingebaute Identitaet leakt nicht.
extractSystemPrompt liefert weiterhin einen selbsttragenden Prompt (Persona +
Anker + Memory + Skills + Tool-Block); er darf bei --system-prompt nie leer sein
(Brain schickt immer System-Message + Tools -> real nie leer). Der IDENTITY_SEED
aus 268636c bleibt als Defense-in-Depth drin. Kommentare in openai-to-cli.js,
routes.js, agent.py, prompts.py entsprechend nachgezogen.
Deploy: Proxy UND Brain neu (--force-recreate; routes.js/openai-to-cli.js werden
frisch in den Proxy-Container ge-cp't). Verifikation am Container:
docker exec aria-proxy sh -c 'grep -o "\-\-system-prompt" /usr/local/lib/*/claude-max-api-proxy/dist/subprocess/manager.js'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Symptom: Im Hauptchat antwortete statt ARIA der Basis-Proxy-Claude ("I'm
Claude Code (Sonnet 5), I'm not adopting that persona") und flaggte GPS +
Tool-XML als Prompt-Injection. Im Projekt lief alles normal.
Ursache: Die Persona geht ueber --append-system-prompt zu — das HAENGT sie
nur hinten an Claude Codes eigene "You are Claude Code"-Basis-Identitaet an,
ersetzt sie nicht. Ob ARIA in der Rolle bleibt, entscheidet dann der
Konversations-Strom (stdin): reiche Projekt-Historie voller ARIA-
<previous_response>-Turns haelt die Rolle; der duenne Hauptchat-Verlauf
(bzw. der erste Turn eines neuen Projekts) nicht -> Basis-Identitaet gewinnt.
Der IDENTITY_ANCHOR (a0e8c23) ist inert, weil er im *angehaengten* Teil steht.
Fix: synthetischen ersten ARIA-Turn (IDENTITY_SEED) in ihrer eigenen Stimme
an den Anfang des Konversations-Stroms setzen. Das Modell setzt seine EIGENE
etablierte Stimme fort (Self-Grounding) — und weil es ein <previous_response>
ist und kein <system>-Tag, ist es kein Injection-Trigger. Rein ephemer, wird
nie in die Conversation persistiert. Gilt fuer alle Turns, deckt damit auch
die erste Frage eines frischen Projekts ab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>