From 32026d4264b65db7c9dee905bb0e6fe818cc2463 Mon Sep 17 00:00:00 2001 From: Jonny Date: Sun, 13 Sep 2026 11:46:21 +0200 Subject: [PATCH] docs: add design spec for cross-chat memory (facts + summaries) Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019iZMEPk2Kt1UC96Lo9bC5w --- .../2026-09-13-cross-chat-memory-design.md | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-13-cross-chat-memory-design.md diff --git a/docs/superpowers/specs/2026-09-13-cross-chat-memory-design.md b/docs/superpowers/specs/2026-09-13-cross-chat-memory-design.md new file mode 100644 index 0000000..e47a0da --- /dev/null +++ b/docs/superpowers/specs/2026-09-13-cross-chat-memory-design.md @@ -0,0 +1,144 @@ +# JARVIS Chatuebergreifendes Gedaechtnis - Design + +**Datum:** 2026-09-13 +**Status:** Approved, bereit fuer Implementierungsplan + +## Kontext + +Der Chat kennt bisher nur den Verlauf der eigenen `conversation_id` +(`main.py:754`, `get_messages(conversation_id)`). Startet man einen neuen +Chat (z.B. ueber den neuen "Neuer Chat"-Button), gehen alle Infos aus +frueheren Unterhaltungen verloren - JARVIS "vergisst" Namen, Vorlieben und +Kontext aus vorherigen Gespraechen. Ziel dieser Phase: ein Gedaechtnis, das +ueber einzelne Conversations hinweg besteht, kombiniert aus zwei Quellen: + +- **Explizite Fakten** - Dinge, die der Nutzer ausdruecklich festhalten + laesst ("merke dir...", "vergiss, dass...") +- **Automatische Zusammenfassungen** - jede Conversation wird laufend + zusammengefasst und per Aehnlichkeitssuche wiedergefunden, wenn sie zum + aktuellen Thema passt + +Die vorhandene Knowledge-Base-Infrastruktur (Phase 3a: `pgvector`, Ollama +`nomic-embed-text` Embeddings) wird fuer die Zusammenfassungen wiederverwendet +statt eine zweite Vector-Search-Loesung einzufuehren. + +## Datenmodell (`migrations/006_memory.sql`) + +```sql +CREATE TABLE memory_facts ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id), + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE conversation_summaries ( + conversation_id INTEGER PRIMARY KEY REFERENCES conversations(id), + summary TEXT NOT NULL, + embedding vector(768) NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +Kein `ivfflat`/`hnsw`-Index vorerst - gleiche Begruendung wie bei +`document_chunks` (Phase 3a): bei wenig Zeilen liefert der Index 0 Treffer, +Sequential Scan ist bei der zu erwartenden Groessenordnung (Dutzende bis +niedrige Hunderte Conversations) schnell genug. Nachtrag folgt, sobald genug +Daten vorhanden sind (gleicher offene Punkt wie bei den Dokumenten). + +## Fakten erfassen: zwei neue Claude-Tools + +Analog zu Kalender/E-Mail in `run_chat_completion()`: + +- `remember_fact(fact: str)` - legt sofort einen Eintrag in `memory_facts` + an, **keine Rueckfrage noetig** (wie `create_calendar_event`) - ein + gespeicherter Fakt ist folgenlos und leicht wieder zu entfernen. +- `forget_fact(query: str)` - sucht per `ILIKE '%query%'` nach passenden + Fakten. Bei einem Treffer: loescht ihn erst **nach Bestaetigung im Chat** + (wie `delete_calendar_event`/`send_email`). Bei mehreren Treffern: gibt sie + Claude als Liste zurueck, damit es im Chat nachfragt, welcher gemeint ist, + statt blind zu loeschen. Bei keinem Treffer: klare Fehlermeldung an Claude + ("kein passender Fakt gefunden"). + +Der System-Prompt bekommt eine kurze Ergaenzung, die Claude anweist, diese +Tools zu nutzen, wenn der Nutzer explizit um Merken/Vergessen bittet - +analog zu den bestehenden Anweisungen fuer Kalender-Loeschen. + +## Automatische Zusammenfassung + +Nach dem Speichern der Assistant-Antwort in `POST /api/v1/chat` +(nach `save_message(..., "assistant", ...)`) wird per `asyncio.create_task` +ein Hintergrund-Job gestartet, der die Chat-Antwort **nicht** blockiert: + +1. Vollstaendigen Verlauf der Conversation laden (`get_messages`) +2. Kurzer zusaetzlicher Claude-Call (kleines `max_tokens`-Limit): "Fasse + dieses Gespraech in 2-3 Saetzen zusammen" +3. Zusammenfassung mit der vorhandenen Ollama-Embedding-Funktion (aus Phase + 3a, bereits fuer `document_chunks` genutzt) embedden +4. `INSERT INTO conversation_summaries (...) VALUES (...) ON CONFLICT + (conversation_id) DO UPDATE SET summary = ..., embedding = ..., + updated_at = CURRENT_TIMESTAMP` + +Laeuft nach **jeder** Turn-Antwort (kein Batching/Intervall-Logik noetig - +bei diesem Nutzungsvolumen vernachlaessigbare Zusatzkosten, ein Client +wartet nicht darauf). Ergebnis ist immer die Zusammenfassung des gesamten +bisherigen Gespraechs, nicht nur der letzten Nachricht. + +## Einbindung in den Chat (`run_chat_completion`) + +Vor dem eigentlichen Claude-Call wird der System-Prompt um zwei Abschnitte +ergaenzt: + +1. **"Bekannte Fakten ueber den Nutzer"** - alle Zeilen aus `memory_facts` + werden vollstaendig geladen und als Liste angehaengt (kleine Tabelle, + kein Retrieval noetig - Vollstaendigkeit hat hier Vorrang vor + Relevanz-Filterung, damit kein gemerkter Fakt durch eine unguenstige + Formulierung der aktuellen Frage durchrutscht). +2. **"Relevante fruehere Gespraeche"** - die aktuelle Nutzernachricht wird + embedded (gleiche Ollama-Funktion), die Top-3 aehnlichsten Zeilen aus + `conversation_summaries` per Cosinus-Distanz geholt (`<=>`-Operator wie + bei der Dokumentensuche), **exklusive der aktuellen Conversation** (die + steht ja bereits vollstaendig in der History). + +Beide Abschnitte werden nur angehaengt, wenn sie nicht leer sind. + +## Fehlerbehandlung + +- Ollama nicht erreichbar waehrend der Retrieval-Suche (Embedding der + aktuellen Nachricht schlaegt fehl): Abschnitt "Relevante fruehere + Gespraeche" wird einfach weggelassen, der Chat laeuft normal weiter + (gleiches Muster wie bestehende `check_ollama()`-Fehlerbehandlung). +- Hintergrund-Summary-Task wirft eine Exception (Claude-Call oder Embedding + schlaegt fehl): wird geloggt, hat **keinen** Einfluss auf die bereits an + den Nutzer gesendete Chat-Antwort (Task laeuft komplett nach dem Response + Return). +- `forget_fact` ohne Treffer: kein Fehler, sondern eine normale + Tool-Antwort an Claude, damit es im Chat mitteilt, dass nichts gefunden + wurde. + +## Testing + +- Unit-Tests (`tests/`, gemockte DB wie bei bestehenden Tool-Tests) fuer: + - `remember_fact`/`forget_fact` (Insert, ILIKE-Suche, Mehrfachtreffer, + kein Treffer) + - Prompt-Zusammensetzung: Facts-Abschnitt korrekt formatiert, leer wenn + keine Fakten vorhanden; Retrieval-Abschnitt korrekt formatiert, leer + wenn Embedding fehlschlaegt +- Manueller End-to-End-Test nach Deployment: in Chat A "merke dir, dass + mein Hund Bruno heisst" sagen, ueber den "Neuer Chat"-Button einen neuen + Chat B starten, dort nach dem Hundenamen fragen (Fakt-Pfad) und ueber ein + thematisch verwandtes, aber nicht identisches Thema aus Chat A fragen + (Zusammenfassungs-Retrieval-Pfad); anschliessend "vergiss, dass mein Hund + Bruno heisst" in einem dritten Chat testen. + +## Out of Scope (bewusst nicht Teil dieser Phase) + +- Automatisches Bereinigen alter/widerspruechlicher Fakten (z.B. wenn sich + eine Info spaeter aendert, muss der Nutzer explizit `forget_fact` + + `remember_fact` nutzen). +- `ivfflat`/`hnsw`-Index fuer `conversation_summaries` (Nachtrag, sobald + Datenmenge es rechtfertigt - siehe Phase 3a Follow-up). +- Mehrere Nutzer/Rollen (weiterhin ein einzelner `DEFAULT_USER_ID`, siehe + bestehende "Security"-Einschraenkung in der Handoff-Doku). +- UI zum Anzeigen/Verwalten der gespeicherten Fakten im Dashboard (aktuell + nur ueber Chat-Kommandos verwaltbar).