diff --git a/src/dashboard.py b/src/dashboard.py
new file mode 100644
index 0000000..a380d0c
--- /dev/null
+++ b/src/dashboard.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+"""
+Second Brain Dashboard - Selbstgenerierendes HTML.
+Keine Web-Frameworks nötig, einfach Python + Browser.
+
+Usage:
+ python -m src.dashboard # Generiert und öffnet dashboard
+ python -m src.dashboard --serve # Startet lokalen HTTP-Server
+"""
+
+import json
+import argparse
+import http.server
+import socketserver
+import webbrowser
+from pathlib import Path
+from uuid import UUID
+
+from .store import EngramStore
+from .engram import Engram, Grounding
+from .retriever import Retriever
+
+DB_PATH = Path(__file__).parent.parent / "data" / "brain.sqlite"
+HTML_PATH = Path(__file__).parent.parent / "data" / "dashboard.html"
+
+
+def generate_dashboard() -> str:
+ """Generiert HTML-Dashboard aus aktuellem Brain-Stand."""
+ store = EngramStore(str(DB_PATH))
+ ret = Retriever(store)
+ stats = ret.stats()
+ egs = store.get_all(limit=100)
+
+ # Farbe nach Confidence
+ def color(conf):
+ if conf > 0.7: return "#2ecc71"
+ if conf > 0.4: return "#f1c40f"
+ return "#e74c3c"
+
+ def marker(conf):
+ if conf > 0.7: return "✅"
+ if conf > 0.4: return "⚠️"
+ return "❌"
+
+ def grounding_name(val):
+ try:
+ return Grounding(int(val)).name
+ except:
+ return "UNKNOWN"
+
+ # Liste der Engramme
+ rows = []
+ for eg in egs:
+ conf = eg.compute_confidence()
+ rows.append(f"""
+
+ | {marker(conf)} |
+ {str(eg.id)[:8]} |
+ {eg.content[:100]}{'...' if len(eg.content) > 100 else ''} |
+ {grounding_name(eg.metadata.get('grounding', 0))} |
+ {eg.metadata.get('source', '?')} |
+ {conf:.2f} |
+ {eg.correctness.confirmations}/{eg.correctness.rejections} |
+ {eg.metadata.get('access_count', 0)} |
+ {', '.join(eg.metadata.get('tags', [])[:3])} |
+
+ """)
+
+ html = f"""
+
+
+
+
+🧠 Second Brain Dashboard
+
+
+
+ 🧠 Second Brain Dashboard
+
+
+
+
{stats['total_engrams']}
Engramme
+
{stats['confirmed']}
Bestätigt
+
{stats['unconfirmed']}
Unbestätigt
+
{len(stats.get('sources', dict()))}
Quellen
+
{stats['db_size_bytes']/1024:.0f}
KB Größe
+
+
+
+
+
+
+
+
+
+ | Status |
+ ID |
+ Inhalt |
+ Grounding |
+ Quelle |
+ Confidence |
+ Feedback |
+ Zugriffe |
+ Tags |
+
+
+
+ {''.join(rows)}
+
+
+
+
+
+
+
+
+"""
+ return html
+
+
+def save_dashboard(path: str = None):
+ """Speichert Dashboard als HTML."""
+ path = path or str(HTML_PATH)
+ html = generate_dashboard()
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(html)
+ print(f"Dashboard saved: {path}")
+ return path
+
+
+def serve_dashboard(port: int = 8050):
+ """Startet lokalen HTTP-Server."""
+ save_dashboard()
+
+ class Handler(http.server.SimpleHTTPRequestHandler):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, directory=str(HTML_PATH.parent), **kwargs)
+
+ def log_message(self, format, *args):
+ pass # Stilles Logging
+
+ print(f"Serving at http://localhost:{port}/dashboard.html")
+ print("Press Ctrl+C to stop")
+ with socketserver.TCPServer(("", port), Handler) as httpd:
+ webbrowser.open(f"http://localhost:{port}/dashboard.html")
+ httpd.serve_forever()
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Second Brain Dashboard")
+ parser.add_argument("--serve", action="store_true", help="Start HTTP server")
+ parser.add_argument("--port", type=int, default=8050)
+ parser.add_argument("--output", default=str(HTML_PATH))
+ args = parser.parse_args()
+
+ if args.serve:
+ serve_dashboard(args.port)
+ else:
+ path = save_dashboard(args.output)
+ webbrowser.open(f"file://{path}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/openclaw_bridge.py b/src/openclaw_bridge.py
new file mode 100644
index 0000000..8e3aa93
--- /dev/null
+++ b/src/openclaw_bridge.py
@@ -0,0 +1,292 @@
+"""
+OpenClaw Bridge - Verbindung zwischen OpenClaw-System und Second Brain.
+
+Ermöglicht:
+1. Automatisches Speichern von Session-Inhalten als Engramme
+2. Proaktive Erinnerungen (Heartbeat)
+3. Selbstheilende Fehlerbehandlung
+4. Feedback-Integration (richtig/falsch)
+"""
+
+import os
+import sys
+import json
+import traceback
+from pathlib import Path
+from datetime import datetime, timezone
+from typing import Optional, List, Dict, Any
+
+# Second Brain Import
+from .store import EngramStore
+from .engram import Engram, Grounding
+from .retriever import Retriever
+
+
+# --- Konfiguration ---
+BRAIN_DB = Path(__file__).parent.parent / "data" / "brain.sqlite"
+
+
+def get_brain() -> EngramStore:
+ """Gibt initialisierten Brain-Store."""
+ BRAIN_DB.parent.mkdir(parents=True, exist_ok=True)
+ return EngramStore(str(BRAIN_DB))
+
+
+# --- 1. Auto-Save: Session-Inhalt als Engramm ---
+
+def save_session_learned(
+ content: str,
+ source: str = "agent",
+ tags: Optional[List[str]] = None,
+ session_id: Optional[str] = None,
+ confidence: float = 0.5,
+ grounding: Grounding = Grounding.ASSUMPTION,
+) -> Engram:
+ """
+ Speichert eine gelernte Information aus einer OpenClaw-Session.
+
+ Nutzung im Agent-Code:
+ from second_brain.src.openclaw_bridge import save_session_learned
+ eg = save_session_learned(
+ "Neues Faktum",
+ tags=["project", "wichtig"],
+ confidence=0.7,
+ )
+ """
+ store = get_brain()
+ eg = Engram.create(
+ content=content,
+ source=source,
+ tags=tags or [],
+ session_id=session_id,
+ confidence=confidence,
+ grounding=grounding,
+ )
+ store.save(eg)
+ return eg
+
+
+# --- 2. Proaktivität: Was soll ich tun? ---
+
+def heartbeat_check() -> Optional[str]:
+ """
+ Prüft ob proaktive Aktion sinnvoll ist.
+
+ Rückgabe: Nachricht für den User, oder None wenn nichts zu tun.
+ """
+ store = get_brain()
+ ret = Retriever(store)
+
+ # A: Unbestätigte Engramme die seit längerem nicht geprüft wurden
+ # B: Hohe-Prioritäts-Themen (tags wie "wichtig", "dringend")
+ # C: Fehler-Engramme die repeating sind
+
+ # Prüfe auf wichtige unbestätigte Engramme
+ egs = store.get_all(limit=50)
+ unconfirmed = [
+ eg for eg in egs
+ if not eg.correctness.confirmed and eg.compute_confidence() > 0.5
+ ][:5]
+
+ if unconfirmed:
+ ids = ", ".join([str(eg.id)[:8] for eg in unconfirmed])
+ contents = "\n".join([f" - {eg.content[:80]}" for eg in unconfimed])
+ return (
+ f"🧠 Second Brain Heartbeat\n"
+ f"Unbestätigte Engramme mit gutem Confidence-Score:\n{contents}\n"
+ f"Bestätige mit: `python -m src.cli confirm `"
+ )
+
+ # Prüfe auf Fehler-Muster
+ errors = store.search_tag("error", limit=5)
+ if len(errors) >= 3:
+ return (
+ f"⚠️ Second Brain: {len(errors)} Fehler-Engramme gespeichert.\n"
+ f"Möglicherweise wiederholendes Problem. Prüfe mit `python -m src.cli search --tag error`"
+ )
+
+ return None
+
+
+# --- 3. Fehlerbehandlung ---
+
+def handle_tool_error(
+ tool_name: str,
+ error_message: str,
+ context: Optional[Dict[str, Any]] = None,
+) -> Engram:
+ """
+ Speichert einen Fehler als Engramm für spätere Analyse.
+
+ Nutzung bei try/except um Tool-Calls:
+ try:
+ result = some_tool_call()
+ except Exception as e:
+ handle_tool_error("tool_name", str(e), context={"args": args})
+ """
+ store = get_brain()
+ content = f"FEHLER: {tool_name}\n{error_message}\n"
+ if context:
+ content += f"\nContext: {json.dumps(context, default=str, ensure_ascii=False)}"
+
+ eg = Engram.create(
+ content=content,
+ source="system",
+ tags=["error", tool_name],
+ confidence=0.0,
+ grounding=Grounding.UNKNOWN,
+ )
+ store.save(eg)
+
+ # Prüfe auf Wiederholung
+ similar = store.search_text(error_message[:50], limit=10)
+ same_errors = [e for e in similar if e.metadata.get("source") == "system" and "error" in e.metadata.get("tags", [])]
+ if len(same_errors) >= 3:
+ # Auto-Fix: Erstelle Engramm mit Lösungsstrategie
+ fix_eg = Engram.create(
+ content=f"AUTO-FIX-STRATEGIE für wiederholten Fehler '{tool_name}':\n"
+ f"1. Retry mit alternativem Ansatz\n"
+ f"2. Fallback auf simpleres Tool\n"
+ f"3. User benachrichtigen wenn Persistenz > 5x",
+ source="system",
+ tags=["auto-fix", tool_name, "strategy"],
+ confidence=0.6,
+ grounding=Grounding.INFERRED,
+ )
+ store.save(fix_eg)
+
+ return eg
+
+
+# --- 4. Feedback-Integration ---
+
+def user_feedback(engram_id: str, is_correct: bool, note: str = "") -> Engram:
+ """
+ Nimmt User-Feedback (richtig/falsch) auf.
+
+ Nutzung nach jeder Agent-Antwort:
+ if user_says_correct:
+ user_feedback(engram_id, True)
+ elif user_says_wrong:
+ user_feedback(engram_id, False, note="Falsches Datum")
+ """
+ store = get_brain()
+ eg = store.get(engram_id)
+ if not eg:
+ raise ValueError(f"Engram {engram_id} nicht gefunden")
+
+ if is_correct:
+ eg.correctness.confirm(by="user", note=note)
+ else:
+ eg.correctness.reject(by="user", note=note)
+
+ store.save(eg)
+ return eg
+
+
+# --- 5. Kontext-anreicherung für Agenten ---
+
+def enrich_context(topic: str, limit: int = 3) -> str:
+ """
+ Holt relevante Engramme und formatiert sie als Kontext für den Agent.
+
+ Nutzung vor Prompt-Generierung:
+ memory_context = enrich_context("Projekt X")
+ # memory_context in das Prompt einbauen
+ """
+ store = get_brain()
+ ret = Retriever(store)
+ results = ret.retrieve(topic, limit=limit, min_confidence=0.3)
+ if not results:
+ return ""
+
+ lines = ["\n📚 Second Brain Kontext:"]
+ for r in results:
+ eg = r["engram"]
+ conf = eg.compute_confidence()
+ marker = "✅" if conf > 0.7 else "⚠️" if conf > 0.4 else "❌"
+ g = Grounding(eg.metadata.get("grounding", 0)).name
+ lines.append(f" {marker} [{g}] {eg.content[:150]}")
+
+ return "\n".join(lines)
+
+
+# --- 6. Backup & Wartung ---
+
+def backup_daily() -> str:
+ """
+ Tägliches Backup als JSONL.
+
+ Sollte via Cron aufgerufen werden:
+ 0 2 * * * python -m second_brain.src.openclaw_bridge backup
+ """
+ store = get_brain()
+ ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
+ path = BRAIN_DB.parent / f"backup_{ts}.jsonl"
+ count = store.export_jsonl(str(path))
+ return f"Backup: {count} Engramme -> {path}"
+
+
+def stats() -> Dict[str, Any]:
+ """Gibt Statistiken zurück."""
+ store = get_brain()
+ return Retriever(store).stats()
+
+
+# --- CLI-Entrypoint ---
+
+def main():
+ """CLI für die Bridge-Funktionen."""
+ import argparse
+ parser = argparse.ArgumentParser(description="Second Brain OpenClaw Bridge")
+ sub = parser.add_subparsers(dest="cmd")
+
+ p_save = sub.add_parser("save", help="Speichere Lerngut")
+ p_save.add_argument("content")
+ p_save.add_argument("--tag", action="append", default=[])
+ p_save.add_argument("--confidence", type=float, default=0.5)
+
+ p_feedback = sub.add_parser("feedback", help="User-Feedback")
+ p_feedback.add_argument("id")
+ p_feedback.add_argument("--correct", action="store_true")
+ p_feedback.add_argument("--note", default="")
+
+ p_heartbeat = sub.add_parser("heartbeat", help="Heartbeat-Check")
+
+ p_backup = sub.add_parser("backup", help="Backup erstellen")
+
+ p_stats = sub.add_parser("stats", help="Statistiken")
+
+ p_context = sub.add_parser("context", help="Kontext holen")
+ p_context.add_argument("topic")
+ p_context.add_argument("--limit", type=int, default=3)
+
+ args = parser.parse_args()
+
+ if args.cmd == "save":
+ eg = save_session_learned(args.content, tags=args.tag, confidence=args.confidence)
+ print(f"Saved: {eg.id} (conf: {eg.compute_confidence():.2f})")
+
+ elif args.cmd == "feedback":
+ eg = user_feedback(args.id, args.correct, args.note)
+ print(f"Feedback recorded: {eg.id} -> {eg.compute_confidence():.2f}")
+
+ elif args.cmd == "heartbeat":
+ msg = heartbeat_check()
+ print(msg or "HEARTBEAT_OK")
+
+ elif args.cmd == "backup":
+ print(backup_daily())
+
+ elif args.cmd == "stats":
+ print(json.dumps(stats(), indent=2))
+
+ elif args.cmd == "context":
+ print(enrich_context(args.topic, args.limit))
+
+ else:
+ parser.print_help()
+
+
+if __name__ == "__main__":
+ main()