feat(brain): memory_save Tool — ARIA schreibt selber in die Qdrant-DB
ARIA hatte bisher KEIN Tool um eigene Notizen sauber zu persistieren — sie ist deshalb aufs Claude-Code-File-Memory ausgewichen (das wir mit dem letzten Commit per tmpfs abgeklemmt haben). Jetzt schliesst sich der Loop: ein echtes memory_save-Tool gegen die Qdrant-DB. Brain: - agent.py: memory_save als Meta-Tool mit Schema (title, content, type, optional category/tags/pinned). Tool-Description erklaert die Type-Wahl (identity/rule/preference/tool/skill = pinned, fact/conversation/reminder = cold) und sagt explizit: "Du hast KEIN File-Memory mehr, schreibe nicht in ~/.claude/projects/..." - Dispatcher: validiert type-enum, ruft self.embedder.embed + self.store.upsert, pushed memory_saved als _pending_events damit Bridge eine Bubble broadcasten kann. Side-Channel-Pipeline (gleich wie skill_created/trigger_created): - Bridge send_to_core + _handle_trigger_fired: forwarden memory_saved als RVS-Event - rvs/server.js: ALLOWED_TYPES += memory_saved - diagnostic/server.js: relayed memory_saved von RVS an Browser - diagnostic UI: addMemorySavedBubble (gelber Border) + Auto-Refresh des Gehirn-Tabs wenn aktiv - android: ChatMessage.memorySaved-Feld, Listener fuer memory_saved, renderMessage-Spezialbubble, History-Replace-Schutz (lokal-only) Damit ist die Architektur konsistent: "merk dir X" → ARIA ruft memory_save → Eintrag in Qdrant → Diagnostic-Gehirn-Tab zeigt's sofort → bei naechstem Turn liefert Cold Memory (Semantic Search) das Wissen wieder rein. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -206,6 +206,44 @@ META_TOOLS = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"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 "
|
||||
"File-Memory mehr — schreibe nicht in `~/.claude/projects/...`, das ist tot.\n\n"
|
||||
"Type-Wahl:\n"
|
||||
"- identity: ARIAs Selbstbild / Wesensart (PINNED)\n"
|
||||
"- rule: harte Regel / Sicherheit / Werte (PINNED)\n"
|
||||
"- preference: Stefans Vorlieben/Arbeitsweise (PINNED)\n"
|
||||
"- tool: Tool-Freigaben / Infrastruktur (PINNED)\n"
|
||||
"- skill: Faehigkeit / Workflow-Anleitung (PINNED)\n"
|
||||
"- fact: Wissen ueber Stefan/Welt/Sachen — z.B. 'Stefan hat eine Cessna'. "
|
||||
"Cold Memory, kommt nur via Semantic Search rein. **Default fuer 'merk-dir-das'-Anfragen.**\n"
|
||||
"- reminder: Termin/Aufgabe. Fuer ARIA-soll-ausloesen lieber trigger_timer.\n\n"
|
||||
"Wenn unsicher: type=fact, pinned=false."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string", "description": "Kurzer Titel (max ~80 Zeichen)"},
|
||||
"content": {"type": "string", "description": "Der eigentliche Inhalt — wird embedded fuer Semantic Search"},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["identity", "rule", "preference", "tool", "skill", "fact", "conversation", "reminder"],
|
||||
"description": "Memory-Typ (siehe oben)",
|
||||
},
|
||||
"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)."},
|
||||
},
|
||||
"required": ["title", "content", "type"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -467,6 +505,42 @@ class Agent:
|
||||
else:
|
||||
lines.append(f"- {t['name']} ({t['type']}, {state})")
|
||||
return "\n".join(lines)
|
||||
if name == "memory_save":
|
||||
title = (arguments.get("title") or "").strip()
|
||||
content = (arguments.get("content") or "").strip()
|
||||
mem_type = (arguments.get("type") or "fact").strip()
|
||||
if not title or not content:
|
||||
return "FEHLER: title und content sind Pflicht."
|
||||
valid_types = {"identity", "rule", "preference", "tool",
|
||||
"skill", "fact", "conversation", "reminder"}
|
||||
if mem_type not in valid_types:
|
||||
return f"FEHLER: type muss einer von {sorted(valid_types)} sein."
|
||||
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))
|
||||
try:
|
||||
from memory import MemoryPoint
|
||||
vec = self.embedder.embed(content)
|
||||
point = MemoryPoint(
|
||||
id="", type=mem_type, title=title, content=content,
|
||||
pinned=pinned, category=category, source="aria", tags=tags,
|
||||
)
|
||||
pid = self.store.upsert(point, vec)
|
||||
saved = self.store.get(pid)
|
||||
self._pending_events.append({
|
||||
"type": "memory_saved",
|
||||
"memory": {
|
||||
"id": saved.id, "type": saved.type, "title": saved.title,
|
||||
"content_preview": (saved.content or "")[:140],
|
||||
"category": saved.category, "pinned": saved.pinned,
|
||||
},
|
||||
})
|
||||
return (f"OK — Memory '{title}' gespeichert "
|
||||
f"(type={mem_type}, pinned={pinned}, id={saved.id[:8]}).")
|
||||
except Exception as e:
|
||||
logger.exception("memory_save fehlgeschlagen")
|
||||
return f"FEHLER beim Speichern: {e}"
|
||||
return f"Unbekanntes Tool: {name}"
|
||||
except Exception as exc:
|
||||
logger.exception("Tool '%s' fehlgeschlagen", name)
|
||||
|
||||
Reference in New Issue
Block a user