From 5c2014cb1146980101d005c6b57bb12c093c6fdb Mon Sep 17 00:00:00 2001 From: root Date: Sat, 28 Feb 2026 19:09:38 -0600 Subject: [PATCH] Fix: Proper session rotation detection (v1.2) Fixes the bug where watcher stayed stuck on old sessions after /new or /reset. Changes: - Added file_score() function combining mtime + size for better detection - Added INACTIVITY_THRESHOLD (30s) - if no new data, check for active session - Tracks last_data_time and file size to detect stale sessions - Switches to newer session when current is inactive The previous v1.1 fix (mtime polling) was incomplete because new sessions can have older mtime than recently-written old sessions. Tested: Watcher now properly follows session rotation on /new and /reset --- .../watcher/realtime_qdrant_watcher.py | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/.local_projects/true-recall-base/watcher/realtime_qdrant_watcher.py b/.local_projects/true-recall-base/watcher/realtime_qdrant_watcher.py index 9fb6a3d..e802911 100644 --- a/.local_projects/true-recall-base/watcher/realtime_qdrant_watcher.py +++ b/.local_projects/true-recall-base/watcher/realtime_qdrant_watcher.py @@ -1,9 +1,15 @@ #!/usr/bin/env python3 """ -TrueRecall v1.1 - Real-time Qdrant Watcher +TrueRecall v1.2 - Real-time Qdrant Watcher Monitors OpenClaw sessions and stores to memories_tr instantly. This is the CAPTURE component. For curation and injection, install v2. + +Changelog: +- v1.2: Fixed session rotation bug - added inactivity detection (30s threshold) + and improved file scoring to properly detect new sessions on /new or /reset +- v1.1: Added 1-second mtime polling for session rotation +- v1.0: Initial release """ import os @@ -134,6 +140,11 @@ def store_to_qdrant(turn: Dict[str, Any], dry_run: bool = False) -> bool: def get_current_session_file(): + """Find the most recently active session file. + + Uses a combination of creation time and modification time to handle + session rotation when /new or /reset is used. + """ if not SESSIONS_DIR.exists(): return None @@ -141,7 +152,20 @@ def get_current_session_file(): if not files: return None - return max(files, key=lambda p: p.stat().st_mtime) + # Score files by: recency (mtime) + size activity + # Files with very recent mtime AND non-zero size are likely active + def file_score(p: Path) -> float: + try: + stat = p.stat() + mtime = stat.st_mtime + size = stat.st_size + # Prefer files with recent mtime and non-zero size + # Add small bonus for larger files (active sessions grow) + return mtime + (size / 1e9) # size bonus is tiny vs mtime + except Exception: + return 0 + + return max(files, key=file_score) def parse_turn(line: str, session_name: str) -> Optional[Dict[str, Any]]: @@ -225,6 +249,10 @@ def watch_session(session_file: Path, dry_run: bool = False): last_position = 0 last_session_check = time.time() + last_data_time = time.time() # Track when we last saw new data + last_file_size = session_file.stat().st_size if session_file.exists() else 0 + + INACTIVITY_THRESHOLD = 30 # seconds - if no data for 30s, check for new session with open(session_file, 'r') as f: while running: @@ -232,15 +260,45 @@ def watch_session(session_file: Path, dry_run: bool = False): print("Session file removed, looking for new session...") return None + current_time = time.time() + # Check for newer session every 1 second - if time.time() - last_session_check > 1.0: - last_session_check = time.time() + if current_time - last_session_check > 1.0: + last_session_check = current_time newest_session = get_current_session_file() if newest_session and newest_session != session_file: print(f"Newer session detected: {newest_session.name}") return newest_session + # Check if current file is stale (no new data for threshold) + if current_time - last_data_time > INACTIVITY_THRESHOLD: + try: + current_size = session_file.stat().st_size + # If file hasn't grown, check if another session is active + if current_size == last_file_size: + newest_session = get_current_session_file() + if newest_session and newest_session != session_file: + print(f"Current session inactive, switching to: {newest_session.name}") + return newest_session + else: + # File grew, update tracking + last_file_size = current_size + last_data_time = current_time + except Exception: + pass + + # Process new lines and update activity tracking + old_position = last_position process_new_lines(f, session_name, dry_run) + + # If we processed new data, update activity timestamp + if last_position > old_position: + last_data_time = current_time + try: + last_file_size = session_file.stat().st_size + except Exception: + pass + time.sleep(0.1) return session_file