Unified hierarchical memory system. Combines all four tiers AND exposes the full MemoryManager-compatible API so core/memory.py can be a transparent shim: load_file, unload_file, touch_file, list_files, build_file_block, select_files_for_context, append_to_summ
| 428 | # ──────────────────────────────────────────────────────────────────────────── |
| 429 | |
| 430 | class Memory: |
| 431 | """ |
| 432 | Unified hierarchical memory system. |
| 433 | |
| 434 | Combines all four tiers AND exposes the full MemoryManager-compatible |
| 435 | API so core/memory.py can be a transparent shim: |
| 436 | |
| 437 | load_file, unload_file, touch_file, list_files, |
| 438 | build_file_block, select_files_for_context, |
| 439 | append_to_summary, compress_summary, get_summary, |
| 440 | tick, clear, evict_stale, status, _files (property) |
| 441 | """ |
| 442 | |
| 443 | def __init__(self): |
| 444 | self.working = WorkingMemory() |
| 445 | self.project = ProjectMemory() |
| 446 | self.longterm = LongTermMemory() |
| 447 | self.episodic = EpisodicMemory() |
| 448 | self._turn = 0 |
| 449 | self._summary = '' # rolling compressed work log |
| 450 | |
| 451 | # ── MemoryManager-compatible file API ──────────────────────────────────── |
| 452 | |
| 453 | def load_file(self, path: str, content: str = None) -> bool: |
| 454 | """ |
| 455 | Load a file into working memory. |
| 456 | Reads from disk if content is not provided. |
| 457 | Also stores in long-term memory (embeddings) if available. |
| 458 | """ |
| 459 | p = Path(path).expanduser() |
| 460 | if content is None: |
| 461 | if not p.exists(): |
| 462 | p = Path(os.getcwd()) / path |
| 463 | if not p.exists(): |
| 464 | return False |
| 465 | try: |
| 466 | content = p.read_text(encoding='utf-8', errors='replace') |
| 467 | except Exception: |
| 468 | return False |
| 469 | key = str(p.resolve()) |
| 470 | tokens = estimate_tokens(content, key) |
| 471 | self.working.add(key, content, tokens) |
| 472 | # Long-term indexing deferred — store_file chunks the content and |
| 473 | # calls the embedding server, which is too heavy during a file write |
| 474 | # (causes OOM on memory-constrained devices). Long-term indexing |
| 475 | # happens lazily on read_file or via /index command instead. |
| 476 | return True |
| 477 | |
| 478 | def unload_file(self, path: str): |
| 479 | """Remove a file from working memory.""" |
| 480 | key = str(Path(path).expanduser().resolve()) |
| 481 | self.working.remove(key) |
| 482 | |
| 483 | def touch_file(self, path: str): |
| 484 | """Mark a file as recently used (prevents LRU eviction).""" |
| 485 | key = str(Path(path).expanduser().resolve()) |
| 486 | self.working.touch(key) |
| 487 |