feat(complete): Tests, README, Heartbeat, Dashboard
- tests/test_core.py: 6 Unit-Tests (alle grün) - README.md: Vollständige Dokumentation - HEARTBEAT.md: Proaktivitäts-Checkliste - Cron: Backup täglich 2 Uhr Closes #1, #6
This commit is contained in:
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# 🧠 Second Brain
|
||||||
|
|
||||||
|
Zweites Gehirn für OpenClaw - Langzeit- und Kurzzeitgedächtnis mit Bewertung, Proaktivität und Selbstheilung.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Engramme** - Gedächtniseinheiten mit Confidence, Korrektheit, Verknüpfungen
|
||||||
|
- **SQLite + FTS5** - Lokaler Speicher ohne externe Abhängigkeiten
|
||||||
|
- **Hybrid-Retrieval** - Keyword-Suche + Reranking (später + Embeddings)
|
||||||
|
- **Correctness-Tracking** - Richtig/Falsch-Feedback mit Lern-Loop
|
||||||
|
- **Proaktivität** - Heartbeat + Cron für selbständige Checks
|
||||||
|
- **Fehlerheilung** - Fehler als Engramme, Mustererkennung, Auto-Fix
|
||||||
|
- **Dashboard** - HTML-Visualisierung, kein Framework nötig
|
||||||
|
- **OpenClaw-Bridge** - Direkte Integration in Agent-Sessions
|
||||||
|
|
||||||
|
## Schnellstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/.openclaw/workspace/second-brain
|
||||||
|
|
||||||
|
# Engramm hinzufügen
|
||||||
|
python3 -m src.cli add "Das ist wichtig" --tag wichtig --source user
|
||||||
|
|
||||||
|
# Suchen
|
||||||
|
python3 -m src.cli search "wichtig"
|
||||||
|
|
||||||
|
# Feedback geben
|
||||||
|
python3 -m src.cli confirm <id>
|
||||||
|
python3 -m src.cli reject <id>
|
||||||
|
|
||||||
|
# Dashboard öffnen
|
||||||
|
python3 -m src.dashboard
|
||||||
|
|
||||||
|
# Stats
|
||||||
|
python3 -m src.cli stats
|
||||||
|
|
||||||
|
# Backup
|
||||||
|
python3 -m src.openclaw_bridge backup
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
python3 -m tests.test_core
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌──────────────┐ ┌────────────────┐
|
||||||
|
│ OpenClaw │────▶│ Bridge │────▶│ Engram Store │
|
||||||
|
│ Agent │ │ (Session) │ │ (SQLite) │
|
||||||
|
└─────────────────┘ └──────────────┘ └────────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────┐ ┌──────────────┐
|
||||||
|
│ Heartbeat │ │ Retriever │
|
||||||
|
│ (Cron/Check) │ │ (FTS + RR) │
|
||||||
|
└─────────────────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ Dashboard │
|
||||||
|
│ (HTML) │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module
|
||||||
|
|
||||||
|
| Datei | Zweck |
|
||||||
|
|-------|-------|
|
||||||
|
| `src/engram.py` | Engramm-Modell, Confidence, Correctness |
|
||||||
|
| `src/store.py` | SQLite-CRUD, FTS5-Index, Backup/Export |
|
||||||
|
| `src/retriever.py` | Suche, Reranking, Verknüpfungen |
|
||||||
|
| `src/cli.py` | Kommandozeilen-Interface |
|
||||||
|
| `src/openclaw_bridge.py` | OpenClaw-Integration, Heartbeat, Fehler-Handling |
|
||||||
|
| `src/dashboard.py` | HTML-Dashboard-Generator |
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
- **Repo**: http://192.168.6.31:3000/Otto/second-brain
|
||||||
|
- **Issues**: 8 offen (Features, Bugs)
|
||||||
|
- **Cron**: Täglich 2 Uhr Backup
|
||||||
|
|
||||||
|
## Nächste Schritte (Phase 2)
|
||||||
|
|
||||||
|
1. Vektor-Embeddings via sentence-transformers
|
||||||
|
2. ChromaDB-Store als Alternative zu SQLite
|
||||||
|
3. PyTorch Neural Scorer
|
||||||
|
4. Streamlit-Dashboard
|
||||||
|
5. Graph-Visualisierung (cytoscape.js)
|
||||||
106
tests/test_core.py
Normal file
106
tests/test_core.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Tests für Second Brain Kern-Module."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
from src.engram import Engram, Grounding, Correctness
|
||||||
|
from src.store import EngramStore
|
||||||
|
from src.retriever import Retriever
|
||||||
|
except ImportError:
|
||||||
|
from engram import Engram, Grounding, Correctness
|
||||||
|
from store import EngramStore
|
||||||
|
from retriever import Retriever
|
||||||
|
|
||||||
|
|
||||||
|
def test_engram_creation():
|
||||||
|
eg = Engram.create("Test content", source="test", tags=["a", "b"])
|
||||||
|
assert eg.content == "Test content"
|
||||||
|
assert eg.metadata["source"] == "test"
|
||||||
|
assert "a" in eg.metadata["tags"]
|
||||||
|
assert eg.compute_confidence() > 0
|
||||||
|
print("✅ test_engram_creation")
|
||||||
|
|
||||||
|
|
||||||
|
def test_correctness():
|
||||||
|
c = Correctness()
|
||||||
|
assert c.score() == 0.5
|
||||||
|
c.confirm("user")
|
||||||
|
assert c.score() == 1.0
|
||||||
|
c.reject("user")
|
||||||
|
assert c.score() == 0.5
|
||||||
|
print("✅ test_correctness")
|
||||||
|
|
||||||
|
|
||||||
|
def test_store_crud():
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False) as f:
|
||||||
|
db_path = f.name
|
||||||
|
|
||||||
|
store = EngramStore(db_path)
|
||||||
|
eg = Engram.create("Store test", tags=["store"])
|
||||||
|
store.save(eg)
|
||||||
|
|
||||||
|
loaded = store.get(str(eg.id))
|
||||||
|
assert loaded is not None
|
||||||
|
assert loaded.content == "Store test"
|
||||||
|
assert store.count() == 1
|
||||||
|
|
||||||
|
store.delete(str(eg.id))
|
||||||
|
assert store.count() == 0
|
||||||
|
|
||||||
|
store.close()
|
||||||
|
os.unlink(db_path)
|
||||||
|
print("✅ test_store_crud")
|
||||||
|
|
||||||
|
|
||||||
|
def test_search():
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False) as f:
|
||||||
|
db_path = f.name
|
||||||
|
|
||||||
|
store = EngramStore(db_path)
|
||||||
|
store.save(Engram.create("Python ist eine Programmiersprache", tags=["coding"]))
|
||||||
|
store.save(Engram.create("SQLite ist eine Datenbank", tags=["db"]))
|
||||||
|
store.save(Engram.create("JavaScript ist eine Sprache", tags=["coding"]))
|
||||||
|
|
||||||
|
ret = Retriever(store)
|
||||||
|
results = ret.retrieve("Programmiersprache Datenbank")
|
||||||
|
assert len(results) >= 2
|
||||||
|
|
||||||
|
results = ret.retrieve("nichtsexistenterbegriff12345")
|
||||||
|
assert len(results) == 0
|
||||||
|
|
||||||
|
store.close()
|
||||||
|
os.unlink(db_path)
|
||||||
|
print("✅ test_search")
|
||||||
|
|
||||||
|
|
||||||
|
def test_links():
|
||||||
|
eg1 = Engram.create("Parent")
|
||||||
|
eg2 = Engram.create("Child")
|
||||||
|
eg1.add_link(eg2)
|
||||||
|
assert eg2.id in eg1.links
|
||||||
|
assert eg1.id in eg2.links
|
||||||
|
print("✅ test_links")
|
||||||
|
|
||||||
|
|
||||||
|
def test_grounding():
|
||||||
|
eg = Engram.create("Test", grounding=Grounding.VERIFIED)
|
||||||
|
assert eg.metadata["grounding"] == 4
|
||||||
|
conf = eg.compute_confidence()
|
||||||
|
# VERIFIED sollte höheren Confidence geben als UNKNOWN
|
||||||
|
eg2 = Engram.create("Test2", grounding=Grounding.UNKNOWN)
|
||||||
|
assert eg.compute_confidence() > eg2.compute_confidence()
|
||||||
|
print("✅ test_grounding")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_engram_creation()
|
||||||
|
test_correctness()
|
||||||
|
test_store_crud()
|
||||||
|
test_search()
|
||||||
|
test_links()
|
||||||
|
test_grounding()
|
||||||
|
print("\n🎉 Alle 6 Tests bestanden!")
|
||||||
Reference in New Issue
Block a user