Feat: Agent-Memory auf Mem0 (auto-lernend, semantisch) via Sidecar

Paket A des Plans. Ersetzt die flache SQLite-Fakten-DB durch Mem0 (LLM-Auto-
Extraktion + Vektor/Chroma-Suche). Architektur erzwungen durch Python-Split:
MC2-Backend laeuft auf 3.14 (kann mem0 nicht importieren), mem0+chromadb nur
auf 3.12 (~/.mem0/venv) -> Mem0-Sidecar (FastAPI, localhost:8765), MC2 spricht
ihn per HTTP. /api/memory-Form bleibt unveraendert (UI + MCP kompatibel).

- mem0_service/: Sidecar (app.py), Migration (migrate.py), deps.
  - Embeddings: neue llama-swap embed-Rolle (Qwen3-Embedding-0.6B, 1024 Dim,
    pooling last) ueber /v1/embeddings.
  - LLM-Extraktion: lokales fast-Hirn; NoThinkLLM schaltet Qwen3-Thinking ab
    (sonst bricht json_object-Extraktion ab), custom_instructions halten Deutsch.
- backend/services/memory.py: duenner HTTP-Client auf den Sidecar (semantische
  Suche mit score, verbatim add, learn()). Router: /api/memory/learn.
- mcp/mcp_memory.py: neues learn-Tool (Auto-Lernen aus Gespraechs-Turns),
  search jetzt semantisch.
- Frontend: Relevanz-Score + Auto/Manuell-Herkunft im Gedaechtnis-Tab.
- deploy/: mem0-service.service + deploy.sh (uv-Install, Migration, Restart).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 19:24:22 +02:00
parent 019f08093d
commit 05bef8642d
13 changed files with 590 additions and 185 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-BtXN9NH2.js"></script>
<script type="module" crossorigin src="/assets/index-BohGf1r3.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-SuFuJOVE.css">
</head>
<body>
+1
View File
@@ -267,6 +267,7 @@ export interface Memory {
source: string
created_at: string
updated_at: string
score?: number // Relevanz bei semantischer Suche (q gesetzt); sonst undefined
}
export interface DedupeResult {
+22 -2
View File
@@ -8,6 +8,9 @@ import { cn } from "@/lib/utils"
const CATEGORIES = ["user", "instruction", "stable", "versioned", "ephemeral"]
// Herkunft: automatisch gelernt (Mem0-Extraktion / Agent) vs. manuell angelegt.
const AUTO_SOURCES = new Set(["auto", "agent"])
const CAT_CONFIG: Record<string, { label: string; icon: any; color: string; border: string; bg: string; text: string }> = {
user: { label: "User", icon: User, color: "text-cyan-400", border: "border-cyan-500/30", bg: "bg-cyan-500/10", text: "text-cyan-400" },
instruction: { label: "Regel", icon: Scroll, color: "text-violet-400", border: "border-violet-500/30", bg: "bg-violet-500/10", text: "text-violet-400" },
@@ -154,7 +157,7 @@ export function MemoryView() {
<input
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="Gedächtnis durchsuchen..."
placeholder="Semantisch durchsuchen (nach Bedeutung, nicht nur Stichwort)..."
className="w-full h-9 pl-9 pr-3 rounded-lg border border-border/60 bg-card/45 text-xs outline-none focus:ring-1 focus:ring-primary/50 text-foreground"
/>
<Search className="absolute left-3 top-2.5 h-3.5 w-3.5 text-muted-foreground" />
@@ -228,7 +231,24 @@ export function MemoryView() {
</div>
<div className="flex items-center gap-3 shrink-0">
<span className="text-[9px] font-mono text-muted-foreground/60 bg-background/20 px-1.5 py-0.5 rounded uppercase tracking-wider">
{typeof m.score === "number" && (
<span
className="text-[9px] font-mono text-primary bg-primary/10 px-1.5 py-0.5 rounded uppercase tracking-wider"
title="Relevanz der semantischen Suche"
>
{Math.round(m.score * 100)}%
</span>
)}
<span
className={cn(
"flex items-center gap-1 text-[9px] font-mono px-1.5 py-0.5 rounded uppercase tracking-wider",
AUTO_SOURCES.has(m.source)
? "text-emerald-400 bg-emerald-500/10"
: "text-muted-foreground/60 bg-background/20"
)}
title={AUTO_SOURCES.has(m.source) ? "Automatisch gelernt" : "Manuell angelegt"}
>
{AUTO_SOURCES.has(m.source) && <Sparkles className="h-2.5 w-2.5" />}
{m.source}
</span>
<button