Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions core/episodic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
L3 EpisodicMemory — async important moments with emotional weight
"""

from shared.constants import DB_NAME
import json
import time
from dataclasses import dataclass
Expand All @@ -26,7 +27,7 @@ def __init__(self, cm: AsyncConnectionManager | None = None):

async def _init_db(self):
await self._cm.execute_script(
"memory.db",
DB_NAME,
"""
CREATE TABLE IF NOT EXISTS episodes (
episode_id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand All @@ -42,7 +43,7 @@ async def _init_db(self):
)

async def save(self, user_id: str, summary: str, emotional_weight: float = 0.5, tags: Optional[list[str]] = None) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"INSERT INTO episodes (user_id, summary, emotional_weight, tags, created_at) VALUES (?, ?, ?, ?, ?)",
(user_id, summary, emotional_weight, json.dumps(tags or []), time.time()),
Expand All @@ -51,7 +52,7 @@ async def save(self, user_id: str, summary: str, emotional_weight: float = 0.5,
return cursor.lastrowid

async def get_episodes(self, user_id: str, limit: int = 20, offset: int = 0) -> list[Episode]:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT * FROM episodes WHERE user_id=? ORDER BY created_at DESC LIMIT ? OFFSET ?",
(user_id, limit, offset),
Expand All @@ -60,7 +61,7 @@ async def get_episodes(self, user_id: str, limit: int = 20, offset: int = 0) ->
return [self._row_to_episode(r) for r in rows]

async def search_by_tag(self, user_id: str, tag: str, limit: int = 10) -> list[Episode]:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT * FROM episodes WHERE user_id=? AND tags LIKE ? ORDER BY created_at DESC LIMIT ?",
(user_id, f'%"{tag}"%', limit),
Expand All @@ -69,7 +70,7 @@ async def search_by_tag(self, user_id: str, tag: str, limit: int = 10) -> list[E
return [self._row_to_episode(r) for r in rows]

async def search(self, user_id: str, query: str, limit: int = 10) -> list:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT * FROM episodes WHERE user_id=? AND summary LIKE ? ORDER BY created_at DESC LIMIT ?",
(user_id, f"%{query}%", limit),
Expand All @@ -79,7 +80,7 @@ async def search(self, user_id: str, query: str, limit: int = 10) -> list:

async def archive_old(self, user_id: str, days: int = 90) -> int:
"""Archive old episodes into ArchivedMemories, then delete them."""
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cutoff = time.time() - (days * 86400)
cursor = await conn.execute(
"SELECT * FROM episodes WHERE user_id=? AND created_at < ? AND emotional_weight < 0.3",
Expand Down Expand Up @@ -112,7 +113,7 @@ async def archive_old(self, user_id: str, days: int = 90) -> int:

async def count(self, user_id: str) -> int:
"""Count episodes for a user (fast COUNT query)."""
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT COUNT(*) as cnt FROM episodes WHERE user_id=?",
(user_id,),
Expand Down
17 changes: 9 additions & 8 deletions core/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
L4 CoreMemory — async key-value facts with importance and typed memory (B7)
"""

from shared.constants import DB_NAME
import json
import logging
import time
Expand Down Expand Up @@ -31,7 +32,7 @@ def __init__(self, cm: AsyncConnectionManager | None = None):

async def _init_db(self):
await self._cm.execute_script(
"memory.db",
DB_NAME,
"""
CREATE TABLE IF NOT EXISTS core_memory (
entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand Down Expand Up @@ -88,7 +89,7 @@ async def save(
logger.warning("memory_kind=%s requires expires_at; auto-set +30d", kind.value)
expires_at = now + 30 * 86400

conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT entry_id FROM core_memory WHERE user_id=? AND key=?",
(user_id, key),
Expand Down Expand Up @@ -119,7 +120,7 @@ async def save(

async def get(self, user_id: str, key: str) -> CoreEntry | None:
"""Get a fact by key. Returns None if not found."""
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("SELECT * FROM core_memory WHERE user_id=? AND key=?", (user_id, key))
row = await cursor.fetchone()
return self._row_to_entry(row) if row else None
Expand All @@ -130,19 +131,19 @@ async def get_or_default(self, user_id: str, key: str, default: str = "") -> str
return entry.value if entry else default

async def get_all(self, user_id: str, limit: int = 50) -> list[CoreEntry]:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("SELECT * FROM core_memory WHERE user_id=? ORDER BY importance DESC LIMIT ?", (user_id, limit))
rows = await cursor.fetchall()
return [self._row_to_entry(r) for r in rows]

async def delete(self, user_id: str, key: str) -> bool:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("DELETE FROM core_memory WHERE user_id=? AND key=?", (user_id, key))
await conn.commit()
return cursor.rowcount > 0

async def search(self, user_id: str, query: str, limit: int = 10) -> list[dict]:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT * FROM core_memory WHERE user_id=? AND (key LIKE ? OR value LIKE ?) ORDER BY importance DESC LIMIT ?",
(user_id, f"%{query}%", f"%{query}%", limit),
Expand All @@ -151,7 +152,7 @@ async def search(self, user_id: str, query: str, limit: int = 10) -> list[dict]:
return [{"key": r["key"], "value": r["value"], "importance": r["importance"]} for r in rows]

async def count(self, user_id: str | None = None) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
if user_id:
cursor = await conn.execute("SELECT COUNT(*) FROM core_memory WHERE user_id=?", (user_id,))
else:
Expand Down Expand Up @@ -179,7 +180,7 @@ async def list_by_kind(
limit: int = 50,
) -> list[dict[str, Any]]:
"""List memories filtered by type."""
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
rows = await (
await conn.execute(
"""SELECT key, value, importance, memory_kind, expires_at,
Expand Down
11 changes: 6 additions & 5 deletions core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
L2 SessionStore — async session history with indexes
"""

from shared.constants import DB_NAME
import json
import time
import uuid
Expand Down Expand Up @@ -29,7 +30,7 @@ def __init__(self, cm: AsyncConnectionManager | None = None):

async def _init_db(self):
await self._cm.execute_script(
"memory.db",
DB_NAME,
"""
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
Expand All @@ -48,7 +49,7 @@ async def _init_db(self):

async def create_session(self, user_id: str) -> str:
session_id = f"sess_{user_id}_{int(time.time())}_{uuid.uuid4().hex[:8]}"
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
await conn.execute(
"INSERT INTO sessions (session_id, user_id, started_at) VALUES (?, ?, ?)",
(session_id, user_id, time.time()),
Expand All @@ -57,15 +58,15 @@ async def create_session(self, user_id: str) -> str:
return session_id

async def close_session(self, session_id: str, summary: str = "", state_deltas: Optional[dict] = None, topics: Optional[list[str]] = None):
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
await conn.execute(
"UPDATE sessions SET summary=?, state_deltas=?, topics=?, ended_at=? WHERE session_id=?",
(summary, json.dumps(state_deltas or {}), json.dumps(topics or []), time.time(), session_id),
)
await conn.commit()

async def get_recent_sessions(self, user_id: str, limit: int = 10) -> list["SessionRecord"]:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT * FROM sessions WHERE user_id=? ORDER BY started_at DESC LIMIT ?",
(user_id, limit),
Expand All @@ -80,7 +81,7 @@ async def get_session_summary(self, user_id: str) -> str:
return "\n".join([f"- {s.summary[:80]}" for s in sessions if s.summary])

async def count_sessions(self, user_id: Optional[str] = None) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
if user_id:
cursor = await conn.execute("SELECT COUNT(*) FROM sessions WHERE user_id=?", (user_id,))
else:
Expand Down
15 changes: 8 additions & 7 deletions features/audit_trail.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
AuditTrail — async, SQLite-based audit logging with rotation
"""

from shared.constants import DB_NAME
import json
import time
from typing import Any, Optional
Expand All @@ -15,7 +16,7 @@ def __init__(self, cm: Optional["AsyncConnectionManager"] = None):

async def _init_db(self):
await self._cm.execute_script(
"memory.db",
DB_NAME,
"""
CREATE TABLE IF NOT EXISTS audit_log (
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand All @@ -32,15 +33,15 @@ async def _init_db(self):
)

async def log(self, user_id: str, action: str, layer: Optional[str] = None, target_id: Optional[str] = None, details: Optional[dict] = None):
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
await conn.execute(
"INSERT INTO audit_log (user_id, action, layer, target_id, details, timestamp) VALUES (?, ?, ?, ?, ?, ?)",
(user_id, action, layer, target_id, json.dumps(details or {}), time.time()),
)
await conn.commit()

async def get_history(self, user_id: str, limit: int = 50, action: Optional[str] = None) -> list[dict[str, Any]]:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
if action:
cursor = await conn.execute(
"SELECT * FROM audit_log WHERE user_id=? AND action=? ORDER BY timestamp DESC LIMIT ?",
Expand All @@ -65,7 +66,7 @@ async def get_history(self, user_id: str, limit: int = 50, action: Optional[str]
]

async def count(self, user_id: Optional[str] = None) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
if user_id:
cursor = await conn.execute("SELECT COUNT(*) FROM audit_log WHERE user_id=?", (user_id,))
else:
Expand All @@ -75,14 +76,14 @@ async def count(self, user_id: Optional[str] = None) -> int:

async def cleanup_old(self, retention_days: int = 30) -> int:
cutoff = time.time() - (retention_days * 86400)
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("DELETE FROM audit_log WHERE timestamp < ?", (cutoff,))
await conn.commit()
return cursor.rowcount

async def archive_and_prune(self, retention_days: int = 30, archive_dir: Optional[str] = None) -> dict[str, int]:
cutoff = time.time() - (retention_days * 86400)
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT * FROM audit_log WHERE timestamp < ? ORDER BY timestamp",
(cutoff,),
Expand Down Expand Up @@ -116,7 +117,7 @@ async def archive_and_prune(self, retention_days: int = 30, archive_dir: Optiona
return {"archived": len(rows), "pruned": cursor.rowcount}

async def count_all(self) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("SELECT COUNT(*) FROM audit_log")
row = await cursor.fetchone()
return row[0] if row else 0
5 changes: 3 additions & 2 deletions features/compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
MemoryCompressor — async dedup and compression
"""

from shared.constants import DB_NAME
import time
from typing import Optional

Expand All @@ -13,7 +14,7 @@ def __init__(self, cm: Optional["AsyncConnectionManager"] = None):
self._cm = cm or connection_manager

async def deduplicate_core(self, user_id: str) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT user_id, key, COUNT(*) as cnt FROM core_memory WHERE user_id=? GROUP BY user_id, key HAVING cnt > 1",
(user_id,),
Expand All @@ -31,7 +32,7 @@ async def deduplicate_core(self, user_id: str) -> int:
return removed

async def compress_episodes(self, user_id: str, min_weight: float = 0.3) -> int:
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cutoff = time.time() - 30 * 86400
cursor = await conn.execute(
"DELETE FROM episodes WHERE user_id=? AND emotional_weight < ? AND created_at < ?",
Expand Down
9 changes: 5 additions & 4 deletions features/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Import/Export — async import/export memory between instances
"""

from shared.constants import DB_NAME
import json
import time
from pathlib import Path
Expand Down Expand Up @@ -34,13 +35,13 @@ async def export_user(self, user_id: str) -> str:
"sessions": sessions,
}

conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("SELECT * FROM core_memory WHERE user_id=?", (user_id,))
rows = await cursor.fetchall()
for r in rows:
core_memory.append({"key": r["key"], "value": r["value"], "importance": r["importance"], "created_at": r["created_at"]})

conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("SELECT * FROM episodes WHERE user_id=?", (user_id,))
rows = await cursor.fetchall()
for r in rows:
Expand All @@ -64,7 +65,7 @@ async def import_user(self, filepath: str, target_user_id: Optional[str] = None)
user_id = target_user_id or data.get("user_id", "default")
imported = {"core_memory": 0, "episodes": 0}

conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
for item in data.get("core_memory", []):
await conn.execute(
"INSERT OR REPLACE INTO core_memory (user_id, key, value, importance, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
Expand All @@ -73,7 +74,7 @@ async def import_user(self, filepath: str, target_user_id: Optional[str] = None)
imported["core_memory"] += 1
await conn.commit()

conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
for item in data.get("episodes", []):
await conn.execute(
"INSERT INTO episodes (user_id, summary, emotional_weight, tags, created_at) VALUES (?, ?, ?, ?, ?)",
Expand Down
9 changes: 5 additions & 4 deletions features/rate_limiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Rate Limiter — async SQLite-based per-user rate limiting + WebSocket connection limiting
"""

from shared.constants import DB_NAME
import threading
import time
from typing import Any, Optional
Expand All @@ -18,7 +19,7 @@ def __init__(self, cm: Optional["AsyncConnectionManager"] = None):

async def _init_db(self):
await self._cm.execute_script(
"memory.db",
DB_NAME,
"""
CREATE TABLE IF NOT EXISTS rate_limits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand All @@ -32,7 +33,7 @@ async def _init_db(self):
async def check(self, user_id: str) -> dict[str, Any]:
now = time.time()
cutoff = now - self._window_seconds
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
await conn.execute("DELETE FROM rate_limits WHERE timestamp < ?", (cutoff,))
await conn.execute("INSERT INTO rate_limits (user_id, timestamp) VALUES (?, ?)", (user_id, now))
cursor = await conn.execute(
Expand All @@ -56,7 +57,7 @@ async def check(self, user_id: str) -> dict[str, Any]:

async def get_stats(self, user_id: str) -> dict[str, Any]:
cutoff = time.time() - self._window_seconds
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute(
"SELECT COUNT(*) as cnt FROM rate_limits WHERE user_id=? AND timestamp >= ?",
(user_id, cutoff),
Expand All @@ -66,7 +67,7 @@ async def get_stats(self, user_id: str) -> dict[str, Any]:

async def cleanup_old(self) -> int:
cutoff = time.time() - (self._window_seconds * 10)
conn = await self._cm.get("memory.db")
conn = await self._cm.get(DB_NAME)
cursor = await conn.execute("DELETE FROM rate_limits WHERE timestamp < ?", (cutoff,))
await conn.commit()
return cursor.rowcount
Expand Down
Loading
Loading