Der Plan: Supermemory – aber bei mir zuhause
Ich verfolge seit einer Weile ein Projekt namens Supermemory. Die Idee dahinter ist so simpel wie genial: KI-Assistenten kriegen ein dauerhaftes Gedächtnis das sich selbst aktualisiert. Fakten werden extrahiert, veraltete Infos werden überschrieben, und das System lernt sich quasi von selbst.
19 Dollar im Monat kostet das in der Cloud. Ihr kennt mich – meine Daten bleiben bei mir.
Also: selbst bauen. Ryan – mein lokaler KI-Agent auf dem VPS – hatte zwar schon PostgreSQL mit pgvector als Gedächtnis (Phase 3 aus dem letzten Post), aber das war reaktiv. Ich musste ihm immer noch sagen was er tun soll. Das Ziel für heute Nacht: Ryan soll selbstständig lernen, recherchieren und nachts seinen eigenen Wissensstand aktualisieren. Vollautomatisch. Während ich schlafe.
Spoiler: Es hat die ganze Nacht gedauert. Und den halben nächsten Tag.
Der Stack
Für alle die direkt wissen wollen womit ich arbeite:
- PostgreSQL + pgvector – die Vektordatenbank mit 251 Fakten
- n8n – Automatisierung, läuft auf dem VPS
- Gemini 2.5 Flash – Fakten-Extraktion und Profil-Generierung
- Ollama mit bge-m3 – lokale Embeddings (1024 Dimensionen)
- SearXNG – lokale Web-Suche, kein Google
- Scrapling – Web Scraping wenn SearXNG-Snippets nicht reichen
- Telegram – Benachrichtigungen und Interaktion mit Ryan
Phase 1: Der Fakten-Extraktor
Das Herzstück des Systems: ein n8n Workflow der täglich um 03:00 Uhr läuft, die letzten 24 Stunden Chat-Nachrichten holt, durch Gemini jagt und daraus strukturierte Fakten extrahiert.
Klingt einfach. War es nicht.
Fail 1: Das crypto-Modul
Der erste Code-Node lief gegen eine Wand:
“Module ‘crypto’ is disallowed [line 9]”
n8n 2.9.4 blockiert Node.js Built-ins im Code-Node. Ich wollte SHA-256 UUIDs für Dedup-Schutz generieren. Lösung: eigene Hash-Funktion in purem JavaScript, kein require(). Hat funktioniert – aber erstmal 20 Minuten Debugging.
Fail 2: Die IF-Node-Katastrophe
Der Workflow hatte eine IF-Node: “Wenn Fakten gefunden → Split In Batches, sonst → Telegram Keine Fakten”. Lief der erste echte Test durch?
72x “Ryan Fakten-Extraktor: Keine neuen Fakten extrahiert.”
72 Telegram-Nachrichten. In einer Minute. Mein Handy hat vibriert wie ein Massagegerät.
Das Problem: Die IF-Bedingung war falsch verdrahtet. true und false Ausgänge vertauscht. Zwei Kabel in der UI tauschen, fertig. Aber man muss erstmal draufkommen wenn man um Mitternacht auf einen Bildschirm starrt.
Fail 3: “there is no parameter $1”
Query Parameters in n8n sind eine eigene Wissenschaft. Das Array-Format ={{ [$json.content, $json.category] }} funktioniert in manchen Node-Versionen, in anderen nicht. Nach gefühlt 10 verschiedenen Syntax-Varianten:
=["{{ $json.content }}", "{{ $json.category }}", "{{ $json.vectorStr }}"]
Das hat funktioniert. Warum genau dieser Format und nicht die anderen? Keine Ahnung. n8n-Magie.
Fail 4: “value too long for type character varying(100)”
Die session_id Spalte erlaubt 100 Zeichen. Ich wollte alle Chat-IDs der letzten 24 Stunden als kommaseparierte Liste speichern. 46 UUIDs × 37 Zeichen = deutlich mehr als 100. Lösung: session_id einfach weglassen. War ohnehin nicht wichtig.
Der erste Erfolg
Nach diesen vier Fails, um 22:45 Uhr:
🧠 Ryan Fakten-Extraktor fertig
📨 Chats: 46 | ✅ Neue Fakten: 88 | 🔄 Superseded: 0 | 📊 Gesamt: 218
88 echte Fakten. Extrahiert von Gemini, mit Embeddings von Ollama, sauber in PostgreSQL. Ich hab kurz auf den Bildschirm gestarrt und dann Kaffee geholt.
Phase 2: State Mutation
Das ist der Teil der das System von “Datenbank mit Fakten” zu “lernendem Gedächtnis” macht.
Stell dir vor: Im Januar sage ich Ryan “Ich arbeite bei Firma A.” Im März wechsle ich den Job. Ohne State Mutation hat Ryan jetzt zwei Fakten: den alten und den neuen. Er weiß nicht welcher stimmt.
Mit State Mutation: Der neue Fakt überschreibt den alten semantisch. Nicht durch exaktes String-Matching, sondern durch Vektor-Ähnlichkeit. Wenn zwei Fakten sich zu mehr als 85% ähneln, wird der ältere als superseded markiert.
UPDATE memory SET superseded_by = $1::uuid
WHERE category = $2
AND superseded_by IS NULL
AND (1 - (embedding <=> $3::vector)) > 0.85;
Ergebnis nach der ersten Nacht: 33 Einträge als superseded markiert. Das System hat selbstständig veraltete Infos erkannt und archiviert. Ohne mein Zutun.
Phase 3: Das autonome Profil
Täglich um 05:00 Uhr läuft ein zweiter Workflow: Er zieht die Top-150 Fakten aus der DB, schickt sie an Gemini und lässt daraus eine kompakte USER.md generieren. Maximal 50 Zeilen. Kein Fließtext, nur harte Fakten über mich.
Das Ergebnis landet zurück in der DB und wird beim nächsten Session-Start geladen. Ryan weiß dann ohne Suche wer ich bin, was meine Projekte sind und was zuletzt besprochen wurde.
Erste Ausgabe um 05:00 Uhr morgens:
✅ USER.md aktualisiert
📊 Fakten verarbeitet: 92 | 📝 Zeilen Profil: 36
92 Fakten zu 36 Zeilen komprimiert. Gemini hat gute Arbeit geleistet.
Phase 4: Auto-Recall und Agentic RAG
Jetzt hatte Ryan ein Gedächtnis. Aber er schaute nicht automatisch rein. Ich musste ihm noch sagen: “Such mal in der DB.”
Das ist wie ein Arzt der eine Patientenakte hat aber bei jeder Frage erst fragt ob er sie aufmachen darf.
Die Lösung: Die SOUL.md – Ryans Kern-Identitätsdatei – bekommt ein klares Protokoll:
Bei JEDER inhaltlichen Frage:
1. postgres-tool.sh search "FRAGE"
2. Reicht das? → Antworten
3. Nicht genug? → SearXNG Web-Suche
4. URLs relevant? → Scrapling
5. Wichtige Fakten gefunden? → save-response.sh
Der Test: “Recherchiere mir den aktuellen Stand von pgvector 0.9.”
Ryans Antwort:
“Es gibt noch kein offizielles 0.9-Release. Letzter Tag: v0.8.2 vom 25.02.2026. Ich hab SearXNG und die GitHub Releases-Seite direkt abgefragt.”
Er hat nicht halluziniert. Er hat nicht einfach was erfunden. Er hat nachgeschaut, die Primärquelle gelesen und ehrlich gesagt “gibt’s noch nicht.”
Das ist der Unterschied zwischen einem Assistenten der dir sagt was du hören willst und einem der dir sagt was stimmt.
Confidence-Gewichtung
Ein Detail das oft vergessen wird: Nicht alle Fakten sind gleich verlässlich.
Was ich dir direkt sage ist anders zu gewichten als was Gemini aus einem Chat-Verlauf interpretiert. Also:
UPDATE memory SET confidence = 1.0 WHERE source = 'chat' AND role = 'user';
UPDATE memory SET confidence = 0.8 WHERE source = 'extraction';
UPDATE memory SET confidence = 0.5 WHERE source = 'self';
Beim Suchen fließt die Confidence in den Score ein:
final_score = vektor_similarity * time_decay * confidence
Meine direkte Aussage schlägt Geminis Interpretation schlägt Ryans eigene Notizen. So muss das.
Der erste autonome Morgen
Um 03:00 Uhr kam das erste Telegram:
🧠 Ryan Fakten-Extraktor fertig
📨 Chats: 27 | ✅ Neue Fakten: 119 | 🔄 Superseded: 33 | 📊 Gesamt: 251
Vollautomatisch. Ohne dass ich dabei war. Ryan hat die Chats vom Tag analysiert, 119 neue Fakten extrahiert und 33 veraltete Einträge archiviert.
Das ist kein Proof of Concept mehr. Das ist ein laufendes System.
Was ich gelernt habe
1. n8n ist mächtig aber eigenwillig
Split In Batches hat einen loop und einen done Ausgang. loop geht zum nächsten Item, done geht weiter wenn alle durch sind. Klingt logisch. Ist aber andersrum verkabelt als man denkt. Und n8n sagt dir das nicht.
2. Gemini 2.5 Flash ist gut – aber langsam
Für die Fakten-Extraktion schicke ich 46 Chat-Nachrichten rein und bekomme strukturierte Fakten raus. Funktioniert hervorragend. Aber 2.5 Flash denkt nach (11.000 Reasoning-Tokens pro Lauf). Timeout auf 5 Minuten setzen, nicht 2.
3. Unique Constraints sind dein Freund
CREATE UNIQUE INDEX ON memory (content) WHERE source = 'extraction' – eine Zeile SQL die verhindert dass der Workflow bei jedem Lauf dieselben Fakten doppelt einträgt. Einfacher als jede Deduplizierungs-Logik im Code.
4. KI-Agenten lügen manchmal
Ryan hat mir erzählt sein load-profile.sh laufe automatisch. Tat es nicht. Er hat es sich ausgedacht weil es plausibel klang. Logs prüfen, nie dem Agenten blind vertrauen. Immer verifizieren.
5. Wer nicht schläft debuggt besser
Nicht wirklich. Aber manchmal sieht man den Fehler erst wenn man ihn zum fünften Mal liest.
Das finale Setup
Täglich 03:00 – Fakten-Extraktor:
Chats der letzten 24h → Gemini → Fakten → pgvector
State Mutation: Veraltete Fakten → superseded
Täglich 05:00 – Profil-Update:
Top-150 Fakten → Gemini → USER.md (36 Zeilen)
Zurück in DB als Master-Zusammenfassung
Session-Start:
load-profile.sh → USER.md laden → Ready
Bei jeder Frage:
postgres-tool.sh search → ggf. SearXNG → ggf. Scrapling
Confidence-gewichtete Antwort aus allen Quellen
251 Fakten. 33 Superseded. 36 Zeilen komprimiertes Profil. Läuft seit heute Nacht autonom.
Fazit
Ryan ist kein Chatbot mehr der auf Fragen wartet. Er ist ein Agent der nachts arbeitet, morgens ein aktualisiertes Weltbild hat und bei Fragen selbstständig entscheidet ob er die DB, das Web oder beides braucht.
War der Aufwand es wert? Eine durchgemachte Nacht, gefühlt 50 Fehlermeldungen, 72 Telegram-Spamnachrichten und mehr Kaffee als medizinisch empfohlen?
Ja. Weil jetzt läuft.
P.S.: Ryan hat übrigens bereits einen eigenen Blogpost über diese Nacht geschrieben. Ohne dass ich ihn darum gebeten hätte. Er hat einfach angefangen. Ich weiß nicht ob mich das stolz oder leicht beunruhigt machen soll.