Files
AIPass/src/aipass/memory/apps/memory.py
T
AIOSAIandClaude Opus 4.6 da4fa82771 feat: port Memory Bank module to AIPass repo (FPLAN-0003)
Port internal Memory Bank system into src/aipass/memory/ with adapted
imports and paths. All 17 Python files compile clean, 13 existing tests
still pass.

Ported components:
- Entry point: apps/memory.py (drone @memory command routing)
- Modules: rollover.py (orchestration), search.py (query routing)
- Handlers: detector, extractor, line_counter, json_handler, indexer,
  embedder, chroma, vector_search, normalize, manager, dashboard_push,
  central_writer, memory_watcher, chroma_subprocess

Adaptations from internal system:
- Removed all /home/aipass/ hardcoded paths
- Removed sys.path manipulation hacks
- Replaced prax logger with stdlib logging
- Replaced cli console/header with Rich (already a dep)
- Changed BRANCH_REGISTRY → AIPASS_REGISTRY references
- Made chromadb/sentence-transformers optional (try/except)
- Removed private branch registry concept
- Subprocess Python uses sys.executable with AIPASS_MEMORY_PYTHON override

Still needs: drone registration, spawn template updates, container
testing, integration tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:49:53 -08:00

369 lines
12 KiB
Python
Executable File

