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
This commit is contained in:
@@ -1,9 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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.
|
Monitors OpenClaw sessions and stores to memories_tr instantly.
|
||||||
|
|
||||||
This is the CAPTURE component. For curation and injection, install v2.
|
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
|
import os
|
||||||
@@ -134,6 +140,11 @@ def store_to_qdrant(turn: Dict[str, Any], dry_run: bool = False) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def get_current_session_file():
|
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():
|
if not SESSIONS_DIR.exists():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -141,7 +152,20 @@ def get_current_session_file():
|
|||||||
if not files:
|
if not files:
|
||||||
return None
|
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]]:
|
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_position = 0
|
||||||
|
|
||||||
last_session_check = time.time()
|
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:
|
with open(session_file, 'r') as f:
|
||||||
while running:
|
while running:
|
||||||
@@ -232,15 +260,45 @@ def watch_session(session_file: Path, dry_run: bool = False):
|
|||||||
print("Session file removed, looking for new session...")
|
print("Session file removed, looking for new session...")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
# Check for newer session every 1 second
|
# Check for newer session every 1 second
|
||||||
if time.time() - last_session_check > 1.0:
|
if current_time - last_session_check > 1.0:
|
||||||
last_session_check = time.time()
|
last_session_check = current_time
|
||||||
newest_session = get_current_session_file()
|
newest_session = get_current_session_file()
|
||||||
if newest_session and newest_session != session_file:
|
if newest_session and newest_session != session_file:
|
||||||
print(f"Newer session detected: {newest_session.name}")
|
print(f"Newer session detected: {newest_session.name}")
|
||||||
return newest_session
|
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)
|
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)
|
time.sleep(0.1)
|
||||||
|
|
||||||
return session_file
|
return session_file
|
||||||
|
|||||||
Reference in New Issue
Block a user