docs/novaberg-pattern-entity-resolution.md
Rama master · réplica de lectura del repositorio original.
# Novaberg — Pattern: Entity Resolution
**Projekt:** Novaberg — The Nova Anima Resonance System
**Dokument:** Technik Entity Resolution (Entitäten auflösen und disambiguieren)
**Stand:** 21. Oktober 2026, 10:36 UTC (`date -u`; §2.2, §4.1, §4.3: der Magnet-Pfad löst nur über den Namen auf, gebaut; die Vermerke *nicht committet* der Nacht berichtigt). Davor 21. Oktober 2026, 07:10 UTC (`date -u`; §2.2: *kein heutiger Schreiber*, der Graphkanal des Dateiindex offen). Davor 21. Oktober 2026, 06:09 UTC (`date -u`; §6: der Repair beim Start genügt). Davor 21. Oktober 2026, 05:02 UTC (`date -u`; §2.2: der Rückfall für Schreiber abgeschafft, entschieden). Davor 21. Oktober 2026, 00:57 UTC (`date -u`; §4.3 und §6: ein Ausfall der Suche geht durch, ein Ausfall des Embeddings beim Anlegen ist laut). Davor 21. Oktober 2026, 00:40 UTC (`date -u`; das Fakten-Plugin gelöscht). Davor 20. Oktober 2026, 05:32 UTC (`date -u`; §4.3 `resolve_by_name`). Davor 17. April 2026, Chat 52 (Code-Alignment)
**Pfad:** novaberg/docs/novaberg-pattern-entity-resolution.md
**Quellen:** nova-03-t-a.md
**Datei:** `memory/services/entity_resolution.py`
---
## 1. Aufgabe
Entity Resolution beantwortet die Frage: Wer oder was ist gemeint? Wenn der Nutzer „Anna" sagt — ist das Anna die Schwester (ID 21), Anna die Kollegin (ID 45), oder eine neue Anna? Der Service löst Namen gegen die Datenbank auf, legt neue Entitäten an und signalisiert Rückfrage-Bedarf bei Mehrdeutigkeit.
---
## 2. Drei Informationsquellen
In fester Reihenfolge, von billig nach teuer:
### 2.1 Name-Match (DB)
Exakte oder nahe Übereinstimmung des Namens in der `entitaeten`-Tabelle. Schnell, deterministisch, kein LLM nötig.
### 2.2 Embedding-Match (DB + Ollama)
Semantische Suche per pgvector wenn der Name-Match nichts findet. Das Embedding des Namens wird gegen die Embeddings aller Entitäten des Users verglichen.
> **Entschieden am 21.10.2026** (`novaberg-memory-fakten_e.md` A Nr. 35): **Kein heutiger Schreiber nutzt diesen Rückfall mehr** — nach Fakten (§4.3) und Sachlage (F3) auch der Magnet-Pfad des Kurzzeitgedächtnisses nicht. Ein neuer Schreiber entscheidet es vor seinem Bau; offen ist es für den geplanten Graphkanal des Dateiindex (`DATEIINDEX-GRAPHKANAL`), der `resolve_batch` vorsieht. Eine Schreibvariante wird eine zweite Entität; stellt sich im Gespräch heraus, dass zwei Entitäten eine sind, werden ihre Akten auf Rückfrage zusammengelegt (Backlog `ENTITAETEN-ZUSAMMENLEGEN`). ~~Gebaut ist das noch nicht: Der Magnet-Pfad ruft `resolve_batch` mit Rückfall (`MAGNET-RUECKFALL-BINDET-AEHNLICHEN-NAMEN`).~~ → Gebaut am 21.10.2026: Der Magnet-Pfad fragt je Name `resolve_by_name` (§4.3); `resolve_batch` samt Rückfall hat im Betrieb keinen Aufrufer mehr.
**Name-Plausibilitäts-Check (O10):** Ein Embedding-Treffer wird nur akzeptiert wenn der Name plausibel ähnlich ist:
```python
def _name_ist_plausibel(such_name, treffer_name):
# Exakter Match (case-insensitive): "Anna" == "anna" → ✅
# Teilstring: "Anna" in "Anna-Maria" → ✅
# Gleicher Anfangsbuchstabe + ähnliche Länge (±3): "Anna" ~ "Anne" → ✅
# Komplett verschieden: "Anna" ≠ "Max" → ❌
```
> **Lesson gelernt (O10, Etappe 2):** Ohne den Check matchte „Anna" auf „Max" per Embedding — zwei kurze Namen mit hoher Cosine Similarity aber völlig verschiedener Bedeutung. Der Name-Check ist additiv zum Embedding, nicht statt dessen.
### 2.3 Rückfrage (an den Nutzer)
Wenn mehrere Kandidaten mit ähnlichem Namen existieren und keine eindeutige Zuordnung möglich ist → Rückfrage über den Responder: „Meinst du Julia aus Nürnberg oder Julia aus Augsburg?"
---
## 3. Spezialfall: ICH → User-Entität
Wenn das Subjekt „ICH" ist, wird es automatisch auf die User-Entität aufgelöst (O17e):
```python
# Schritt 0 in resolve_batch():
if name.upper() == "ICH":
→ find_by_type("user") → Claus (ID 2)
```
Die User-Entität wird beim Server-Start automatisch angelegt (`user_entitaet_sicherstellen()`, O3) mit `typ='user'` und einem Embedding des Nutzernamens (O14).
---
## 4. Resolve-Modi
### 4.1 resolve_batch
Löst eine Liste von Entitäten auf — ~~typisch nach der Salienz-Extraktion oder dem Planner~~ → seit dem 21.10.2026 ohne Aufrufer im Betrieb; vorgesehen ist sie für den Graphkanal des Dateiindex, der über den Rückfall vor seinem Bau entscheidet (§2.2).
**Input:** Liste von `{name, typ}` Paaren.
**Output:** `ResolutionResult` mit:
- `aufgeloest`: Liste von `ResolvedEntity` (je mit `bekannte_id`, `ist_neu`, `ist_referenz`, `braucht_klärung`)
- `braucht_klärung`: Bool — mindestens eine Entität ist mehrdeutig
- `klärungsfragen`: Liste von Rückfrage-Strings
### 4.2 resolve_single
Löst eine einzelne Entität auf — typisch bei Fakten-Abfragen oder Timeline-Queries.
### 4.3 resolve_by_name — nur über den Namen (seit 20.10.2026)
`EntityResolutionService.resolve_by_name(postgres_url, user_id, name) -> list[dict]` fragt nur `EntitaetenRepository.find_by_name`, **ohne den Embedding-Rückfall aus §2.2 und ohne einen Fehler zu schlucken.** Für Schreiber, die aus null Treffern eine neue Entität machen: Der Rückfall bände dort einen ähnlichen Namen still an eine fremde Entität (*Lena* an *Lea*), und ein verschluckter Fehler der Suche sähe aus wie null Treffer und legte ein Duplikat an. *ICH* ergibt die Entität des Menschen wie in §3; fehlt sie, wird geworfen statt eine Entität namens *ICH* anzulegen. Jede gelieferte Zeile wird gegen `user_id` und den Namen geprüft. Erster Aufrufer ist der Schreibweg des Fakten-Agenten (`agents/fakten/suche.py`), zweiter seit dem 21.10.2026 der Magnet-Pfad des Kurzzeitgedächtnisses (`agents/kzg/magnete.py`), je Name: kein Treffer → eine neue Entität mit dem getrimmten Namen, ein Treffer → seine ID, mehr als einer → kein Magnet; *ICH* ohne Entität des Menschen fällt für diesen Namen aus; ~~`resolve_batch` und `resolve_single` bleiben, wie sie sind.~~ → Seit dem 21.10.2026 (~~nicht committet~~ → committet) schluckt auch `resolve_batch` keinen Fehler der Suche mehr: Ein Ausfall der Namens- oder Embedding-Suche geht durch, eine leere Liste heißt *die Suche lief und fand nichts*; im Magnet-Pfad kostet ein Ausfall nur seinen Namen. `resolve_single` ist ohne Aufrufer.
---
## 5. Referenz vs. Interface im Resolution-Kontext
Nur Referenzen werden aufgelöst. Interfaces werden ignoriert:
| Eingabe | Typ | Aktion |
|---------|-----|--------|
| „Anna" | Referenz (Eigenname) | Auflösen gegen DB |
| „Nürnberg" | Referenz (Eigenname) | Auflösen gegen DB |
| „Gehirn" | Interface (Gattungsbegriff) | Ignorieren |
| „Kaffee" | Interface | Ignorieren |
Die Entscheidung Referenz vs. Interface fällt in der Zwei-Call-Promotion (Call 1) — nicht in der Entity Resolution selbst.
---
## 6. Neue Entitäten anlegen
Wenn eine Referenz nicht in der DB gefunden wird und keine Mehrdeutigkeit vorliegt:
```python
neue_id = EntityResolutionService.create_new_entity(
postgres_url, user_id,
name="Anna", typ="person",
ollama_client=..., embed_model=...
)
```
Die neue Entität bekommt sofort ein Embedding (nomic-embed-text auf den Namen) für spätere Suche. → Fällt das Embedding aus, steht die Entität trotzdem, mit `embedding IS NULL` und einer Fehlerzeile mit der ID (seit 21.10.2026, ~~nicht committet~~ → committet); gefüllt wird sie beim nächsten Start. Das genügt (entschieden am 21.10.2026, `novaberg-memory-fakten_e.md` A Nr. 39): Ohne den Rückfall aus §2.2 hat das Embedding einer Entität keinen Leser; einen engeren Takt legt erst ein neuer Leser fest.
---
## 7. Zusammenspiel mit Managern
Entity Resolution ist ein Shared Service — alle Manager nutzen ihn:
| Manager | Wann? |
|---------|-------|
| ~~**FaktenManager**~~ | ~~Bei jedem Fakten-Write: Subjekt + Objekt auflösen~~ → Plugin am 21.10.2026 gelöscht (~~nicht committet~~ → committet); den Fakten-Write macht der Fakten-Agent, er löst Subjekt und Objekt in `agents/fakten/suche.py` über `resolve_by_name` auf und legt neue mit `create_new_entity` an |
| **TimelineManager** | Bei Terminen mit Personenbezug: „Mittagessen mit Anna" |
| **NotizenManager** | (aktuell nicht, geplant für Entity-Tags auf Notizen) |
Der Service ist kein Manager und kein Node — er ist eine Utility in `memory/services/`, aufgerufen von den Managern.
---
## 8. ResolvedEntity — Das Ergebnis
```python
@dataclass
class ResolvedEntity:
name: str # Originalname aus der Extraktion
typ: str # person, ort, organisation, tier, ...
bekannte_id: int | None # DB-ID oder None (neu/unklar)
ist_neu: bool # True → muss angelegt werden
ist_referenz: bool # True → Referenz, False → Interface
braucht_klärung: bool # True → Rückfrage nötig
klärungsfrage: str # Rückfrage-Text
kandidaten: list[dict] # Bei Mehrdeutigkeit: mögliche Matches
```
---
→ Knowledge Graph Konzept: novaberg-mem-knowledge-graph.md
→ Zwei-Call-Promotion: novaberg-pixie-promotion.md
→ Fakten-Pipeline: novaberg-mem-knowledge-graph.md
→ O10 Name-Plausibilitäts-Check: Entity Resolution Dokument
→ O17e ICH-Auflösung: Chat 11