diff --git a/k8s/openclaw/setup-compaction-cron.sh b/k8s/openclaw/setup-compaction-cron.sh index 0ccb056..04e2914 100755 --- a/k8s/openclaw/setup-compaction-cron.sh +++ b/k8s/openclaw/setup-compaction-cron.sh @@ -2,7 +2,13 @@ # ============================================================================== # setup-compaction-cron.sh # Declaratively registers the Multi-Agent Nightly Session Compaction in OpenClaw. -# Schedule: Every night at 03:30 Europe/Berlin (cron: 30 3 * * *) +# Schedule: Every night at 04:45 Europe/Berlin (02:45 UTC) (cron: 45 4 * * *) +# +# Architecture: +# - Tier A: Daily Hard-Reset to 0 for koch (Topic 21), hebamme (Topic 22), shopping (Topic 115) +# (100% externalized state in DuckDB/JSONL; resets prevent cross-day hallucination) +# - Tier B: Smart Compaction for long-running project agents (itguy, recherche, regensburg, fitness, doktor) +# - WAL checkpoints and SQLite optimization without blocking full VACUUM # ============================================================================== set -euo pipefail @@ -12,21 +18,24 @@ POD_NAME="${POD_NAME:-openclaw-0}" CONTAINER_NAME="${CONTAINER_NAME:-openclaw}" JOB_NAME="nightly-session-compaction" -CRON_SCHEDULE="30 3 * * *" +CRON_SCHEDULE="45 4 * * *" TIMEZONE="Europe/Berlin" -echo "=== Registering Multi-Agent Nightly Session Compaction in OpenClaw ($POD_NAME) ===" +echo "=== Registering Multi-Agent Nightly Session Maintenance in OpenClaw ($POD_NAME) ===" -# 1. Install or ensure python compaction script is present on PVC +# 1. Install or ensure python maintenance script is present on PVC echo "Ensuring /home/node/.openclaw/scripts/compact_sessions.py exists..." kubectl exec -i -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- /bin/sh -c 'cat << "EOF" > /home/node/.openclaw/scripts/compact_sessions.py #!/usr/bin/env python3 """ -Nightly multi-agent session compaction and maintenance script for OpenClaw. +Nightly multi-agent session maintenance and compaction script for OpenClaw. 1. Pre-warms OS page cache for SQLite databases on spinning disks. -2. Prunes abandoned isolated cron sessions (>48h old) to prevent token/state bloat. -3. Compacts all active forum topics and main sessions exceeding the token threshold. -4. Checkpoints SQLite WAL journals across all agent databases to disk. +2. Recovers any wedged or locked sessions via recover_sessions.py. +3. Prunes abandoned isolated cron sessions (>48h old) to prevent token/state bloat. +4. Tier A Hard-Reset: Resets koch, hebamme, and shopping topics to 0 tokens (clean archive). +5. Tier B Smart-Compaction: Compacts active sessions exceeding token thresholds (5s pacing). +6. Checkpoints SQLite WAL journals across all agent databases to disk. +7. Optimizes SQLite query planner without blocking full VACUUM. """ import glob import json @@ -42,6 +51,14 @@ THRESHOLD_EVENT_COUNT = 40 CRON_MAX_AGE_HOURS = 48 RPC_TIMEOUT_MS = 180000 +# Tier A: Event-logging agents whose state is 100% externalized (DuckDB / JSONL / Groceries DB). +# Nightly reset to 0 prevents cross-day hallucination and eliminates LLM compaction token costs. +TIER_A_RESET_TARGETS = [ + ("koch", "agent:koch:telegram:group:-1004326921510:topic:21"), + ("hebamme", "agent:hebamme:telegram:group:-1004326921510:topic:22"), + ("shopping", "agent:shopping:telegram:group:-1004326921510:topic:115"), +] + def prewarm_page_cache(): print("=== Pre-warming SQLite databases into OS page cache ===") t0 = time.time() @@ -128,6 +145,32 @@ def prune_stale_cron_sessions(): print(f"Cron pruning complete: {total_pruned} sessions pruned, {total_tokens_reclaimed} tokens reclaimed.") +def reset_tier_a_sessions(): + print("=== Tier A: Daily Hard-Reset to 0 (koch, hebamme, shopping) ===") + for agent, key in TIER_A_RESET_TARGETS: + db_path = f"/home/node/.openclaw/agents/{agent}/agent/openclaw-agent.sqlite" + if not os.path.exists(db_path): + continue + try: + con = sqlite3.connect(db_path, timeout=10.0) + row = con.execute("SELECT current_session_id FROM session_nodes WHERE session_key=?", (key,)).fetchone() + con.close() + if not row or not row[0]: + print(f" [Tier A] {agent} ({key}) already at 0 (no active session).") + continue + print(f" [Tier A] Resetting {agent} ({key})...") + r = subprocess.run( + ["openclaw", "sessions", "delete", key, "--agent", agent, "--yes"], + capture_output=True, text=True, timeout=60.0 + ) + if r.returncode == 0: + print(f" ✓ Reset success: {r.stdout.strip()}") + else: + print(f" ✗ Reset error: {r.stderr.strip()}") + except Exception as e: + print(f" ✗ Exception resetting {agent}: {e}") + time.sleep(1) + def get_session_metrics(agent, key, sid=None): db_path = f"/home/node/.openclaw/agents/{agent}/agent/openclaw-agent.sqlite" if not os.path.exists(db_path): @@ -151,7 +194,8 @@ def get_session_metrics(agent, key, sid=None): return 0, 0 def compact_active_sessions(): - print(f"=== Running OpenClaw Multi-Agent Compaction (threshold: {THRESHOLD_TOKENS} tokens / {THRESHOLD_TRANSCRIPT_BYTES//1024}KB, LLM summarization) ===") + tier_a_keys = set(t[1] for t in TIER_A_RESET_TARGETS) + print(f"=== Tier B: Running Multi-Agent Compaction (threshold: {THRESHOLD_TOKENS} tokens / {THRESHOLD_TRANSCRIPT_BYTES//1024}KB, LLM summarization) ===") res = subprocess.run(["openclaw", "sessions", "--all-agents", "--json"], capture_output=True, text=True) if res.returncode != 0: print(f"Failed to list sessions: {res.stderr}") @@ -167,6 +211,10 @@ def compact_active_sessions(): tokens = s.get("totalTokens") or 0 sid = s.get("sessionId") + # Skip Tier A agents as they are handled via hard-reset + if key in tier_a_keys: + continue + # Target main sessions and all telegram sessions (topics and direct) is_target = ( key == "agent:main:main" or @@ -204,7 +252,7 @@ def compact_active_sessions(): compacted += 1 else: print(f" ✗ Fallback failed: {fb_res.stderr.strip()}") - time.sleep(2) + time.sleep(5) # 5s pause to avoid rotational HDD I/O saturation else: skipped += 1 @@ -228,17 +276,16 @@ def checkpoint_wal_journals(): print(f"WAL Checkpoint complete: {wal_freed / (1024*1024):.2f} MB merged to disk.") def vacuum_and_optimize(): - print("=== Running SQLite VACUUM and OPTIMIZE across all databases ===") + print("=== Running SQLite OPTIMIZE across all databases ===") dbs = ["/home/node/.openclaw/state/openclaw.sqlite"] + glob.glob("/home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite") for db in dbs: try: - con = sqlite3.connect(db, timeout=60.0) + con = sqlite3.connect(db, timeout=30.0) con.execute("PRAGMA optimize;") - con.execute("VACUUM;") con.close() except Exception as e: - print(f" ✗ Error vacuuming {db}: {e}") - print("SQLite VACUUM and optimization complete.") + print(f" ✗ Error optimizing {db}: {e}") + print("SQLite optimization complete.") def main(): prewarm_page_cache() @@ -247,6 +294,7 @@ def main(): except Exception: pass prune_stale_cron_sessions() + reset_tier_a_sessions() compact_active_sessions() checkpoint_wal_journals() vacuum_and_optimize() @@ -258,16 +306,29 @@ chmod +x /home/node/.openclaw/scripts/compact_sessions.py ' # 2. Register / Update the OpenClaw cron job -echo "Registering OpenClaw Cron automation '$JOB_NAME' ($CRON_SCHEDULE @ $TIMEZONE)..." -kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- openclaw cron add \ - --name "Nightly Session Compaction" \ - --declaration-key "$JOB_NAME" \ - --description "Nightly multi-agent session compaction and SQLite WAL checkpointing" \ - --cron "$CRON_SCHEDULE" \ - --tz "$TIMEZONE" \ - --session "isolated" \ - --no-deliver \ - --command "python3 /home/node/.openclaw/scripts/compact_sessions.py" +echo "Checking existing OpenClaw Cron automation '$JOB_NAME'..." +EXISTING_ID=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- openclaw cron list --json 2>/dev/null | python3 -c 'import sys, json; data=json.load(sys.stdin); print(next((j["id"] for j in data.get("jobs", []) if j.get("declarationKey") == "'"$JOB_NAME"'"), ""))' 2>/dev/null || true) -echo "=== Compaction Cron automation successfully registered! ===" -kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- openclaw cron show "$JOB_NAME" 2>/dev/null || true +if [ -n "$EXISTING_ID" ]; then + echo "Updating existing OpenClaw Cron automation '$EXISTING_ID' ($CRON_SCHEDULE @ $TIMEZONE)..." + kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- openclaw cron edit "$EXISTING_ID" \ + --cron "$CRON_SCHEDULE" \ + --tz "$TIMEZONE" \ + --command "python3 /home/node/.openclaw/scripts/compact_sessions.py" +else + echo "Registering new OpenClaw Cron automation '$JOB_NAME' ($CRON_SCHEDULE @ $TIMEZONE)..." + kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- openclaw cron add \ + --name "Nightly Session Compaction" \ + --declaration-key "$JOB_NAME" \ + --description "Nightly multi-agent session compaction and SQLite WAL checkpointing" \ + --cron "$CRON_SCHEDULE" \ + --tz "$TIMEZONE" \ + --session "isolated" \ + --no-deliver \ + --command "python3 /home/node/.openclaw/scripts/compact_sessions.py" +fi + +echo "=== Compaction Cron automation successfully registered/updated! ===" +if [ -n "$EXISTING_ID" ]; then + kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER_NAME" -- openclaw cron get "$EXISTING_ID" 2>/dev/null || true +fi diff --git a/knowledge-wiki b/knowledge-wiki index 10d024d..12b5053 160000 --- a/knowledge-wiki +++ b/knowledge-wiki @@ -1 +1 @@ -Subproject commit 10d024da2dba80ad091da35e5c6dc641d540bc85 +Subproject commit 12b50535a6714a298391ffef005c0e9f3f83f7b1