""" Nexus Experience Store — Embodied Memory SQLite-backed store for lived experiences only. The model remembers what it perceived, what it thought, and what it did — nothing else. Each row is one cycle of the perceive→think→act loop. """ import sqlite3 import json import time from pathlib import Path from typing import Optional DEFAULT_DB = Path.home() / ".nexus" / "experience.db" MAX_CONTEXT_EXPERIENCES = 20 # Recent experiences fed to the model class ExperienceStore: def __init__(self, db_path: Optional[Path] = None): self.db_path = db_path or DEFAULT_DB self.db_path.parent.mkdir(parents=True, exist_ok=True) self.conn = sqlite3.connect(str(self.db_path)) self.conn.execute("PRAGMA journal_mode=WAL") self.conn.execute("PRAGMA synchronous=NORMAL") self._init_tables() def _init_tables(self): self.conn.executescript(""" CREATE TABLE IF NOT EXISTS experiences ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp REAL NOT NULL, perception TEXT NOT NULL, thought TEXT, action TEXT, action_result TEXT, cycle_ms INTEGER DEFAULT 0, session_id TEXT ); CREATE TABLE IF NOT EXISTS summaries ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp REAL NOT NULL, summary TEXT NOT NULL, exp_start INTEGER NOT NULL, exp_end INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_exp_ts ON experiences(timestamp DESC); CREATE INDEX IF NOT EXISTS idx_exp_session ON experiences(session_id); """) self.conn.commit() def record( self, perception: str, thought: Optional[str] = None, action: Optional[str] = None, action_result: Optional[str] = None, cycle_ms: int = 0, session_id: Optional[str] = None, ) -> int: """Record one perceive→think→act cycle.""" cur = self.conn.execute( """INSERT INTO experiences (timestamp, perception, thought, action, action_result, cycle_ms, session_id) VALUES (?, ?, ?, ?, ?, ?, ?)""", (time.time(), perception, thought, action, action_result, cycle_ms, session_id), ) self.conn.commit() return cur.lastrowid def recent(self, limit: int = MAX_CONTEXT_EXPERIENCES) -> list[dict]: """Fetch the most recent experiences for context.""" rows = self.conn.execute( """SELECT id, timestamp, perception, thought, action, action_result, cycle_ms FROM experiences ORDER BY timestamp DESC LIMIT ?""", (limit,), ).fetchall() return [ { "id": r[0], "timestamp": r[1], "perception": r[2], "thought": r[3], "action": r[4], "action_result": r[5], "cycle_ms": r[6], } for r in reversed(rows) # Chronological order ] def format_for_context(self, limit: int = MAX_CONTEXT_EXPERIENCES) -> str: """Format recent experiences as natural language for the model.""" experiences = self.recent(limit) if not experiences: return "You have no memories yet. This is your first moment." lines = [] for exp in experiences: ago = time.time() - exp["timestamp"] if ago < 60: when = f"{int(ago)}s ago" elif ago < 3600: when = f"{int(ago / 60)}m ago" else: when = f"{int(ago / 3600)}h ago" line = f"[{when}] You perceived: {exp['perception']}" if exp["thought"]: line += f"\n You thought: {exp['thought']}" if exp["action"]: line += f"\n You did: {exp['action']}" if exp["action_result"]: line += f"\n Result: {exp['action_result']}" lines.append(line) return "Your recent experiences:\n\n" + "\n\n".join(lines) def count(self) -> int: """Total experiences recorded.""" return self.conn.execute( "SELECT COUNT(*) FROM experiences" ).fetchone()[0] def save_summary(self, summary: str, exp_start: int, exp_end: int): """Store a compressed summary of a range of experiences. Used when context window fills — distill old memories.""" self.conn.execute( """INSERT INTO summaries (timestamp, summary, exp_start, exp_end) VALUES (?, ?, ?, ?)""", (time.time(), summary, exp_start, exp_end), ) self.conn.commit() def get_summaries(self, limit: int = 5) -> list[dict]: """Fetch recent experience summaries.""" rows = self.conn.execute( """SELECT id, timestamp, summary, exp_start, exp_end FROM summaries ORDER BY timestamp DESC LIMIT ?""", (limit,), ).fetchall() return [ {"id": r[0], "timestamp": r[1], "summary": r[2], "exp_start": r[3], "exp_end": r[4]} for r in reversed(rows) ] def close(self): self.conn.close()