DB-Struktur, Vektoren, Verknüpfung, Pflege-Zyklen — und warum es Kontaktnotizen UND Knowledge-Items gibt. Alle Zahlen live von der Prod-Box.
Ohne diese vier versteht man den Rest nur halb. Mit ihnen ist alles unten simpel.
Ein Text wird in eine lange Zahlenreihe übersetzt (bei uns 1536 Zahlen). Der Trick: Texte mit ähnlicher Bedeutung bekommen ähnliche Zahlen. „Emre will mehr Leads" und „Emre braucht Pipeline" liegen als Zahlenreihen nah beieinander, obwohl kein Wort gleich ist. Diese Zahlenreihe nennt man Vektor oder Embedding.
Eine normale Suche findet nur exakte Wörter (wie Strg+F). Eine Vektor-Suche fragt: welche gespeicherten Zahlenreihen liegen am nächsten an meiner Frage? — und findet damit auch Dinge, die anders formuliert sind. Wir nutzen dafür keine Extra-Datenbank, sondern Postgres mit der pgvector-Erweiterung: eine zusätzliche Spalte + ein spezieller Index (HNSW), der Nachbarschafts-Suche schnell macht.
Ein Modell weiß nur, was in seinem Kontextfenster steht. „Context Engineering" heißt: vor jedem Agenten-Turn die relevanten Wissens-Zeilen raussuchen (Retrieval) und als kurzen Brief oben reinlegen. Deshalb weiß Jürgen Dinge über Emre, ohne alle Transkripte zu lesen — er kriegt die 10 relevanten Zeilen serviert, nicht 500 Seiten.
Wie beim Menschen: tagsüber sammeln, nachts sortieren. Dubletten zusammenlegen, Bestätigtes befördern, Widersprüche zur Klärung vorlegen, Veraltetes markieren. Ohne diesen Schritt wird jede Wissenssammlung zur Müllhalde.
Kurz: Es gibt nur EIN Gehirn. Die Kontaktnotiz ist keine zweite Wissensablage, sondern die Karten-Oberfläche — eine Projektion des Gehirns auf den einzelnen Kontakt.
knowledge_itemscontact_notescontact_note_add) + der automatische Spiegel aus dem Gehirnabout_type=Contact), schreibt der ContactCardWriter automatisch eine kompakte Karten-Notiz — upsert-by-source (knowledge:<item_id>): eine Notiz pro lebender Wissens-Zeile. Wird das Wissen im Gehirn superseded, aktualisiert sich die Karte in place. Die Karte lügt nie, das Gehirn bleibt die Wahrheit.PHP orchestriert, Modell assistiert. Das Modell schreibt NIE direkt in die DB — es emittiert JSON, ein einziger Writer entscheidet.
Die blassen Arme liefern faktisch nichts (2 Transkripte, 1 Mail insgesamt) — das ist die Hunger-Lücke aus Abschnitt 6.
episodesPostgres-VIEW, kein KopierenEine read-only UNION-Sicht über alle Quelltabellen, normalisiert auf (source_type, source_id, owner, occurred_at, excerpt). Dokumente werden also nicht importiert oder dupliziert — die Originale bleiben, wo sie sind; das Gehirn merkt sich Zeiger + Zitat.
Pro Quelle ein Cursor (distill_cursors). Ein Batch-Agent liest neue Episoden und emittiert knowledge_distill.v1-JSON — Vorschläge, keine Schreibzugriffe. Consent-Gate: Quellen mit ai_processing=none werden übersprungen.
Entscheidet pro Vorschlag: ADD / SUPERSEDE / NOOP. Validator prüft (u. a. Zitat-Pflicht bei Mail/WA-Fakten), dann Embedding, dann Karten-Spiegel. Marcus/Markus-Invariante: ein unaufgelöster Name legt NIE einen Kontakt an — er wird eine Pflege-Frage.
Sicheres Wissen → knowledge_items. Unsicheres (unklare Entität, Widerspruch, Lücke) → curation_items = die Karten in deinem Pflege-Drawer. Deine Antwort dort füttert das Gehirn zurück.
HybridSearch → Wissens-Briefs im Agenten-Kontext (Jürgen & Co. wissen Dinge) · GroundTruthRules → deine bestätigten Umgebungs-Regeln · knowledge_search-Tool · seit #96: der Kontaktkarten-Spiegel.
knowledge_items — die eine Kern-Tabelle (ULID-PK, Soft-Deletes):
| Spalte | Typ | Bedeutung |
|---|---|---|
| Inhalt | ||
| type | string+CHECK | fact · decision · learning · playbook · preference · reference · instruction |
| title / body / quote | string / text | Kern-Aussage, Ausführung, wörtliches Beleg-Zitat aus der Quelle |
| Verknüpfung („worüber?") | ||
| about_type / about_id | morph | Contact · Client · Project · Repo · User · Topic — das Subjekt des Wissens |
| client_id | FK denorm | Roll-up: alles zu einem Kunden mit einem Query holbar |
| Lebenszyklus (temporal, nichts wird gelöscht) | ||
| status | string+CHECK | draft (frisch distilliert) → confirmed (korroboriert/bestätigt) → superseded |
| is_current / valid_from / ended_at | bool / ts | Zeitachse: wann galt das? |
| superseded_by_id | Self-FK | Kette zur neueren Wahrheit — Historie bleibt als Provenienz stehen |
| Provenienz („woher?") | ||
| source_type / source_id | string | Zeiger auf das Original (krisp_call, mail_message, agent_run, …) — verbatim Episode-Token |
| created_by_run_id | FK agent_runs | Welcher Agent-Lauf hat es geschrieben |
| confidence | double 0–1 | Wie sicher war der Distiller |
| Suche (die Antwort auf „haben wir Vektoren?") | ||
| embedding | vector(1536) | pgvector + HNSW-Cosine-Index — semantische Suche |
| search | tsvector GENERATED | Deutscher Volltext (GIN) — immer da, auch ohne Embedding |
| Sichtbarkeit | ||
| visibility / private_owner_user_id | string / FK | shared (Agentur) oder privat-an-einen — CHECK erzwingt Owner bei privat |
Die Pflege-Queue: Unsicheres, Widersprüche, Wissens-Lücken als Karten an dich. Deine Antworten fließen zurück.
Pro Quelle: „bis wohin habe ich gelesen". Cursor rückt nur bei erfolgreichem Distill vor — nichts wird übersprungen.
Karten-Oberfläche (Abschnitt 1): note, kind, created_by_run_id, Sichtbarkeit spiegelt den Kontakt.
Kein Speicher — nur die normalisierte Lese-Brille über die Roh-Tabellen.
Jede Anfrage läuft zweigleisig: Cosine-Ähnlichkeit über embedding (pgvector, HNSW) + deutscher Volltext über search (tsvector, GIN). Reciprocal-Rank-Fusion mischt beide Ranglisten. Ein Rerank-Service existiert, hängt aber nur in Batch-Läufen — im interaktiven Suchpfad (noch) nicht. Fehlt das Embedding einer Zeile, degradiert die Suche sauber auf Volltext — nie auf null.
Subjekt (about → Kontakt/Client/Projekt/Topic) · Zeit (supersede-Kette, is_current) · Herkunft (source + Zitat + Run). Es ist kein freier Wissens-Graph — Kanten zwischen beliebigen Items gibt es (noch) nicht; die Struktur ist Entität-zentriert.
Das Gehirn wird jede Nacht dreifach angefasst — Ingestion, Konsolidierung, Lücken-Erkennung:
Neue Episoden aller Quellen → Wissens-Vorschläge → UpsertPipeline. Seit #97 alle zwei Stunden statt 1× nachts (der alte 40-Zeilen-Nacht-Pass war 10× zu langsam für den Zufluss). Krisp-Calls werden zusätzlich SOFORT nach dem Call verarbeitet (Hot-Path).
Deterministischer Pre-Pass: exakte Dubletten superseden, draft→confirmed bei Bestätigung aus zwei unabhängigen Quellen, Stale-Flags. Modell-Judge nur für Fast-Dubletten & Widersprüche — zero-side-effect, entscheidet, schreibt nicht selbst.
Pures PHP, kein Modell: erkennt Wissens-LÜCKEN (Clients/Kontakte ohne bestätigtes Wissen, Beziehungs-Kategorien über dich) und legt gap_question-Karten in den Pflege-Drawer.
Es lag NICHT am Consent (78 von 80 Kontakten erlauben volle Verarbeitung) und nicht an den Filtern. Die Diagnose mit Zahlen: die Lese-Cursor starteten am Anfang der Spiegel-Historie (WhatsApp: 2016!) und der Distill las nur 40 Zeilen pro Quelle pro NACHT — bei 35.158 WA-Nachrichten wären das 2,4 Jahre Aufholzeit, und selbst danach 10× zu langsam für ~395 neue WA-Nachrichten am Tag. Fix (live): Cursor auf die letzten 14 Tage gesetzt + Distill läuft jetzt alle 2 Stunden statt 1× nachts. Ab jetzt fließt WhatsApp + Mail wirklich ein, inkl. deiner Agent-Sessions als 6. Arm.
Präzisierte Ursache: der Embedding-Treiber lief, aber nur einer von zehn Schreibpfaden (der Distill) berechnete beim Einsortieren einen Vektor — alles aus Kuration, Konsolidierung, Seeds und Tools blieb ohne. Fix (live): einmaliger Backfill gelaufen → 613 von 613 Zeilen haben jetzt einen Vektor, die Bedeutungs-Suche ist voll scharf. Ein nächtlicher Sweep (04:10) fängt ab jetzt jede neue Zeile, die ein Schreibpfad auslässt.
Das Gehirn sammelte Fakten, dachte aber nie nach. Jetzt gebaut und aktiv: die Reflection-Lane läuft jede Nacht um 04:00, clustert das vorhandene Wissen pro Kunde/Kontakt/Thema und destilliert daraus Lehren, Playbooks und Präferenzen — durch dieselbe geprüfte Pipeline, mit Dedup, sodass dieselbe Erkenntnis nie doppelt entsteht. Erste Ergebnisse: morgen früh.
Konsolidierung existiert (03:00-Zyklus), aber sie räumt nur auf — sie macht das Wissen nicht tiefer. Der Plan aus dem Research, auf dem Bestehenden aufsetzend:
| # | Baustein | Was es bringt |
|---|---|---|
| ✅ | Kontaktkarten-Spiegel + agent_run-Quelle | Seit #96 scharf |
| ✅ | Embedding-Backfill + Nacht-Sweep | Seit #97: 613/613 vektorisiert, Sweep 04:10 fängt Nachzügler |
| ✅ | Reflection-Lane | Seit #97 live (04:00) — erzeugt learning/playbook aus den rohen Decisions |
| ✅ | Zufluss-Fix + Hot-Path Krisp | Seit #97: Cursor auf letzte 14 Tage, Distill alle 2h, Krisp-Calls sofort verarbeitet |
| ✅ | Kontakt-Befüllung | Seit #97: nächtliche Vorschläge aus Meeting-Teilnehmern, WA-Chats, Mail-Absendern — One-Tap im Pflege-Drawer, nie Auto-Create |
| 1 | Importance-Score + Decay/Refresh | Nächster Baustein: Wichtiges bleibt vorn, Veraltetes wird zur Prüf-Frage statt stiller Karteileiche |
| 2 | Mail-Konzept umsetzen | Entscheidungsvorlage liegt vor (mael-Ops-Fix + Kontakt-zentrierte Klassifikation) — wartet auf Davids 9 Entscheidungen |