Ctrl K
ComunidadGuías…

docs/novaberg-node-dispatcher_l.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 02_L_d — Lesson: Doppelspeicherung Salienz + Planner

**Projekt:** Novaberg — The Nova Anima Resonance System
**Dokument:** Lesson — Zwei Systeme schreiben gleichzeitig
**Stand:** 12. April 2026, Chat 44 (migriert, Inhalt unverändert)
**Pfad:** novaberg/docs/novaberg-node-dispatcher_l.md
**Ursprung:** nova-02-l-d.md
**Typ:** Lesson (L)
**Entdeckt:** Chat 11 (24. März 2026), P5 + P6
**Betrifft:** Salienz (`01_M_g`), Planner (`01_M_d`), Dispatcher (`01_M_h`)

---

## 1. Symptom

Zwei Varianten des gleichen Problems:

**P5 — Doppelte Notiz + Fakt:** „Merk dir: Einkaufsliste — Milch, Eier, Brot" → Der Planner legte eine Notiz an (korrekt). Die Salienz extrahierte zusätzlich den Fakt `ICH EINKAUFSLISTE Milch, Eier, Brot` und schrieb ihn in die Fakten-Tabelle (unerwünscht). Ergebnis: Dieselbe Information existierte als Notiz und als Fakt.

**P6 — Doppelter Termin:** „Trag ein: Zahnarzt am Donnerstag um 14 Uhr" → Der Planner legte den Termin an (14:00 MEZ). Die Salienz erkannte `temporal_fact` und legte einen zweiten Termin an (13:00 UTC — da die Timezone-Konvertierung im Salienz-Pfad noch nicht gefixt war). Ergebnis: Zwei Zahnarzttermine.

---

## 2. Ursache: Zwei unabhängige Schreibsysteme

Der Graph hat zwei Quellen für `pending_writes`:

1. **Planner** — bei Management-Aktionen (explizite Befehle: „merk dir", „trag ein")
2. **Salienz** — bei speicherwürdigem Content (implizite Erkennung: Fakten, Termine)

Beide laufen im selben Turn. Beide erkennen dieselben Informationen. Beide schreiben `pending_writes`. Der Dispatcher führt alle aus — ohne zu wissen, dass sie sich überlappen.

```
"Trag ein: Zahnarzt am Donnerstag"
    │
    ├── Planner: management_action="create" → pending_write (timeline)
    │
    └── Salienz: temporal_fact erkannt → pending_write (timeline)
    
    → Dispatcher führt beide aus → 2 Termine
```

---

## 3. Warum wir es nicht vorhergesehen haben

Die Entscheider/Arbeiter-Trennung (A1) war korrekt — Salienz entscheidet, Dispatcher führt aus. Aber die Trennung berücksichtigte nicht, dass es *zwei* Entscheider gibt: Planner und Salienz. Beide haben die gleiche Berechtigung, `pending_writes` zu erzeugen. Die Architektur hatte keine Regel, wer Vorrang hat.

---

## 4. Die Lösung: Salienz-Guard

Ein einfacher Guard in der Salienz:

```python
planner_aktiv = bool(state.get("management_action", ""))

if facts and planner_aktiv:
    logger.info("Salienz: Fakten erkannt, aber Planner aktiv — übersprungen")
    
if temporal_fact and planner_aktiv:
    logger.info("Salienz: Temporaler Fakt erkannt, aber Planner aktiv — übersprungen")
```

**Wenn der Planner im selben Turn aktiv war, unterdrückt die Salienz ihre Fakten- und Timeline-Writes.** Der Planner hat die Daten bereits verarbeitet — über den strukturierten Management-Pfad (Router → Planner → Manager), nicht über den impliziten Salienz-Pfad.

**KZG-Writes bleiben aktiv.** Die Salienz darf weiterhin ins KZG schreiben — das ist ein anderer Zweck (Gesprächskontext vs. strukturierte Daten). Doppelte KZG-Einträge werden über die Ähnlichkeitssuche (Cosine ≥ 0.85) abgefangen.

---

## 5. Generalisierbare Erkenntnis

> **Das Dual-Writer-Problem:** Wenn zwei unabhängige Komponenten die gleiche Information erkennen und in den gleichen Speicher schreiben können, entsteht unweigerlich Duplikation — es sei denn, eine explizite Vorrang-Regel existiert.

Die Regel ist simpel: **Explizit schlägt implizit.** Der Planner (explizite Management-Aktion) hat Vorrang vor der Salienz (implizite Erkennung). Wenn der Nutzer sagt „Merk dir X", will er den Management-Pfad — nicht dass beide Pfade gleichzeitig feuern.

> **Für zukünftige Manager:** Jeder neue Manager, der sowohl über den Planner (explizit) als auch über die Salienz (implizit) beschrieben werden kann, braucht denselben Guard. Das ist kein Bug-Fix — das ist ein Architektur-Pattern.

---

→ Salienz (Guard implementiert): `01_M_g`
→ Planner: `01_M_d`
→ Dispatcher: `01_M_h`
→ Entscheider/Arbeiter-Trennung: `00_A`, Abschnitt 6