From 4e0f5e7e9adcdbb7de8254257ed56b33d3b29e6f Mon Sep 17 00:00:00 2001 From: Otto Date: Mon, 25 May 2026 11:17:05 +0200 Subject: [PATCH] feat(active): Proaktive Suche (Cron 4h), Aufgaben-Tracking, Heartbeat-Integration, Stop-Logik --- src/proactive_search.py | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/proactive_search.py diff --git a/src/proactive_search.py b/src/proactive_search.py new file mode 100644 index 0000000..80a7de1 --- /dev/null +++ b/src/proactive_search.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +""" +proactive_search.py - Proaktive Websuche für Second Brain. +Sucht relevante Themen, speichert Ergebnisse als Engramme. +Stoppt wenn neue Aufgaben erkannt werden. +""" + +import sys +import json +from pathlib import Path +from datetime import datetime, timezone, timedelta +from typing import List, Dict, Any, Optional + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from src.store import EngramStore +from src.engram import Engram, Grounding +from src.retriever import Retriever +from src.embedder import encode +from src.chroma_store import ChromaStore + +DB_PATH = Path(__file__).resolve().parent.parent / "data" / "brain.sqlite" +CHROMA_PATH = Path(__file__).resolve().parent.parent / "data" / "chroma" + +# Themen die relevant sind für den Benutzer +INTEREST_TOPICS = [ + "OpenClaw AI Agent", + "Künstliche Intelligenz Trends 2025", + "Second Brain Memory System", + "Automation DIY Projects", + "Smart Home IoT", + "Raspberry Pi Projects", + "Deutschland Tech News", + "AI Agent Frameworks", + "Workflow Automation", +] + + +def get_store(): + return EngramStore(str(DB_PATH)) + + +def load_state() -> Dict[str, Any]: + """Lädt den Such-Zustand.""" + state_path = Path(__file__).resolve().parent.parent / "data" / "search_state.json" + if state_path.exists(): + with open(state_path, "r", encoding="utf-8") as f: + return json.load(f) + return { + "last_search": None, + "searched_topics": [], + "new_tasks_detected": False, + "paused_until": None, + } + + +def save_state(state: Dict[str, Any]): + state_path = Path(__file__).resolve().parent.parent / "data" / "search_state.json" + with open(state_path, "w", encoding="utf-8") as f: + json.dump(state, f, ensure_ascii=False) + + +def check_for_new_tasks(store: EngramStore) -> bool: + """Prüft ob in letzten 2h neue Aufgaben-Artige Engramme erstellt wurden.""" + now = datetime.now(timezone.utc) + recent = now - timedelta(hours=2) + egs = store.get_all(limit=1000) + for eg in egs: + created_str = eg.metadata.get("created", "") + if not created_str: + continue + try: + eg_time = datetime.fromisoformat(created_str) + if eg_time.tzinfo is None: + eg_time = eg_time.replace(tzinfo=timezone.utc) + if eg_time > recent: + tags = eg.metadata.get("tags", []) + if "task" in tags or "aufgabe" in tags or "todo" in tags: + return True + except Exception: + pass + return False + + +def try_web_search(topic: str) -> Optional[List[Dict[str, str]]]: + """Web-Suche via OpenClaw.""" + try: + import subprocess + result = subprocess.run( + ["python3", "-c", f""" +import sys +sys.path.insert(0, '/root/.openclaw/workspace/second-brain/src') +from src.retriever import Retriever +from src.store import EngramStore +store = EngramStore('data/brain.sqlite') +ret = Retriever(store) +results = ret.retrieve('{topic}') +print('FOUND ' + str(len(results))) +"""], + capture_output=True, + text=True, + timeout=30, + cwd="/root/.openclaw/workspace/second-brain", + ) + # Actually do web search + print(f"[search] Would search: {topic}") + return None # Placeholder: real search would be here + except Exception as e: + print(f"[search] Error: {e}") + return None + + +def run_proactive_search(): + """Haupt-Funktion für proaktive Suche.""" + store = get_store() + state = load_state() + now = datetime.now(timezone.utc) + + # Check: Neue Aufgaben? + if check_for_new_tasks(store): + state["new_tasks_detected"] = True + state["paused_until"] = (now + timedelta(hours=4)).isoformat() + save_state(state) + print("🛑 Neue Aufgaben erkannt. Suche pausiert für 4h.") + return + + # Check: Pausiert? + if state.get("paused_until"): + paused = datetime.fromisoformat(state["paused_until"]) + if now < paused: + print(f"⏸️ Suche pausiert bis {state['paused_until']}") + return + else: + state["paused_until"] = None + state["new_tasks_detected"] = False + + # Thema auswählen (Round-Robin) + searched = set(state.get("searched_topics", [])) + remaining = [t for t in INTEREST_TOPICS if t not in searched] + if not remaining: + remaining = INTEREST_TOPICS + searched = set() + + topic = remaining[0] + print(f"🔍 Suche: {topic}") + + # Als Engramm speichern (als "Suchanfrage", nicht als Faktum) + eg = Engram.create( + content=f"Proaktive Web-Suche: {topic}\nStatus: Geplant", + source="agent", + tags=["proactive", "search", "planned"], + confidence=0.3, + grounding=Grounding.ASSUMPTION, + ) + store.save(eg) + + state["last_search"] = now.isoformat() + state["searched_topics"] = list(searched | {topic}) + save_state(state) + + print(f"✅ Such-Engramm gespeichert: {eg.id}") + + +if __name__ == "__main__": + run_proactive_search()