feat(brain): ARIA pflegt ihr Gedaechtnis selbst — Anleitung, Merkzeilen, Loeschen, ehrlichere Destillation
Anlass: Im Projekt Software Bude hat ARIA Test- (aria-wohnung) und
Prod-Schmiede (172.0.2.46) verwechselt, richtige Berichte fuer erfunden
erklaert und auf der Testumgebung ein Projekt angelegt. Ihr Wissen dazu lag
ungepinnt und veraltet ("ALTE Testinstanz") im kalten Gedaechtnis.
- Systemprompt: neuer Abschnitt "DEIN GEDAECHTNIS — so pflegst du es":
von sich aus speichern, was nicht live nachsehbar ist; was NICHT rein
gehoert (laufende Staende, eigene ungepruefte Berichte); Schubladen
(tool = Infrastruktur, angepinnt); vor dem Speichern suchen, aktualisieren
statt doppeln, Falsches loeschen; Erinnern != Wissen; jeder Stand-Bericht
nennt Quelle (wo/womit/wann).
- Verzeichnis: Titel + Merkzeile + Datum statt nur Titel; Cold-Treffer mit Datum.
- memory_save: identity/rule/preference/tool/skill werden angepinnt wie
beschrieben (vorher nur mit explizitem pinned=true — Infrastruktur landete
kalt). memory_update kann die Schublade aendern. Neu: memory_delete.
- Destillat: Fakten nur aus dem, was Stefan sagt/entscheidet/bestaetigt;
keine live nachsehbaren Staende (Ticket-Nummern, Status, Commit-IDs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+79
-6
@@ -1016,6 +1016,27 @@ META_TOOLS = [
|
||||
"category": {"type": "string", "description": "Neue Kategorie (optional)"},
|
||||
"tags": {"type": "array", "items": {"type": "string"}, "description": "Neue Tags (ueberschreibt komplett)"},
|
||||
"pinned": {"type": "boolean", "description": "Pinning aendern (optional)"},
|
||||
"type": {"type": "string", "enum": ["identity", "rule", "preference", "tool", "skill", "fact", "conversation", "reminder"],
|
||||
"description": "Schublade aendern (optional), z.B. fact → tool fuer Infrastruktur. tool/rule/preference/skill/identity werden dabei angepinnt."},
|
||||
},
|
||||
"required": ["id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_delete",
|
||||
"description": (
|
||||
"Loesche einen Gedaechtnis-Eintrag, der FALSCH oder UEBERHOLT ist "
|
||||
"(ID aus memory_search oder Cold-Memory). Ist er nur unvollstaendig, "
|
||||
"lieber memory_update. identity-Eintraege kann nur Stefan loeschen."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string", "description": "Memory-ID (UUID)"},
|
||||
"reason": {"type": "string", "description": "Kurz warum (landet im Log)"},
|
||||
},
|
||||
"required": ["id"],
|
||||
},
|
||||
@@ -1027,8 +1048,10 @@ META_TOOLS = [
|
||||
"name": "memory_save",
|
||||
"description": (
|
||||
"Speichere eine Information dauerhaft in deinem Gedaechtnis (Qdrant-DB). "
|
||||
"Nutze das wenn Stefan 'merk dir das' sagt oder du selbst etwas Wichtiges "
|
||||
"festhalten willst. ALTERNATIVEN VERMEIDEN: du hast KEIN persistentes "
|
||||
"Nutze das VON DIR AUS, sobald du etwas Nicht-Offensichtliches lernst "
|
||||
"(siehe 'DEIN GEDAECHTNIS' im Prompt) — nicht erst auf 'merk dir das'. "
|
||||
"VORHER memory_search: gibt es schon einen Eintrag, nimm memory_update. "
|
||||
"Keine live nachsehbaren, sich aendernden Staende speichern. ALTERNATIVEN VERMEIDEN: du hast KEIN persistentes "
|
||||
"File-Memory mehr — schreibe nicht in `~/.claude/projects/...`, das ist tot.\n\n"
|
||||
"Type-Wahl:\n"
|
||||
"- identity: ARIAs Selbstbild / Wesensart (PINNED)\n"
|
||||
@@ -1072,7 +1095,7 @@ META_TOOLS = [
|
||||
},
|
||||
"category": {"type": "string", "description": "Optional, freier Tag z.B. 'meine-sachen', 'kunden', 'persoenlichkeit'"},
|
||||
"tags": {"type": "array", "items": {"type": "string"}, "description": "Optionale Tags"},
|
||||
"pinned": {"type": "boolean", "description": "Default false. Nur true wenn die Info IMMER im System-Prompt liegen muss (Identitaet/Regeln/Praeferenzen)."},
|
||||
"pinned": {"type": "boolean", "description": "Weglassen = automatisch nach Schublade (identity/rule/preference/tool/skill angepinnt, fact nicht). Nur angeben, um davon abzuweichen."},
|
||||
"attach_paths": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
@@ -2372,6 +2395,9 @@ class Agent:
|
||||
# mehrstufig weiterdenkt. Info-Tools (web_search/memory_search) sind bewusst
|
||||
# AUSGENOMMEN: deren Inhalt darf das Wort "Fehler" tragen ohne dass der
|
||||
# Tool-Call scheiterte. Eskalieren ist immer sicher (nur langsamer).
|
||||
# Schubladen, die angepinnt sind (immer im System-Prompt)
|
||||
_PINNED_TYPES = {"identity", "rule", "preference", "tool", "skill"}
|
||||
|
||||
_SOFT_FAIL_MARKERS = (
|
||||
"fehler", "fehlgeschlagen", "nicht gefunden", "not found",
|
||||
"konnte nicht", "keine verbindung", "nicht verfuegbar",
|
||||
@@ -4405,6 +4431,15 @@ class Agent:
|
||||
existing.category = str(arguments["category"]).strip()
|
||||
if "tags" in arguments and arguments["tags"] is not None:
|
||||
existing.tags = [str(t).strip() for t in (arguments["tags"] or []) if str(t).strip()]
|
||||
if arguments.get("type"):
|
||||
new_type = str(arguments["type"]).strip()
|
||||
if new_type not in {"identity", "rule", "preference", "tool", "skill",
|
||||
"fact", "conversation", "reminder"}:
|
||||
return f"FEHLER: unbekannter type {new_type!r}."
|
||||
if new_type != existing.type:
|
||||
existing.type = new_type
|
||||
if arguments.get("pinned") is None:
|
||||
existing.pinned = new_type in self._PINNED_TYPES
|
||||
if "pinned" in arguments and arguments["pinned"] is not None:
|
||||
existing.pinned = bool(arguments["pinned"])
|
||||
existing.updated_at = _dt.datetime.now(_dt.timezone.utc).isoformat()
|
||||
@@ -4432,6 +4467,29 @@ class Agent:
|
||||
except Exception as e:
|
||||
logger.exception("memory_update fehlgeschlagen")
|
||||
return f"FEHLER: {e}"
|
||||
if name == "memory_delete":
|
||||
pid = (arguments.get("id") or "").strip()
|
||||
reason = (arguments.get("reason") or "").strip()
|
||||
if not pid:
|
||||
return "FEHLER: id ist Pflicht."
|
||||
existing = self.store.get(pid)
|
||||
if not existing:
|
||||
return f"FEHLER: Memory mit id={pid[:8]} nicht gefunden."
|
||||
if existing.type == "identity":
|
||||
return "FEHLER: identity-Eintraege loescht nur Stefan (Diagnostic)."
|
||||
try:
|
||||
self.store.delete(pid)
|
||||
logger.info("[memory] geloescht: %r (%s) — Grund: %s", existing.title, pid[:8], reason or "-")
|
||||
self._pending_events.append({
|
||||
"type": "memory_saved", "action": "deleted",
|
||||
"memory": {"id": pid, "type": existing.type, "title": existing.title,
|
||||
"content_preview": (existing.content or "")[:140],
|
||||
"category": existing.category, "pinned": existing.pinned},
|
||||
})
|
||||
return f"OK — Memory '{existing.title}' geloescht."
|
||||
except Exception as e:
|
||||
logger.exception("memory_delete fehlgeschlagen")
|
||||
return f"FEHLER: {e}"
|
||||
if name == "memory_save":
|
||||
title = (arguments.get("title") or "").strip()
|
||||
content = (arguments.get("content") or "").strip()
|
||||
@@ -4445,7 +4503,13 @@ class Agent:
|
||||
category = (arguments.get("category") or "").strip()
|
||||
tags_in = arguments.get("tags") or []
|
||||
tags = [str(t).strip() for t in tags_in if str(t).strip()] if isinstance(tags_in, list) else []
|
||||
pinned = bool(arguments.get("pinned", False))
|
||||
# Schubladen identity/rule/preference/tool/skill sind laut
|
||||
# Beschreibung ANGEPINNT — frueher nur, wenn pinned=true explizit
|
||||
# mitkam (Default false), Infrastruktur landete so im kalten Teil.
|
||||
if arguments.get("pinned") is None:
|
||||
pinned = mem_type in self._PINNED_TYPES
|
||||
else:
|
||||
pinned = bool(arguments.get("pinned"))
|
||||
attach_paths_in = arguments.get("attach_paths") or []
|
||||
attach_paths = [str(p).strip() for p in attach_paths_in if str(p).strip()] if isinstance(attach_paths_in, list) else []
|
||||
try:
|
||||
@@ -4695,13 +4759,22 @@ class Agent:
|
||||
|
||||
# Konversation als Klartext bauen
|
||||
transcript = "\n".join(
|
||||
f"[{t.role.upper()}] {t.content}" for t in old_turns
|
||||
f"[{'STEFAN' if t.role == 'user' else 'ARIA'}] {t.content}" for t in old_turns
|
||||
)[:30000] # Cap auf 30k Zeichen damit der Prompt nicht explodiert
|
||||
|
||||
system = (
|
||||
"Du extrahierst aus einer Konversation zwischen Stefan und ARIA die "
|
||||
"wichtigsten dauerhaft relevanten Fakten — keine Smalltalk-Details, "
|
||||
"keine flüchtigen Zustände. Antworte AUSSCHLIESSLICH mit gültigem JSON "
|
||||
"keine flüchtigen Zustände.\n"
|
||||
"QUELLE: Fakten kommen aus dem, was STEFAN sagt, entscheidet oder "
|
||||
"bestätigt. ARIAs Nachrichten sind Berichte und können falsch sein — "
|
||||
"übernimm daraus nur, was Stefan danach bestätigt hat.\n"
|
||||
"NICHT aufnehmen: Systemzustände, die man live nachsehen kann und die "
|
||||
"sich ändern (Projekt-/Ticket-Nummern im Fluss, Ticket-Status, Zeilen-"
|
||||
"zahlen, Commit-IDs, wer gerade woran arbeitet, 'gerade nachgesehen'-"
|
||||
"Befunde). Gut sind: Entscheidungen und ihr Grund, Stefans Vorlieben, "
|
||||
"dauerhafte Regeln/Abläufe, wo etwas läuft (falls Stefan es bestätigt).\n"
|
||||
"Antworte AUSSCHLIESSLICH mit gültigem JSON "
|
||||
"im Format: {\"facts\": [{\"title\": \"kurz, max 80 Zeichen\", "
|
||||
"\"content\": \"1-3 Sätze, konkret und nützlich\"}]}. "
|
||||
"Mindestens 0, höchstens 7 Facts. Wenn nichts wichtig genug ist: leeres Array."
|
||||
|
||||
Reference in New Issue
Block a user