diff --git a/pyrightconfig.json b/pyrightconfig.json index 28ab1b05..b4be9b72 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,5 +1,5 @@ { - "extraPaths": ["src"], + "extraPaths": ["src", "src/aipass/memory/.venv/lib/python3.12/site-packages"], "pythonVersion": "3.10", "reportMissingImports": "error", "reportAttributeAccessIssue": "error", diff --git a/src/aipass/memory/apps/handlers/intake/pool_processor.py b/src/aipass/memory/apps/handlers/intake/pool_processor.py index 4213c0db..4de03ca8 100644 --- a/src/aipass/memory/apps/handlers/intake/pool_processor.py +++ b/src/aipass/memory/apps/handlers/intake/pool_processor.py @@ -1,13 +1,12 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: pool_processor.py - Memory Pool Intake Handler # Date: 2025-11-27 -# Version: 0.1.0 -# Category: memory_bank/handlers/intake +# Version: 0.2.0 +# Category: memory/handlers/intake # # CHANGELOG (Max 5 entries): +# - v0.2.0 (2026-03-16): Modernize imports, paths, remove MEMORY_BANK references # - v0.1.0 (2025-11-27): Initial version - process memory_pool files to vectors # # CODE STANDARDS: @@ -36,13 +35,13 @@ from datetime import datetime from typing import List, Dict, Any # Paths -MEMORY_BANK_ROOT = Path.home() / "MEMORY_BANK" -CONFIG_PATH = MEMORY_BANK_ROOT / "memory_bank_json" / "memory_bank.config.json" -MEMORY_POOL_PATH = MEMORY_BANK_ROOT / "memory_pool" -CHROMA_PATH = MEMORY_BANK_ROOT / ".chroma" +_MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent # handlers/intake/ → handlers/ → apps/ → memory/ +CONFIG_PATH = _MEMORY_ROOT / "config" / "memory_bank.config.json" +MEMORY_POOL_PATH = _MEMORY_ROOT / "memory_pool" +CHROMA_PATH = _MEMORY_ROOT / ".chroma" -AI_MAIL_PATH = Path.home() / "aipass_core" / "ai_mail" / "apps" / "ai_mail.py" +AI_MAIL_PATH = _MEMORY_ROOT.parent / "ai_mail" / "apps" / "ai_mail.py" def _notify_failure(subject: str, message: str) -> None: @@ -53,7 +52,7 @@ def _notify_failure(subject: str, message: str) -> None: capture_output=True, text=True, timeout=30, - cwd=str(MEMORY_BANK_ROOT) + cwd=str(_MEMORY_ROOT) ) except Exception: pass # Best-effort - don't let notification failure break processing @@ -61,7 +60,7 @@ def _notify_failure(subject: str, message: str) -> None: def _update_central_and_dashboard() -> None: """ - Update MEMORY_BANK.central.json and push dashboard section after vector writes. + Update memory central stats and push dashboard section after vector writes. Uses subprocess to call sibling handlers (central_writer, dashboard_push) to maintain handler independence. Failures are silent. @@ -72,8 +71,7 @@ def _update_central_and_dashboard() -> None: try: subprocess.run( [sys.executable, "-c", - "import sys; sys.path.insert(0, str(__import__('pathlib').Path.home()));" - "from MEMORY_BANK.apps.handlers.central_writer import update_central;" + "from aipass.memory.apps.handlers.central_writer import update_central;" "update_central()"], capture_output=True, text=True, timeout=30 ) @@ -84,8 +82,7 @@ def _update_central_and_dashboard() -> None: try: subprocess.run( [sys.executable, "-c", - "import sys; sys.path.insert(0, str(__import__('pathlib').Path.home()));" - "from MEMORY_BANK.apps.handlers.dashboard_push import push_memory_bank_dashboard;" + "from aipass.memory.apps.handlers.dashboard_push import push_memory_bank_dashboard;" "push_memory_bank_dashboard()"], capture_output=True, text=True, timeout=60 ) @@ -128,7 +125,7 @@ def load_config() -> dict: return {'enabled': False, 'error': str(e)} -def get_pool_files(extensions: List[str] = None) -> List[Path]: +def get_pool_files(extensions: List[str] | None = None) -> List[Path]: """ Get all files from memory_pool sorted by modification time (newest first). @@ -328,7 +325,7 @@ def archive_old_files(keep_recent: int, archive_path: str = 'memory_pool_archive archive_files = files[keep_recent:] # Create archive directory - archive_dir = MEMORY_BANK_ROOT / archive_path + archive_dir = _MEMORY_ROOT / archive_path archive_dir.mkdir(exist_ok=True) archived_count = 0 @@ -373,6 +370,9 @@ def process_memory_pool() -> dict: Returns: dict with full processing results """ + # Ensure memory_pool directory exists for file drops + MEMORY_POOL_PATH.mkdir(parents=True, exist_ok=True) + config = load_config() if not config.get('enabled', False): diff --git a/src/aipass/memory/apps/handlers/rollover/orchestrator.py b/src/aipass/memory/apps/handlers/rollover/orchestrator.py index cbce635b..9d2182e4 100644 --- a/src/aipass/memory/apps/handlers/rollover/orchestrator.py +++ b/src/aipass/memory/apps/handlers/rollover/orchestrator.py @@ -455,6 +455,53 @@ def execute_rollover() -> Dict[str, Any]: if failed: logger.error(f"[rollover] {len(failed)} operations failed") + # ========================================================================= + # Post-rollover processing chain + # ========================================================================= + if success_count > 0: + + # Post-rollover: fire event for trigger system + try: + from aipass.trigger.apps.modules.core import Trigger + Trigger.fire('rollover_complete', + triggers_count=len(triggers), + success_count=success_count, + failed_count=len(failed)) + logger.info("[rollover] Fired rollover_complete event") + except Exception: + pass # Trigger system optional + + # Post-rollover: update central stats + try: + from aipass.memory.apps.handlers.central_writer import update_central + central_result = update_central() + if central_result and central_result.get('success'): + logger.info("[rollover] Central stats updated") + else: + logger.warning(f"[rollover] Central update returned: {central_result}") + except Exception as e: + logger.warning(f"[rollover] Central writer unavailable: {e}") + + # Post-rollover: push dashboard + try: + from aipass.memory.apps.handlers.dashboard_push import push_memory_bank_dashboard + dash_ok = push_memory_bank_dashboard() + if dash_ok: + logger.info("[rollover] Dashboard pushed") + else: + logger.warning("[rollover] Dashboard push returned False") + except Exception as e: + logger.warning(f"[rollover] Dashboard push unavailable: {e}") + + # Post-rollover: process memory pool if files waiting + try: + from aipass.memory.apps.handlers.intake.pool_processor import process_memory_pool + pool_result = process_memory_pool() + if pool_result and pool_result.get('files_processed', 0) > 0: + logger.info(f"[rollover] Memory pool: {pool_result['files_processed']} files processed") + except Exception as e: + logger.info(f"[rollover] Memory pool check: {e}") + return { 'success': success_count > 0 or len(triggers) == 0, 'triggers_count': len(triggers), diff --git a/src/aipass/memory/apps/handlers/symbolic/chroma_client.py b/src/aipass/memory/apps/handlers/symbolic/chroma_client.py index f9c6b617..e9f0bb25 100644 --- a/src/aipass/memory/apps/handlers/symbolic/chroma_client.py +++ b/src/aipass/memory/apps/handlers/symbolic/chroma_client.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: chroma_client.py - Shared ChromaDB Client Handler @@ -25,7 +23,7 @@ write contention from multiple competing PersistentClient instances against the same .chroma/ directory. ALL handlers that need ChromaDB access MUST import from here: - from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_client + from aipass.memory.apps.handlers.symbolic.chroma_client import get_client Key Functions: - get_client(db_path) - get singleton PersistentClient (preferred) @@ -33,21 +31,17 @@ Key Functions: - get_collection() - get or create a collection with correct settings """ -import sys from typing import Dict, Any from pathlib import Path -# Infrastructure setup -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) -sys.path.insert(0, str(Path.home())) - # ============================================================================= # CONSTANTS # ============================================================================= -DEFAULT_DB_PATH = Path.home() / "MEMORY_BANK" / ".chroma" +# memory/ root resolved from symbolic/chroma_client.py +_MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent +DEFAULT_DB_PATH = _MEMORY_ROOT / ".chroma" # Singleton client cache - keyed by resolved path string _clients: Dict[str, Any] = {} @@ -66,7 +60,7 @@ def get_client(db_path: Path | str | None = None): PersistentClient instances to avoid write contention on .chroma/. Args: - db_path: Optional path to ChromaDB (default: MEMORY_BANK/.chroma) + db_path: Optional path to ChromaDB (default: memory/.chroma) Accepts Path or str. Returns: diff --git a/src/aipass/memory/apps/handlers/symbolic/deduplicator.py b/src/aipass/memory/apps/handlers/symbolic/deduplicator.py index 212dc908..ee1c9e63 100644 --- a/src/aipass/memory/apps/handlers/symbolic/deduplicator.py +++ b/src/aipass/memory/apps/handlers/symbolic/deduplicator.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: deduplicator.py - Symbolic Fragment Deduplication Handler @@ -31,14 +29,11 @@ Key Functions: import json import re -import sys from typing import Dict, List, Any, Optional from pathlib import Path -# Infrastructure setup -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) -sys.path.insert(0, str(Path.home())) +# memory/ root resolved from symbolic/deduplicator.py +_MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent # ============================================================================= @@ -121,9 +116,11 @@ def deduplicate_fragment( from pathlib import Path api_key = None + _aipass_root = _MEMORY_ROOT.parent # memory/ -> aipass/ for env_path in [ - Path.home() / "aipass_core" / "api" / "apps" / ".env", - Path.home() / "aipass_core" / "api" / ".env", + Path.home() / ".secrets" / "aipass" / ".env", + _aipass_root / "api" / "apps" / ".env", + _aipass_root / "api" / ".env", ]: if env_path.exists(): with open(env_path, encoding="utf-8") as f: diff --git a/src/aipass/memory/apps/handlers/symbolic/extractor.py b/src/aipass/memory/apps/handlers/symbolic/extractor.py index bbae46fb..322307c0 100644 --- a/src/aipass/memory/apps/handlers/symbolic/extractor.py +++ b/src/aipass/memory/apps/handlers/symbolic/extractor.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: extractor.py - Symbolic Memory Extractor Handler @@ -28,14 +26,13 @@ v2 (LLM): extract_fragments_llm, analyze_conversation_llm import json import re -import sys from collections import Counter from datetime import datetime from pathlib import Path from typing import Dict, List, Any, Optional -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) +# memory/ root resolved from symbolic/extractor.py +_MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent # ============================================================================= # v1 REGEX EXTRACTION (fallback) @@ -364,9 +361,11 @@ def extract_fragments_llm(chat_history: List[Dict[str, Any]]) -> Dict[str, Any]: # Load API key from env file api_key = None + _aipass_root = _MEMORY_ROOT.parent # memory/ -> aipass/ for env_path in [ - Path.home() / "aipass_core" / "api" / "apps" / ".env", - Path.home() / "aipass_core" / "api" / ".env", + Path.home() / ".secrets" / "aipass" / ".env", + _aipass_root / "api" / "apps" / ".env", + _aipass_root / "api" / ".env", ]: if env_path.exists(): with open(env_path, encoding="utf-8") as f: diff --git a/src/aipass/memory/apps/handlers/symbolic/hook.py b/src/aipass/memory/apps/handlers/symbolic/hook.py index 1a5bf67b..6c1bb572 100644 --- a/src/aipass/memory/apps/handlers/symbolic/hook.py +++ b/src/aipass/memory/apps/handlers/symbolic/hook.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: hook.py - Fragmented Memory Hook Handler @@ -36,22 +34,19 @@ import time from pathlib import Path from typing import Dict, List, Any, Tuple -# Infrastructure setup -import sys -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) -sys.path.insert(0, str(Path.home())) - # Handler imports (domain-organized, no modules) -from MEMORY_BANK.apps.handlers.symbolic import retriever -from MEMORY_BANK.apps.handlers.json import json_handler +from aipass.memory.apps.handlers.symbolic import retriever +from aipass.memory.apps.handlers.json import json_handler + +# memory/ root resolved from symbolic/hook.py +_MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent # ============================================================================= # CONSTANTS # ============================================================================= -DEFAULT_CONFIG_PATH = Path.home() / "MEMORY_BANK" / "apps" / "json_templates" / "custom" / "fragmented_memory_config.json" +DEFAULT_CONFIG_PATH = _MEMORY_ROOT / "apps" / "json_templates" / "custom" / "fragmented_memory_config.json" DEFAULT_CONFIG = { "enabled": True, diff --git a/src/aipass/memory/apps/handlers/symbolic/retriever.py b/src/aipass/memory/apps/handlers/symbolic/retriever.py index 73fb6722..3aa6dae1 100644 --- a/src/aipass/memory/apps/handlers/symbolic/retriever.py +++ b/src/aipass/memory/apps/handlers/symbolic/retriever.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: retriever.py - Symbolic Fragment Retrieval Handler @@ -34,17 +32,12 @@ Key Functions: - search_by_triggers() - keyword matching in triggers field """ -import sys from typing import Dict, List, Any from pathlib import Path -# Infrastructure setup -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) -sys.path.insert(0, str(Path.home())) - # Handler imports (domain-organized, no modules) -from MEMORY_BANK.apps.handlers.vector import embedder +from aipass.memory.apps.handlers.vector import embedder +from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client # ============================================================================= @@ -54,9 +47,6 @@ from MEMORY_BANK.apps.handlers.vector import embedder COLLECTION_NAME = "symbolic_fragments" DEFAULT_N_RESULTS = 5 -# Import shared chroma client -from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_chroma_client - # ============================================================================= # VECTOR SIMILARITY SEARCH @@ -76,7 +66,7 @@ def search_by_vector( Args: query: Search query text n_results: Number of results to return - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'results' list containing fragments with scores @@ -163,7 +153,7 @@ def search_by_dimensions( Args: dimension_filters: Dict of dimension_key: value pairs to match n_results: Number of results to return - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'results' list of matching fragments @@ -245,7 +235,7 @@ def search_by_triggers( Args: keywords: List of keywords to search for in triggers n_results: Number of results to return - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'results' list of matching fragments @@ -354,7 +344,7 @@ def retrieve_fragments( dimension_filters: Optional dict of dimension filters trigger_keywords: Optional list of trigger keywords n_results: Number of results to return - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'results' list with relevance scores diff --git a/src/aipass/memory/apps/handlers/symbolic/storage.py b/src/aipass/memory/apps/handlers/symbolic/storage.py index bb1688c2..c20f0a86 100644 --- a/src/aipass/memory/apps/handlers/symbolic/storage.py +++ b/src/aipass/memory/apps/handlers/symbolic/storage.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: storage.py - Symbolic Fragment Storage Handler @@ -35,19 +33,13 @@ Key Functions: - store_llm_fragments_batch() - batch storage for LLM-extracted fragments """ -import sys import uuid from datetime import datetime from typing import Dict, List, Any from pathlib import Path -# Infrastructure setup -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) -sys.path.insert(0, str(Path.home())) - # Handler imports (domain-organized, no modules) -from MEMORY_BANK.apps.handlers.vector import embedder +from aipass.memory.apps.handlers.vector import embedder # ============================================================================= @@ -248,7 +240,7 @@ def store_fragment( Args: fragment: Fragment dict from create_fragment() - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', storage details @@ -260,7 +252,7 @@ def store_fragment( } # Import shared client - from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_chroma_client + from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client frag_content = fragment.get('content', '') frag_id = fragment.get('id', '') @@ -340,7 +332,7 @@ def store_fragments_batch( Args: fragments: List of fragment dicts from create_fragment() - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', batch storage details @@ -405,7 +397,7 @@ def store_fragments_batch( batch_metadatas.append(flat_result['metadata']) # Import shared client - from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_chroma_client + from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client try: client = get_chroma_client(db_path) @@ -458,7 +450,7 @@ def store_llm_fragment( fragment: LLM-extracted fragment dict with summary, insight, type, triggers, emotional_tone, technical_domain source_branch: Optional branch name for filtering (e.g., 'SEED') - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'fragment_id', 'collection', 'total_fragments' @@ -479,7 +471,7 @@ def store_llm_fragment( } # Import shared client - from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_chroma_client + from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client # Generate unique ID timestamp = datetime.now() @@ -570,7 +562,7 @@ def store_llm_fragments_batch( Args: fragments: List of LLM-extracted fragment dicts source_branch: Optional branch name for filtering (e.g., 'SEED') - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'stored' count, 'collection', 'total_fragments' @@ -649,7 +641,7 @@ def store_llm_fragments_batch( batch_embeddings.append(vec) # Import shared client - from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_chroma_client + from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client try: client = get_chroma_client(db_path) @@ -694,12 +686,12 @@ def delete_fragment( Args: fragment_id: The ChromaDB document ID to delete - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'deleted_id', 'total_fragments' """ - from MEMORY_BANK.apps.handlers.symbolic.chroma_client import get_chroma_client + from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client try: client = get_chroma_client(db_path) diff --git a/src/aipass/memory/apps/modules/rollover.py b/src/aipass/memory/apps/modules/rollover.py index b922e1af..74aa524c 100755 --- a/src/aipass/memory/apps/modules/rollover.py +++ b/src/aipass/memory/apps/modules/rollover.py @@ -40,6 +40,7 @@ from ..handlers.rollover.orchestrator import ( execute_rollover as _handler_execute_rollover, sync_line_counts as _handler_sync_line_counts, ) +from ..handlers.monitor.memory_watcher import check_and_rollover # ============================================================================= diff --git a/src/aipass/memory/apps/modules/symbolic.py b/src/aipass/memory/apps/modules/symbolic.py index d9e2cb30..86ad854d 100644 --- a/src/aipass/memory/apps/modules/symbolic.py +++ b/src/aipass/memory/apps/modules/symbolic.py @@ -1,5 +1,3 @@ -#!/home/aipass/MEMORY_BANK/.venv/bin/python3 - # ===================AIPASS==================== # META DATA HEADER # Name: symbolic.py - Symbolic Memory Orchestration Module @@ -34,21 +32,30 @@ import time from pathlib import Path from typing import List, Dict, Any -# Infrastructure setup -AIPASS_ROOT = Path.home() / "aipass_core" -sys.path.insert(0, str(AIPASS_ROOT)) -sys.path.insert(0, str(Path.home())) # For MEMORY_BANK package imports - # Service imports -from prax.apps.modules.logger import system_logger as logger -from cli.apps.modules import console, header +from aipass.prax import logger +from aipass.cli.apps.modules import console, header # Handler imports (domain-organized) -from MEMORY_BANK.apps.handlers.symbolic import extractor -from MEMORY_BANK.apps.handlers.symbolic import storage -from MEMORY_BANK.apps.handlers.symbolic import retriever -from MEMORY_BANK.apps.handlers.symbolic import hook -from MEMORY_BANK.apps.handlers.symbolic import deduplicator +from aipass.memory.apps.handlers.symbolic import extractor +from aipass.memory.apps.handlers.symbolic import storage +from aipass.memory.apps.handlers.symbolic import retriever +from aipass.memory.apps.handlers.symbolic import hook +from aipass.memory.apps.handlers.symbolic import deduplicator + + +# ============================================================================= +# SUBCOMMAND REGISTRY +# ============================================================================= + +_SUBCOMMANDS = { + "demo": "Run demonstration analysis (v1 + v2 mock)", + "analyze": "Analyze a conversation JSON file (v1 pipeline)", + "extract": "Extract fragments via LLM and store (v2 pipeline)", + "bootstrap": "Populate fragments from session JSONLs", + "fragments": "Search symbolic fragments (v1 + v2)", + "hook-test": "Test hook with sample conversation text", +} # ============================================================================= @@ -167,7 +174,7 @@ def create_fragment( result = storage.create_fragment(analysis, content, source_branch) if result.get('success'): try: - from trigger.apps.modules.core import trigger + from aipass.trigger.apps.modules.core import trigger trigger.fire('fragment_created', fragment_id=result['fragment'].get('id'), source_branch=source_branch or 'unknown') @@ -184,7 +191,7 @@ def store_fragment( result = storage.store_fragment(fragment, db_path) if result.get('success'): try: - from trigger.apps.modules.core import trigger + from aipass.trigger.apps.modules.core import trigger trigger.fire('fragment_stored', fragment_id=result.get('fragment_id')) except Exception: pass # Trigger optional @@ -203,7 +210,7 @@ def store_fragments_batch( Args: fragments: List of fragment dicts from create_fragment() - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', batch storage details @@ -323,11 +330,11 @@ def extract_and_store_llm( error_msg = extract_result.get('error', 'Unknown extraction error') logger.error(f"[symbolic] LLM extraction failed: {error_msg}") try: - from trigger.apps.modules.errors import report_error + from aipass.trigger.apps.modules.errors import report_error report_error( error_type="ExtractionError", message=error_msg, - component="MEMORY_BANK", + component="memory", severity="high" ) except Exception: @@ -460,7 +467,7 @@ def retrieve_fragments( dimension_filters: Optional dict of dimension filters trigger_keywords: Optional list of trigger keywords n_results: Number of results to return - db_path: Optional ChromaDB path (default: MEMORY_BANK/.chroma) + db_path: Optional ChromaDB path (default: memory/.chroma) Returns: Dict with 'success', 'results' list with relevance scores @@ -644,6 +651,54 @@ def get_hook_session_state() -> Dict[str, Any]: return hook.get_session_state() +# ============================================================================= +# INTROSPECTION (seedgo standard) +# ============================================================================= + +def _discover_handlers() -> dict[str, list[str]]: + """Auto-discover handler directories and their Python files.""" + handlers_dir = Path(__file__).resolve().parent.parent / "handlers" + result: dict[str, list[str]] = {} + if not handlers_dir.exists(): + return result + for d in sorted(handlers_dir.iterdir()): + if not d.is_dir() or d.name.startswith("__"): + continue + py_files = sorted( + f.name for f in d.iterdir() + if f.is_file() and f.suffix == ".py" and f.name != "__init__.py" + ) + if py_files: + result[d.name] = py_files + return result + + +def print_introspection() -> None: + """Display module introspection (seedgo standard: no args = structure/discovery).""" + console.print() + console.print("[bold cyan]symbolic[/bold cyan] — Fragmented Memory Extraction") + console.print("[dim]Extracts symbolic dimensions from conversations and stores as searchable vector fragments[/dim]") + console.print() + + handlers = _discover_handlers() + if "symbolic" in handlers: + console.print("[yellow]Connected Handlers:[/yellow]") + for f in handlers["symbolic"]: + console.print(f" [dim]{f}[/dim]") + console.print() + + console.print("[yellow]Subcommands:[/yellow]") + for sub, desc in _SUBCOMMANDS.items(): + console.print(f" [green]{sub:<20}[/green] {desc}") + console.print() + + console.print("[yellow]Next:[/yellow]") + console.print(" [green]drone @memory symbolic demo[/green] [dim]# run demo analysis[/dim]") + console.print(" [green]drone @memory symbolic fragments [/green] [dim]# search fragments[/dim]") + console.print(" [green]drone @memory symbolic --help[/green] [dim]# full usage guide[/dim]") + console.print() + + # ============================================================================= # COMMAND HANDLERS # ============================================================================= @@ -667,10 +722,65 @@ def handle_command(command: str, args: List[str]) -> bool: Returns: True if command handled, False otherwise """ + # Top-level help (backward compat -- entry point may send these) if command in ('--help', '-h', 'help'): print_help() return True + if command == 'symbolic': + # No args -> introspection (seedgo standard) + if not args: + print_introspection() + return True + + # --help / -h / help -> full help + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + + # Subcommand routing + sub = args[0] + remaining = args[1:] + + if sub == 'demo': + run_demo() + return True + + if sub == 'analyze': + if not remaining: + console.print("[red]Error:[/red] File path required") + console.print("Usage: symbolic analyze ") + return True + analyze_file(remaining[0]) + return True + + if sub == 'extract': + if not remaining: + console.print("[red]Error:[/red] File path required") + console.print("Usage: symbolic extract ") + return True + extract_file(remaining[0], source_branch=remaining[1] if len(remaining) > 1 else None) + return True + + if sub == 'bootstrap': + max_sessions = 8 + for arg in remaining: + if arg.startswith('--max='): + max_sessions = int(arg.split('=')[1]) + bootstrap_from_jsonl(max_sessions=max_sessions) + return True + + if sub == 'fragments': + search_fragments_cli(remaining) + return True + + if sub == 'hook-test': + run_hook_test(remaining) + return True + + return False + + # Backward-compat: direct command routing (entry point may send these) if command == 'demo': run_demo() return True @@ -1161,7 +1271,7 @@ def run_hook_test(args: List[str]) -> None: def analyze_file(file_path: str) -> None: """Analyze a conversation JSON file""" - from MEMORY_BANK.apps.handlers.json import json_handler + from aipass.memory.apps.handlers.json import json_handler path = Path(file_path) if not path.exists(): @@ -1220,7 +1330,7 @@ def extract_file(file_path: str, source_branch: str | None = None) -> None: file_path: Path to JSON conversation file source_branch: Optional branch name tag for stored fragments """ - from MEMORY_BANK.apps.handlers.json import json_handler + from aipass.memory.apps.handlers.json import json_handler path = Path(file_path) if not path.exists(): diff --git a/src/aipass/memory/config/memory_bank.config.json b/src/aipass/memory/config/memory_bank.config.json index 6232916a..daeed13e 100644 --- a/src/aipass/memory/config/memory_bank.config.json +++ b/src/aipass/memory/config/memory_bank.config.json @@ -6,6 +6,14 @@ }, "per_branch": {} }, + "memory_pool": { + "enabled": true, + "path": "memory_pool", + "supported_extensions": [".md", ".txt"], + "collection_name": "memory_pool_docs", + "keep_recent": 50, + "archive_path": "memory_pool_archive" + }, "plans": { "enabled": true, "path": "src/aipass/flow/processed_plans", diff --git a/src/aipass/memory/dev.local.md b/src/aipass/memory/dev.local.md deleted file mode 100644 index 7191b6ec..00000000 --- a/src/aipass/memory/dev.local.md +++ /dev/null @@ -1,20 +0,0 @@ -# dev.local.md - MEMORY -``` -Branch: /home/patrick/Projects/AIPass/src/aipass/memory -Created: 2026-03-07 -``` - -## Issues - -- `search` command fails — missing `torch`/`sentence-transformers` deps -- 5 commands in `--help` not implemented: push-templates, diff-templates, template-status, symbolic demo, symbolic fragments -- `status` shows 0 branches — detector may not resolve registry/branch paths -- Help text is aspirational, not grounded in code reality - ---- - -## Todos - -- Fix or remove unimplemented commands from --help -- Investigate why status shows 0 branches (registry path resolution) -- Decide on torch/sentence-transformers — install or provide graceful fallback