150 lines
5.2 KiB
Python
Executable File
150 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Save all conversation context to Redis (not just new turns).
|
|
|
|
Unlike hb_append.py which only saves NEW turns since last run,
|
|
this script saves ALL context from the session (or resets and saves fresh).
|
|
|
|
Usage: python3 save_mem.py [--user-id rob] [--reset]
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import redis
|
|
import argparse
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
# Config
|
|
REDIS_HOST = os.getenv("REDIS_HOST", "127.0.0.1")
|
|
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))
|
|
USER_ID = os.getenv("USER_ID", "yourname")
|
|
|
|
# Paths (portable)
|
|
WORKSPACE = Path(os.getenv("OPENCLAW_WORKSPACE", str(Path.home() / ".openclaw" / "workspace")))
|
|
SESSIONS_DIR = Path(os.getenv("OPENCLAW_SESSIONS_DIR", str(Path.home() / ".openclaw" / "agents" / "main" / "sessions")))
|
|
STATE_FILE = WORKSPACE / ".mem_last_turn"
|
|
|
|
def get_session_transcript():
|
|
"""Find the current session JSONL file."""
|
|
files = list(SESSIONS_DIR.glob("*.jsonl"))
|
|
if not files:
|
|
return None
|
|
return max(files, key=lambda p: p.stat().st_mtime)
|
|
|
|
def parse_all_turns():
|
|
"""Extract ALL conversation turns from current session."""
|
|
transcript_file = get_session_transcript()
|
|
if not transcript_file or not transcript_file.exists():
|
|
return []
|
|
|
|
turns = []
|
|
turn_counter = 0
|
|
try:
|
|
with open(transcript_file, 'r') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
entry = json.loads(line)
|
|
if entry.get('type') == 'message' and 'message' in entry:
|
|
msg = entry['message']
|
|
role = msg.get('role')
|
|
|
|
if role == 'toolResult':
|
|
continue
|
|
|
|
content = ""
|
|
if isinstance(msg.get('content'), list):
|
|
for item in msg['content']:
|
|
if isinstance(item, dict):
|
|
if 'text' in item:
|
|
content += item['text']
|
|
# Do not mix thinking into the main content buffer.
|
|
elif 'thinking' in item:
|
|
pass
|
|
elif isinstance(msg.get('content'), str):
|
|
content = msg['content']
|
|
|
|
if content and role in ('user', 'assistant'):
|
|
turn_counter += 1
|
|
turns.append({
|
|
'turn': turn_counter,
|
|
'role': role,
|
|
'content': content[:2000],
|
|
'timestamp': entry.get('timestamp', datetime.now(timezone.utc).isoformat()),
|
|
'user_id': USER_ID,
|
|
'session': str(transcript_file.name).replace('.jsonl', '')
|
|
})
|
|
except json.JSONDecodeError:
|
|
continue
|
|
except Exception as e:
|
|
print(f"Error reading transcript: {e}", file=sys.stderr)
|
|
return []
|
|
|
|
return turns
|
|
|
|
def save_to_redis(turns, user_id, reset=False):
|
|
"""Save turns to Redis. If reset, clear existing first."""
|
|
if not turns:
|
|
return 0
|
|
|
|
try:
|
|
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True)
|
|
key = f"mem:{user_id}"
|
|
|
|
# Clear existing if reset
|
|
if reset:
|
|
r.delete(key)
|
|
print(f"Cleared existing Redis buffer ({key})")
|
|
|
|
# Add all turns (LPUSH puts newest at front, so we reverse to keep order)
|
|
for turn in reversed(turns):
|
|
r.lpush(key, json.dumps(turn))
|
|
|
|
return len(turns)
|
|
except Exception as e:
|
|
print(f"Error writing to Redis: {e}", file=sys.stderr)
|
|
return 0
|
|
|
|
def update_state(last_turn_num):
|
|
"""Update last turn tracker."""
|
|
try:
|
|
with open(STATE_FILE, 'w') as f:
|
|
f.write(str(last_turn_num))
|
|
except Exception as e:
|
|
print(f"Warning: Could not save state: {e}", file=sys.stderr)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Save all conversation context to Redis")
|
|
parser.add_argument("--user-id", default=USER_ID, help="User ID for key naming")
|
|
parser.add_argument("--reset", action="store_true", help="Clear existing buffer first")
|
|
args = parser.parse_args()
|
|
|
|
# Get all turns
|
|
turns = parse_all_turns()
|
|
|
|
if not turns:
|
|
print("No conversation turns found in session")
|
|
sys.exit(0)
|
|
|
|
# Save to Redis
|
|
count = save_to_redis(turns, args.user_id, reset=args.reset)
|
|
|
|
if count > 0:
|
|
# Update state to track last turn
|
|
max_turn = max(t['turn'] for t in turns)
|
|
update_state(max_turn)
|
|
|
|
action = "Reset and saved" if args.reset else "Saved"
|
|
print(f"✅ {action} {count} turns to Redis (mem:{args.user_id})")
|
|
print(f" State updated to turn {max_turn}")
|
|
else:
|
|
print("❌ Failed to save to Redis")
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|