feat(memory): modernize symbolic + pool_processor, wire post-rollover chain

- Modernize symbolic.py module: seedgo introspection gate, aipass.memory imports
- Modernize 6 symbolic handlers: remove MEMORY_BANK paths, use _MEMORY_ROOT
- Modernize pool_processor.py: fix config path (memory_json → config), add memory_pool config section
- Wire post-rollover chain in orchestrator.py: Trigger.fire('rollover_complete'), central_writer, dashboard_push, pool_processor
- Expose check_and_rollover() via modules/rollover.py for cross-branch access
- Update pyrightconfig.json with memory venv site-packages path

FPLAN-0054, FPLAN-0062

Co-Authored-By: @memory <memory@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-03-17 00:46:21 -07:00
co-authored by @memory Claude Opus 4.6
parent 79b09e8c1d
commit 35526b8754
13 changed files with 246 additions and 133 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extraPaths": ["src"],
"extraPaths": ["src", "src/aipass/memory/.venv/lib/python3.12/site-packages"],
"pythonVersion": "3.10",
"reportMissingImports": "error",
"reportAttributeAccessIssue": "error",
@@ -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):
@@ -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),
@@ -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:
@@ -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:
@@ -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:
@@ -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,
@@ -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
@@ -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)
@@ -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
# =============================================================================
+132 -22
View File
@@ -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 <q>[/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 <conversation.json>")
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 <conversation.json>")
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():
@@ -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",
-20
View File
@@ -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