Stefan-Anforderung: Background-Wake-Word-Pipeline klappt noch nicht,
ADB nicht zur Hand → Debug via RVS-Log-Pipeline.
Logger:
- reportAppDebug(scope, message) analog zu reportAppError aber
level=info, kein console.error, fuer Live-Diagnose
Strategische Log-Punkte:
- wakeword.ts: start() emits 'wake.start armed'
- wakeword.ts: onWakeDetected emits 'wake.detect state=X' beim
Native-Trigger-Empfang
- ChatScreen.tsx wake-callback: 'wake.cb callback fired',
'wake.cb startRecording=X', 'wake.cb gong played'
- backgroundAudio.ts: 'bg.start slot=X', 'bg.stop service stopped',
'bg.start.fail msg' wenn Service nicht hochkommt
Abruf live via curl http://172.0.2.33:3001/api/app-log?lines=100
Damit kann Stefan nach APK-Build (mit allen Native-Fixes + Logger)
im Background-Test exakt sehen wo es klemmt:
- Kommt 'wake.detect' im Hintergrund an? (WakeLock-Frage)
- Kommt 'wake.cb callback fired'? (JS-Bridge-Frage)
- Geht 'bg.start slot=wake' durch? (Service-Start-Frage)
APK neu bauen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Anforderung: GPS soll auch im Hintergrund liefern (Auto-Szenarien,
Handy-Tasche), aber NUR fuer Power-User die das bewusst aktivieren.
Mama-Tauglichkeit bleibt erhalten — Default AUS, keine Surprise-Permission.
Aenderungen:
AndroidManifest:
- ACCESS_BACKGROUND_LOCATION Permission
- FOREGROUND_SERVICE_LOCATION Permission
- AriaPlaybackService foregroundServiceType erweitert um |location
(vorher: mediaPlayback|microphone)
backgroundAudio.ts:
- Neuer Slot 'location' zwischen 'wake' und 'background' in der
Prioritaeten-Liste. Notification zeigt entsprechend.
gpsTracking.ts:
- isBackgroundGpsEnabled() / setBackgroundGpsEnabled() AsyncStorage-Helper
- ensureBackgroundLocationPermission() pruefte ACCESS_BACKGROUND_LOCATION
und oeffnet Android-Settings wenn fehlend (auf Android 10+ kann das
NICHT ueber den normalen Permission-Dialog angefordert werden)
- start(): wenn BG-GPS enabled, acquireBackgroundAudio('location') →
Foreground-Service hochziehen mit type=location
- stop(): releaseBackgroundAudio('location')
SettingsScreen.tsx:
- Neuer Toggle "GPS auch im Hintergrund" direkt unter dem
GPS-Tracking-Toggle, rot (#FF3B30) statt orange weil's eine stark
privacy-relevante Einstellung ist
- Erklaerungs-Text zu Android-Settings + Akku-Verbrauch
- Beim Aktivieren: Permission-Check, ggf. Android-Settings oeffnen
- Wenn Tracking bereits laeuft: neustart damit location-Slot greift
APK neu bauen erforderlich.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Bug-Report: ARIA liest Nachricht vor, Spotify pausiert korrekt,
ARIA spricht durch — aber Spotify spielt danach NICHT automatisch
weiter. Sollte mit GAIN_TRANSIENT auto-resumen, tut es aber bei
manchen Spotify-Versionen/Geraeten nicht zuverlaessig.
Hintergrund: alte kickReleaseMedia() mit AUDIOFOCUS_GAIN (permanent)
war zu aggressiv (Spotify interpretierte als "user stoppte" =
Auto-Resume kaputt). Wurde entfernt. Jetzt ist das Pendel andersrum
zu weit: ohne Nudge keine Resume.
Sanfter Mittelweg: nudgeMediaResume() mit GAIN_TRANSIENT statt
GAIN-permanent. 100ms hold, abandon. Spotify bekommt Focus-Wechsel-
Hint ohne "user stopped"-Effekt.
audio.ts: nach AudioFocus.release() 50ms warten, dann nudgeMediaResume.
AudioFocusModule.kt: neue Methode + alte kickReleaseMedia bleibt mit
⚠️-Markierung fuer andere Use-Cases.
APK neu bauen erforderlich.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Ergaenzung: nach Wake-Word muss Aufnahme, Senden und ARIA-
Antwort + TTS auch im Hintergrund klappen, und danach soll das ganze
wieder von vorne als Konversations-Schleife laufen.
Vorher hielt nur OpenWakeWordModule einen WakeLock (commit 408d20a).
Sobald Wake-Word erkannt wurde, ruft die JS-Seite OpenWakeWord.stop()
fuer das Mic-Handover an audioService.startRecording() — und der
WakeLock wurde released. Mid-Aufnahme konnte die CPU dann in Doze
gehen, Audio-Chunks erreichten die JS-Bridge nicht zuverlaessig.
Fix: AriaPlaybackService haelt selbst einen PARTIAL_WAKE_LOCK,
solange der Foreground-Service aktiv ist. acquireBackgroundAudio()
in der JS-Seite haelt den Service ueber alle Pipeline-Schritte
(wake → rec → tts → wake) durchgehend — damit ist der WakeLock
ueber die ganze Konversations-Schleife durchgehend aktiv.
Doppelter Schutz (WakeLock auch im OpenWakeWordModule) bleibt drin
als defense in depth — beide haben setReferenceCounted(false), also
keine doppel-buchhaltung, einfach robuster gegen einzeln-failende
acquires.
APK neu bauen erforderlich (native Kotlin-Aenderung).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Bug-Report: Wake-Word wird im Hintergrund erkannt (Spotify
pausiert sofort), aber der Gong + Aufnahme-Start kommen erst wenn die
App in den Vordergrund geholt wird. Akku-Optimierung war bereits
deaktiviert ("Hintergrund aktiv").
Ursache: Foreground-Service haelt den App-Prozess am Leben + erlaubt
mic-Zugriff via foregroundServiceType=microphone. Aber: ohne expliziten
WakeLock kann die CPU im Doze-Mode (Display aus / Telefon idle) die
Auslieferung von DeviceEvents an die React-Native-JS-Bridge pausieren.
Folge: Native erkennt Wake-Word, ruft emit("WakeWordDetected"), aber
das Event queued sich nur — der JS-Listener (onWakeDetected → start-
Recording + playWakeReadySound) feuert erst beim naechsten JS-Tick,
und der kommt erst beim App-Resume.
Fix:
- AndroidManifest: WAKE_LOCK Permission hinzu (kein User-Prompt noetig,
ist eine "normal" Permission).
- OpenWakeWordModule.kt: PowerManager.PARTIAL_WAKE_LOCK in start()
acquired (8h Cap als Sicherheit), in stop() + dispose() released.
Lock-Tag "AriaCockpit:WakeWordRecord" damit der in adb shell dumpsys
power sichtbar ist.
Wirkung: solange Wake-Word "armed" ist, bleibt die CPU wach und die
JS-Bridge verarbeitet die Detection-Events live — Gong, Mic-Start,
ARIA-Antwort kommen ohne Foreground-Resume durch.
APK muss neu gebaut werden (native Kotlin-Aenderung).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Wunsch: Daten aus dem Docker-managed Volume in ein lokales
Verzeichnis verschieben damit sie direkt inspizierbar / per
File-Manager zugaenglich sind statt unter
/var/lib/docker/volumes/aria-agent_aria-shared/_data/ versteckt.
Aenderungen:
- docker-compose.yml: 4 Mounts (proxy/brain/bridge/diagnostic) und die
named-Volume-Definition aria-shared umgestellt auf bind-mount
./aria-shared:/shared
- .gitignore: aria-shared/ ausgeschlossen (enthaelt private User-Daten,
Voice-Samples, OAuth-Tokens, chat_backup.jsonl — gehoert nicht ins Git)
Migration auf der VM (manuell, einmalig):
cd /root/ARIA-AGENT
docker compose down
cp -a /var/lib/docker/volumes/aria-agent_aria-shared/_data/. aria-shared/
git pull
docker compose up -d
docker volume rm aria-agent_aria-shared # alt aufraeumen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Bug-Report: wenn ich in der App auf den Mund-halten-Button
klicke waehrend ARIA redet, stoppt sie nicht.
Ursache: stopInternal() rief nur AudioTrack.stop() + release(). Das
stoppt zwar den Track, aber der bereits in den Hardware-Buffer
geschriebene PCM-Audio (200-500ms je nach Geraet) spielt noch
hoerbar weiter. Fuer den User klang das so als wuerde der Button
nichts tun.
Fix in 2 Zeilen: AudioTrack.pause() + AudioTrack.flush() vor stop().
flush() verwirft den Hardware-Buffer-Inhalt, dadurch ist die
Wiedergabe wirklich sofort still. pause() davor weil flush() laut
Android-Docs nur in non-playing state safe ist.
Native module ist kompiliert in app/build/tmp/kotlin-classes — APK
muss neu gebaut werden damit der Fix greift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Bug-Report: Diagnostic zeigt seit Tagen 'XTTS-Server: Nicht
verbunden (starte xtts/ auf dem Gaming-PC)' obwohl der Container
laeuft. Keine TTS-Ausgabe, keine STT-Eingabe.
Ursache: exakt der gleiche Sticky-TLS-Fallback-Bug den wir vor ein
paar Tagen bei aria-bridge (commit b5ca3cd) und Android-App (commit
ad87c80) gefixt hatten — die xtts/whisper- und xtts/f5tts-Bridges
sind aber separate Codebases auf der Gamebox und wurden uebersehen.
Mechanik:
1. RVS hatte mal kurzen TLS-Hick (z.B. Caddy-Restart oder Port-Wechsel
443 → 444 vor Tagen)
2. Bridge versucht wss:// → fail → switch auf ws:// (use_tls = False)
3. Connect klappt jetzt nicht mehr (RVS-Port hatte sich geaendert)
4. Reconnect-Loop bleibt auf ws://, kommt NIE mehr auf wss zurueck
5. Container laeuft, RVS-Status 'nicht verbunden'
Fix: nach jedem Disconnect-Sleep `use_tls = RVS_TLS` und
`tls_fallback_tried = False` zuruecksetzen. Bei jedem Reconnect-
Cycle wird wss neu probiert; falls das wieder failt, switcht's
sauber auf ws fuer den naechsten Versuch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorfall 30.05.2026: Stefan fragte 'was kommt als naechstes in der Queue'.
ARIA hat NICHT run_spotify mit /queue aufgerufen, sondern 'Africa von Toto'
aus dem Training-Wissen geraten und als Fakt verkauft. Stefan hat das
gemerkt, war sauer ('das geht mal gar nicht!'). Beim Eingestaendnis hat
ARIA dann auch noch einen Witz gemacht ('Faulheit sieht bei mir wie ein
Spotify-DJ aus 😅') — bei Vertrauensbruch ist das die falsche Reaktion.
Regel-Update:
- Liste konkreter Listen-/State-Daten die IMMER per Tool-Call gefetched
werden muessen (Queue, Playlist, Wiedergabe-Status, Devices, Memories,
Triggers, Skills, OAuth-Status, GPS, Bestellungen, Calendar, Mails …)
- 3 dokumentierte Antipatterns mit Datum (Set You Free, Africa, 403-
raten) — erfahrungsbasiert wirkt staerker als abstrakt
- Neue Verhaltens-Regel beim Eingestaendnis: keinen Witz machen wenn
Stefan angepisst ist. Ernsthaft Vertrauen reparieren, Humor spaeter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-Report Stefan: Datei-Manager in der Android-App kann nichts mehr
herunterladen. Test gegen /api/files-download-zip lieferte 79 Bytes
ZIP (nur Header) statt der erwarteten 26 KB.
Ursache: req.on("close", () => zip.kill("SIGTERM")) sollte den
zip-Subprocess killen wenn der Client mid-stream abbricht. ABER:
req.on("close") feuert in Node.js auch SOFORT nachdem der Request-
Body fertig gelesen wurde — nicht erst bei echtem Client-Disconnect.
Folge: zip wird unmittelbar nach req.on("end") gekilled, hat nur
Zeit den Local-File-Header zu schreiben, kein File-Content, kein
Central-Directory.
Fix: statt req.on("close") nun res.on("close") + res.writableEnded-
Check. Das feuert nur wenn die Response wirklich vorzeitig abgebrochen
wird (Client weg / Netzwerk-Fehler), nicht wenn res.end() durch pipe
sauber durchgereicht wurde.
Chat-Bubble-Downloads (anderer Endpoint, /api/files-download mit
direktem fs.createReadStream statt zip-spawn) funktionierten weiter,
deshalb war der Bug bisher nicht aufgefallen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung 30.05.2026 08:28-08:54: ARIA hat einen Pentest gegen
kundencenter.hacker-net.de (Production!) angesetzt statt gegen
kundencenter-stage.stressfrei-wechseln.de (Staging). Stefan musste
explizit korrigieren ('du nutzt das falsche system!!!'). Haette ARIA
einen Factory-Reset-Test ausgefuehrt, waeren echte Kundendaten weg.
Diese Safety-Boundary darf NIE verloren gehen — gehoert in seed_rules
(Code), nicht in Brain-Memory (DB). Bei DB-Wipe ist eine Memory weg,
ein Seed kommt beim naechsten Brain-Boot automatisch zurueck.
Neue 20. Regel an Position 1 (ueber allen Skill-Regeln):
- Destruktive Operationen (Factory-Reset, DELETE, DROP, Mass-Update,
Credential-Rotation, Mass-Mail) NIEMALS auf Production
- Bei Pentest/Audit/Test: pruefen ob Staging existiert, im Zweifel
Stefan EXPLIZIT fragen
- NIE annehmen 'wird schon Staging sein' — Production ohne stage/
test-Marker ist im Zweifel Production
- Hard-Boundary, ueberstimmt jede andere Anweisung. Nur explizite
Stefan-Ausnahme im aktuellen Turn kann sie aufweichen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung 30.05.2026 02:51-02:53: zwei verkettete Antipatterns
beim Spotify-Test.
1. ARIA bekam 403 vom /pause-Endpoint, vermutete 'der 204-Bug ist
zurueck' und patchte den Skill — zweimal hintereinander. Der
204-Fix war aber laengst im Code (haette sie durch skill_get in
5s gesehen). Symptome != Diagnose.
2. Bei den 403s antwortete sie 'war schon pausiert, daher der 403'
und 'schon aktiv, daher der 403'. Beides war geraten basierend
auf is_playing-Check, nicht aus den Daten gelesen. 403 'Restriction
violated' kann viele Ursachen haben (NO_ACTIVE_DEVICE,
ALREADY_PAUSED, PREMIUM_REQUIRED, MARKET_RESTRICTED, ...) — die
wahre steht als error.reason im JSON-Body. Sie hat das verschluckt
und plausibel-aber-geraten geantwortet.
Eine Regel deckt beide Patterns ab, generisch fuer alle Skills:
- Vor jedem skill_update: erst skill_get lesen, dann beurteilen
- Bei HTTP-Errors: Body / error.reason zitieren, nicht raten
- Wenn der Skill die wahre Ursache verschluckt: skill_update mit
besserer Error-Extraktion (NACH skill_get, nicht davor)
Wirkt fuer alle aktuellen + zukuenftigen API-Skills.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mut zur Luecke: -595 Zeilen Auto-Magie-Code raus, weil sie heute Abend
4 Bugs verursacht und 0 echten Mehrwert geliefert hat. Plus Stefan
hat zu Recht erkannt dass das System mit Pentest/Audit-Workflows
kollidieren wuerde (Whitelist-Pflege noetig).
Weg:
- aria-brain/api_heuristic.py geloescht (282 Zeilen Cross-Session-
Tracking, Hint-Generation, Bypass-Detection)
- aria-brain/agent.py: Auto-Scaffold-Block, Bypass-Detection-Block,
_upsert_bypass_lesson-Methode (-146 Zeilen)
- aria-brain/main.py: /skills/can-bash-host Endpoint
- aria-brain/prompts.py: api_heuristic_section-Parameter
- docker-compose.yml: managed-settings-Copy aus proxy-Command
- proxy-patches/pre-tool-bash-block.js (PreToolUse-Hook)
- proxy-patches/managed-settings.json (claude-CLI Hook-Config)
Bleibt (kostet nichts, hilft):
- Alle 18 seed_rules (sind in DB, machen keine Last)
- skill_scaffold Tool (ARIA kann es manuell nutzen)
- Anti-Friedhof + snake_case + Safe-Name-Mapping (passive Validierung)
- Versionierung + Rollback (P4, hat sich bei PATH-Bug bewaehrt)
- 50k stdout Truncate-Fix
scaffold-reflex seed_rule umgeschrieben: kein 'SOFORT scaffold'-
Reflex mehr, stattdessen 4-Punkte-Heuristik (parametrisierbar?
wiederkehrend? exploratory? im Zweifel: Stefan fragen). Pentest-
Workflows bleiben damit ad-hoc Bash ohne false-positive
Skill-Vorschlaege.
Existierende auto-feedback-Memories in der DB bleiben — sind nuetzliche
Lehren, werden nicht mehr automatisch erweitert. Stefan kann sie via
Diagnostic-Gehirn-Tab loeschen wenn sie nerven.
Dank git ist alles rueckholbar. Wenn doch wieder Auto-Magie gewuenscht:
git revert auf 8d5991f.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung 30.05.2026 02:22: Stefan bat 'vorheriges lied'. ARIA hat
POST /previous gemacht — Spotify gab 204 No Content zurueck (Erfolgs-
Antwort ohne Body), aber der alte Skill-Code warf JSON-Parse-Error
weil kein Body zum Parsen. ARIA interpretierte das als 'Skill kaputt',
patchte ihn UND fuehrte previous nochmal aus.
Folge: Stefan landete ZWEI Lieder zurueck statt eins. Aergerlich weil
unerwartete Zustandsaenderung.
Neue Regel adressiert das:
- Side-Effect-Tools (POST/PUT/DELETE, next/previous/play/pause, send-
message etc.) sind NICHT idempotent — Retry verdoppelt den Effekt.
- Bei unklarem Result IMMER zuerst State pruefen (currently-playing,
list-Endpoint etc.), dann beurteilen ob Wiederholung noetig.
- HTTP 204 No Content ist KEIN Fehler bei POST/PUT — typische Spotify-
Antwort. Skill darf 204 NICHT als Parse-Error werten.
- GET-Calls / Search sind retry-safe, hier keine Sorge.
ARIAs zweiter Skill-Patch ist uebrigens technisch korrekt (ARG_-
Konvention zurueck, 204 handled, strukturierte Ausgabe fuer
currently-playing). Nur das doppelte Side-Effect war das Problem.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung 30.05.2026: ARIA hat beim skill_update des spotify-Skills
die ARG_-Konvention verloren. Statt os.environ.get('ARG_PATH', '')
hat sie os.environ.get('PATH', '') geschrieben. PATH ist aber die
reservierte Linux-Environment-Variable fuer den Executable-Suchpfad
(/usr/local/sbin:/usr/local/bin:...).
Folge: Skill las den System-PATH als URL-Pfad, rief
https://api.spotify.com/usr/local/sbin:/usr/local/bin:... → 404
zurueck. Stefan dachte Spotify sei kaputt. Rollback noetig
(Auto-Archive hat geholfen — alte Version war gluecklicherweise
noch da).
Neue Regel macht das explizit:
- ARG_<UPPER_NAME> ENV ist Pflicht-Konvention vom Skill-Runner
- Liste reservierter ENV-Namen die NICHT genommen werden duerfen:
PATH, HOME, USER, SHELL, LANG, TERM, PWD, OLDPWD,
BRAIN_INTERNAL_URL, SKILL_DIR, SHARED_UPLOADS, CFG_*
- Mit Praefix ARG_ keine Kollision moeglich
Plus skill_create Tool-Description um den gleichen Hinweis
ergaenzt: 'Args lesen via os.environ['ARG_<UPPER_NAME>'] — der
Praefix ARG_ ist Pflicht. NIEMALS direkt PATH/METHOD/BODY etc.
abrufen — das sind reservierte System-ENV (PATH = Executable-
Suchpfad, nicht Dein arg!).'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung 30.05.2026: Stefan bittet ARIA via skill_update den
spotify-Skill so anzupassen dass currently-playing strukturiert
ausgegeben wird (Track/Artist/Album/Device/Zeit). ARIA antwortet
mit Defensiv-Reflex: 'Der Skill ist nur ein OAuth2-Wrapper, ich
kann das nicht im Wrapper bauen — ich schlage einen zweiten Skill
spotify_now_playing vor'.
Quatsch. Skills sind beliebiger Python-Code. Ein
`if path.endswith('currently-playing'): pretty_output()` waere
trivial im Skill drin gewesen. Stefan haette das nicht selbst
erkennen muessen — genau dafuer ist ARIA da.
Neue Regel macht das explizit:
- skill_get + skill_update ist der Standard-Workflow fuer
Skill-Anpassungen
- Skills duerfen if-Verzweigungen, json-Parsing, Output-Filterung,
mehrere Endpoints in einem Skill etc.
- 'Kann ich nicht in den Wrapper bauen' ist Antipattern
- 'Ich schlage einen zweiten Skill vor' ohne erst skill_update
zu pruefen ist Antipattern
- Stefan ist KEIN Python-Entwickler — er nennt das ZIEL, ARIA
baut das WIE.
Plus skill_update Tool-Description um den gleichen Gedanken
ergaenzt: 'Skills sind ganz normaler Python-Code, du kannst sie
beliebig erweitern.'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim Hook-Deploy-Test (30.05.2026, 01:51-52): ARIA versucht
run_spotify zuerst als nativen Tool-Use → 'No such tool available'
weil claude-CLI nur seine eigenen Tools (Bash/Read/Write/etc.) kennt;
Brain-Tools sind als Prompt-Instruction injiziert.
Erst nach dem 'No such tool'-Fehler wechselt ARIA aufs XML-Tag-Format
<tool_call name="...">{...}</tool_call>, das der proxy parsed und ans
Brain weiterleitet. Dieser Lernzyklus pro Anfrage kostet ~30s.
Die Regel erklaert die Architektur (claude-CLI vs Proxy vs Brain) und
gibt das richtige Format vor — direkt XML-Tag, nicht native Tool-Use.
Beilaeufige Bestaetigung an Stefan: seed_rules.py ist System-Code, wird
bei jedem Brain-Lifespan-Start aufgespielt — frische DB nach Wipe wird
beim ersten Boot mit den 15 Regeln gesetzt, idempotent ueber
migration_key. Im Gegensatz zu brain-import/ (gitignored, manuelle
Migration via Diagnostic-Klick).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante A endlich umgesetzt: echter Hard-Block vor Bash-Ausfuehrung.
Anders als 14 seed_rules + Bypass-Lehre, die ARIA ignorieren kann,
ist das ein technisch erzwungener Reject auf claude-CLI-Ebene.
Komponenten:
1. aria-brain main.py: neuer Endpoint POST /skills/can-bash-host
Bekommt {command}, parst https-URLs raus, prueft gegen aktive Skills
(stem-match: 'spotify' im Hostname 'api.spotify.com'). Returnt
{block, host, skill, safe_tool} wenn ein Skill den Host abdeckt.
2. proxy-patches/pre-tool-bash-block.js: Node-Script das vom claude-CLI
als PreToolUse-Hook fuer das Bash-Tool aufgerufen wird. Liest Tool-
Use-Payload via stdin, ruft Brain-Endpoint mit kurzem Timeout (3s),
bei block=true → exit 2 mit Stderr-Message. claude-CLI gibt Stderr
als tool_use_error an das LLM zurueck — echter Fehler, nicht
ignorierbar.
Fail-open bei Brain-Down / Timeout / JSON-Fehler: kein Lockout.
3. proxy-patches/managed-settings.json: claude-CLI Hook-Config mit
PreToolUse-Matcher 'Bash' der das Node-Script ausfuehrt.
/etc/claude-code/managed-settings.json hat Vorrang vor User-Settings
und betrifft NICHT Stefans Host-~/.claude/settings.json.
4. docker-compose.yml: proxy-Command erweitert um
`mkdir -p /etc/claude-code && cp managed-settings.json dorthin`
damit beim Container-Start die Hook-Config aktiv ist.
Beobachtung die das motiviert: 14 seed_rules + Bypass-Lehre +
Auto-Scaffold + Safe-Names. ARIA hat trotzdem letzten Test mit 2
verschachtelten Bash-curls bedient statt run_spotify zu rufen
(content_len=73, tool_calls=0). Prompt-Engineering ausgereizt.
ARIA bekommt jetzt:
🚨 BASH GEGEN api.spotify.com BLOCKIERT.
Es existiert bereits ein Skill 'spotify' fuer diesen Host. ...
Konkret: nutze JETZT `run_spotify` mit den passenden Parametern
(method/path/body) statt curl.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DER eigentliche Bug warum ARIA Spotify-Tracks halluziniert hat. Lange
Diagnose-Session am 30.05.2026 zeigte: ARIA RUFT run_spotify echt auf
(im Brain-Log zu sehen als tool_calls=1 + skill liefert echte Daten).
Aber bevor das Ergebnis an Claude zurueckging, hat dieser Code:
snippet = (res.get("stdout") or "")[:2000]
es auf 2000 Zeichen abgeschnitten. Spotify-JSON ist 5-15 KB —
"album":{"name":"..."} steht frueh drin (kommt durch), aber
"item":{"name":"..."} (Track-Name selbst) und alle Detail-Felder
liegen weiter hinten und wurden verworfen.
Folge: ARIA bekam nur den Anfang vom JSON inkl. Album-Name, hat dann
den bekanntesten Track aus dem Album geraten (Album "Loneliness" ->
Track "Loneliness"; Album "Sound Of Belgium" -> Track "House of
House"). Semi-Halluzination weil halbe Information.
Fix: 50000 Zeichen Limit fuer stdout (Claude verkraftet das locker,
hunderte KB Context). stderr von 500 auf 4000. Bei Ueberlauf wird die
Original-Byte-Anzahl im Result mitgegeben damit ARIA weiss dass mehr
Daten da gewesen waeren.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live-Beobachtung am 30.05.2026: ARIA spawnte `Agent` (Sub-Agent) mit
Anweisung 'Call run_spotify...' statt das Tool direkt aufzurufen. Der
Sub-Agent ist eine isolierte Claude-CLI-Session ohne Brain-Tools, hat
also 'No such tool: run_spotify' gemeldet. ARIA hat dann halluzinierte
Track-Namen ausgegeben ('Set You Free – N-Trance', 'Tomcraft –
Loneliness'), als waeren das echte Spotify-Daten.
Drei distinkte Probleme, zwei neue Regeln:
13. seed/skill-rule/no-subagent-for-skills:
Brain-Tools (run_*, oauth_*, memory_* …) NIEMALS via Agent-Subagent
aufrufen — die sind isoliert und sehen die Brain-Tools nicht.
Direkt in der Haupt-Session aufrufen. Subagent nur fuer Code-Search
/ Web-Recherche / parallele unabhaengige Aufgaben.
14. seed/rule/no-hallucinated-results (Kategorie 'ehrlichkeit'):
Bei Tool-Fail / abgeschnittenem Response / fehlendem Tool: ehrlich
sagen, NICHT raten. Anti-Antipattern: 'Stefan vertraut Deinen
Antworten — wenn Du raetst und es als Fakt verkaufst, bricht das
Vertrauen'. Mit konkreten Formulierungs-Beispielen.
Beide Regeln sind erfahrungsbasiert (mit Datum + konkretem Vorfall) —
ARIA sieht im Hot-Memory was sie selbst falsch gemacht hat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Letzter Fix-Commit nutzt re.sub() in _skill_to_tool und im Dispatcher,
aber re wurde nie oben importiert. Folge: NameError beim ersten chat()
Aufruf nach Restart. Stefan bekam Brain-Error 500.
Trivial-Fix: import re bei den anderen stdlib-Imports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Frage: 'weiss sie in zukunft unterstriche statt bindestriche?'
Antwort vorher: nein — Tool-Description sagte 'kebab-case'. Genau das
hat die Bindestrich-Skills produziert die gestern die Tool-Liste kippten.
Drei Aenderungen:
- skill_create Tool-Description: 'kurz, kebab-case' → 'snake_case (NUR
a-z 0-9 _). KEINE Bindestriche — die brechen das Tool-Schema beim
claude-max-api-proxy. Statt yt-dlp-download → yt_dlp_download.'
- skill_scaffold Tool-Description: gleiche Klarstellung.
- 12. seed_rule snake-case-names: erklaert das Verbot mit Begruendung
(proxy-Limitierung), Beispielen RICHTIG/FALSCH und Hinweis dass
historische Skills mit Bindestrich ueber das Safe-Name-Mapping laufen
(nicht umbenennen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim zweiten Live-Test (01:13:41): ARIA versuchte echten
Tool-Call `run_spotify` — bekam aber Error: 'No such tool available'.
Ursache: _skill_to_tool baute Tool-Namen via `run_{s['name']}`. Bei
Skills wie 'yt-dlp-download' wurde daraus 'run_yt-dlp-download' mit
Bindestrich. Anthropic-Tool-Name-Schema ist eigentlich [a-zA-Z0-9_-],
ABER der claude-max-api-proxy konvertiert intern auf OpenAI-Format
und faellt bei Bindestrichen um — wenn EIN Tool ungueltig ist, kippt
die GANZE Tool-Liste, ARIA sieht nichts von 'run_*' inklusive
'run_spotify' obwohl der ja Bindestrich-frei war.
Fix:
- _skill_to_tool: name = "run_" + re.sub(r"[^a-zA-Z0-9_]", "_", s["name"])
→ run_yt_dlp_download statt run_yt-dlp-download.
- Dispatcher: bei tool_name='run_X' wird zuerst X als skill_name probiert,
bei Miss wird ueber die Liste der existierenden Skills gemappt — der
Skill mit safe_name(name)==X wird dann genommen.
- Bypass-Lesson + Bypass-Section: gleiche safe-Logik fuer den
empfohlenen run_<tool>-String im Memory/Prompt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim ersten Live-Test (00:58:33): Auto-Scaffold legte den
spotify-Skill mid-chat() an, all_skills + active_skills wurden refreshed,
ABER die `tools=`-Liste die an den Proxy/claude-CLI geschickt wird
nicht. Folge: ARIA sah im System-Prompt-Skills-Block dass `spotify`
existiert und wusste sie soll `run_spotify` nutzen — aber claude-CLI
kannte das Tool nicht weil dessen tool-schema noch ohne run_spotify
war. Sie hat dann <tool_call name="run_spotify">...</tool_call> als
XML in den Text geschrieben, das wurde nirgends ausgefuehrt (siehe
"Pausiert" / "Restricted & NIKSTER" Antworten waren halluziniert).
Fix in 4 Zeilen: nach scaffolded_any auch `tools = list(META_TOOLS) +
[_skill_to_tool(s) for s in active_skills]` neu bauen. Damit kennt der
CLI-Subprocess den frischen Skill-Tool sofort und kann ihn echt aufrufen.
Beim naechsten chat-Turn waere es eh richtig (Tools werden neu gebaut),
aber genau der erste Turn nach Auto-Scaffold ist der wichtigste —
da soll's klappen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante 3+ (Lerneffekt-Variante): Variante C scaffolded zwar Skills auto,
aber ARIA lernt nicht — sie wird beim naechsten Mal trotzdem zu Bash
greifen. Stefans Punkt: Lernen geht nur ueber Brain-Memory.
Mechanik:
1. api_heuristic.detect_recent_bypass(skills, since_sec=600):
schaut letzte 10 Min im agent_stream.jsonl, findet Bash-curl gegen
Hosts fuer die bereits ein matching Skill existiert. Returnt
{host, skill_name, count, last_ts}.
2. api_heuristic.build_bypass_section(events):
Drastischer Markdown-Block "## 🚨 SKILL-BYPASS ERKANNT" mit konkretem
run_<skill>-Hint pro betroffenem Host. Landet direkt im System-Prompt
noch VOR dem normalen API-Heuristik-Block.
3. agent.py._upsert_bypass_lesson(ev):
Schreibt eine pinned type=rule Memory mit source=auto-feedback und
migration_key=auto/skill-bypass/<skill_name>. Idempotent: bei
Wiederholung wird die alte Memory ueberschrieben (Counter aktualisiert),
keine Karteileichen. Content nennt konkret den run-Tool-Namen und
Performance-Vergleich (3s Tool-Call vs 13-20s Bash-Wrapper).
Diese Memory ist permanent pinned → kommt bei jedem Chat-Turn,
cross-session, cross-restart als Hot-Memory durch. Damit lernt ARIA
es im wortlichen Sinne, nicht nur Reibung in der aktuellen Konversation.
Idempotenz wichtig: bei jedem Bypass-Detection-Lauf wird die Memory
upgedatet (nicht dupliziert). Stefan kann sie via Diagnostic-Gehirn-Tab
loeschen falls sie nervt.
Stefan-Frage beantwortet: 'sie wuerde es aber nur lernen wenn sie es
auch im gehirn speichert oder?' — exakt. Schimpfen im Prompt ist
Reibung dieser Session, pinned Memory ist permanenter Lerneffekt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante C: ARIA hat selbst mit Heuristik-Block + 11 seed_rules den
expliziten skill_scaffold-Befehl ignoriert (32x Spotify-Bash-Calls in
24h, kein einziger scaffold-Aufruf). Verhaltens-Traegheit ist staerker
als jeder Prompt-Hint.
Loesung: Brain wartet nicht mehr. Bei jedem chat()-Aufruf wird die
Heuristik berechnet. Findet sie einen Host mit bekannter Suggestion
(Spotify, GitHub, OpenAI, OpenWeather, Telegram, Microsoft, Discord,
Notion, Reddit) der noch keinen Skill hat → Brain ruft selbst
`scaffold_skill(name, template, params)` mit author='aria-auto'.
Der frische Skill ist sofort im Prompt sichtbar (Skill-Liste wird nach
Scaffold refreshed, Heuristik-Cache invalidiert, Hints neu gerechnet).
Side-Channel-Event 'skill_created' mit Flag 'auto_scaffolded' geht an
die UI — Stefan sieht im Chat dass Brain einen Skill angelegt hat.
ARIA findet beim Tool-Use-Loop einen passenden `run_<name>`-Skill vor
und nutzt ihn idealerweise statt wieder Bash. Macht sie's nicht und
curlt trotzdem weiter, ist der Counter beim naechsten Mal wieder hoch
und Brain scaffolded weiter — aber dann ist der Skill ja schon da, also
nur ein Pfad.
Toggle: BRAIN_AUTO_SCAFFOLD=false zum Abschalten.
scaffold-reflex Regel angepasst: ARIA wird informiert dass Brain
manchmal selbst scaffolded (author=aria-auto) und sie den Skill via
run_<name> nutzen soll statt zu curlen. Bei Hinweisen OHNE Suggestion
(unbekannter Host) soll sie selbst skill_scaffold rufen.
Stefan-Zitat aus der Diskussion ("ARIA lernt es so nicht"): stimmt
inhaltlich, aber pragmatisch wichtiger ist dass Stefans Wartezeit von
20s auf 3s sinkt. Lernen kann sie spaeter — der Skill ist da, sie sieht
den Pfad jedes Mal beim Tool-Listing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante B: scaffold-reflex Regel allein reicht nicht weil jede Chat-
Anfrage eine eigene claude-CLI-Session ist. ARIA sieht in der aktuellen
Session nicht dass sie gestern auch schon 10x dieselbe API gecurled hat.
Beobachtung: 5+ Spotify-Bash-Calls hintereinander, kein Skill angelegt.
Loesung: Brain trackt server-side aus dem persistierten agent_stream.jsonl.
Bei jedem chat() wird der Log gescanned (cache 5min), Bash-curl-Calls
nach Hostname aggregiert. Hosts mit >=3 Calls in 24h ohne passenden
Skill landen als '## API-Heuristik'-Block im System-Prompt mit konkretem
skill_scaffold-Vorschlag.
Neue Module:
- aria-brain/api_heuristic.py:
- compute_hints(existing_skills, force): Aggregiert + filtert
- build_section(hints): formatiert als kompakten Markdown-Block
- Smart suggestions mapping (api.spotify.com → oauth-api template etc.)
- Ignoriert interne Hosts (aria-brain, localhost, docker-bridge)
- 5-min Cache damit nicht jeder Turn die JSONL parst
- aria-brain/prompts.py: build_system_prompt nimmt api_heuristic_section
als optionalen Block direkt nach Skills-Section.
- aria-brain/agent.py: vor build_system_prompt Heuristik berechnen mit
aktueller Skill-Liste, Block durchreichen.
- 11. seed_rule scaffold-reflex umgeschrieben: kein 'in einer Session'
mehr (das ergab keinen Sinn — jeder Turn neue Session). Stattdessen:
'## API-Heuristik'-Block ist Dein Cross-Session-Gedaechtnis. Wenn da
was steht: scaffolden BEVOR Du Bash machst.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante C: niedrigere Huerde zum Skill-Bau. Statt einen kompletten
Python-Skill via skill_create zu generieren (~100 Zeilen Code, teuer in
Tokens und fehleranfaellig), waehlt ARIA ein Template + minimale params,
Brain expandiert das Skelett in ~1s zu fertigem Skill.
Beobachtung: ARIA driftet bei Spotify, PDF etc. zu Bash-curl statt
einen Skill zu bauen, weil die Skill-Bau-Huerde zu hoch ist (Code,
README, args, pip_packages, config_schema). Mit Templates ist die
Huerde minimal.
Neue Module:
- aria-brain/skill_templates.py: drei mitgelieferte Templates
- oauth-api: OAuth2-API (Spotify, GitHub, Reddit, Google, Discord, ...).
Token via BRAIN_INTERNAL_URL/oauth/<s>/token mit Auto-Refresh.
Args: method/path/body/base_url
- apikey-api: API mit statischem Key (OpenWeather, OpenAI, Twilio).
Key liegt im config_schema -> CFG_<NAME> ENV, KEIN hardcoden.
Konfigurierbar: auth_header (Authorization|X-Api-Key), auth_prefix.
- file-process: Skelett fuer File-In/File-Out (PDF, Bild, JSON).
process()-Funktion ist Stub, ARIA fuellt sie via skill_update.
Templates nutzen Token-Replacement statt f-Strings (sonst Konflikt
mit dem skill-internen Python-Code).
- aria-brain/skills.py: scaffold_skill(name, template, params, author)
wrappt create_skill mit den expandierten Feldern.
- aria-brain/agent.py: neues Brain-Tool skill_scaffold mit detaillierter
Description (Template-Liste + params-Schema). Dispatcher-Handler
schickt skill_created Side-Channel-Event analog zu skill_create.
- aria-brain/main.py: POST /skills/scaffold + GET /skills/templates
(letzteres listet alle Templates fuer UI/Diagnostic).
- 11. seed_rule scaffold-reflex: bei 2x derselben API per Bash-curl
SOFORT skill_scaffold rufen. Belohnung explizit benannt
("welches lied" von 20s auf 3s).
README mit Skills-Scaffold-Tabelle ergaenzt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim "ueberspringe Lied"-Test (29.05.2026): 47 Sekunden mit
12 fehlgeschlagenen Bash-Versuchen weil ARIA glaubte sie sei im
aria-brain Container. Sie hat probiert:
- python3/python/jq (Alpine — alle nicht installiert)
- cd /data/skills/spotify-control (existiert nur im Brain)
- curl localhost:8080/oauth/... (localhost = aria-proxy, nicht Brain)
- 8s Timeout auf localhost (kein TCP Reset)
Erst nach 9 Versuchen brain:8080 erraten und dann den Token-Wert
hardcoded in den naechsten curl gepackt.
Die neue Regel beschreibt die echte Topologie explizit:
- Du bist die claude-CLI als Subprocess IM aria-proxy (node:22-alpine)
- KEIN python3/python/jq verfuegbar
- /data/skills/ existiert NUR im aria-brain
- localhost in Deinem Bash heisst aria-proxy; Brain ist aria-brain:8080
- BRAIN_INTERNAL_URL ist NUR in laufenden Skills gesetzt
- Brain-Resources via Brain-Tools (oauth_get_token, memory_search,
run_<skill_name>), NICHT via Bash
- SSH zur VM-Host: `ssh aria@host` (ed25519-Key liegt im Proxy)
- Externe APIs direkt per curl mit Token aus oauth_get_token
Plus das Anti-Pattern dokumentiert ("47 Sekunden Stefan-Lebenszeit") —
ARIA soll bei jedem Bash-Reflex gegen "lokale" Brain-Resources erst
denken oder die Brain-Tool-Ebene nehmen.
README in Skills-Architektur-Sektion entsprechend ergaenzt (10 Regeln).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /api/agent-stream akzeptiert jetzt ?page=N&perPage=M zusaetzlich zu
?lines=N. page=1 = neueste Eintraege, hoehere Pages = aelter.
Antwort enthaelt page/perPage/pagesTotal/total fuer Client-Nav.
- Live-View hat neuen 📜 Archiv-Button neben Leeren/Auto-Scroll.
- Modal mit PerPage-Selector (50/100/500/1000), «‹›» Navigation und
reload-Button. Pagination-Buttons werden auf den Grenzen disabled.
- renderArchiveLine spiegelt das Live-View-Rendering (Tool-Calls in
cyan, Results in gruen, Thinking kursiv) im Modal-Container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Befund aus chat_backup.jsonl-Analyse heute: ARIA ist 3x auf oauth_authorize
gefallen statt oauth_get_token (Stefan musste manuell einloggen), und beim
PDF-Skill ist sie nach Stefans "Variante bitte" zu Ad-hoc-Bash-Befehlen
auf der VM gedriftet ("ich lass den Code direkt laufen") — Skill wurde
unbrauchbar. Beides genau die Antipattern die wir mit den seed_rules
abdecken wollten, nur waren die zu schwach formuliert.
seed_rules (jetzt 9 statt 7):
- oauth-reauth-reflex: bei 401 ZUERST oauth_get_token, NUR bei dessen
Fehler oauth_authorize. Stefan zu Re-Login schicken ist das aergerlichste
Antipattern (er sitzt im Auto, muss Handy rauskramen).
- no-skill-drift: kaputter Skill -> skill_logs + skill_update, NIEMALS
zu Ad-hoc-Bash wechseln (Skill wird Karteileiche). Plus: "ich baue
dir einen Skill" SAGEN ohne skill_create zu rufen ist verboten —
Stefan checkt die Liste und verliert das Vertrauen.
agent_stream-Persistenz:
- diagnostic/server.js schreibt jeden agent_stream-Event parallel zum
Broadcast in /shared/logs/agent_stream.jsonl (soft-cap 50 MB mit
half-truncate beim Ueberlauf).
- Live-View laedt beim Page-Load + Sub-Tab-Switch die letzten 200
Eintraege via /api/agent-stream. Browser-Reload / Standby verliert
damit den Verlauf nicht mehr.
Debug-API ohne SSH:
- GET /api/chat-backup?lines=N (Default 200, Max 5000) — geparstes JSON
der letzten N Zeilen aus chat_backup.jsonl
- GET /api/agent-stream?lines=N — gleiches fuer den persistierten Stream
README:
- Neuer Abschnitt "## Skills — Architektur" mit Skill-Layout,
Drei-Stufen-Daten-Modell (OAuth / config_schema / Brain-Daten),
Versionierung, Anti-Friedhof, seed_rules (alle 9 aufgelistet).
- Diagnostic-Sektion um agent_stream-Persistenz + neue Debug-Endpoints
ergaenzt.
- Roadmap: Phase B "Skill-Architektur P0-P4" abgehakt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA wusste bisher nichts von BRAIN_INTERNAL_URL — sie hatte den Endpoint
zwar, aber keinen Grund ihn zu nutzen. Zwei neue rule-Memories:
- "BRAIN_INTERNAL_URL ist deine Brain-Schnittstelle" — listet die
wichtigsten Endpoints (oauth/<service>/token, memory/search,
memory/pinned, skills/list) und macht klar dass auch Daten wie
Stefans Standort, Memories oder andere Skills aus dem Skill heraus
abrufbar sind.
- "Auth-Strategie fuer externe APIs" — zwingt ARIA bei jedem API-Skill
in eine Checkliste: erst OAuth2 pruefen (Spotify, Google, GitHub,
Reddit, …), sonst statischer Key per config_schema, NIEMALS hardcoden.
Damit kommt sie eigenstaendig auf "Spotify = OAuth2 = Brain-Endpoint"
ohne dass Stefan das jedes Mal sagen muss. Insgesamt jetzt 7 seed_rules
statt 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P1+P2-Infrastruktur:
- Neuer Endpoint GET /oauth/{service}/token liefert aktuelles access_token
mit Auto-Refresh (< 60s Restzeit). Skills rufen das ueber
BRAIN_INTERNAL_URL ab statt client_secret hardzucoden.
- run_skill setzt BRAIN_INTERNAL_URL als ENV (Default http://localhost:8080,
override via Brain-Env). Skills laufen im Brain-Container, localhost passt.
- skills.create_skill: _check_anti_graveyard rejected Versions-Suffixe
(-v2, _v3, -new, -fixed, -old, -alt, -copy, -final, -clean) und
Prefix-Kollisionen (z.B. spotify-aria wenn spotify schon existiert) — die
zwei Patterns hinter dem alten Skill-Friedhof.
Tool-Description fuer skill_create um PFLICHT-VORHER-Block ergaenzt
(skill_list, kein Versionssuffix, oauth_get_token, config_schema) damit
ARIA die Regeln direkt im Schema sieht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefans Skill-Friedhof (9 Spotify-Skills, hardcoded Credentials) hatte
keine systemische Ursache im Code, sondern im fehlenden Leitplanken-
Memory. Lösung: System-Seed-Regeln als pinned Hot Memory, mit jedem
Deploy ausgerollt.
- aria-brain/seed_rules.py: 5 rule-type Memories (skill_list-vor-create,
no-version-suffix, update-not-recreate, no-hardcoded-credentials,
config-schema-for-settings), source="seed", pinned=true
- Lifespan ruft seed_rules.apply() beim Brain-Start — idempotent via
migration_key (alte Versionen werden vor dem Schreiben gelöscht)
- skill_create Tool-Description um PFLICHT-VORHER-Block ergänzt:
skill_list-check, kein Versionssuffix, oauth_get_token bei OAuth,
config_schema statt hardcoded Werte
Editieren = SEED_RULES-Liste anpassen, Brain neu starten. Im Gegensatz
zu brain-import/ (User-Saatgut, gitignored, manueller Diagnostic-Klick)
gehört das hier zum Brain-Code und rollt mit jedem Deploy aus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei kleine Robustness-Verbesserungen:
1) chat_history-Handler im Frontend: jede Bubble jetzt in try/catch. Wenn
eine Bubble bei der Render-Pipeline (escape/linkify/regex-replace) eine
Exception wirft, brach die ganze for-Schleife ab und alle nachfolgenden
Bubbles wurden nicht mehr in den DOM geschrieben — beim Reload sah man
dann nur die ersten N Eintraege und Stefan dachte die letzten Antworten
waeren weg. Jetzt: Fehler-Bubble mit "⚠ Render-Fehler" + console.error,
restliche Bubbles laufen weiter durch.
2) Diagnostic-Server RVS-Reconnect: TLS-Fallback war auch bei reinen
Netz-Fehlern (EHOSTUNREACH, ECONNREFUSED, ENETUNREACH, ETIMEDOUT,
ENOTFOUND, EAI_AGAIN) gefeuert — bringt nichts weil der Server eh tot
ist, generiert aber doppelte Reconnect-Versuche + Log-Spam. Jetzt nur
noch bei wirklichen TLS/Handshake-Fehlern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan musste seit der HTTPS-Umstellung nach jedem Hintergrund-Rueckkehr
manuell auf "Verbinden" tippen, meist 3x bis es ging. Gleiche Bug-Klasse
wie auf der Bridge davor (Sticky-Fallback), plus zwei App-spezifische
Symptome.
Drei Ursachen:
1. usingTLSFallback klebt: einmal nach onerror auf true gesetzt, blieb
es bei allen folgenden Reconnects → App versuchte ws://...:443 gegen
den TLS-only Caddy → HTTP 400 → endlos. Reset war NUR im manuellen
connect(), nicht in onclose oder scheduleReconnect.
Fix: in onclose `usingTLSFallback = false` damit der naechste
Reconnect wieder primary (wss://) probiert.
2. Zombie-WebSocket: Android kann den TCP-Socket im Background still
killen, der JS-State zeigt aber noch readyState === OPEN. Stefans
manueller "Verbinden"-Klick rief connect() → "Bereits verbunden"
No-Op statt sich neu aufzubauen.
Fix: connect(force=true) optional, bestehendes WS-Objekt wird hart
geschlossen (mit onclose=null gegen Doppel-Reconnect) bevor neuer
Aufbau startet.
3. Keine aktive Reconnect-Sequence bei Foreground-Resume: App war
abhaengig von onclose-Events die bei Zombie-WS nicht zwingend
feuern.
Fix: AppState-Listener in App.tsx, bei background → active
automatischer rvs.connect(true).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bei kurzem TLS-Fehler beim ersten Connect (z.B. Caddy noch im ACME-
Setup) wechselte die Bridge auf den ws://-Fallback und blieb dort
permanent kleben. Jeder spaetere Reconnect-Versuch landete dann auf
plain ws:// gegen den TLS-only Caddy-Endpoint → HTTP 400 → erneut
Connection lost → endlos.
Fix: Bei jeder ConnectionClosed/Refused/InvalidMessage-Exception wird
using_fallback=False und current_url=self.rvs_url (= primary wss://)
zurueckgesetzt. Bridge probiert bei jedem Reconnect zuerst primary,
faellt nur einmal pro Connect-Cycle auf ws:// zurueck. Sobald TLS
verfuegbar ist, ist sie auf wss:// stabil.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OAuth-Provider (Spotify, Dropbox, ...) verlangen HTTPS fuer non-localhost
Redirect-URIs. Bisher lief der RVS direkt auf einem TCP-Port ohne TLS —
Spotify hat den Callback abgewiesen.
Loesung: Caddy im selben Compose-Stack davor. Holt automatisch ein
Let's Encrypt-Zertifikat fuer PUBLIC_URL (HTTP-01 ueber Port 80),
terminiert TLS auf 443 und routet alles inkl. WebSocket-Upgrades an
den internen RVS-Container (Port 3000).
- rvs/docker-compose.yml: caddy-Service hinzu (image caddy:latest,
command 'caddy reverse-proxy --from ${PUBLIC_URL} --to rvs:3000'),
rvs-Service verliert ports-Block (nur intern via aria-rvs-net),
data-Volumes fuer Caddy-ACME-State (persistent, Rate-Limit-Schutz).
- rvs/.env.example neu: dokumentiert PUBLIC_URL + DNS/Port-
Voraussetzungen.
- rvs/.gitignore neu: .env + data/ (sonst landen die Zertifikate
versehentlich im Repo).
- README RVS-Sektion: Setup-Schritte mit Caddy + Hinweis wie man's
auskommentiert wenn ein eigener Reverse-Proxy davor steht.
Wer schon einen TLS-Terminator hat (nginx/Traefik): caddy-Service in
der Compose auskommentieren, rvs wieder einen ports-Block geben.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA hat jetzt das META-Tool oauth_register_provider. Wenn Stefan einen
Service nutzen will, der nicht in den (auf Spotify reduzierten) Defaults
ist, kann sie auth_url/token_url/scopes/client_auth selbst eintragen —
ARIA kennt typische OAuth-Endpunkte (Dropbox, Discord, Notion, Slack,
Zoom, Trello, LinkedIn, Reddit, Twitch) aus ihrem Training. Sie traegt
NUR die URLs ein, client_id/secret bleiben Stefans Job (Diagnostic /
App-UI) — bewusste Trennung damit Credentials nicht im Chat-Verlauf
landen.
DEFAULT_PROVIDERS auf Spotify reduziert — Rest war aktuell ungenutzt
und macht den Code unnoetig "groß". ARIA registriert on-demand.
Diagnostic-UI:
- Custom-Provider zeigen auth_url/token_url/scopes als sichtbare Felder
- Defaults verstecken die Felder hinter "Default-URLs ueberschreiben
(advanced)" damit man die Spotify-URLs nicht versehentlich loescht
- "+ Custom OAuth-Provider hinzufuegen" Button mit Prompts fuer
Name/URLs/Scopes
- 🗑-Icon bei Custom-Services (Service komplett entfernen)
App-UI (neu fuer unterwegs):
- Settings → Sektion 🔑 "OAuth-Apps" zwischen Skills und Protokoll
- OAuthBrowser-Komponente analog zu Trigger/Skill-Browser:
Liste mit Status, Tap → Edit-Modal mit client_id/secret +
Advanced-Toggle fuer URLs. "Autorisieren ↗" oeffnet System-Browser
via Linking.openURL, redirected zur RVS-Callback-Page,
Status-Refresh nach 8s.
- "+ Custom"-Button → Full-Screen-Modal fuer Service-Anlage.
- brainApi um listOAuthServices/getOAuthApps/saveOAuthApp/
deleteOAuthApp/authorizeOAuth/revokeOAuth erweitert.
Workflow ist jetzt: "verbinde mich mit Dropbox" → ARIA registriert
Provider → "trag client_id/secret in Settings ein" → Stefan macht das
in App oder Diagnostic → "Autorisieren ↗" → fertig.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App-Bugs:
- Trigger-Liste war leer: brainApi.listTriggers() cast'te {triggers: [...]}
direkt als Array, t.sort() warf — TriggerBrowser blieb leer. Fix: unwrap.
- GPS-Tracking startete erst bei SettingsScreen-Mount, nicht beim App-Boot.
Wenn Stefan direkt in den Chat ging, blieb GPS aus. Fix: restoreFromStorage()
in App.tsx useEffect.
- Text in Chat-Bubbles nicht markierbar / kein Copy-Mechanismus: Bubble jetzt
Pressable mit onLongPress + neues ⎘-Icon in Status-Row → openBubbleActions().
Alert-Menu mit "Ganzen Text teilen" + pro extrahierte URL/Mail/Tel eine
eigene Option. Share.share() — keine neuen Native-Deps noetig.
Brain — Skill-Mgmt:
- ARIA legte beim Skill-Umbau neue Versionen mit Suffix an (Skill-Friedhof),
weil sie kein Update/Delete-Tool kannte. Zwei neue META_TOOLS in agent.py:
skill_update (kann entry_code, readme, pip_packages, args, description,
active patchen — venv wird bei pip_packages-Aenderung rebuilt) + skill_delete.
- skills.py update_skill um entry_code/readme/pip_packages erweitert,
venv-Rebuild bei pip-Aenderung.
Bridge — Voice-Speed persistent:
- _next_speed_override war pro-Request-Override ohne Persistenz. Bei
Diagnostic-Chats / Trigger-Replies ohne vorherigen App-Chat fiel der Speed
auf 1.0 zurueck, ebenso nach Bridge-Restart. Jetzt: _persistent_xtts_speed
aus voice_config.json (xttsSpeed), wird nach jedem App-chat mit speed
autopersistiert. TTS-Generation faellt zurueck: per-Request > persistent > 1.0.
App — Feature 6:
- SkillBrowser.tsx: Liste aller Skills, Toggle aktiv/inaktiv, Detail-Modal
mit Args-Inputs, Ausfuehren mit Live-stdout/stderr, Logs der letzten 20
Runs, Loeschen. Settings-Sektion "Skills" (🛠️) zwischen Trigger und
Protokoll. brainApi.listSkills/getSkill/runSkill/updateSkill/deleteSkill/
getSkillLogs ergaenzt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bridge hat seit 5+h still gehangen — Container Up, asyncio idle im
selectors.select(), TCP-Verbindung zum RVS ESTABLISHED, aber keine
Events mehr verarbeitet. Klassischer Fall: NAT-Tabelle/Firewall hat
die TCP-Verbindung still gekillt (kein RST), Linux-Kernel mit Default-
Keepalive (2h idle) hat's nicht gemerkt, und der ws.ping()-Future hat
im Limbo gehangen ohne Exception zu werfen.
Schicht 1 — TCP-Keepalive aufm Socket:
SO_KEEPALIVE=1, TCP_KEEPIDLE=30s, TCP_KEEPINTVL=10s, TCP_KEEPCNT=3.
Halb-tote Verbindungen werden in ~1 min mit ECONNRESET sichtbar statt
nach 2h. Loest 80% der Faelle direkt.
Schicht 2 — Asyncio-Watchdog (_rvs_heartbeat_watchdog):
Separate Coroutine parallel zu _rvs_heartbeat. Letzterer markiert
_last_heartbeat_ok nach jedem erfolgreichen pong. Watchdog checkt
alle 20s: > 60s stale → ws.close() + transport.close() als Notausgang.
Schuetzt gegen ws.ping()-Limbo.
Schicht 3 — File-Based Liveness Thread:
Separater OS-Thread (NICHT asyncio) — immun gegen asyncio-Hangs.
Schreibt /shared/health/bridge_alive periodisch. Wenn
_last_heartbeat_ok > 180s stale: os._exit(1), Docker restart_policy
uebernimmt. Last-Resort wenn Schichten 1+2 versagen.
Plus: chat_history-Render nach Reload bezog nur #chat-box, nicht
#chat-box-fs (Vollbild). Wer im FS-Modus reloaded hat sah eine leere
Box statt der History. Jetzt rendert der Handler in beide Boxen
(gleicher Pattern wie addChat / addAriaFile).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>