Gedaechtnis-Bereiche: alltag/projekt-Feld statt zweitem mem0
- classify_facts ordnet neue Fakten zusaetzlich einem Bereich zu (alltag=Person, projekt=Technik); Metadatum bereich, in /memory-Items sichtbar. - GET /memory?bereich=... filtert; Alt-Fakten ohne Feld laufen IMMER mit. - docs/wissen/GEDAECHTNIS-BEREICHE.md: Entscheid (kein zweites mem0), Stand, 4-Wochen-Selbstpruefung und kompletter Bauauftrag fuer den Recall-Filter - damit die Werkstatt das ohne externe Hilfe fertigbauen kann. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
# Gedächtnis-Bereiche: alltag vs. projekt (Stand 19.07.2026)
|
||||||
|
|
||||||
|
## Warum
|
||||||
|
Lucys mem0 sammelte Coding-/Ops-Schutt (execute_code-Bugs, Session-Zahlen,
|
||||||
|
Hardware-Debugging) — Rauschen im Alltags-Recall. Entscheid (Commander, 19.07.):
|
||||||
|
**KEIN zweites mem0** (das Coding-Gedächtnis ist das Repo: AGENTS.md, Code, Git,
|
||||||
|
Karten). Stattdessen bekommt jeder Fakt ein Bereichs-Feld — die Trennung liegt in
|
||||||
|
den Daten, nicht in einer zweiten Datenbank.
|
||||||
|
|
||||||
|
## Was seit 19.07. existiert (gebaut, live)
|
||||||
|
- `mem0_service/app.py` → `classify_facts()` ordnet jeden neu gelernten Fakt
|
||||||
|
zusätzlich einem **Bereich** zu: `alltag` (Nutzer als Person: Vorlieben, Leben,
|
||||||
|
Arbeitsweise, Grundsatz-Entscheidungen) oder `projekt` (Technik: Code, Modelle,
|
||||||
|
Server, Infrastruktur, Fehler-/Projektstände). Gespeichert als Metadatum
|
||||||
|
`bereich`, ausgegeben in jedem `/memory`-Item.
|
||||||
|
- `GET /memory?bereich=alltag|projekt` filtert. **Alt-Fakten ohne Feld laufen bei
|
||||||
|
jedem Filter MIT** (nichts verschwindet durch die Umstellung).
|
||||||
|
- Quellen-Hygiene: Die IDE-Welt (Zed+OpenCode) schreibt seit 19.07. GAR NICHT mehr
|
||||||
|
ins mem0; Junk-Guard + Lern-Bündelung + monatliche Konsolidierung (3., 06:40)
|
||||||
|
laufen. Der Neuzugang an Schutt sollte dadurch bereits stark sinken.
|
||||||
|
|
||||||
|
## Selbstprüfung nach 4 Wochen (Cron „gedaechtnis-check", einmalig Mitte August)
|
||||||
|
Auftrag an Lucy: Die 50 neuesten Fakten ansehen (`GET /api/memory` sortiert
|
||||||
|
neueste zuerst). Frage: Wie viele davon sind Tool-/Fehler-/Momentaufnahmen-Schutt
|
||||||
|
(Muster: Tool-Namen, Fehlermeldungen, Session-/Commit-Zahlen, „blockiert/behoben"-
|
||||||
|
Zustände)? **> 10 von 50 → Werkstatt-Karte anlegen** mit Verweis auf dieses
|
||||||
|
Dokument und den Bauauftrag unten. Sonst: kurze Entwarnung an den Commander.
|
||||||
|
|
||||||
|
## Bauauftrag FALLS nötig: Recall-Filter (klein, KEINE neue Schicht)
|
||||||
|
Ziel: In Alltags-Gesprächen streut kein Projekt-Schutt in den Recall.
|
||||||
|
Umsetzung in `hermes/plugins/mc2-memory/__init__.py`, Funktion `prefetch()`:
|
||||||
|
1. Neuer Env-Schalter `MC2_MEMORY_RECALL_BEREICH` (leer = aus, Default).
|
||||||
|
2. Wenn gesetzt: an den Request `params={"q": q, "bereich": <wert>}` anhängen —
|
||||||
|
der Sidecar filtert serverseitig (Fakten ohne Feld laufen mit, siehe oben).
|
||||||
|
3. Setzen NUR für Lucys Voice-/Chat-Gateway (Umgebung der hermes-gateway-Unit),
|
||||||
|
NICHT für Worker-Profile — Worker sollen Projekt-Fakten ja sehen.
|
||||||
|
4. Verify: zwei Test-Learns (ein Alltags-, ein Projekt-Fakt), dann `/memory?q=…`
|
||||||
|
mit und ohne Filter vergleichen; Latenz unverändert (< 1 s Overhead).
|
||||||
|
Aufwand: < 1 Stunde. Regeln: deploy braucht Commander-Ja; Plugin-Änderung =
|
||||||
|
Gateway-Neustart.
|
||||||
|
|
||||||
|
## Bewusst NICHT tun
|
||||||
|
- Kein zweites mem0/zweite Collection (doppelte Pflege, doppelte Konsolidierung).
|
||||||
|
- Keinen Alt-Bestand automatisch umtaggen (die Monats-Konsolidierung räumt ohnehin;
|
||||||
|
der Filter behandelt ungetaggte Fakten konservativ als „immer sichtbar").
|
||||||
+40
-11
@@ -166,6 +166,7 @@ def _to_item(r: dict, score=None) -> dict:
|
|||||||
"id": r.get("id"),
|
"id": r.get("id"),
|
||||||
"content": r.get("memory", ""),
|
"content": r.get("memory", ""),
|
||||||
"category": meta.get("category") or "stable",
|
"category": meta.get("category") or "stable",
|
||||||
|
"bereich": meta.get("bereich") or "",
|
||||||
"source": meta.get("source") or "auto",
|
"source": meta.get("source") or "auto",
|
||||||
"created_at": r.get("created_at") or "",
|
"created_at": r.get("created_at") or "",
|
||||||
"updated_at": r.get("updated_at") or r.get("created_at") or "",
|
"updated_at": r.get("updated_at") or r.get("created_at") or "",
|
||||||
@@ -235,8 +236,10 @@ def _rerank(query: str, items: list[dict]) -> list[dict]:
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/memory")
|
@app.get("/memory")
|
||||||
def list_memory(q: str = "", category: str = "") -> list[dict]:
|
def list_memory(q: str = "", category: str = "", bereich: str = "") -> list[dict]:
|
||||||
"""q gesetzt → semantische Suche (mit Relevanz-Score). Sonst → alle Fakten."""
|
"""q gesetzt → semantische Suche (mit Relevanz-Score). Sonst → alle Fakten.
|
||||||
|
bereich='alltag'|'projekt' filtert (Fakten OHNE Bereichs-Feld laufen immer mit,
|
||||||
|
damit Alt-Bestand nie verschwindet)."""
|
||||||
if q:
|
if q:
|
||||||
res = mem().search(q, filters={"user_id": USER_ID}, top_k=SEARCH_TOP_K)
|
res = mem().search(q, filters={"user_id": USER_ID}, top_k=SEARCH_TOP_K)
|
||||||
items = [_to_item(r, r.get("score")) for r in res.get("results", [])]
|
items = [_to_item(r, r.get("score")) for r in res.get("results", [])]
|
||||||
@@ -248,6 +251,8 @@ def list_memory(q: str = "", category: str = "") -> list[dict]:
|
|||||||
items.sort(key=lambda i: i.get("created_at", ""), reverse=True)
|
items.sort(key=lambda i: i.get("created_at", ""), reverse=True)
|
||||||
if category:
|
if category:
|
||||||
items = [i for i in items if i["category"] == category]
|
items = [i for i in items if i["category"] == category]
|
||||||
|
if bereich in ("alltag", "projekt"):
|
||||||
|
items = [i for i in items if i.get("bereich") in (bereich, "")]
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
@@ -349,8 +354,14 @@ def graph(min_score: float = 0.45, top_k: int = 3) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def classify_facts(facts: list[tuple[str, str]]) -> dict:
|
def classify_facts(facts: list[tuple[str, str]]) -> dict:
|
||||||
"""Ordnet jeden Fakt per LLM GENAU einer Kategorie zu → {id: category}.
|
"""Ordnet jeden Fakt per LLM einer Kategorie UND einem Bereich zu →
|
||||||
Nutzt dasselbe (Thinking-freie) Hirn wie die Extraktion. Fehler = leeres Mapping (Fallback)."""
|
{id: {"category": <slug>, "bereich": "alltag"|"projekt"}}.
|
||||||
|
|
||||||
|
Bereich (19.07.2026, Trennung der Welten): "alltag" = der Nutzer als Person
|
||||||
|
(Vorlieben, Leben, Arbeitsweise, Entscheidungen), "projekt" = Technik-/Code-/
|
||||||
|
Infrastruktur-Stände. Grundlage für einen späteren Recall-Filter, damit
|
||||||
|
Projekt-Schutt nicht in Alltags-Gespräche streut — OHNE zweites Gedächtnis.
|
||||||
|
Nutzt dasselbe (Thinking-freie) Hirn wie die Extraktion. Fehler = leeres Mapping."""
|
||||||
if not facts:
|
if not facts:
|
||||||
return {}
|
return {}
|
||||||
import json as _json
|
import json as _json
|
||||||
@@ -359,7 +370,11 @@ def classify_facts(facts: list[tuple[str, str]]) -> dict:
|
|||||||
system = (
|
system = (
|
||||||
"Du bist ein präziser Klassifikator für ein Langzeitgedächtnis. Ordne jeden Fakt GENAU EINER "
|
"Du bist ein präziser Klassifikator für ein Langzeitgedächtnis. Ordne jeden Fakt GENAU EINER "
|
||||||
"Kategorie zu. Verfügbare Kategorien (nur diese Slugs verwenden):\n" + cat_lines +
|
"Kategorie zu. Verfügbare Kategorien (nur diese Slugs verwenden):\n" + cat_lines +
|
||||||
"\nAntworte NUR als JSON-Objekt der Form {\"<id>\": \"<slug>\", …}."
|
"\nOrdne zusätzlich jeden Fakt GENAU EINEM Bereich zu: 'alltag' (der Nutzer als Person: "
|
||||||
|
"Vorlieben, Leben, Anrede, Arbeitsweise, grundsätzliche Entscheidungen) oder 'projekt' "
|
||||||
|
"(Technik: Code, Modelle, Server, Infrastruktur, Projekt- und Fehlerstände). "
|
||||||
|
"Antworte NUR als JSON-Objekt der Form "
|
||||||
|
"{\"<id>\": {\"kategorie\": \"<slug>\", \"bereich\": \"alltag|projekt\"}, …}."
|
||||||
)
|
)
|
||||||
body = "\n".join(f"{fid}: {txt}" for fid, txt in facts)
|
body = "\n".join(f"{fid}: {txt}" for fid, txt in facts)
|
||||||
try:
|
try:
|
||||||
@@ -370,7 +385,18 @@ def classify_facts(facts: list[tuple[str, str]]) -> dict:
|
|||||||
)
|
)
|
||||||
m = _re.search(r"\{.*\}", resp or "", _re.S)
|
m = _re.search(r"\{.*\}", resp or "", _re.S)
|
||||||
data = _json.loads(m.group(0)) if m else {}
|
data = _json.loads(m.group(0)) if m else {}
|
||||||
return {str(k): v for k, v in data.items() if v in CATEGORIES}
|
out: dict = {}
|
||||||
|
for k, v in data.items():
|
||||||
|
if isinstance(v, str): # Alt-Form {"id": "slug"} weiter akzeptieren
|
||||||
|
if v in CATEGORIES:
|
||||||
|
out[str(k)] = {"category": v, "bereich": ""}
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
cat = v.get("kategorie") or v.get("category") or ""
|
||||||
|
ber = v.get("bereich") or ""
|
||||||
|
if cat in CATEGORIES or ber in ("alltag", "projekt"):
|
||||||
|
out[str(k)] = {"category": cat if cat in CATEGORIES else DEFAULT_CATEGORY,
|
||||||
|
"bereich": ber if ber in ("alltag", "projekt") else ""}
|
||||||
|
return out
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("Auto-Klassifikation fehlgeschlagen")
|
log.exception("Auto-Klassifikation fehlgeschlagen")
|
||||||
return {}
|
return {}
|
||||||
@@ -434,17 +460,20 @@ def learn(body: LearnIn) -> dict:
|
|||||||
if r.get("id") and r.get("memory") and r.get("event") in ("ADD", "UPDATE")]
|
if r.get("id") and r.get("memory") and r.get("event") in ("ADD", "UPDATE")]
|
||||||
cats = classify_facts(to_classify)
|
cats = classify_facts(to_classify)
|
||||||
for r in results:
|
for r in results:
|
||||||
cat = cats.get(r.get("id"))
|
info = cats.get(r.get("id"))
|
||||||
if cat:
|
if info:
|
||||||
|
meta = {"category": info["category"], "source": body.source}
|
||||||
|
if info.get("bereich"):
|
||||||
|
meta["bereich"] = info["bereich"]
|
||||||
try:
|
try:
|
||||||
mem().update(r["id"], data=r.get("memory", ""),
|
mem().update(r["id"], data=r.get("memory", ""), metadata=meta)
|
||||||
metadata={"category": cat, "source": body.source})
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("Kategorie-Update fehlgeschlagen für %s", r.get("id"))
|
log.debug("Kategorie-Update fehlgeschlagen für %s", r.get("id"))
|
||||||
|
|
||||||
return {"results": [
|
return {"results": [
|
||||||
{"id": r.get("id"), "content": r.get("memory"), "event": r.get("event"),
|
{"id": r.get("id"), "content": r.get("memory"), "event": r.get("event"),
|
||||||
"category": cats.get(r.get("id"), body.category)}
|
"category": (cats.get(r.get("id")) or {}).get("category", body.category),
|
||||||
|
"bereich": (cats.get(r.get("id")) or {}).get("bereich", "")}
|
||||||
for r in results
|
for r in results
|
||||||
]}
|
]}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user