# ===================AIPASS====================
# META DATA HEADER
# Name: memory.py - Memory System Entry Point
# Date: 2025-11-15
# Version: 0.2.0
# Category: memory
#
# CHANGELOG (Max 5 entries):
# - v0.2.0 (2026-03-06): Adapted for AIPass public repo - removed internal deps
# - v0.1.0 (2025-11-15): Initial version - modular architecture, module discovery
#
# CODE STANDARDS:
# - Follows AIPass architecture patterns (learned from Seed)
# - Module auto-discovery with handle_command() interface
# =============================================
"""
Memory System - Central Memory Archive & Rollover System
PURPOSE: Manages memory rollover, archival, and retrieval across all AIPass branches.
ARCHITECTURE:
- Entry point auto-discovers modules
- Modules orchestrate workflow by calling handlers
- Handlers implement domain-specific business logic
"""
import sys
import time
import signal
import logging
from pathlib import Path
from typing import List, Any
import importlib
from rich.console import Console
from rich.panel import Panel
from rich import box
from rich.table import Table
# =============================================================================
# INFRASTRUCTURE SETUP
# =============================================================================
logger = logging.getLogger(__name__)
console = Console()
# Package name for module discovery (relative imports within this package)
_PACKAGE_BASE = "aipass.memory.apps.modules"
# =============================================================================
# INTROSPECTION DISPLAY
# =============================================================================
def print_introspection():
"""Display discovered modules and status"""
console.print()
console.print("[bold cyan]Memory - Central Memory Archive System[/bold cyan]")
console.print()
console.print("[dim]Manages memory rollover and archival across AIPass branches[/dim]")
console.print()
# Discover modules
modules = discover_modules()
console.print(f"[yellow]Discovered Modules:[/yellow] {len(modules)}")
console.print()
for module in modules:
module_name = module.__name__.split('.')[-1]
console.print(f" [cyan]*[/cyan] {module_name}")
if not modules:
console.print(" [dim]No modules discovered yet[/dim]")
console.print()
console.print("[dim]Run 'python3 -m aipass.memory.apps.memory --help' for usage information[/dim]")
console.print()
# =============================================================================
# HELP SYSTEM
# =============================================================================
def print_help():
"""Display Rich-formatted help"""
console.print()
console.print(Panel.fit(
"[bold cyan]Memory - Central Memory Archive System[/bold cyan]\n[dim]Vector search, memory rollover, and fragmented memory for AIPass[/dim]",
border_style="cyan",
box=box.ROUNDED
))
console.print()
# What is Memory section
what_content = (
"Memory is the [bold]central memory archive[/bold] that:\n\n"
" [green]>[/green] Provides semantic search across all branch memories\n"
" [green]>[/green] Archives memories when branches hit rollover limits\n"
" [green]>[/green] Extracts symbolic dimensions from conversations"
)
console.print(Panel(
what_content,
title="[bold cyan]What is Memory?[/bold cyan]",
border_style="dim",
box=box.ROUNDED
))
console.print()
console.print("[bold cyan]AVAILABLE COMMANDS:[/bold cyan]")
console.print()
# Commands table showing actual drone commands
table = Table(show_header=True, header_style="bold cyan", border_style="dim")
table.add_column("Command", style="green")
table.add_column("Description", style="dim")
# Core commands
table.add_row("search <query>", "Semantic search across all branch memories")
table.add_row("rollover", "Execute memory rollover for files over 600 lines")
table.add_row("status", "Show rollover statistics for all branches")
table.add_row("check", "Check which files need rollover (dry run)")
table.add_row("watch", "Start memory watcher (auto-rollover on changes)")
table.add_row("sync-lines", "Update line count metadata for all branches")
table.add_row("push-templates", "Push template updates to all branches")
table.add_row("push-templates --dry-run", "Preview template changes without writing")
table.add_row("diff-templates", "Show template differences per branch")
table.add_row("template-status", "Show template version and push status")
table.add_row("symbolic demo", "Run fragmented memory demonstration")
table.add_row("symbolic fragments <q>", "Search symbolic fragments")
console.print(table)
console.print()
console.print("-" * 70)
console.print()
console.print("[bold cyan]USAGE:[/bold cyan]")
console.print()
console.print(" [yellow]Via Drone (recommended):[/yellow]")
console.print(" [dim]drone @memory search \"error handling\"[/dim]")
console.print(" [dim]drone @memory status[/dim]")
console.print(" [dim]drone @memory symbolic demo[/dim]")
console.print()
console.print(" [yellow]Direct execution:[/yellow]")
console.print(" [dim]python3 -m aipass.memory.apps.memory search \"query\"[/dim]")
console.print(" [dim]python3 -m aipass.memory.apps.memory rollover[/dim]")
console.print()
console.print("-" * 70)
console.print()
console.print("[bold cyan]SEARCH OPTIONS:[/bold cyan]")
console.print()
console.print(" [cyan]--branch BRANCH[/cyan] Filter by branch (e.g., SEED, CLI)")
console.print(" [cyan]--type TYPE[/cyan] Filter by memory type (observations, local)")
console.print(" [cyan]--n N[/cyan] Number of results (default: 5)")
console.print()
console.print(" [yellow]Example:[/yellow]")
console.print(" [dim]drone @memory search \"registry bugs\" --branch SEED --n 10[/dim]")
console.print()
console.print("-" * 70)
console.print()
console.print("[bold cyan]WATCH MODE:[/bold cyan]")
console.print()
console.print(" [yellow]Start memory file watcher:[/yellow]")
console.print(" [dim]drone @memory watch[/dim]")
console.print(" [dim]Monitors all branches, auto-rolls when limit exceeded[/dim]")
console.print(" [dim]Press Ctrl+C to stop[/dim]")
console.print()
console.print("-" * 70)
console.print()
console.print("Commands: search, rollover, status, check, watch, sync-lines, push-templates, diff-templates, template-status, symbolic")
console.print()
# =============================================================================
# MODULE DISCOVERY
# =============================================================================
MODULES_DIR = Path(__file__).parent / "modules"
def discover_modules() -> List[Any]:
"""
Auto-discover modules in modules/ directory
Pattern: Any .py file in modules/ that implements handle_command()
gets automatically discovered and registered.
Returns:
List of module objects with handle_command() method
"""
modules = []
if not MODULES_DIR.exists():
return modules
for file_path in MODULES_DIR.glob("*.py"):
if file_path.name.startswith("_"): # Skip __init__.py, __pycache__, etc.
continue
module_name = f"{_PACKAGE_BASE}.{file_path.stem}"
try:
module = importlib.import_module(module_name)
# Duck typing: If it has handle_command(), it's a module
if hasattr(module, 'handle_command'):
modules.append(module)
logger.info(f"[memory] Discovered module: {module_name}")
except Exception as e:
logger.error(f"[memory] Failed to load module {module_name}: {e}")
return modules
def route_command(command: str, args: List[str], modules: List[Any]) -> bool:
"""
Route command to appropriate module
Pattern: Each module's handle_command() returns True if it handled the command
Args:
command: Command name (e.g., 'rollover', 'search', 'status')
args: Additional arguments
modules: List of discovered modules
Returns:
True if command was handled, False otherwise
"""
# Built-in commands handled by entry point
if command == 'watch':
start_watch()
return True
for module in modules:
try:
if module.handle_command(command, args):
return True
except Exception as e:
logger.error(f"[memory] Module {module.__name__} error: {e}")
return False
# =============================================================================
# WATCH MODE
# =============================================================================
def start_watch() -> None:
"""
Start memory watcher - monitors branch memory files for auto-rollover
Watches all branches from AIPASS_REGISTRY.json. When a memory file
exceeds 600 lines, automatically triggers rollover.
Press Ctrl+C to stop.
"""
from ..handlers.monitor.memory_watcher import (
start_memory_watcher,
stop_memory_watcher,
is_memory_watcher_active,
get_watcher_status
)
from .modules.rollover import get_rollover_stats
# Signal handler for graceful shutdown
def signal_handler(sig, frame):
"""Handle SIGINT for graceful watcher shutdown."""
console.print("\n")
console.print("[dim]Stopping watcher...[/dim]")
stop_memory_watcher()
console.print("[green]>[/green] Watcher stopped")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
console.print()
console.print(Panel.fit(
"[bold cyan]Memory - Watch Mode[/bold cyan]",
border_style="cyan",
box=box.ROUNDED
))
console.print()
# Start the watcher
result = start_memory_watcher()
if not result.get('success'):
console.print(f"[red]x Failed to start watcher: {result.get('error')}[/red]")
return
console.print(f"[green]>[/green] Watching {result.get('count', 0)} branch directories")
console.print("[dim]Auto-rollover enabled when files exceed 600 lines[/dim]")
console.print("[dim]Press Ctrl+C to stop[/dim]")
console.print()
# Show initial status
stats = get_rollover_stats()
if stats.get('success'):
ready = stats.get('files_ready', 0)
total = stats.get('files_checked', 0)
status_marker = "[red]![/red]" if ready > 0 else "[green]OK[/green]"
console.print(f"{status_marker} Current: {total} files monitored, {ready} ready for rollover")
console.print()
# Keep running until Ctrl+C
console.print("[dim]Watcher active. Waiting for file changes...[/dim]")
while True:
time.sleep(1)
# =============================================================================
# MAIN ENTRY POINT
# =============================================================================
def main():
"""Main entry point - routes commands or shows help"""
# Parse arguments
args = sys.argv[1:]
# Show introspection when run without arguments
if len(args) == 0:
print_introspection()
return
# Version flag
if args[0] in ['--version', '-V']:
console.print("memory v1.0.0")
return
# Show help only for explicit help flags
if args[0] in ['--help', '-h', 'help']:
print_help()
return
# Command provided - try to route to modules
modules = discover_modules()
command = args[0]
remaining_args = args[1:] if len(args) > 1 else []
if route_command(command, remaining_args, modules):
return # Module handled it successfully
else:
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]python3 -m aipass.memory.apps.memory --help[/dim] for available commands")
console.print()
return
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
console.print("\n\nOperation cancelled by user")
sys.exit(0)
except Exception as e:
logger.error(f"[memory] Entry point error: {e}", exc_info=True)
console.print(f"\nError: {e}")
sys.exit(1)