Merge pull request #62 from AIOSAI/feat/memory-modernize-symbolic-orchestrator
feat(memory): modernize symbolic + pool_processor, wire post-rollover chain
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user