"""Pre-Compaction Snapshot — Captures the "hot zone" before memory loss. Orchestrates: thread tracker → hot snapshot → narrative → boot assembler. """ import json import subprocess from datetime import datetime, timezone from pathlib import Path from .common import get_workspace_dir, get_reboot_dir, get_nats_env from .thread_tracker import run as run_thread_tracker from .narrative_generator import run as run_narrative from .boot_assembler import run as run_boot_assembler def fetch_recent_messages(count: int = 20, workspace: Path = None) -> list[str]: """Fetch last N messages from NATS for snapshot.""" ws = workspace or get_workspace_dir() env = get_nats_env(ws) messages = [] try: result = subprocess.run( ["nats", "stream", "get", "openclaw-events", "--last", str(count), "--raw"], capture_output=True, text=True, timeout=15, env=env ) if result.returncode == 0 and result.stdout.strip(): for line in result.stdout.strip().split("\n"): try: evt = json.loads(line) content = evt.get("content", evt.get("message", evt.get("text", ""))) sender = evt.get("sender", evt.get("agent", evt.get("role", "?"))) if content and len(content.strip()) > 3: short = content.strip()[:200] if len(content.strip()) > 200: short += "..." messages.append(f"[{sender}] {short}") except json.JSONDecodeError: continue except Exception as e: messages.append(f"(NATS fetch failed: {e})") return messages def build_snapshot(messages: list[str]) -> str: """Build the hot snapshot markdown.""" now = datetime.now(timezone.utc) parts = [ f"# Hot Snapshot — {now.isoformat()[:19]}Z", "## Last ~30min before compaction", "", ] if messages: parts.append("**Recent conversation:**") for msg in messages[-15:]: parts.append(f"- {msg}") else: parts.append("(No recent messages captured)") parts.append("") return "\n".join(parts) def run(dry_run: bool = False, workspace: Path = None, **assembler_kwargs): """Run the full pre-compaction pipeline.""" ws = workspace or get_workspace_dir() reboot_dir = get_reboot_dir(ws) hot_snapshot_file = reboot_dir / "hot-snapshot.md" print("🔥 Pre-Compaction Snapshot — capturing hot zone...") # 1. Thread tracker (last 1h) print(" 1/4 Thread tracker (last 1h)...") run_thread_tracker(hours=1, workspace=ws) # 2. Hot snapshot print(" 2/4 Capturing recent messages...") messages = fetch_recent_messages(20, ws) snapshot = build_snapshot(messages) if dry_run: print(snapshot) else: hot_snapshot_file.write_text(snapshot) print(f" ✅ hot-snapshot.md written ({len(snapshot)} chars)") # 3. Narrative (no LLM for speed during compaction) print(" 3/4 Narrative generator...") run_narrative(no_llm=True, workspace=ws) # 4. Boot assembler print(" 4/4 Boot assembler...") run_boot_assembler(workspace=ws, **assembler_kwargs) print("🔥 Pre-Compaction Snapshot — done!") def main(): import argparse parser = argparse.ArgumentParser(description="Pre-Compaction Snapshot") parser.add_argument("--dry-run", action="store_true") parser.add_argument("--workspace", type=str, help="Workspace directory") args = parser.parse_args() ws = Path(args.workspace) if args.workspace else None run(dry_run=args.dry_run, workspace=ws) if __name__ == "__main__": main()