| Server IP : 157.230.181.24 / Your IP : 216.73.217.11 Web Server : Apache/2.4.58 (Ubuntu) System : Linux conductive 6.8.0-117-generic #117-Ubuntu SMP PREEMPT_DYNAMIC Tue May 5 19:26:24 UTC 2026 x86_64 User : ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /proc/self/root/opt/aim-bots/ |
Upload File : |
"""Persistent buddy memory — facts learned across AIM sessions."""
from __future__ import annotations
import json
import logging
import re
import threading
import time
from datetime import datetime, timezone
from difflib import SequenceMatcher
from pathlib import Path
def _now_iso() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
class BuddyMemoryStore:
"""Per-buddy JSON memory that survives session resets and bot restarts."""
def __init__(
self,
persona_dir: Path,
friend_name: str = "Caleb",
max_facts: int = 50,
) -> None:
self.persona_dir = persona_dir
self.mem_dir = persona_dir / "memories"
self.mem_dir.mkdir(exist_ok=True)
self.friend_name = friend_name
self.max_facts = max_facts
self.log = logging.getLogger(f"memory.{persona_dir.name}")
self._lock = threading.Lock()
self._cache: dict[str, dict] = {}
def _path(self, buddy: str) -> Path:
safe = re.sub(r"[^\w.-]", "", buddy.lower()) or "buddy"
return self.mem_dir / f"{safe}.json"
def load(self, buddy: str) -> dict:
key = buddy.lower()
with self._lock:
if key in self._cache:
return self._cache[key]
path = self._path(buddy)
if path.exists():
data = json.loads(path.read_text())
else:
data = {"buddy": buddy, "facts": [], "updated_at": None}
self._cache[key] = data
return data
def _save(self, buddy: str, data: dict) -> None:
key = buddy.lower()
data["updated_at"] = _now_iso()
path = self._path(buddy)
path.write_text(json.dumps(data, indent=2) + "\n")
self._cache[key] = data
def facts(self, buddy: str) -> list[dict]:
return list(self.load(buddy).get("facts", []))
def format_for_prompt(self, buddy: str) -> str | None:
items = self.facts(buddy)
if not items:
return None
recent = items[-25:]
lines = []
for item in recent:
text = item.get("text", "").strip()
if text:
lines.append(f"- {text}")
if not lines:
return None
return (
f"LONG-TERM MEMORY about {self.friend_name} (from past AIM convos — days or weeks ago):\n"
+ "\n".join(lines)
+ "\n\nUse this naturally when relevant. Don't recite the list. Don't say you 'remember' or 'stored' anything."
)
@staticmethod
def _similar(a: str, b: str) -> bool:
return SequenceMatcher(None, a.lower().strip(), b.lower().strip()).ratio() >= 0.82
def add_facts(self, buddy: str, new_facts: list[str], source: str = "") -> list[str]:
cleaned = [f.strip() for f in new_facts if f and len(f.strip()) >= 8]
if not cleaned:
return []
with self._lock:
data = self.load(buddy)
facts: list[dict] = list(data.get("facts", []))
added: list[str] = []
for text in cleaned:
if any(self._similar(text, f.get("text", "")) for f in facts):
facts = [
{**f, "text": text, "learned_at": _now_iso(), "source": source or f.get("source", "")}
if self._similar(text, f.get("text", ""))
else f
for f in facts
]
if text not in added:
added.append(text)
continue
facts.append(
{
"text": text,
"learned_at": _now_iso(),
"source": source[:200] if source else "",
}
)
added.append(text)
if len(facts) > self.max_facts:
facts = facts[-self.max_facts :]
data["facts"] = facts
self._save(buddy, data)
if added:
self.log.info("learned %d fact(s) for %s: %s", len(added), buddy, added[:3])
return added
def should_try_learn(self, message: str) -> bool:
clean = message.strip()
if len(clean) < 12:
return False
lower = clean.lower()
if re.match(
r"^(hey|sup|yo|hi|hello|lol|haha|hahah|rofl|ok|k|kk|bye|later|ttyl|bai|nm|nothing)\.?!?$",
lower,
):
return False
if re.search(
r"\b(i|im|i'm|i've|my|me|we|our|got|have|going|work|job|school|girl|guy|show|"
r"concert|tomorrow|tonight|weekend|next week|today|dating|broke|mom|dad|parent|"
r"moving|car|interview|sick|tired|stressed|excited|nervous|crush)\b",
lower,
):
return True
return len(clean.split()) >= 10
def learn_from_exchange(
self,
llm,
buddy: str,
user_msg: str,
bot_reply: str,
recent_context: str,
) -> None:
if not self.should_try_learn(user_msg):
return
try:
facts = llm.extract_memory_facts(
self.friend_name,
user_msg,
bot_reply,
recent_context,
)
if facts:
self.add_facts(buddy, facts, source=user_msg[:200])
except Exception as exc:
self.log.warning("memory learn failed for %s: %s", buddy, exc)
def learn_async(
self,
llm,
buddy: str,
user_msg: str,
bot_reply: str,
recent_context: str,
) -> None:
threading.Thread(
target=self.learn_from_exchange,
args=(llm, buddy, user_msg, bot_reply, recent_context),
daemon=True,
name=f"memory-{buddy}",
).start()