feat(systemd): Dashboard-Service, brain_rules, 18 Engramme bewertet, Cron persistent
Neu: - systemd: secondbrain-dashboard.service (Port 8501, autostart) - cron_rules.py: Auto-Confirm ab 3x, Archiv nach 30d - cron_tasks/: heartbeat + backup + brain_rules (persistent) - openclaw_cron_wrapper.py: subprocess-Isolation (kein SessionTakeover) - chat_autosave.py: Auto-Save von Chat + Kontext-Anreicherung Daten: - 18 unbestätigte Engramme bewertet: - 14x CONFIRMED (Fakten/Definitionen korrekt) - 3x ARCHIVIERT (historisch, nicht aktuell) - 1x CONFIRMED (Regel 73624013) - 0 offene unbestätigte Closes Gitea-Issue: #9
This commit is contained in:
22
cron_tasks/backup_secondbrain.py
Normal file
22
cron_tasks/backup_secondbrain.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Backup-Task für Second Brain - isoliert, persistent."""
|
||||
import json, os, sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
BRAIN_DIR = Path("/root/.openclaw/workspace/second-brain")
|
||||
sys.path.insert(0, str(BRAIN_DIR))
|
||||
from src.store import EngramStore
|
||||
|
||||
def main():
|
||||
brain_db = os.environ.get("BRAIN_DB", str(BRAIN_DIR / "data" / "brain.sqlite"))
|
||||
store = EngramStore(brain_db)
|
||||
ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = Path(brain_db).parent / f"backup_{ts}.jsonl"
|
||||
count = store.export_jsonl(str(backup_path))
|
||||
result = {"timestamp": datetime.now(timezone.utc).isoformat(), "backup_path": str(backup_path), "count": count, "success": True}
|
||||
print(f"BACKUP: {count} Engramme -> {backup_path}")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
53
cron_tasks/brain_rules.py
Normal file
53
cron_tasks/brain_rules.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Brain-Regeln - Automatische Bestaetigungs- und Archivierungslogik.
|
||||
Wird von Cron und Agent aufgerufen.
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, "/root/.openclaw/workspace/second-brain")
|
||||
|
||||
from src.engram import Engram, Grounding
|
||||
from src.store import EngramStore
|
||||
|
||||
DB = "/root/.openclaw/workspace/second-brain/data/brain.sqlite"
|
||||
|
||||
|
||||
def apply_rules():
|
||||
store = EngramStore(DB)
|
||||
egs = store.get_all(limit=1000)
|
||||
actions = []
|
||||
|
||||
for eg in egs:
|
||||
conf = eg.compute_confidence()
|
||||
age_days = eg._age_days(eg.metadata.get("created", ""))
|
||||
correct = eg.correctness
|
||||
|
||||
# Regel 1: Triple-Confirm → Auto-Verifiziert
|
||||
if not correct.confirmed and correct.confirmations >= 3:
|
||||
correct.confirmed = True
|
||||
correct.confirmations += 1
|
||||
store.save(eg)
|
||||
actions.append(f"Auto-Confirm: {str(eg.id)[:8]} (3x confirmed)")
|
||||
|
||||
# Regel 2: Lang unbestaetigt → ASSUMPTION Tag
|
||||
if age_days > 30 and not correct.confirmed and "archiviert" not in eg.metadata.get("tags", []):
|
||||
eg.metadata.setdefault("tags", []).append("archiviert")
|
||||
eg.metadata["archivgrund"] = f"Unbestaetigt seit {age_days} Tagen"
|
||||
store.save(eg)
|
||||
actions.append(f"Archiviert: {str(eg.id)[:8]} (Alter {age_days}d)")
|
||||
|
||||
# Regel 3: Rejected mit 2+ Rejections → loeschen (Sanft: Tag statt rm)
|
||||
if correct.rejections >= 2:
|
||||
eg.metadata.setdefault("tags", []).append("deleted")
|
||||
store.save(eg)
|
||||
actions.append(f"Deleted-Tag: {str(eg.id)[:8]} ({correct.rejections}x rejected)")
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
actions = apply_rules()
|
||||
print("Brain-Regeln angewendet:")
|
||||
for a in actions or ["Keine Aktionen noetig"]:
|
||||
print(f" {a}")
|
||||
47
cron_tasks/heartbeat_secondbrain.py
Normal file
47
cron_tasks/heartbeat_secondbrain.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Heartbeat-Task für Second Brain - isoliert, persistent.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
BRAIN_DIR = Path("/root/.openclaw/workspace/second-brain")
|
||||
sys.path.insert(0, str(BRAIN_DIR))
|
||||
|
||||
from src.engram import Engram, Grounding
|
||||
from src.store import EngramStore
|
||||
|
||||
def main():
|
||||
output_file = os.environ.get("CRON_OUTPUT_FILE", "/tmp/heartbeat_result.json")
|
||||
brain_db = os.environ.get("BRAIN_DB", str(BRAIN_DIR / "data" / "brain.sqlite"))
|
||||
store = EngramStore(brain_db)
|
||||
|
||||
egs = store.get_all(limit=50)
|
||||
unconfirmed = [eg for eg in egs if not eg.correctness.confirmed and eg.compute_confidence() > 0.5][:5]
|
||||
errors = store.search_tag("error", limit=5)
|
||||
|
||||
result = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"total_engrams": len(egs),
|
||||
"unconfirmed_count": len(unconfirmed),
|
||||
"error_count": len(errors),
|
||||
"has_action": bool(unconfirmed) or len(errors) >= 3,
|
||||
"message": None,
|
||||
}
|
||||
|
||||
if unconfirmed:
|
||||
contents = "\n".join([f" - {eg.content[:80]}" for eg in unconfirmed])
|
||||
result["message"] = f"🧠 Unbestätigte Engramme:\n{contents}"
|
||||
elif len(errors) >= 3:
|
||||
result["message"] = f"⚠️ {len(errors)} Fehler-Engramme gespeichert."
|
||||
|
||||
Path(output_file).write_text(json.dumps(result, indent=2))
|
||||
print(f"HEARTBEAT: {result['unconfirmed_count']} unconfirmed, {result['error_count']} errors")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user