# =================== AIPass ==================== # Name: memory.py # Description: Entry point CLI for drone @memory # Version: 1.0.0 # Created: 2026-03-08 # Modified: 2026-03-08 # ============================================= """ 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 os import sys import time import signal from pathlib import Path from typing import List, Any import importlib if sys.platform == "win32": os.environ.setdefault("PYTHONUTF8", "1") for _stream in (sys.stdout, sys.stderr): _reconfigure = getattr(_stream, "reconfigure", None) if _reconfigure is not None: _reconfigure(encoding="utf-8", errors="replace") from rich.panel import Panel from rich import box from rich.table import Table from aipass.prax import logger from aipass.cli.apps.modules import console, error # ============================================================================= # INFRASTRUCTURE SETUP # ============================================================================= # 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"[bold cyan]Discovered Modules:[/bold cyan] {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 'drone @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" ) 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 — only those with backing modules table.add_row("rollover", "Show rollover module introspection") table.add_row("rollover run", "Execute memory rollover") table.add_row("rollover status", "Show rollover statistics") table.add_row("rollover check", "Dry run — check what needs rollover") table.add_row("rollover sync-lines", "Update line count metadata") table.add_row("rollover push", "⚠ Reset ALL per_branch limits to defaults (system-wide)") table.add_row("search ", "Semantic search across all branch memories") table.add_row("symbolic ", "Symbolic/fragmented memory extraction and search") table.add_row("templates ", "Living template push, diff, and status") table.add_row("pool process", "Process pool files + check/run rollover") table.add_row("pool status", "Show pool file count, config, vector stats") table.add_row("verify ", "Check if a plan is vectorized in ChromaDB") table.add_row("lint [@branch]", "Audit .trinity entries for over-limit violations (read-only)") table.add_row("watch", "Start memory watcher (auto-rollover on changes)") console.print(table) console.print() console.print("-" * 70) console.print() console.print("[bold cyan]USAGE:[/bold cyan]") console.print() console.print(" [bold]Via Drone (recommended):[/bold]") console.print(' [dim]drone @memory search "performance patterns"[/dim]') console.print(" [dim]drone @memory rollover status[/dim]") console.print(" [dim]drone @memory rollover run[/dim]") console.print() console.print(" [bold]Direct execution:[/bold]") console.print(' [dim]drone @memory search "query"[/dim]') console.print(" [dim]drone @memory rollover run[/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., SEEDGO, 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(" [bold]Example:[/bold]") console.print(' [dim]drone @memory search "registry bugs" --branch SEEDGO --n 10[/dim]') console.print() console.print("-" * 70) console.print() console.print("[bold cyan]WATCH MODE:[/bold cyan]") console.print() console.print(" [bold]Start memory file watcher:[/bold]") 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 [run|status|check|sync-lines|push], lint," " pool [process|status], symbolic, templates, verify, watch" ) 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 if command == "push": return route_command("rollover", ["push"], modules) 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 entry counts exceed v2 limits, automatically triggers rollover. Press Ctrl+C to stop. """ from ..handlers.monitor.memory_watcher import ( # type: ignore[import-not-found] start_memory_watcher, stop_memory_watcher, ) from ..handlers.monitor.detector import get_rollover_stats # type: ignore[import-not-found] # 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"): error(f"Failed to start watcher: {result.get('error')}") return console.print(f"[green]>[/green] Watching {result.get('count', 0)} branch directories") console.print("[dim]Auto-rollover enabled when files exceed limits[/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 remaining_args and remaining_args[0] in ["--help", "-h"]: remaining_args = ["--help"] for module in modules: if module.handle_command(command, remaining_args): return print_help() return if route_command(command, remaining_args, modules): return # Module handled it successfully else: console.print() error(f"Unknown command: {command}", suggestion="Run 'drone @memory --help' for available commands") console.print() return if __name__ == "__main__": try: main() except KeyboardInterrupt: logger.info("[memory] Operation cancelled by user (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) error(str(e)) sys.exit(1)