From 883fee36a3e47dd8d2918b4ab6b0f3bcd618be06 Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Tue, 17 Feb 2026 10:09:31 -0600 Subject: [PATCH] 3.4: Add per-agent memory scoping MemorySystem now accepts optional scope parameter. When set: - Memory files go to memory/{scope}/ subdirectory - Fallback search covers both scoped and shared directories Unscoped agents (scope="") use the shared memory/ root directory. This enables agents to have private memory while still searching shared knowledge. Co-Authored-By: Claude Opus 4.6 --- cheddahbot/memory.py | 49 +++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/cheddahbot/memory.py b/cheddahbot/memory.py index 98e7d80..006022c 100644 --- a/cheddahbot/memory.py +++ b/cheddahbot/memory.py @@ -23,10 +23,21 @@ log = logging.getLogger(__name__) class MemorySystem: - def __init__(self, config: Config, db: Database): + def __init__(self, config: Config, db: Database, scope: str = ""): self.config = config self.db = db - self.memory_dir = config.memory_dir + self.scope = scope + + # Scoped agents get their own subdirectory; shared memory stays in root + if scope: + self.memory_dir = config.memory_dir / scope + self.memory_dir.mkdir(parents=True, exist_ok=True) + else: + self.memory_dir = config.memory_dir + + # Shared memory dir (for cross-scope search) + self._shared_memory_dir = config.memory_dir + self._embedder = None self._embed_lock = threading.Lock() self._embed_db_path = self.memory_dir / "embeddings.db" @@ -235,18 +246,28 @@ class MemorySystem: self._embed_conn.commit() def _fallback_search(self, query: str, top_k: int) -> list[dict]: - """Simple keyword search when embeddings are unavailable.""" + """Simple keyword search when embeddings are unavailable. + + Searches both scoped and shared memory directories. + """ results = [] query_lower = query.lower() - for path in self.memory_dir.glob("*.md"): - try: - content = path.read_text(encoding="utf-8") - except Exception: - continue - for line in content.split("\n"): - stripped = line.strip().lstrip("- ") - if len(stripped) > 10 and query_lower in stripped.lower(): - results.append({"id": path.name, "text": stripped, "score": 1.0}) - if len(results) >= top_k: - return results + + # Collect directories to search (avoid duplicates) + search_dirs = [self.memory_dir] + if self.scope and self._shared_memory_dir != self.memory_dir: + search_dirs.append(self._shared_memory_dir) + + for search_dir in search_dirs: + for path in search_dir.glob("*.md"): + try: + content = path.read_text(encoding="utf-8") + except Exception: + continue + for line in content.split("\n"): + stripped = line.strip().lstrip("- ") + if len(stripped) > 10 and query_lower in stripped.lower(): + results.append({"id": path.name, "text": stripped, "score": 1.0}) + if len(results) >= top_k: + return results return results