Phase 2 Drei-Welten-Plan: No-Progress-Bremse + Betrieb-Playbook + mem0-Lern-Buendelung
- no-progress-bremse.py: generische, turn-uebergreifende Schleifenbremse (pre/post_tool_call); Muster-Hash statt hartkodierter Fehler-Strings, Selbst-Lernen via neue-signaturen.jsonl. Schliesst die Luecke der nativen guardrails (pro Turn, nur klassifizierte Fehler). - ensure-profile-hooks.py: kann Eintraege in BESTEHENDE Event-Bloecke einfuegen (zweiter pre_tool_call-Key haette den Tabu-Guard lautlos verschluckt) + registriert Bremse. - betrieb-playbook-Skill: SSH-/Deploy-Diagnose-Checklisten (Passphrase-Falle zuerst), Triage-Regel im Steckbrief-Hook; Skill wird in alle Profile gesynct. - mc2-memory: Lern-Extraktion gebuendelt (4 Turns oder 180 s idle = EIN Lauf) statt pro Turn - Extraktion konkurrierte mit Lucys Hirn um die Slots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,12 @@ MIN_USER_LEN = int(os.environ.get("MC2_MEMORY_MIN_USER_LEN", "25")) # zu Kurzes
|
||||
# wandern und Tage später ungefragt wiederverwendet werden.
|
||||
SKIP_UNTRUSTED = os.environ.get("MC2_MEMORY_SKIP_UNTRUSTED", "1") not in ("0", "false", "False", "no")
|
||||
UNTRUSTED_MARKERS = ("[Bildschirm-Sicht",)
|
||||
# Lern-Buendelung (Phase 2, 19.07.2026): Die LLM-Extraktion lief bisher nach JEDEM Turn und
|
||||
# konkurrierte dabei mit Lucys Hirn um die Slots (gleiches Modell, --parallel 2). Jetzt werden
|
||||
# Turns gesammelt und als EIN Gespraechs-Block extrahiert: nach BATCH_TURNS Turns oder wenn
|
||||
# BATCH_IDLE_S Sekunden Ruhe war. Ein Batch = ein Extraktionslauf statt N.
|
||||
BATCH_TURNS = max(1, int(os.environ.get("MC2_MEMORY_BATCH_TURNS", "4")))
|
||||
BATCH_IDLE_S = float(os.environ.get("MC2_MEMORY_BATCH_IDLE", "180"))
|
||||
|
||||
# Triviale Turns, die keinen dauerhaften Fakt tragen (reine Begrüßung/Quittung, Aufwärm-/Test-
|
||||
# Kommandos) → gar nicht erst zum Auto-Lernen schicken. Spart Extraktions-Läufe und verhindert
|
||||
@@ -77,6 +83,10 @@ class MC2MemoryProvider(MemoryProvider):
|
||||
self._worker: Optional[threading.Thread] = None
|
||||
self._stop = threading.Event()
|
||||
self._recall_cache: Dict[str, str] = {}
|
||||
# Turn-Puffer der Lern-Buendelung (siehe BATCH_TURNS/BATCH_IDLE_S).
|
||||
self._buf: List[List[Dict[str, str]]] = []
|
||||
self._buf_lock = threading.Lock()
|
||||
self._buf_last_add = 0.0
|
||||
|
||||
# -- Identität / Verfügbarkeit -------------------------------------------
|
||||
def name(self) -> str:
|
||||
@@ -164,13 +174,29 @@ class MC2MemoryProvider(MemoryProvider):
|
||||
a = (a + f"\n[genutzte Tools: {', '.join(tools[:6])}]").strip()
|
||||
if a:
|
||||
msgs.append({"role": "assistant", "content": a[:4000]})
|
||||
try:
|
||||
self._q.put_nowait(msgs)
|
||||
except queue.Full:
|
||||
log.debug("mc2-memory learn queue full — Turn übersprungen")
|
||||
with self._buf_lock:
|
||||
self._buf.append(msgs)
|
||||
self._buf_last_add = time.time()
|
||||
full = len(self._buf) >= BATCH_TURNS
|
||||
if full:
|
||||
self._drain_buffer()
|
||||
# Neue Fakten können gelandet sein → Recall-Cache invalidieren.
|
||||
self._recall_cache.clear()
|
||||
|
||||
def _drain_buffer(self) -> None:
|
||||
"""Gepufferte Turns als EINEN Gespraechs-Block in die Lern-Queue geben."""
|
||||
with self._buf_lock:
|
||||
if not self._buf:
|
||||
return
|
||||
merged: List[Dict[str, str]] = []
|
||||
for turn in self._buf:
|
||||
merged.extend(turn)
|
||||
self._buf.clear()
|
||||
try:
|
||||
self._q.put_nowait(merged)
|
||||
except queue.Full:
|
||||
log.debug("mc2-memory learn queue full — Batch übersprungen")
|
||||
|
||||
def _post_learn(self, msgs: list) -> None:
|
||||
try:
|
||||
httpx.post(f"{MC_URL}/api/memory/learn",
|
||||
@@ -182,8 +208,14 @@ class MC2MemoryProvider(MemoryProvider):
|
||||
def _run(self) -> None:
|
||||
while not self._stop.is_set():
|
||||
try:
|
||||
msgs = self._q.get(timeout=1.0)
|
||||
msgs = self._q.get(timeout=5.0)
|
||||
except queue.Empty:
|
||||
# Idle-Flush: Ruhe im Gespraech → angesammelte Turns jetzt lernen.
|
||||
with self._buf_lock:
|
||||
idle = (self._buf and
|
||||
time.time() - self._buf_last_add >= BATCH_IDLE_S)
|
||||
if idle:
|
||||
self._drain_buffer()
|
||||
continue
|
||||
try:
|
||||
self._post_learn(msgs)
|
||||
@@ -193,6 +225,7 @@ class MC2MemoryProvider(MemoryProvider):
|
||||
def _flush(self) -> None:
|
||||
"""Offene Turns garantiert rausschreiben — bei Session-Ende/CLI-Exit, wo der
|
||||
Prozess sofort beendet wird (daemon-Worker würde sonst mitten im POST sterben)."""
|
||||
self._drain_buffer()
|
||||
self._stop.set()
|
||||
if self._worker and self._worker.is_alive():
|
||||
self._worker.join(timeout=95.0) # laufenden Worker-POST zu Ende lassen
|
||||
|
||||
Reference in New Issue
Block a user