Persistentes chronologisches Log was ARIA intern macht — gefuettert aus
agent_activity-Events (thinking/tool/assistant/idle). Bleibt zwischen
Denk-Phasen stehen, neue Eintraege kommen unten dran, lange Pausen
werden mit Trennlinie + Minuten-Hint sichtbar gemacht.
App (ChatScreen.tsx):
- 💭-Icon in der Statusleiste neben 🗂️ und 🔍, zeigt Eintrags-Anzahl
- Bottom-Sheet (60% Hoehe) mit chronologischer Liste, Tap auf Hintergrund
schliesst, 🗑-Confirm zum Leeren
- Persistierung in AsyncStorage (aria_thought_stream, capped 500)
- Dedup gegen direkt aufeinanderfolgende identische Events
Diagnostic (index.html):
- 💭 Gedanken-Button im Chat-Test-Header neben „Vollbild"
- Zentrales Modal (720px x 70vh), Live-Update wenn neue Eintraege kommen
(autoscroll ans Ende), 🗑 Leeren-Button mit Confirm
- Persistierung in localStorage, gleiche cap/dedup-Logik wie App
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prompt sagte 'Harte Regel — IMMER Skill anlegen wenn pip-Library
noetig'. ARIA hat das wortwoertlich genommen: bei einer einfachen
pdf-extract-Frage hat sie sofort skill_create gerufen → Brain blockiert
12 Min im venv+pip-Install-subprocess.run, App zeigt 'ARIA denkt',
Diagnostic emitted nach 5 Min Timeout idle, Stefan blieb stundenlang
ohne Antwort.
Neue Regel:
- Goldene Regel: NIE ungefragt Skills anlegen.
- Aufgabe zuerst inline loesen (Bash, direkter pip install, Workaround).
- Skill nur wenn Stefan EXPLIZIT sagt 'mach daraus einen Skill' /
'leg den als Skill an'.
- Die vier Kriterien (wiederkehrend/nicht-trivial/parametrisierbar/
wiederverwendbar) sind jetzt Checkliste NACH expliziter Anfrage —
fehlt eines, soll ARIA nachfragen statt blind anzulegen.
- Begruendung steht jetzt im Prompt: Setup blockt Brain bis zu 12 Min.
Greift auf der VM ohne Re-Build, prompts.py wird beim Start geladen
(docker compose restart aria-brain reicht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Race nach Etappe-3-Reconnect-Fix: lokale failed-Bubble (mit clientMsgId)
und Server-Backup-Eintrag (ohne clientMsgId, aus alter Bridge-Version)
landeten beide im Merge → User sah Doppelpost: einmal ueber der
ARIA-Antwort (Server), einmal mit Retry-Knopf darunter (lokal). Plus
ACK-Timer konnte weiterlaufen obwohl die Bubble schon delivered war —
Retry pushte den Status zurueck auf sending und nach 30 s auf failed.
App:
- chat_history_response-Merge faellt zusaetzlich auf text+timestamp-
Heuristik im 5-Min-Fenster zurueck wenn die Server-Bubble keine
clientMsgId hat → lokale Kopie wird verworfen, kein Doppelpost
- messagesRef + dispatchWithAck prueft vor Send/Retry ob die Bubble
bereits delivered ist → kein verspaetetes failed mehr
- ARIA-Reply cleart ALLE laufenden ACK-Timer (Bridge hat unsere
Messages ja offensichtlich verarbeitet)
Docs:
- issue.md: neuer Block 'Chat-Stabilitaet' mit den drei Etappen +
beiden Race-Fixes; AsyncStorage-Race-Punkt aus 'Offen' abgehakt
- README.md: Chat-Such-Zeile aktualisiert (highlight statt filter),
Jump-to-Bottom + Delivery-Status-Bubbles dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Race-Bug nach Etappe 3: Beim Reconnect schickt die App parallel
chat_history_request und (via flushQueuedMessages) die offline gestaute
Nachricht. Die history_response kam an bevor die Bridge die Bubble in
chat_backup.jsonl geschrieben hatte → Server-Liste ohne unsere Bubble →
Merge ersetzte den lokalen Stand → Bubble weg (im Diagnostic war sie
gleich danach drin).
Bridge: _append_chat_backup nimmt clientMsgId mit auf. send_to_core
reicht sie als kwarg durch (chat- und audio-Pfad).
App: chat_history_response-Merge dedupt per clientMsgId. Lokale User-
Bubbles deren clientMsgId der Server noch nicht kennt bleiben erhalten
(localOnly-Filter erweitert). Server-User-Bubbles mit clientMsgId
kriegen deliveryStatus='delivered' damit das ✓✓ auch nach Reload sichtbar
bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei Etappen Chat-Fixes:
Etappe 1 — Such-Scroll permanent springen weg:
- invertedMessages raus aus dem useEffect-Deps; neue ARIA-Nachrichten triggern den Scroll-Effect nicht mehr. Aktueller Snapshot via Ref.
- onScrollToIndexFailed: statt 3 cascading Retries (120/320/600ms) nur noch EINE Retry nach 300ms. Cascading-Retries waren der Endlos-Cascade-Bug (jeder Failed-Retry triggerte 3 weitere).
Etappe 2 — AsyncStorage-Race + Stuck-Thinking:
- Init-Load merged statt overwrite — Nachrichten die zwischen Mount und Load-Done reinkommen werden nicht mehr verschluckt.
- Stuck-Thinking-Watchdog: 180s ohne agent_activity-Update → Auto-Reset auf idle + Timeout-Bubble. Gegen "App haengt auf 'ARIA denkt'".
Etappe 3 — Delivery-Handshake (WhatsApp-Style):
- Pro User-Bubble: clientMsgId + deliveryStatus (queued/sending/sent/delivered/failed).
- Offline-Queue: Send waehrend disconnected → 'queued' → flush bei Reconnect.
- Bridge sendet chat_ack zurueck → Bubble auf 'sent' (✓).
- ARIA-Reply → alle vorigen User-Bubbles 'delivered' (✓✓).
- ACK-Timeout 30s, bis zu 3 Retries, danach 'failed' (rotes Tap-fuer-Retry).
- Bridge: LRU-Idempotenz (200 cmids) verhindert Doppelte beim Retry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei kleine UX-Fixes im Chat:
1. Jump-Down-Button (↓): Bei inverted FlatList erscheint rechts ueber
der Eingabe ein blauer FAB, sobald man mehr als 250px von der
neuesten Nachricht weg gescrollt ist. Tap → scrollToOffset(0)
animated → wieder unten. Auto-hide wenn man unten ist.
2. Such-Sprung landet jetzt am TEXT-ANFANG der Treffer-Bubble:
viewPosition 0.5 (Mitte) → 0 (Item-Top am Viewport-Top). Plus
Retry-Folge (180/420/800ms) gegen Layout-Race bei langen Listen.
Vorher musste man oft nochmal hoch scrollen um den Anfang zu sehen.
onScrollToIndexFailed-Fallback genauso mit viewPosition 0.
3. issue.md: "Bilder: Claude Vision direkt nutzen" raus aus den
offenen Punkten — ist durch Stufe E (Memory-Anhaenge, Read-Tool
multi-modal) längst geloest. ARIA sieht Bilder echt.
Folge-Etappen: Such-Sprung-Resilienz war Teil davon (mehrere Retries
abgedeckt). Naechste Brocken: Doppel-Send-Haenger, AsyncStorage-Race,
Offline-Queue mit Idempotenz.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README.md:
- Diagnostic-Trigger-Tab-Beschreibung erweitert um die drei GPS-Funktionen
(near / entered_near / left_near) mit Use-Cases pro Modus
- Plus Auflösung erklaert: 8s-Tick + event-getrieben bei location_update
fuer Auto-Vorbeifahrten. 5-min-Age-Schutz gegen Phantom-Fires
- Phase B Punkt 5 in der Roadmap entsprechend nachgezogen
issue.md: neuer Block "GPS-Trigger-Verbesserungen" mit drei Punkten —
Timing-Fix, Age-Schutz, drei Modi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: bei aktuellen near()-Watcher gibt's nur "solange drin". Reale
Szenarien wollen aber differenzieren:
- VORWARNUNG vor Ziel (Blitzer-Warner 2 km vorher) → entered_near mit grossem r
- ANKUNFT exakt am Ziel → entered_near mit kleinem r
- VERLASSEN (Parkplatz, hast du was vergessen) → left_near
- KONTINUIERLICH-DRIN (bin noch in der Naehe?) → near (Default, throttled)
Zwei neue Funktionen in der Condition-Whitelist:
- entered_near(lat, lon, r): True NUR im Moment des Uebergangs
draussen → innen. Fires einmal pro Eintritt.
- left_near(lat, lon, r): True NUR im Moment des Uebergangs innen →
draussen. Fires einmal pro Austritt.
State-Tracking:
- pro Trigger pro near-Aufruf wird der letzte Auswertungs-Wert (true/
false) im Watcher-Manifest gespeichert (Field "near_states", Key
"lat.6,lon.6,radius"). Background-Loop liest's vor dem Eval, gibt's
per collect_variables(prev_near_states=...) in die Closure, schreibt
nach dem Eval die neuen Werte zurueck — UNABHAENGIG ob gefeuert
wurde, sonst greift die Uebergangs-Erkennung nicht.
Background _tick:
- Aufteilung in Watcher-Pass (mit prev_near_states pro Trigger) und
Timer-Pass (ohne State, gemeinsame vars). Bisher war collect_variables
einmal pro Tick — jetzt einmal pro Watcher. Disk-Stats sind teuer
aber unter 30 Watchern unkritisch; bei mehr koennen wir cachen.
ARIA-Tool-Description erweitert (trigger_watcher): erklaert die drei
Modi mit Use-Cases und empfohlenen Throttle-Werten (kurz fuer entered/
left, lang fuer near).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist mehrmals an einem 300m-near()-Watcher (DRK Kreyenbrueck)
vorbeigefahren, kein Fire. Ursache: Background-Loop tickte alle 30s,
Auto-Durchfahrt durch 600m-Durchmesser-Radius dauert bei 50-120 km/h
nur 18-43 Sekunden — der Tick konnte komplett dazwischen liegen.
Drei Fixes (A + B aus Stefans Vorschlag):
A1. Background-Loop-Frequenz: TICK_SEC 30 → 8.
Garantiert mind. 2 Checks auch bei 120 km/h durch 300m. Loop ist
billig (paar Dateilesungen + AST-Eval), Brain merkt das nicht.
A2. near() bekommt Age-Schutz (watcher.py NEAR_MAX_AGE_SEC=300):
Wenn location_age_sec > 5 min, gilt die Position als unbekannt
und near() liefert False. Verhindert Phantom-Fires wenn Tracking
aus ist oder Mobilfunk weg war — vorher haette der letzte
bekannte Wert weiter ausgewertet werden koennen.
B. Event-getriebener Tick:
- background.py: tick_now()-Funktion + Module-Slot fuer
agent_factory damit man von ausserhalb des Lifespan-Pfads
einen Tick triggern kann
- main.py: POST /triggers/check-now Endpoint ruft tick_now()
- bridge: _persist_location feuert nach jedem Save ein fire-and-
forget POST /triggers/check-now (run_in_executor, timeout 8s,
blockt nichts wenn Brain stockt)
Damit fires near() sofort wenn die App ein location_update schickt —
Polling ist nur noch der Fallback fuer Watcher OHNE GPS-Bezug
(disk_free, hour_of_day etc.) und als Sicherheits-Tick falls
location_update mal ausfaellt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Heute Tag-2 nach dem Memory-Editor-Hauptbau:
issue.md: neuer Block "App-Memory-Editor + Crash-Reporting" mit 8
Punkten (Bubble-Header dynamic, Tap-Modal, Inbox, Settings-Editor,
RVS-Brain-Proxy, App-Crash-Reporting, memory_search+update Tools,
Bugfixes-Cluster).
README.md:
- App-Features um Notizen-Inbox + Memory-Editor + Bubble-Header
dynamic + App-Crash-Reporting ergaenzt
- Roadmap um "Memory-Editor in der App" und "App-Crash-Reporting via
RVS" als eigene Bullets — beide sitzen unter dem letzten
Memory-Anhaenge-Eintrag und schliessen damit den App-UX-Loop:
ARIA hat jetzt im Diagnostic UND in der App vollwertiges Memory-
CRUD inkl. Anhaenge, plus Crashes sind ohne ADB diagnostizierbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: Memory-Liste in Settings → Gedaechtnis-Sektion laesst sich
nicht scrollen. Klassisches FlatList-in-ScrollView-Problem auf
Android: die aeussere ScrollView (Settings-Screen-Container) faengt
alle Gesten ab, die innere FlatList (MemoryBrowser) bleibt regungslos.
Fix:
- MemoryBrowser FlatList bekommt nestedScrollEnabled={true}
- SettingsScreen-aeussere-ScrollView ebenfalls nestedScrollEnabled
- Plus keyboardShouldPersistTaps="handled" damit Taps auf Filter-
Buttons nicht von der Tastatur weggefangen werden
In der Inbox-Modal-Nutzung ist's egal — dort hat MemoryBrowser
flex:1 und der Container ist kein ScrollView.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: nach App-Cache-Leeren in Settings tippt er auf eine Datei im
Chat oder im memorySaved-Anhang → "Oeffnen fehlgeschlagen" Toast,
aber kein Re-Download. Datei hing als Geister-uri im State (RNFS-Cache
weg, State-attachment.uri zeigt noch auf den entfernten Pfad).
Fix in beiden Tap-Handlern (item.attachments im normalen Chat +
memorySaved.attachments via localUri):
1. RNFS.exists(localPath) pruefen
2. Wenn ja → openFileWithIntent
3. Wenn nein:
- State bereinigen (uri/localUri auf undefined setzen, UI zeigt
dann "tippen zum Laden")
- Toast "Cache leer — lade nach..."
- file_request via RVS triggern
- autoOpenPaths-Marker setzen, sodass file_response → Datei
speichern + automatisch oeffnen
Bilder-Branch hatte schon onError-Handler (Image-Komponente meldet
self) — der ist unveraendert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inbox-Crash gefunden via App-Crash-Reporter (commit 21a315c):
"URLSearchParams.set is not implemented"
at MemoryBrowser → brainApi.listMemories
React Native's Hermes-Polyfill kennt zwar new URLSearchParams() aber
nicht die .set()-Methode darauf. Pickup-Bug — auf iOS / aelteren
Versionen geht's, Stefan's Android-Build crasht.
Fix: kleine _qs()-Helper im brainApi.ts der einen Query-String aus
einem flachen Object baut, ohne URLSearchParams:
_qs({q:'cessna', k:5, type:'fact'}) → "?q=cessna&k=5&type=fact"
Plus: undefined/null/empty Werte werden ausgelassen — saubererer als
URLSearchParams.set wo man manuell prefilten muss.
ErrorBoundary aus 21a315c hat den Crash sauber abgefangen, statt der
App-Tot war ne Error-Box im Inbox-Modal mit der vollen Stack-Trace.
Stefan konnte den Log via tools/fetch-app-logs.sh holen ohne ADB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist unterwegs, ADB nicht moeglich. Dieses Script ist die andere
Haelfte des Crash-Reportings (commit 21a315c hat die App-Seite + Bridge-
Endpoint gebaut):
Nutzung:
tools/fetch-app-logs.sh # 200 letzte Eintraege
tools/fetch-app-logs.sh --limit 50
tools/fetch-app-logs.sh --watch # alle 5s pollen + Diff ausgeben
tools/fetch-app-logs.sh --clear # Log auf VM nach Abholen leeren
Liest $ARIA_DIAG_URL aus .claude/aria-vm.env, ruft GET /api/app-log.
Speichert komplette JSON-Response in .aria-debug/app-log-<ts>.json
(gitignored). Stdout zeigt kompakt: Uhrzeit, Level, Scope, Message,
erste 8 Stack-Frames pro Eintrag.
.gitignore: .aria-debug/ ist komplett ausgeschlossen (Crashes
koennen private Daten enthalten).
tools/README.md: kurze Doku des Workflows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist unterwegs, ADB-Zugriff nicht moeglich. Loesung: die App
loggt ihre eigenen Crashes via RVS, Bridge sammelt sie in
/shared/logs/app.log, Diagnostic-Server liefert sie als JSON.
Damit braucht's keinen ADB mehr — Crashes sind sofort vom Browser
(oder Claude per curl) lesbar.
Komponenten:
1. App components/ErrorBoundary.tsx
- React-ErrorBoundary fuer kritische Sections
- componentDidCatch → reportAppError (RVS-Send)
- UI zeigt Error-Box statt White-Screen + Reset-Button
2. App services/logger.ts
- reportAppError(scope, message, stack) → rvs.send('app_log', ...)
- installGlobalCrashReporter() haengt sich an ErrorUtils.setGlobalHandler
UND HermesInternal.enablePromiseRejectionTracker — fangt sowohl
ungefangene Errors als auch unhandled Promise-Rejections
- Konsole bleibt parallel aktiv (damit ADB im Dev-Build weiter
was sieht)
3. App App.tsx: installGlobalCrashReporter() im useEffect zusammen
mit initLogger.
4. App ChatScreen.tsx:
- Inbox-Modal mit ErrorBoundary umschlossen (scope: InboxModal,
onReset schliesst Modal)
- MemoryDetailModal mit ErrorBoundary umschlossen
- DetailModal wird nur noch konditional gerendert (memoryDetailId
!= null) statt immer visible-toggle — vermeidet potentielles
Modal-Stacking-Problem
5. RVS server.js: ALLOWED_TYPES += "app_log"
6. Bridge aria_bridge.py:
- elif msg_type == "app_log": haengt eine Zeile an
/shared/logs/app.log (JSONL, jedes Item {ts, platform, level,
scope, message, stack})
- Plus log.info Hinweis fuer das normale Bridge-Log
7. Diagnostic server.js:
- GET /api/app-log[?limit=N] → letzte N Eintraege als JSON
- POST /api/app-log/clear → log-Datei loeschen
Workflow zum Debuggen des Inbox-Crashes:
Stefan rebuilded App → drueckt Inbox → ErrorBoundary fangt den
Crash (oder Global-Handler bei ungefangenem Error) → reportAppError
→ RVS → Bridge schreibt nach /shared/logs/app.log → Stefan
oder Claude rufen GET /api/app-log auf → sehen Stacktrace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: App crasht beim Tap auf Inbox-Button. Zwei Ursachen:
1. Modal-in-Modal-Stacking (Inbox-Modal enthielt MemoryBrowser, der
wiederum ein MemoryDetailModal gerendered hat). Android Modal hat
damit Probleme — der Native-Layer mag nur eine Modal-Instance
gleichzeitig zuverlaessig.
2. MemoryBrowser nutzte Alert.prompt fuer "Neue Memory anlegen" —
das ist iOS-only, Android wirft eine Warnung oder crasht.
Fix:
- MemoryBrowser bekommt optionalen onOpenMemory-Callback. Wenn der
Parent diesen liefert, mounted MemoryBrowser KEIN eigenes
DetailModal mehr. ChatScreen mountet das DetailModal nur einmal
auf seiner Ebene; Inbox-Modal schliesst sich beim Tap und delegiert
die ID an memoryDetailId-State. Damit ist immer maximal ein Modal
aktiv.
- Alert.prompt durch eigenes kleines Dialog-Modal ersetzt: TextInput
fuer Titel, Anlegen/Abbrechen-Buttons. Cross-platform stabil.
SettingsScreen-Nutzung von MemoryBrowser bleibt unveraendert (kein
Callback → eingebautes DetailModal, aber dort kein Modal-Stacking
weil Settings kein Modal ist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei Bugs aus Stefans Screenshot:
1. memorySaved/triggerCreated/skillCreated bleiben permanent unten im
Chat-Verlauf statt mit den anderen Bubbles zu scrollen — sieht aus
wie Werbe-Bumper. Fix: chatVisibleMessages-Filter raus aus
FlatList-Source, diese Bubbles werden im Chat ueberhaupt nicht mehr
gerendert.
Stefans urspruengliche Idee war ja "trigger und gedächtnis bubble
in ein extra modal fenster" — genau das ist die Inbox jetzt.
2. Inbox-Emoji 🗂️ wurde als Literal "🗂️"-Text
gerendert. Letztes Edit hat es ohne JSX-String-Literal-Schutz
eingefuegt. Fix: {'🗂️'} statt direktes Emoji-Token.
Modal-Header analog.
Inbox-Modal erweitert:
- Neue Section "AUS DIESEM CHAT" oben: kompakte Liste der Spezial-
Bubbles aus messages (chronologisch neueste oben). Memory-Eintraege
oeffnen MemoryDetailModal (mit Tap auf den Pfeil). Trigger/Skills
zeigen nur Title+Meta — keine Edit-UI, dafuer gibt's die jeweiligen
Tabs im Diagnostic.
- Darunter wie bisher der volle MemoryBrowser mit allen DB-Memories.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Etappe 4 — 🗂️ Notizen-Inbox-Button neben der Lupe:
- Statusleiste hat jetzt zwei Icons: 🗂️ Inbox + 🔍 Suche
- Tap auf Inbox-Icon oeffnet ein Vollbild-Modal mit MemoryBrowser-
Komponente. User sieht alle Memories aus der DB, kann suchen,
filtern, neu anlegen, und in den Detail/Edit-Modus springen.
Etappe 5 — Memory-Editor in App-Settings:
- SETTINGS_SECTIONS um Eintrag 🧠 "Gedächtnis" erweitert
- Sektion rendert MemoryBrowser (selbe Komponente wie Inbox) in
einer 600px-Box — vom Diagnostic-Gehirn-Tab inspiriert, aber
fuer's Handy optimiert
- Beide Stellen recyclen MemoryBrowser+MemoryDetailModal aus
Etappe 2/3 — kein doppelter Code
MemoryBrowser (neue Komponente components/MemoryBrowser.tsx):
- Lazy-Load aller Memories via brainApi.listMemories
- Client-side Filter: Volltext-Suche (Title+Content+Category+Tags),
Type-Dropdown, Pinned/Cold/Alle-Toggle
- "+ Neu" Knopf mit Alert.prompt fuer Titel, automatisch type=fact,
oeffnet danach den DetailModal zum Editieren des Contents
- Item-Render mit Pinned-Marker, Anhang-Badge 📎N, Type-Label,
Category, 2-Zeilen-Content-Preview
- Tap auf Item oeffnet MemoryDetailModal → CRUD weiter dort
Damit sind alle 5 Etappen aus Stefans Wunsch-Trio durch:
- Bubble-Header dynamic (Etappe 1, committed gestern)
- Tap-Modal mit Detail (Etappe 2)
- Edit + Anhang-Upload im Modal (Etappe 3)
- Notizen-Inbox-Button (Etappe 4)
- Memory-Editor in Settings (Etappe 5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Etappe 1 von Stefans App-Memory-UX-Wunsch:
Brain agent.py: memory_save Dispatcher pushed jetzt action="created",
memory_update Dispatcher pushed action="updated" mit demselben
memory_saved-Event-Typ. Bridge reicht das action-Feld im Payload mit
durch (in beiden Side-Channel-Pfaden — send_to_core + trigger-fired).
App ChatScreen: ChatMessage.memorySaved.action ('created' | 'updated'
| 'deleted'). Bubble-Header je nach Aktion:
- created → "🧠 ARIA hat etwas gemerkt" (gelb)
- updated → "🧠 ARIA hat eine Notiz geändert" (gelb)
- deleted → "🧠 ARIA hat eine Notiz gelöscht" (rot)
Naechste Etappen folgen (Detail-Modal beim Tap, Edit + Anhang-Upload,
Notizen-Inbox neben Lupe, Memory-Editor in Settings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-Report von Stefan: er hat im Diagnostic den Baujahr-Memory von
1972 auf 1974 geaendert, ARIA wusste das nicht und beharrte auf 1972
(weil ihr letzter Conversation-Turn noch '1972' enthielt). Sie konnte
auch nicht nachpruefen, sagte selbst: "Qdrant kann ich nicht aktiv
durchsuchen".
Fix: zwei neue Meta-Tools im agent.py.
memory_search(query, mode='text'|'semantic', k=5):
- Volltext oder semantic via store.search_text / store.search
- Liefert Liste mit Titel, ID, Content, Anhaengen
- Tool-Description sagt explizit: "Memory ist Truth ueber dem
Conversation-Window" — wenn beide unterschiedlich sind, gilt
Memory. Plus Anker-Anwendungsfaelle: 'schau in deinem Gedaechtnis',
'ich hab das aktualisiert', 'pruef ob's schon was zum Thema gibt'
memory_update(id, title?, content?, category?, tags?, pinned?):
- Patch existierender Memory per ID (aus memory_search oder Cold-Memory)
- Content-Change triggert Re-Embedding fuer Search, sonst nur
Payload-Update
- Pushed memory_saved-Event analog zu memory_save (App/Diagnostic
refreshen)
- Tool-Description empfiehlt explizit Update statt neuem Save bei
Korrekturen/Ergaenzungen — vermeidet Fragmentierung
Damit kann Stefan jetzt sagen "schau in deinem Gedaechtnis" und ARIA
findet den aktualisierten Eintrag. Plus bei spaeteren Korrekturen
("ach nee, 1974") nutzt ARIA memory_update statt memory_save +
hinterlaesst einen sauberen Eintrag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
issue.md: neuer Block "Memory-Anhaenge mit Vision (Stufe A-E +
attach_paths)" mit den 7 Punkten (Storage-Layer, Backend-Endpoints,
Diagnostic-UI, App-UI, System-Prompt-Integration, Vision via Read-
Tool, attach_paths fuer einarmigen memory_save+attach-Workflow).
README.md: Diagnostic-Gehirn-Tab-Beschreibung um 📎-Anhaenge erweitert,
plus neuer Roadmap-Eintrag "Memory-Anhaenge mit Vision-Pipeline" der
das End-to-End-Erlebnis erklaert (User-Foto → ARIA liest via Read →
extrahiert Kennzeichen/Marken/Texte → speichert als Memory mit Foto-
Anhang → spaetere Detail-Fragen lassen ARIA das Bild nochmal lesen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Letzter Baustein vor Stefan's End-to-End-Test:
memory_attachments.attach_from_path(memory_id, src_path):
- Kopiert eine bestehende Datei aus /shared/uploads/ oder
/shared/memory-attachments/ in das Anhang-Verzeichnis der Memory
- Pfadschutz: nur ALLOWED_SOURCE_PREFIXES (/shared/uploads/,
/shared/memory-attachments/) — kein Zugriff auf Root-FS oder
SSH-Keys
- Groessen-Limit wie save_attachment (20 MB Default)
agent.py memory_save:
- Neuer optionaler Parameter `attach_paths: List[str]`
- Nach dem upsert: pro Pfad attach_from_path → Payload update mit
neuen Anhang-Metadaten
- Fehler beim Anhang sind nicht fatal (Memory bleibt gespeichert,
Hinweis in der Tool-Response)
- Tool-Description deutlich erweitert: expliziter Workflow-Hinweis
bei Bildern → erst `Read <pfad>` aufrufen (Claude Code Read ist
multi-modal), Texte/Kennungen/Marken in den content extrahieren,
dann erst memory_save mit attach_paths. Beispiel-Workflow als
Pseudocode mit Cessna 172 / Kennung D-EAAA.
End-to-End-Workflow ist jetzt einarmig moeglich:
User: "Ich hab eine Cessna 172" + Bild im Attachment
ARIA: Read /shared/uploads/aria_xy.jpg → sieht "Kennung D-EAAA"
ARIA: memory_save(content="Stefan besitzt eine Cessna 172,
Kennung D-EAAA, weiss/rot lackiert.",
attach_paths=["/shared/uploads/aria_xy.jpg"])
→ 🧠-Bubble mit Anhang in der App
→ Spaetere Frage "welche Kennung hat mein Flieger?" liefert via
Cold-Memory den Eintrag inkl. Kennung aus dem content
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wir mussten den Proxy nicht patchen. Claude Code's eingebautes
Read-Tool ist multi-modal-faehig — uebergibt man eine Bilddatei,
geht die durch das gleiche Vision-Modell wie via Anthropic-Vision-API.
ARIA hat eh "Tool-Freigaben — Vollzugriff" pinned (inkl. Read), also
muss sie nur wissen dass sie das nutzen darf.
prompts._attachments_line erweitert: bei image/* im Anhang haengen
wir den Hinweis an "Bilder kannst du via `Read <pfad>` direkt ansehen".
ARIA ruft dann selbststaendig Read mit dem Memory-Anhang-Pfad, sieht
das Bild und kann antworten was drauf ist.
Heisst: Stefan sagt "schau dir mein Cessna-Foto an" → ARIA findet
Memory via Cold-Search → sieht die Read-Anweisung → ruft Read auf →
Vision-Modell beschreibt das Bild → ARIA antwortet im Chat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stufe C — App:
- ChatMessage.memorySaved.attachments [{name, mime, size, path, localUri}]
- memory_saved-Listener uebernimmt payload.attachments
- renderMessage memorySaved-Bubble zeigt Anhaenge als Tap-Reihen
(Icon 🖼/📄 + Filename + Hint). Tap → file_request via Bridge,
beim ersten Mal "(tippen zum Laden)" → nach file_response cached
+ bei Bildern setFullscreenImage, bei anderen openFileWithIntent
- file_response-Handler updated zusaetzlich memorySaved.attachments
per serverPath-Match
- Styles fuer memoryAttachmentRow/Icon/Name/Meta
Stufe D — System-Prompt:
- prompts._attachments_line: pro Memory eine Zeile
"📎 Anhaenge: foo.jpg (image/jpeg, 109 KB) — Pfad: /shared/memory-attachments/<id>/"
- Wird in build_hot_memory_section + build_cold_memory_section
nach dem Content angehangen
- ARIA "weiss" damit dass Anhaenge da sind und kann via Bash darauf
zugreifen (file, head, base64 …). Echt sehen kann sie sie erst mit
Multi-Modal-Pipeline (Stufe E)
- memory_save Dispatcher: attachments-Liste auch im memory_saved-Event
(vermutlich [] beim Save, aber konsistent fuer spaeteres
Speichern-mit-Anhaengen-Pattern)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic-Gehirn-Tab kann jetzt Bilder/Dateien an Memory-Eintraege
haengen — drag+drop ueber den File-Input im Memory-Modal.
Memory-Modal (Edit-Modus):
- Neuer Block "📎 Anhaenge" unter Pinned-Checkbox, nur sichtbar wenn
Memory eine ID hat (Edit). Bei "Neue Memory" stattdessen Hinweis
"Anhaenge nach Speichern hinzufuegbar".
- "⬆ Datei waehlen" oeffnet File-Picker (multiple), Upload via
multipart/form-data POST an /memory/{id}/attachments/upload.
- Liste zeigt pro Anhang: Thumbnail (Bilder) oder 📄-Icon,
Filename, Mime + Groesse, 🗑 Loeschen-Button.
- Bild-Thumbnails sind klickbar → openLightbox.
- Status-Zeile zeigt Upload-Progress + Erfolgsmeldung.
Memory-Liste:
- 📎N-Badge erscheint hinter dem Titel wenn N > 0 Anhaenge da sind.
Diagnostic-Server:
- Brain-Reverse-Proxy-Timeout dynamisch: 120s fuer /attachments-Routen
(Upload), 60s sonst (vorher pauschal 30s — zu wenig fuer chat/distill).
- multipart-Body wird ueber req.pipe(proxyReq) durchgereicht (FastAPI
liest File via UploadFile, Content-Type-Header bleibt erhalten).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan's Test scheiterte: ein normales Handy-Foto als Base64 in der
curl-d-Argumentliste sprengt Bash's ARG_MAX (typisch 128KB-2MB). Plus:
Browser-FormData und curl -F sind eh der Standard fuer File-Uploads.
Fix: zusaetzlicher Endpoint
POST /memory/{id}/attachments/upload (multipart/form-data, field: file)
Beispiel auf der VM:
curl -F file=@/pfad/zu/foto.jpg \
"$ARIA_BRAIN_URL/memory/<id>/attachments/upload" | jq
Base64-Endpoint (/memory/{id}/attachments) bleibt fuer kleine
Uploads + interne JSON-Tools. Beide rufen am Ende den gleichen
_commit_attachment_meta-Helper, der das Memory-Payload um den
neuen Anhang updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pro Memory koennen jetzt Dateien (Bilder, PDFs, Sound, ...) angehaengt
werden. Use-Case: Stefan sagt "ich hab eine Cessna 172" und pinnt
gleich ein Foto dran — ARIA sieht spaeter neben dem Memory auch die
visuelle Referenz (Stufe E = Multi-Modal-Pipeline).
Stufe A baut nur den Backend-Layer; UI kommt in Stufe B (Diagnostic)
und C (App). Anhaenge werden in Stufe A nur via HTTP-API gepflegt
(curl), ARIA selbst kann sie noch nicht hochladen — sinnvoll erst
wenn die Vision-Pipeline (Stufe E) steht.
Komponenten:
- memory_attachments.py: neuer Storage-Helper. Layout
/shared/memory-attachments/<memory-id>/<safe-filename>.
Filename-Sanitization (kein Path-Traversal), Limit 20 MB
konfigurierbar, save/list/delete/read_bytes + delete_all fuer
Cleanup beim Memory-Delete.
- vector_store.py: MemoryPoint.attachments (List[dict]) — Metadaten
{name, mime, size, path} im Qdrant-Payload damit Suche/Anzeige
sie ohne Filesystem-Lookup kennt.
- main.py:
- MemoryIn akzeptiert attachments-Liste (fuer Restore-Faelle)
- MemoryOut liefert attachments
- GET /memory/{id}/attachments → Liste vom FS
- POST /memory/{id}/attachments → Base64-Upload,
schreibt FS + updated Payload-Liste
- DELETE /memory/{id}/attachments/{filename} → FS + Payload-Eintrag weg
- GET /memory/{id}/attachments/{filename} → Bytes mit MIME serve
- /memory/delete cleanup: ruft attachments.delete_all damit kein
Verzeichnis verwaist
Smoke-Test nach Brain-Rebuild (Stefan auf VM):
# Memory-ID rauspicken
ID=$(curl -s "$ARIA_BRAIN_URL/memory/list?type=fact" | python3 -c "import sys,json;print(json.load(sys.stdin)[0]['id'])")
# Bild als Base64 hochladen
B64=$(base64 -w0 /pfad/zu/foto.jpg)
curl -s -X POST "$ARIA_BRAIN_URL/memory/$ID/attachments" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"foto.jpg\",\"data_base64\":\"$B64\"}" | jq
# Liste anzeigen
curl -s "$ARIA_BRAIN_URL/memory/$ID/attachments" | jq
# Datei wieder laden
curl -s "$ARIA_BRAIN_URL/memory/$ID/attachments/foto.jpg" -o /tmp/back.jpg
Stufe B (Diagnostic-UI) folgt sobald A getestet ist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Was alles seit dem letzten Doc-Update dazukam:
issue.md (Bugfixes):
- Cold Memory Crosstalk durch Score-Threshold
- Pinned-/Type-Filter bei aktiver Suche
- Memory-Liste refresh nach Delete
- Thinking-Indikator im RVS-Chat wieder sichtbar
- Memory-Suche filtert Rauschen (score_threshold am Endpoint)
- Cessna-Phantom-Wissen aus System-Prompt raus
- Claude-Code-Auto-Memory abgeklemmt (tmpfs)
issue.md (Features):
- Neuer Block "Memory-System (Phase B Punkt 5+ Bonus)" mit
memory_save Tool, Volltext-Suche, Advanced Search, Muelltonne,
Druckansicht, klappbare Kategorien
- Neuer Block "DB als Single Source of Truth" mit brain-import als
Drop-Folder, DB-Cleanup 60→31, .claude/aria-vm.env Setup
README.md:
- aria-data/brain-import Tabelle-Beschreibung aktualisiert
- .claude/aria-vm.env als neue Zeile in der Konfig-Tabelle
- Diagnostic Gehirn-Tab Beschreibung ausgebaut (Wortlich/Semantisch,
Advanced Search, klappbare Kategorien, Druckansicht)
- App-Features: Muelltonne pro Bubble erklaert
- Roadmap-Eintrag "Single Source of Truth — Qdrant" als zentrales
Abschluss-Item nach Tool-Use-Patch
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: Agent.chat() rief store.search() OHNE score_threshold — die
Top-5 wurden ungefiltert in den 'Moeglicherweise relevant'-Block
des System-Prompts gepackt. Bei kleiner DB hatte das absurde Folgen:
Stefan fragte 'hab ich ein flugzeug?', Cold-Search lieferte Top-1
'Watcher-Latenzproblem' mit Score 0.138 + 'Firmenadresse' mit 0.094,
ARIA wob die Firmenadresse in die Antwort ein ('Die Adresse habe ich
aus meinem Gedaechtnis...') — obwohl der User gar nicht danach gefragt
hat.
Fix: Konstante COLD_SCORE_THRESHOLD=0.30 in Agent eingefuehrt und an
store.search() durchgereicht. Treffer unter 0.30 werden als Rauschen
verworfen, ARIA bekommt nur substantielle Memories ins Cold-Set.
Konsistent mit dem Threshold im /memory/search HTTP-Endpoint und dem
Diagnostic UI.
MiniLM-multilingual gibt fuer unverwandte deutsche Texte gerne 0.10-
0.25 Score — alles darunter ist Embedder-Noise, kein echter Bezug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1: Memory loeschen + Liste zeigt geloeschten Eintrag weiter
deleteMemory rief loadBrainMemoryList — die fiel bei aktiver Such-
Ansicht in den Cache-Pfad und renderte den geloeschten Eintrag aus
brainMemoryCache/brainSearchIds wieder. Fix: nach Delete den Cache-
Eintrag + brainSearchIds bereinigen und bei aktiver Suche re-search
ausfuehren (single oder advanced), sonst Vollliste vom Server.
Bug 2: "ARIA denkt..."-Indikator erscheint nicht mehr im Chat-Fenster
Diagnostic-Server hatte fuer RVS-eingehende agent_activity-Events
keinen Relay an die Browser-Clients. Bridge sendet die Events brav,
Diagnostic schluckt sie still. Fix: agent_activity vom RVS an
Browser broadcasten (mit dem gleichen settled-window-Schutz wie
beim alten Gateway-Pfad — Trailing-Events nach chat:final werden
weiter ignoriert).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA hatte beim 'weisst du ob ich ein Flugzeug habe?'-Test richtig
geantwortet ('nein'), aber transparent erklaert dass sie das Wort
'Cessna' aus dem memory_save Tool-Description kennt — wo es als
Beispiel fuer den fact-Type stand. Ein Beispiel-Text der jedes
Chat-Turn im System-Prompt landet ist suboptimal, auch wenn ARIA
ihn korrekt einordnet.
Fix: das konkrete Beispiel durch eine generische Aufzaehlung
ersetzt (Vorlieben/Besitz/Orte/Termine/Personen). Ohne Stefan-
spezifisches Phantom-Wissen. Selber Spirit in der search-text
Docstring im main.py (geht zwar nicht in den Prompt, aber lieber
konsistent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: runBrainSearch und runAdvancedSearch ignorierten den
brain-filter-pinned Dropdown — egal ob "Nur Pinned" oder "Nur Cold"
gewaehlt war, kam immer alles was die Such-Kriterien erfuellte.
Plus: Dropdown-onchange rief loadBrainMemoryList und brach damit
die Suche ab statt sie mit dem neuen Filter neu auszufuehren.
Fix:
- Neue Helfer brainSearchActive() (erkennt single/advanced/none) und
applyPinnedFilter() (client-side Filter nach 'all'/'pinned'/'cold').
- runBrainSearch + runAdvancedSearch wenden applyPinnedFilter nach
dem Backend-Hit an. Info-Box zeigt zusaetzlich an wenn
Pinned-Filter aktiv war ("... · 📌 nur pinned"), bei 0 Treffern
auch der unfiltered Count fuer Debug ("X Treffer ohne Pinned-Filter").
- Type+Pinned-Dropdowns onchange → onBrainFiltersChanged: bei
aktiver Suche re-search, sonst loadBrainMemoryList.
Backend bleibt unveraendert (include_pinned all-or-none reicht —
Feinheit "nur pinned" macht der Client).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA hatte bisher KEIN Tool um eigene Notizen sauber zu persistieren —
sie ist deshalb aufs Claude-Code-File-Memory ausgewichen (das wir mit
dem letzten Commit per tmpfs abgeklemmt haben). Jetzt schliesst sich
der Loop: ein echtes memory_save-Tool gegen die Qdrant-DB.
Brain:
- agent.py: memory_save als Meta-Tool mit Schema (title, content,
type, optional category/tags/pinned). Tool-Description erklaert
die Type-Wahl (identity/rule/preference/tool/skill = pinned,
fact/conversation/reminder = cold) und sagt explizit: "Du hast
KEIN File-Memory mehr, schreibe nicht in ~/.claude/projects/..."
- Dispatcher: validiert type-enum, ruft self.embedder.embed +
self.store.upsert, pushed memory_saved als _pending_events damit
Bridge eine Bubble broadcasten kann.
Side-Channel-Pipeline (gleich wie skill_created/trigger_created):
- Bridge send_to_core + _handle_trigger_fired: forwarden
memory_saved als RVS-Event
- rvs/server.js: ALLOWED_TYPES += memory_saved
- diagnostic/server.js: relayed memory_saved von RVS an Browser
- diagnostic UI: addMemorySavedBubble (gelber Border) + Auto-Refresh
des Gehirn-Tabs wenn aktiv
- android: ChatMessage.memorySaved-Feld, Listener fuer memory_saved,
renderMessage-Spezialbubble, History-Replace-Schutz (lokal-only)
Damit ist die Architektur konsistent:
"merk dir X" → ARIA ruft memory_save → Eintrag in Qdrant →
Diagnostic-Gehirn-Tab zeigt's sofort → bei naechstem Turn liefert
Cold Memory (Semantic Search) das Wissen wieder rein.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Code CLI (im Proxy-Container) hat ein eingebautes Auto-Memory-
Feature das Markdown-Files in ~/.claude/projects/<project>/memory/
schreibt. Weil das CLI als ARIAs LLM laeuft, hat sie da ueber Wochen
ihre eigene Schatten-Wissensbasis aufgebaut (cessna, persoenlichkeit,
projects) — komplett parallel zu unserer Qdrant-DB. Genau die doppelte
Truth-Source die wir vermeiden wollten.
Fix: tmpfs ueber das projects/-Verzeichnis im Proxy-Container.
Effekt:
- Claude Code sieht beim Spawn ein leeres projects/ — keine Auto-
Memory-Files werden geladen
- Schreibt sie was rein, landet's nur im Container-RAM
- Beim Container-Recreate ist alles weg
- Stefans persoenlicher ~/.claude/projects/ auf der VM bleibt
unangetastet (Volume ist immer noch gemountet, nur das Subdir
wird ueberlagert)
Migration auf der VM (Stefan einmalig):
rm -rf ~/.claude/projects/-/memory/
docker compose up -d --force-recreate proxy
Auto-Memory ist damit deaktiviert. Naechster Schritt (5): ARIA bekommt
einen eigenen memory_save Tool damit sie Sachen sauber in Qdrant
ablegen kann statt aufs File-Memory auszuweichen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Statt fest 3 Felder gibt's jetzt eine erweiterbare Reihen-Liste:
- "+ Feld"-Button fuegt eine Reihe hinzu (UND/ODER + Eingabe)
- ✕-Button pro Reihe (ausser der ersten) entfernt sie
- Erste Reihe ist immer "Start" ohne Operator
- syncAdvancedRowsFromDOM rettet Eingaben vor jedem Re-Render
- runAdvancedSearch iteriert ueber alle Reihen mit Inhalt, leere
werden ignoriert
Damit ist die Boolean-Suche so lang wie noetig — Stefan kann auch
5-6 Begriffe verknuepfen ohne UI-Hack. Min. 1 Feld bleibt immer
(clearAdvancedSearch reseted auf eine leere Start-Reihe).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Klappbares Panel unter dem Suchbalken — Stefan kann bis zu 3 Begriffe
eingeben und mit AND/OR verknuepfen, links nach rechts ausgewertet.
Backend bleibt simpel: pro Begriff einmal /memory/search-text aufgerufen,
die Treffer-Set-IDs werden client-seitig per AND (intersect) oder OR
(union) kombiniert.
UI:
- "⌃ Erweitert" Button rechts neben ✕ klappt das Panel auf
- 3 Eingabefelder mit 2 Operator-Dropdowns dazwischen (UND/ODER)
- "Suchen"-Button im Panel
- "Felder leeren" reseted
- Leere Felder werden ignoriert — sind nur 2 belegt, gibt's nur 1 Operator
- Typ-Filter aus dem Hauptbalken wird mit angewandt
- Info-Banner zeigt die kombinierte Suchformel zurueck
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan wollte ne richtige Suche statt nur "klingt aehnlich". Beide
Modi sind jetzt verfuegbar, Default ist Volltext:
- 📝 Wortlich (Substring, case-insensitive ueber Title + Content +
Category + Tags) — neuer Endpoint /memory/search-text. Full-Scan
via Qdrant scroll, k=50. Findet "cessna" exakt im Content. Bei
kleiner DB (<1000 Eintraege) unkritisch performant.
- 🧠 Semantisch (Embedder + score_threshold 0.30) — bestehender
/memory/search Endpoint. Findet konzeptuell verwandte Eintraege.
Diagnostic UI: Dropdown neben dem Suchfeld zum Modus-Wechsel.
Info-Banner zeigt klar welcher Modus aktiv ist.
Warum Wortlich Default: bei kleiner DB liefert Semantic gern False
Positives mit Score 0.30-0.45 fuer komplett unverwandte Begriffe
(z.B. "cessna" matched "Tageslog fuehren" mit 0.43). Wortlich ist
deterministisch und vermeidet das Rauschen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan kann jetzt einzelne Chat-Bubbles loeschen (mit Rueckfrage).
Die Bubble verschwindet aus chat_backup.jsonl (Bridge), Brain-
Conversation (rolling window + jsonl) und allen Clients (App +
Diagnostic). Genauso wichtig fuer ARIA: der gloeschte Turn ist im
naechsten Chat-Prompt nicht mehr im Window.
Pipeline:
UI 🗑 + confirm
→ RVS delete_message_request {ts}
→ Bridge._delete_chat_message:
- chat_backup.jsonl Zeile mit ts entfernen (atomar via tmp+rename)
- Brain POST /conversation/delete-turn (role+content match)
- RVS broadcast chat_message_deleted {ts}
→ App + Diagnostic entfernen Bubble lokal per ts-Match
Backend-Aenderungen:
- aria-brain/conversation.py: remove_by_match(role, content, ts_hint)
+ _rewrite_file (atomar). Match nahester Turn bei mehrfach gleichem
content.
- aria-brain/main.py: POST /conversation/delete-turn (POST statt DELETE
weil FastAPI keine Bodys auf DELETE erlaubt)
- bridge/aria_bridge.py: HTTP-Listener /internal/delete-chat-message
+ RVS-Handler delete_message_request. _append_chat_backup gibt jetzt
ts zurueck, _process_core_response packt backupTs ins chat-Event.
- rvs/server.js: ALLOWED_TYPES um delete_message_request +
chat_message_deleted erweitert.
- diagnostic/server.js: delete_chat_message-Action + chat_message_deleted
Relay zum Browser.
Frontend-Aenderungen:
- diagnostic/index.html: 🗑 erscheint on-hover in Bubbles mit data-ts,
confirm()-Dialog, addChat + chat_history setzen data-ts. WS-Listener
fuer chat_message_deleted entfernt Bubble per data-ts.
- android/ChatScreen.tsx: backupTs in ChatMessage, Muelltonne-Button
unten rechts in jeder Bubble, Alert-confirm, RVS-Listener fuer
chat_message_deleted entfernt aus messages-State.
Live-User-Bubbles (sofort gerendert vom eigenen Send) haben noch
keinen backupTs bis der Bridge-Roundtrip durch ist — die Muelltonne
erscheint dort erst nach kurzer Verzoegerung / Reload. Folgekommit
kann das polieren wenn noetig.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: bei kleiner DB (31 Eintraege) lieferte die Suche fuer JEDES Wort
fast alles als Treffer zurueck — k=20 Top-N ohne Threshold sorgte
dafuer dass auch "banane" zehn vermeintliche Treffer mit Scores
0.09-0.22 (= Rauschen) zurueckgab.
Fix:
- vector_store.search() bekommt optional score_threshold (an Qdrant
durchgereicht, das nimmt's nativ)
- /memory/search endpoint hat score_threshold-Query-Param (default 0.30)
- Diagnostic schickt k=10 + score_threshold=0.30 statt k=20 ohne Threshold
- "Keine Treffer"-Info-Box wenn alle Treffer < Threshold
MiniLM-multilingual liefert typischerweise:
>0.50 → starker Treffer
0.30-0.50 → relevant
0.20-0.30 → grenzwertig
<0.20 → Rauschen
Mit score_threshold=0 (oder None) bleibt die alte Top-N-Semantik
fuer Aufrufer die Rauschen explizit wollen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan wirft MDs rein wenn er was migrieren will, klickt im
Diagnostic-Gehirn-Tab auf "Migration aus brain-import/", fertig.
Was nicht migriert ist, liegt halt rum — gehoert aber nicht ins Repo
(private Daten, ephemerer Kram).
.gitignore-Pattern:
aria-data/brain-import/*
!.gitkeep
!README.md
Alte spezifische USER.md-Zeile durch das catch-all ersetzt — wir
mussten USER.md.example und Co. eh nicht mehr im Repo halten.
README in dem Verzeichnis entsprechend angepasst (Drop-Folder, nicht
"leerer Restposten").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AGENT.md/BOOTSTRAP.md enthielten Duplikate, OpenClaw-Referenzen und
fast-Memory-Hinweise auf das alte file-basierte System. Nach dem
DB-Cleanup (60 → 31 Eintraege) sind die alten MDs nicht mehr nuetzlich
— Stefan kuratiert direkt im Diagnostic-Gehirn-Tab, Backup laeuft via
Bootstrap-Snapshot (JSON) oder Komplett-tar.gz.
TOOLING.md.example + USER.md.example mit raus (auch obsolet).
.gitkeep haelt das Verzeichnis im Repo, README dokumentiert wofuer
es mal war und wann man es wieder braucht (Disaster-Recovery ohne
Snapshot, neues ARIA von Null).
Migration-Code (aria-brain/migration.py) bleibt — falls jemand mal
frische MDs reinpackt um sie zu parsen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Damit kann die Dev-Maschine (wo Claude Code laeuft) die aria-wohnung-VM
ueber Diagnostic Port 3001 erreichen, ohne die interne IP im Git zu
haben. Pro Maschine wird .claude/aria-vm.env aus dem .example kopiert
und mit der lokalen Routing-Info gefuellt.
Nutzung:
source .claude/aria-vm.env
curl -s "$ARIA_BRAIN_URL/memory/stats"
Im docker-compose-Netz aria-net leben die Hostnamen (aria-brain etc.)
weiterhin direkt — das brauchst nur Hosts AUSSERHALB der VM.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beim ersten Aufruf (kein localStorage-Eintrag) sind alle Type-Sections
collapsed. Stefan klappt gezielt auf was er sehen will, statt eine
Wand of Text zu sehen. Sobald er einmal getoggelt hat, ueberschreibt
sein persistiertes State den Default — also nicht aufdringlich.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Button "📄 Drucken / PDF" im Gehirn-Tab oeffnet eine sauber
formatierte Print-View in neuem Tab. Druck-CSS optimiert (page-break-
inside:avoid pro Entry, schwarze Borders fuer Print, Action-Bar wird
versteckt). Aktueller Type+Pinned-Filter wird respektiert.
Browser-eigenes "Als PDF speichern" greift dann — kein Tool noetig.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX im Memory-Browser geschaerft, Stefan-Wunsch:
1. Klappbare Type-Gruppen:
Jeder Type-Header (Identität, Regeln, ...) hat jetzt einen ▼/▶
Indikator und reagiert auf Click. Eingeklappte Sektionen werden
in localStorage gemerkt — bleiben ueber Reloads stabil.
2. Category-AutoSuggest:
Das Kategorie-Feld im Neu/Edit-Modal hat jetzt ein <datalist>
mit allen schon in der DB existierenden Categories als Vorschlag.
Neue Categories sind weiterhin frei eintippbar. Liste wird bei
jedem renderBrainList-Aufruf aus dem Cache aktualisiert.
3. Info-Button (ℹ) neben dem Typ-Dropdown:
Erklaert welche Types FEST im System-Prompt eine eigene Sektion
bekommen (identity/rule/preference/tool/skill — Hot Memory)
und welche nur via semantischer Cold-Search reinkommen (fact/
conversation/reminder). Konsistent mit prompts.py:TYPE_HEADINGS.
Auch dokumentiert dass Category ein freier Tag ist und den
Prompt nicht direkt beeinflusst.
Type-Dropdown-Labels selbst zeigen jetzt (FEST) / (Cold) als Hinweis.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: Wenn der Brain-Background-Loop einen Timer/Watcher feuert, ruft
er agent.chat() direkt im eigenen Prozess. Die Antwort wurde nur ins
Trigger-Log geschrieben — kein RVS-Broadcast, kein TTS, nichts in
App/Diagnostic sichtbar.
Fix: Bridge ↔ Brain bekommen einen internen HTTP-Push-Kanal.
Bridge (Port 8090, nicht exposed, nur aria-net intern):
asyncio.start_server-basierter HTTP-Listener.
POST /internal/trigger-fired
body: {reply, trigger_name, type, events}
→ _handle_trigger_fired feuert Side-Channel-Events
(trigger_created/skill_created/location_tracking) erst,
dann _process_core_response(reply) — exakt der gleiche Pfad
wie normale Chat-Antworten (Chat-Bubble + TTS + chat_backup).
Brain background.py:
Nach agent.chat() in _fire wird agent.pop_events() ausgelesen
und zusammen mit dem Reply via urllib an aria-bridge:8090
gepostet (run_in_executor damit es den asyncio-Loop nicht
blockiert). Failures werden geloggt, der Trigger selbst bleibt
trotzdem als 'fired' markiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der claude-max-api-proxy ignoriert das OpenAI-tools-Feld komplett:
openai-to-cli.js wandelt nur messages in einen String, manager.js
spawnt 'claude --print' ohne Tools. Claude Code nutzt dann ihre
internen Tools (Bash, etc.) — bei 'Timer in 2min' macht sie ein
'sleep 120' intern und meldet 'erledigt' ohne dass wir je einen
trigger_timer-Call sehen.
Fix: zwei eigene Adapter-Files unter proxy-patches/ die zur
Container-Startzeit ueber die npm-Version kopiert werden:
openai-to-cli.js:
- tools-Feld wird als <system>-Block mit Tool-Schemas + klarer
Anweisung "Antworte <tool_call name=...>{json}</tool_call>"
in den Prompt injiziert
- role=tool messages werden als <tool_result>-Blocks eingewoben
→ Claude sieht den ganzen Tool-Use-Loop
- assistant tool_calls werden als <tool_call>-Bloecke
re-serialisiert, damit History-Roundtrips funktionieren
- Multimodal-content (Array von text-Parts) unveraendert
unterstuetzt (Original-sed-Patch eingebaut)
cli-to-openai.js:
- parsed <tool_call name="X">{json}</tool_call> aus result.result
- liefert OpenAI-konforme tool_calls + finish_reason=tool_calls
- Pre-Tool-Text bleibt im content erhalten
- normalizeModelName null-safe (Original-sed-Patch eingebaut)
docker-compose.yml: zwei sed-Patches die jetzt in den Files leben
sind raus, dafuer ein /proxy-patches:ro-Mount + zwei cp-Kommandos.
Smoke-Tests mit Node lokal alle gruen (single + multi tool_calls,
mit/ohne Pre-Text, History-Replay mit tool_result).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnose-Log um Trigger-Hang zu klaeren: warum legt ARIA keinen Timer
an, obwohl trigger_timer als Tool definiert ist? Wir loggen jetzt nach
jedem Proxy-Call:
- finish_reason
- alle Keys aus der message
- tool_calls-Anzahl + content-Laenge
- die rohe message (truncated 1500 chars)
So sehen wir ob der Proxy tool_calls leer liefert (Proxy schluckt
tools-Feld?), ob Claude ignoriert (Anthropic-Native-Format statt
OpenAI?), oder ob unser Dispatch falsch parsed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA wusste nicht wieviel Uhr es ist (kein Bash, kein Time-Tool, kein
Timestamp im System-Prompt) und konnte fires_at als ISO-UTC schlicht
nicht ausrechnen. Zwei Fixes:
1. prompts.py: build_time_section() injiziert UTC + lokale Zeit
(Europa/Berlin Sommer/Winter-Heuristik) als '## Aktuelle Zeit'-Block
oben in den System-Prompt. Hilft auch beim Einordnen von
Watcher-Conditions wie hour_of_day == 8.
2. agent.py trigger_timer-Tool: neuer Parameter `in_seconds` als
Alternative zu fires_at. Bei relativen Angaben ('in 2 Minuten')
rechnet jetzt der Server den absoluten Timestamp aus — keine
Rechnerei in der LLM noetig. fires_at bleibt fuer feste Termine.
required nur noch name + message.
Diagnostic-API (/triggers/timer) bleibt absolute-only, da der Browser
selbst datetime hat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA kann jetzt GPS-Watcher mit near() effektiv nutzen: die App liefert
kontinuierliche Position, Brain wertet sie in den Background-Triggers aus.
rvs/server.js
ALLOWED_TYPES: location_update (App→Bridge) + location_tracking (Brain→App).
bridge/aria_bridge.py
location_update Handler: persistiert {lat, lon} via _persist_location in
/shared/state/location.json — selber Pfad wie chat/audio-events, aber als
eigenes Event ohne Chat-Overhead.
aria-brain/agent.py
Neues Meta-Tool request_location_tracking(on, reason). Dispatcher fuegt
{type: "location_tracking", on, reason} zu _pending_events hinzu →
Bridge forwarded als RVS-Message zur App.
aria-brain/prompts.py
Trigger-Section bekam neuen Block "GPS-Watcher mit near()": ARIA wird
angewiesen request_location_tracking(on=true) zu rufen wenn sie einen
near()-Watcher anlegt, und wieder false beim Loeschen des letzten.
android/src/services/gpsTracking.ts (NEU)
Singleton-Service. start(reason) → Geolocation.watchPosition mit
distanceFilter 30m + interval 15s, sendet location_update an RVS.
stop(reason) → clearWatch. Persistiert Status in 'aria_gps_tracking',
restoreFromStorage() beim Settings-Mount. Permission-Request fuer
ACCESS_FINE_LOCATION + Toast-Benachrichtigung bei An/Aus.
android/src/screens/SettingsScreen.tsx
Neuer Switch im "Standort"-Block: "GPS-Tracking (kontinuierlich)" mit
Hinweis-Text. Subscribe auf gpsTrackingService.onChange damit Toggle
reflektiert wenn ARIA das per Tool umschaltet.
RVS-Handler: location_tracking → gpsTrackingService.start/stop mit
Reason aus Brain-Tool.
Ablauf Stefan→ARIA→Blitzer:
1. Stefan: "Warn mich vor Blitzern auf Route nach Rhauderfehn"
2. ARIA: skill_create("blitzer-warner") falls noch nicht da
3. ARIA: run_blitzer-warner → Liste {lat,lon,name}
4. ARIA: pro Eintrag trigger_watcher mit near(lat,lon,500)
5. ARIA: request_location_tracking(on=true, reason="Blitzer-Warner aktiv")
6. App: GPS-Tracking startet, sendet alle 15s location_update
7. Bridge: /shared/state/location.json wird aktuell gehalten
8. Brain-Background-Loop: alle 30s near()-Check pro Trigger
9. Bei Erfolg: ARIA spricht "Blitzer A31 km 12 in 500m"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA kann jetzt GPS-basierte Watcher-Trigger anlegen (Blitzer-Warner-Use-Case),
plus erweiterte Time-, System- und Activity-Variablen.
bridge/aria_bridge.py
_persist_state() schreibt atomar nach /shared/state/<key>.json.
Bei jedem chat- und audio-Event:
- location → /shared/state/location.json {lat, lon, ts_unix}
- last_user_ts → /shared/state/activity.json
Brain-Watcher lesen das fuer die GPS- und Activity-Variablen.
aria-brain/watcher.py — komplett ueberarbeitet
Neue Variablen-Sets:
GPS: current_lat, current_lon, location_age_sec (-1 = nie gesehen)
Zeit (+): minute_of_hour, day_of_month, month, year, is_weekend, unix_timestamp
System: ram_free_mb (MemAvailable), cpu_load_1min (loadavg)
Activity: last_user_message_ago_sec
Memory: pinned_count (zusaetzlich zu memory_count)
Neue Funktion fuer Conditions:
near(lat, lon, radius_m) Haversine-Distanz von current_lat/lon
zum Punkt. False wenn keine Position bekannt.
Parser-Erweiterung:
ast.Call jetzt erlaubt, ABER nur fuer direkte Funktionsnamen aus der
Whitelist (_ALLOWED_FUNCTIONS = {"near"}). Keine Attribute-Access,
keine Keywords, Args nur Constants/Names/UnaryOp.
Selbsttest blockt korrekt:
__import__("os")... → "Funktionsaufruf nur ueber direkten Namen"
memory_count.__class__ → "Verbotener Ausdruck: Attribute"
(lambda: 1)() → "Funktionsaufruf nur ueber direkten Namen"
aria-brain/main.py
/triggers/conditions liefert jetzt zusaetzlich {functions:[...]} mit
Signaturen + Beschreibungen. current-Snapshot filtert callable() raus
damit JSON serialisierbar bleibt.
aria-brain/prompts.py + agent.py
build_triggers_section bekommt condition_funcs als 4tes Argument und
listet die im System-Prompt unter "Verfuegbare Funktionen". Operatoren-
Hinweis ergaenzt mit Beispielen + Regeln (keine Variablen in Funktions-
Args, keine Schachtelung).
diagnostic/index.html
Trigger-Create-Modal: Variablen-Info-Block zeigt jetzt sowohl Variablen
(mit aktuellen Werten) als auch Funktionen (Signatur + Beschreibung).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA hatte bisher nur ein "User fragt → Brain antwortet"-Modell. Neu:
Trigger laufen passiv im Hintergrund (kein LLM-Call) und wecken ARIA
nur dann auf wenn ein Event tatsaechlich passiert.
Drei Typen, zwei aktuell implementiert:
timer — einmalig zu festem ISO-Timestamp ("erinner mich in 10min")
watcher — Polling alle N Sek einer Condition, feuert bei True mit Throttle
(z.B. "disk_free_gb < 5", max 1x/h)
cron — Platzhalter fuer spaeter
aria-brain/triggers.py
CRUD auf /data/triggers/<name>.json + /data/triggers/logs/<name>.jsonl.
create_timer, create_watcher, mark_fired, list_logs, etc.
aria-brain/watcher.py
Built-in Condition-Variablen: disk_free_gb, disk_free_pct, uptime_sec,
hour_of_day, day_of_week, rvs_connected, memory_count.
Sicherer Condition-Parser via ast — Whitelist auf Vergleich + BoolOp +
Name + Const. Kein eval, kein exec, keine Builtins.
aria-brain/background.py
Async Loop laeuft alle 30s, sammelt einmalig Variables, geht durch
Trigger-Liste, _should_fire-Check (Timer: fires_at vergangen / Watcher:
check_interval + throttle respektiert + condition true). Fire ruft
agent.chat(prompt, source="trigger") — ARIA bekommt das wie eine
Push-Nachricht und antwortet via Bridge → RVS → App.
aria-brain/main.py
/triggers/list, /{name}, /{name}/logs, /timer, /watcher, PATCH, DELETE,
/triggers/conditions (Variablen + aktuelle Werte). Lifespan-Handler
startet den Background-Loop beim Container-Start, stoppt beim Shutdown.
aria-brain/agent.py
Meta-Tools fuer ARIA: trigger_timer, trigger_watcher, trigger_cancel,
trigger_list. ARIA legt Trigger via Tool-Call selbst an wenn Stefan das
wuenscht. Side-Channel-Event 'trigger_created' wird in chat-Response
mitgeschickt damit App + Diagnostic eine Bubble zeigen.
aria-brain/prompts.py
Neue System-Prompt-Section: Liste aktiver Triggers + verfuegbare
Condition-Variablen mit aktuellen Werten + Operatoren-Erklaerung.
ARIA weiss damit immer was es schon gibt und welche Vars sie nutzen kann.
bridge/aria_bridge.py + rvs/server.js
trigger_created als neuer RVS-Message-Type, Bridge forwarded das aus
data.events analog zu skill_created.
diagnostic/index.html
Neuer Top-Tab "Trigger". Liste mit Type-Badges (⏱ TIMER / 👁 WATCHER),
Status, Fire-Count, last_fired. Aktivieren/Deaktivieren + Löschen pro
Trigger. "+ Neu"-Modal mit Type-Dropdown, Timer-Minuten oder
Watcher-Condition + Vars-Anzeige + Throttle. Info-Modal-Eintrag mit
Erklaerung. Live-Bubble im Chat wenn ARIA selbst einen anlegt.
android/src/screens/ChatScreen.tsx
trigger_created RVS-Handler → eigene Bubble (gelber Border, "⏰ ARIA
hat einen Trigger angelegt", Type/Detail/Message/Zeit). ChatMessage
bekam triggerCreated-Feld. Lokal-only-Schutz beim Server-Sync analog
zu skill_created.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Diagnostic-Chat zeigt nur ARIA-Dateien (file_from_aria), Text-
Antworten kamen nicht an. STT-Eintraege + User-Messages waren sichtbar.
Ursache: Im rvs_chat-Handler stand
if (sender === 'aria') return;
Die alte Begruendung war "ARIA-Antworten kommen schon via Gateway (chat:final)".
Das galt zu OpenClaw-Zeit, wo Diagnostic eine direkte WS zum aria-core hatte.
Gateway ist seit dem Abriss weg, ARIA-Antworten kommen jetzt ausschliesslich
via RVS → der return blockte sie still.
Fix: chatType + label je nach sender:
- aria → received-Bubble, Label "ARIA"
- stt → sent-Bubble, Label "🎤 Spracheingabe" (wie vorher)
- sonst → sent-Bubble, Label "via RVS (<sender>)"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1: "ARIA denkt..." in der App bleibt stehen
_process_core_response setzte am Ende kein idle — die alten Aufrufe waren
in der OpenClaw-WS-Loop, in der Brain-HTTP-Variante fehlten sie. Plus
send_to_core schickte agent_activity direkt via _send_to_rvs ohne den
_last_activity_state-Cache zu pflegen → _emit_activity("idle") wurde
spaeter dedupliziert.
Fix:
- _emit_activity statt direktem _send_to_rvs fuer thinking
- _emit_activity("idle") am Ende von _process_core_response
- _last_chat_final_at bewusst NICHT setzen — die 3s-Cooldown war fuer
trailing OpenClaw-Events, wuerde bei Voice die naechste thinking-Welle
unterdruecken
Bug 2: App Chat-Suche scrollt nicht zur Stelle
scrollToIndex wurde zu fruh aufgerufen (Layout noch nicht fertig) und
viewPosition: 0.4 in inverted-FlatList war ungenau.
Fix:
- requestAnimationFrame um den Scroll-Aufruf
- viewPosition: 0.5 (mittig)
- onScrollToIndexFailed: erst grob scrollen via averageItemLength,
dann nach 250ms praeziser nachfassen
Bug 3: Voice-Bubble bekommt STT-Text erst mit ARIA-Antwort
_process_app_audio rief erst send_to_core (blockt synchron auf Brain,
kann 300s dauern), DANN STT-Broadcast. App sah den eigenen Text erst
wenn ARIA fertig war.
Fix: Reihenfolge getauscht — STT-Broadcast zuerst, dann send_to_core.
Voice-Bubble bekommt jetzt den erkannten Text sofort.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App-Chat-Sync ist seit Commit 3497aa2 "Server is Source of Truth" — bei
jedem Reconnect KOMPLETTER Server-Stand statt incremental. Doku angepasst:
- App leert sich wenn Server leer ist (z.B. nach "Konversation zuruecksetzen")
- Lokal-only Bubbles bleiben erhalten (Skill-Notifications, Voice ohne STT)
- Bridge schreibt chat_backup.jsonl pro Turn — als Server-Backing-Store
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Diagnostic-Server hat leere Chat-History (z.B. nach "Konversation
zuruecksetzen" oder Wipe), App zeigt aber weiterhin ihren alten lokalen
Stand. Wer das Wipe-Event verpasst hat (App offline), bleibt veraltet.
Ursache: App schickte beim Reconnect chat_history_request {since: lastSync}
und ignorierte leere Antworten. Wenn der Server ueberhaupt nichts mehr hat
liefert er korrekt [] zurueck — App behielt aber lokalen State.
Fix:
- App schickt jetzt {since: 0, limit: 200} → KOMPLETTER Server-Stand
- Handler ersetzt die persistierte Chat-History mit dem Server-Stand
(statt zu mergen)
- Lokal-only Bubbles bleiben erhalten:
* Skill-Created-Notifications (skillCreated gesetzt)
* Laufende Sprachnachrichten ohne STT-Result (audioRequestId gesetzt
und text leer/Placeholder)
- Wenn Server leer: lastSync ebenfalls geloescht (sauberer Restart-State)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README.md
- Phase B erledigt-Liste um zwei Punkte erweitert:
Token/Call-Metrics + Multi-Select-Datei-Manager
- Diagnostic-Tabs in der Installations-Sektion aktualisiert
(5 Tabs statt 4 — Skills als eigener Tab)
- Diagnostic-Sektion weiter unten: Tabs-Liste komplett ueberholt,
Token-Metrics als eigener Bullet in "Was zusaetzlich drin steckt"
issue.md
- "Token/Call-Metrics" als erledigter Punkt in "Diagnostic/App-Features"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan hat den Max 5x Plan (~\$90-100/Monat), ungefaehres Limit 225 Calls pro
5h-Fenster fuer Sonnet. Damit nicht in eine Tool-Loop-Schleife laufen ohne
es zu merken: kleine Metrics-Pipeline, sichtbar in der Diagnostic.
aria-brain/metrics.py
Append-only JSONL Logger unter /data/metrics.jsonl. Pro Claude-Call eine
Zeile {ts, model, in, out} mit Token-Schaetzung (chars/4, Anthropic-
Heuristik). aggregate(window) zaehlt die letzten N Sekunden.
Auto-Rotate bei 50k Zeilen → 25k behalten (~70 KB/Monat bei 1k Calls/Tag,
Cap also weit oben).
aria-brain/proxy_client.py
chat_full() ruft am Ende metrics.log_call(model, messages_in, reply).
Failed/exception-Pfade loggen nicht (sonst false positives).
aria-brain/main.py
GET /metrics/calls → {h1, h5, h24, d30}, jedes Window mit calls,
tokens_in, tokens_out, by_model.
diagnostic/index.html
Neue Card "Token / Calls" im Gehirn-Tab. Plan-Dropdown
(Pro / Max 5x / Max 20x / Custom), localStorage-persistiert. 4 Metric-
Zellen fuer 1h/5h/24h/30d mit Calls + Tokens. Progress-Bar oben zeigt
5h-Counter gegen Plan-Limit. Warn-Klassen: gelb bei 80%, rot bei 90%.
Auto-Refresh alle 30s wenn Gehirn-Tab offen, plus bei Tab-Wechsel.
Info-Modal erklaert die Limits + dass HTTP-Call != User-Frage (Tool-Use
kann pro Frage bis zu 8 Calls verursachen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App Chat-Suche umgebaut von Filter zu Highlight+Navigation
Vorher: searchQuery filtert die FlatList, zeigt nur Treffer.
Jetzt: Suche filtert NICHT mehr, alle Nachrichten bleiben sichtbar.
Treffer wird gelb (FFD60A) umrandet, FlatList scrollt automatisch
dorthin.
- Suchleiste: Input + Counter "N/M" + ▲ + ▼ + ✕
- ▲ / ▼ navigieren chronologisch durch alle Matches (zyklisch)
- searchMatchIds via useMemo, searchIndex separates State
- scrollToIndex mit viewPosition: 0.4 (Treffer landet im oberen Drittel)
- onScrollToIndexFailed Fallback nach 200ms (Layout noch nicht fertig)
Diagnostic Sprachausgabe-Layout
Export/Import-Buttons wandern aus dem Section-Header in den Details-Block
neben "Anwenden" (Stefan's Wunsch). Header zeigt nur noch den Titel.
File-Input bleibt versteckt im Section-Top, wird vom neuen Button-Block
unten ueber click() getriggert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic + App bekommen Mehrfach-Auswahl im Datei-Manager. Mehr als eine
Datei ausgewaehlt → Download als ZIP. Genau eine ausgewaehlt → einzeln.
Bulk-Delete loescht alle markierten in einem Rutsch.
diagnostic/Dockerfile
zip via apk add — fuer das ZIP-Streaming im /api/files-download-zip.
diagnostic/server.js
POST /api/files-download-zip Body: {paths:[...]} → spawnt 'zip -j -q -',
Pipes stdout in Response. Whitelist auf
/shared/uploads/.
POST /api/files-delete-batch Body: {paths:[...]} → loescht alle, broadcastet
file_deleted pro Pfad an Browser + RVS.
diagnostic/index.html
filesSelected Set + Checkbox-UI pro Datei + "Alle markieren". Wenn 2+
ausgewaehlt: POST an /api/files-download-zip, Browser saugt das als
Blob runter. Bei 1: normaler Single-Download.
bridge/aria_bridge.py
file_delete_batch_request → ruft Diagnostic /api/files-delete-batch,
antwortet mit file_delete_batch_response.
file_zip_request {paths,reqId} → ruft Diagnostic /api/files-download-zip,
base64-kodiert, capped auf 30 MB,
sendet file_zip_response.
rvs/server.js
ALLOWED_TYPES: file_delete_batch_request/response, file_zip_request/response.
android/src/screens/SettingsScreen.tsx
fileManagerSelected Set + Checkbox-UI pro Datei + "Alle markieren"-Zeile
oben. Bulk-Bar oben mit count, "⬇ ZIP" / "⬇ Download" (je nach Anzahl),
und "🗑 Löschen". ZIP-Response landet base64 → RNFS in Downloads-Folder
(aria-files-<timestamp>.zip), Toast mit Pfad.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei zusammenhaengende Bugs:
1. App aktualisierte nicht wenn die Diagnostic "Konversation komplett
zuruecksetzen" gedrueckt hat — die App hatte den lokalen Stand weiter
2. Nachrichten die kamen waehrend die App offline/geschlossen war,
wurden nicht nachgeladen
Loesung: chat_backup.jsonl wird wieder geschrieben (Bridge statt Diagnostic,
weil OpenClaw-Code-Pfad tot ist) und dient als Server-Truth fuer App+Diagnostic.
bridge/aria_bridge.py
_append_chat_backup() schreibt jeden Turn (User + ARIA) als JSONL-Zeile
in /shared/config/chat_backup.jsonl. Trigger: send_to_core (User) +
_process_core_response (Assistant, inkl. file-Attachments).
_read_chat_backup_since(since_ms, limit) liest die Datei, filtert auf
ts > since_ms, gibt max limit neueste zurueck. Honoriert file_deleted-Marker.
Neuer RVS-Handler chat_history_request {since, limit?} → antwortet mit
chat_history_response {messages: [...], since}.
diagnostic/server.js
/api/chat-history-clear broadcastet jetzt zusaetzlich chat_cleared via
RVS (sendToRVS_raw), damit App ihre lokale Liste auch leert. Vorher nur
Browser-Clients via broadcast() — App war aussen vor.
rvs/server.js
ALLOWED_TYPES um chat_history_request, chat_history_response, chat_cleared.
android/src/screens/ChatScreen.tsx
- Bei (re)connect: AsyncStorage 'aria_chat_last_sync' lesen → send
chat_history_request {since}
- Handler chat_history_response: incoming → ChatMessage[] mappen,
Attachments aus 'files'-Array rekonstruieren, mergen (Dedup via timestamp),
lastSync hochziehen
- Handler chat_cleared: setMessages([]) + AsyncStorage 'chat_messages' +
'last_sync' weg
- Bei jeder eingehenden chat-Message: 'aria_chat_last_sync' updaten damit
Reconnect nicht doppelt nachzieht
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Main-Tab "ARIA BRAIN"-Card blieb auf "Lade..." haengen. Klick auf
"Status pruefen" tat scheinbar nichts.
Ursache: loadBrainStatus() suchte nur brain-status (Gehirn-Tab). Die Card
im Main-Tab hat aber andere IDs (brain-dot + brain-status-short + brain-error),
die wurden nirgends mehr befuellt seit updateState() das nicht mehr macht.
Fix: loadBrainStatus update jetzt BEIDE Anzeigen synchron — kompakte Main-Card
mit Dot/Status/Error UND die ausfuehrliche Gehirn-Tab-Zeile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reusable Info-Modal-System: kleines (ℹ)-Button neben Ueberschriften, beim
Klick oeffnet ein Modal mit ausfuehrlicher Erklaerung. Fuer die 4 wichtigsten
Brain-Konzepte sind die Texte vor-definiert (INFO_TEXTS dict).
- Gehirn — Status online/offline, N Memories, Qdrant-Endpoint
- Konversation Rolling Window 50, Schwelle 60, Destillat-Logik,
Hinweis warum chat_backup ≠ conversation.jsonl
- Memories Hot vs. Cold, alle 8 Typen erklaert, semantische Suche
- Bootstrap Die drei Wege (Migration / Snapshot / Komplett-Gehirn)
Plus inline-ℹ-Button neben der "Konversation: N Turns"-Zeile in der
Status-Card, damit man dort wo's relevant ist sofort die Erklaerung findet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei zusammenhaengende Fixes — alle aus der OpenClaw-Umstellung uebrig.
Brain-Card im Main-Tab
- updateState() schrieb "aria-core entfernt — Brain-Loop in Arbeit" in
die Card, weil state.gateway noch den alten OpenClaw-Status-Text
transportiert hat. Brain ist online, sah aber auf der Diagnostic
aus als waere alles tot.
- Fix: updateState laesst die Brain-Card jetzt in Ruhe. Daten kommen
exklusiv von loadBrainStatus() (fetch /api/brain/health).
- WS-onopen ruft jetzt loadBrainStatus(), plus 15s-Interval fuer
Live-Refresh.
Runtime-Konfiguration
- Hinweis-Text erwaehnte noch aria.env + "Bridge-Container neu starten".
aria.env gibt's nicht mehr, und Brain liest die Config auch.
- Neu: ".env / Bridge UND Brain lesen sie beim Start — nach Aenderung
den jeweiligen Container neu starten (Reparatur-Section oben)."
Konversation-Reset
- Vorher zwei verschiedene Konversations-Stores ohne klare UI:
chat_backup.jsonl (Diagnostic-Anzeige) und conversation.jsonl
(Brain Rolling Window). "Konversation leeren" leerte nur Brain,
die UI zeigte alle Turns weiter → verwirrend.
- Neu: ein Button "🧹 Konversation komplett zurücksetzen" leert
beides parallel (Promise.all auf /api/brain/conversation/reset
+ /api/chat-history-clear) plus die lokale Chat-View.
- Erklaerungstext darunter macht klar was passiert + was bleibt
(destillierte Facts + Memories in der Vector-DB).
- Neuer Endpoint /api/chat-history-clear loescht chat_backup.jsonl
und broadcastet leere chat_history an alle Browser-Clients.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die Diagnostic-Seite lud nicht mehr richtig: bei jedem state-Update vom
Server crashte updateState() auf null.className weil 'gw-dot' nicht mehr
existiert (Gateway-Card wurde durch Brain-Card ersetzt). Mehrere weitere
Stellen waren ebenfalls auf nicht mehr existierende Elemente geleitet.
Bereinigt:
- updateState() nutzt jetzt brain-dot/short/error (null-safe)
- openCoreTerminal entfernt (aria-core ist raus)
- closeTermModal null-safe fuer btn-core-term
- 'core_auth' WS-Event entfernt
- 'session_restarted' WS-Event entfernt (perms-status-Element war auch raus)
- 'openclaw_config' WS-Event entfernt
- rc-compact-after read/write aus loadRuntimeConfig/saveRuntimeConfig raus
(Compact-After-Messages-Setting wurde mit aria-core entfernt)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
War kaputt nach OpenClaw-Abriss: handleGetModel/handleSetModel haben gegen
aria-core (dockerExec + node-script in den Container) gearbeitet, der gibt's
nicht mehr.
diagnostic/server.js
- handleGetModel/handleSetModel lesen/schreiben jetzt brainModel in
/shared/config/runtime.json
- RUNTIME_CONFIG_FIELDS um "brainModel" erweitert
- Tote Variante (findSettingsFile + base64-node-script) komplett raus
aria-brain/proxy_client.py
- Liest brainModel aus runtime.json beim Container-Start
- Fallback: BRAIN_MODEL env → "claude-sonnet-4" Default
- Bei Aenderung in Diagnostic: aria-brain restarten damit's greift
(Hinweis steht in der UI)
diagnostic/index.html
- Section "Model" → "Sprachmodell (Brain)"
- Hinweis-Block mit Default-Erklaerung und Restart-Hinweis
- Modelle: claude-sonnet-4 (default), claude-opus-4, claude-haiku-4-5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tools/export-jsonl-to-md.js
CLI-Konverter fuer OpenClaw-Session-JSONL zu Markdown. Mit OpenClaw raus.
tools/-Ordner ist jetzt leer und auch entfernt.
aria-data/docs/tool-permissions.md
Geschichts-Dokument zum OpenClaw-Tool-Permission-Bug. Nicht mehr relevant —
Brain hat eigenes Tool-Use-System (Skills + Meta-Tools), keine granulare
Permission-Maske. aria-data/docs/ Ordner war leer, mit raus.
diagnostic/index.html
- "Tool-Berechtigungen"-Section aus den Einstellungen raus. War nur ein
disabled Info-Toggle aus OpenClaw-Zeit ("Claude darf alle Tools").
Brain weiss zur Laufzeit selbst welche Tools es hat (META_TOOLS +
pro aktivem Skill ein dynamisches run_<skill>).
- .perm-grid / .perm-item / .perm-info CSS-Klassen entfernt (sonst nirgends
referenziert).
issue.md
- tools/export-jsonl-to-md.js-Eintrag mit "(mit OpenClaw raus)" markiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aria-setup.sh
Von 142 Zeilen auf 50 zurechtgestutzt. Alle OpenClaw-Schritte raus
(openclaw.json, exec-approvals, Permissions fuer .openclaw, doctor).
Es bleibt nur der SSH-Key-Setup fuer aria-wohnung — Brain + Proxy
teilen sich denselben Key via Bind-Mount aria-data/ssh/.
init.sh
Legt jetzt aria-data/brain/data + aria-data/brain/qdrant an damit die
Bind-Mounts beim ersten docker compose up nicht ins Leere zeigen.
OpenClaw Tutorial _ MI.pdf
Geloescht — OpenClaw ist abgerissen, das Tutorial gehoert nicht
mehr ins Repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei neue RVS-Handler in der F5-TTS-Bridge:
xtts_export_voice
Packt <name>.wav + <name>.txt aus VOICES_DIR als tar.gz in-memory,
sendet base64-codiert als xtts_voice_exported zurueck. Diagnostic
baut daraus den Browser-Download.
xtts_import_voice
Empfaengt base64 tar.gz mit <name>.wav (+ optional <name>.txt),
legt sie in VOICES_DIR ab (sanitized name, Path-Traversal-Schutz),
sendet xtts_voice_imported zurueck. Anschliessend handle_list_voices
damit App/Diagnostic die neue Stimme sofort sehen.
So koennen Stimmen zwischen mehreren Gameboxen wandern, ohne die
WAV-Files manuell rumzukopieren.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei groessere Aenderungen in der Android-App.
Datei-Manager (Settings → Dateien)
- Neuer Eintrag im Settings-Menue → Modal mit Liste
- Suche + Filter (Alle / Von ARIA / Vom User)
- Per Eintrag: ARIA/USER-Badge, Groesse, Datum, Loeschen-Button
- file_list_request via RVS → Bridge → Diagnostic-HTTP → response
- file_delete_request loescht serverseitig, file_deleted-Event
aktualisiert ALLE Chat-Bubbles (Attachment.deleted = true mit
Strikethrough-Name + 🗑️-Icon)
Skill-Created-Bubble
- Neuer ChatMessage.skillCreated Typ — eigenes Render mit gelbem
Border, Skill-Name, Beschreibung, Execution-Mode, Active-Status
- Falls Skill-Setup fehlschlug: ⚠ Setup-Fehler-Zeile direkt in der Bubble
- Stefan sieht in der Chat-History immer wenn ARIA selbst einen
Skill angelegt hat — Transparenz statt schweigend im Hintergrund
Pinch-Zoom rewriten (ZoomableImage.tsx)
- Multi-Touch-Race-Bugs in der alten Variante geloest:
* Touch-Count jetzt aus e.nativeEvent.touches.length statt
gestureState.numberActiveTouches (war nicht zuverlaessig)
* Re-Snapshot bei JEDEM Finger-Wechsel (1↔2) → keine Spruenge mehr
* Doppel-Tap via onPanResponderRelease + Bewegungs-Cap
* pointerEvents="none" auf Image-Wrapper → Touches gehen garantiert
an PanResponder-View
* collapsable={false} verhindert Android-View-Flattening
- 2-Finger-Pinch 1x..5x, simultaner Pan via Focal,
1-Finger-Pan nur wenn gezoomt (>1.02x), Doppel-Tap toggelt 1x↔2.5x
App SettingsScreen Repair-Section
- aria-core-spezifische Buttons raus: 🔧 Reparieren, 🚨 ARIA hart neu,
🧹 Konversation komprimieren (OpenClaw ist abgerissen)
- Stattdessen generischer container_restart fuer aria-bridge/brain/qdrant
- Repair-Buttons aus der "ARIA denkt..."-Bubble entfernt (nur Abbrechen)
ChatScreen
- skill_created und file_deleted Handler im RVS-Message-Switch
- file_list_response (Modal-State liegt in SettingsScreen)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>