feat(brain): memory_save Tool — ARIA schreibt selber in die Qdrant-DB

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>
This commit is contained in:
2026-05-13 01:27:20 +02:00
parent 9dd95709b9
commit 5f96ace469
6 changed files with 189 additions and 0 deletions
+17
View File
@@ -1376,6 +1376,17 @@ class ARIABridge:
})
logger.info("[brain] location_tracking Request: on=%s (%s)",
event.get("on"), event.get("reason", ""))
elif etype == "memory_saved":
# ARIA hat selber etwas in die Vector-DB gespeichert.
# Eigene Bubble in App + Diagnostic (gelb wie skill/trigger).
await self._send_to_rvs({
"type": "memory_saved",
"payload": event.get("memory", {}),
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})
logger.info("[brain] ARIA hat eine Memory angelegt: %s (type=%s)",
event.get("memory", {}).get("title"),
event.get("memory", {}).get("type"))
# _process_core_response uebernimmt alles weitere:
# File-Marker extrahieren + broadcasten, NO_REPLY-Check, Chat-
@@ -2635,6 +2646,12 @@ class ARIABridge:
},
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})
elif etype == "memory_saved":
await self._send_to_rvs({
"type": "memory_saved",
"payload": event.get("memory", {}),
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})
except Exception:
logger.exception("[trigger-fire] Side-Channel-Event %s fehlgeschlagen", etype)