claude-mpm 3.9.11__py3-none-any.whl → 4.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            #!/usr/bin/env python3
         | 
| 2 4 | 
             
            """Indexed memory service for high-performance memory queries.
         | 
| 3 5 |  | 
| @@ -16,25 +18,24 @@ WHY indexed memory: | |
| 16 18 |  | 
| 17 19 | 
             
            import bisect
         | 
| 18 20 | 
             
            import hashlib
         | 
| 19 | 
            -
            import json
         | 
| 20 21 | 
             
            import mmap
         | 
| 21 22 | 
             
            import os
         | 
| 22 23 | 
             
            import pickle
         | 
| 23 24 | 
             
            import re
         | 
| 24 25 | 
             
            import time
         | 
| 25 | 
            -
            from collections import  | 
| 26 | 
            +
            from collections import Counter, defaultdict
         | 
| 26 27 | 
             
            from dataclasses import dataclass, field
         | 
| 27 28 | 
             
            from datetime import datetime, timedelta
         | 
| 28 | 
            -
            from pathlib import Path
         | 
| 29 29 | 
             
            from typing import Any, Dict, List, Optional, Set, Tuple, Union
         | 
| 30 30 |  | 
| 31 | 
            -
            from ...core.logger import get_logger
         | 
| 32 31 | 
             
            from ...core.cache import FileSystemCache, get_file_cache
         | 
| 32 | 
            +
            from ...core.logger import get_logger
         | 
| 33 33 |  | 
| 34 34 |  | 
| 35 35 | 
             
            @dataclass
         | 
| 36 36 | 
             
            class MemoryEntry:
         | 
| 37 37 | 
             
                """Single memory entry with metadata."""
         | 
| 38 | 
            +
             | 
| 38 39 | 
             
                id: str
         | 
| 39 40 | 
             
                agent_id: str
         | 
| 40 41 | 
             
                content: str
         | 
| @@ -48,6 +49,7 @@ class MemoryEntry: | |
| 48 49 | 
             
            @dataclass
         | 
| 49 50 | 
             
            class QueryResult:
         | 
| 50 51 | 
             
                """Result from a memory query."""
         | 
| 52 | 
            +
             | 
| 51 53 | 
             
                entries: List[MemoryEntry]
         | 
| 52 54 | 
             
                total_count: int
         | 
| 53 55 | 
             
                query_time: float
         | 
| @@ -57,14 +59,14 @@ class QueryResult: | |
| 57 59 |  | 
| 58 60 | 
             
            class InvertedIndex:
         | 
| 59 61 | 
             
                """Inverted index for fast text searches.
         | 
| 60 | 
            -
             | 
| 62 | 
            +
             | 
| 61 63 | 
             
                WHY inverted index:
         | 
| 62 64 | 
             
                - Maps words to document IDs for O(1) lookups
         | 
| 63 65 | 
             
                - Supports boolean queries (AND, OR, NOT)
         | 
| 64 66 | 
             
                - Enables relevance scoring with TF-IDF
         | 
| 65 67 | 
             
                - Efficient for full-text search
         | 
| 66 68 | 
             
                """
         | 
| 67 | 
            -
             | 
| 69 | 
            +
             | 
| 68 70 | 
             
                def __init__(self):
         | 
| 69 71 | 
             
                    # Word -> Set of memory IDs
         | 
| 70 72 | 
             
                    self.index: Dict[str, Set[str]] = defaultdict(set)
         | 
| @@ -72,73 +74,69 @@ class InvertedIndex: | |
| 72 74 | 
             
                    self.doc_freqs: Dict[str, Counter] = {}
         | 
| 73 75 | 
             
                    # Total documents
         | 
| 74 76 | 
             
                    self.doc_count = 0
         | 
| 75 | 
            -
             | 
| 77 | 
            +
             | 
| 76 78 | 
             
                    self.logger = get_logger("inverted_index")
         | 
| 77 | 
            -
             | 
| 79 | 
            +
             | 
| 78 80 | 
             
                def add_entry(self, entry_id: str, text: str):
         | 
| 79 81 | 
             
                    """Add entry to inverted index."""
         | 
| 80 82 | 
             
                    # Tokenize text
         | 
| 81 83 | 
             
                    words = self._tokenize(text)
         | 
| 82 | 
            -
             | 
| 84 | 
            +
             | 
| 83 85 | 
             
                    # Update word frequencies
         | 
| 84 86 | 
             
                    self.doc_freqs[entry_id] = Counter(words)
         | 
| 85 | 
            -
             | 
| 87 | 
            +
             | 
| 86 88 | 
             
                    # Update inverted index
         | 
| 87 89 | 
             
                    for word in set(words):
         | 
| 88 90 | 
             
                        self.index[word].add(entry_id)
         | 
| 89 | 
            -
             | 
| 91 | 
            +
             | 
| 90 92 | 
             
                    self.doc_count += 1
         | 
| 91 | 
            -
             | 
| 93 | 
            +
             | 
| 92 94 | 
             
                def remove_entry(self, entry_id: str):
         | 
| 93 95 | 
             
                    """Remove entry from index."""
         | 
| 94 96 | 
             
                    if entry_id not in self.doc_freqs:
         | 
| 95 97 | 
             
                        return
         | 
| 96 | 
            -
             | 
| 98 | 
            +
             | 
| 97 99 | 
             
                    # Remove from inverted index
         | 
| 98 100 | 
             
                    words = self.doc_freqs[entry_id].keys()
         | 
| 99 101 | 
             
                    for word in words:
         | 
| 100 102 | 
             
                        self.index[word].discard(entry_id)
         | 
| 101 103 | 
             
                        if not self.index[word]:
         | 
| 102 104 | 
             
                            del self.index[word]
         | 
| 103 | 
            -
             | 
| 105 | 
            +
             | 
| 104 106 | 
             
                    # Remove document frequency
         | 
| 105 107 | 
             
                    del self.doc_freqs[entry_id]
         | 
| 106 108 | 
             
                    self.doc_count -= 1
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                def search(
         | 
| 109 | 
            -
                    self,
         | 
| 110 | 
            -
                    query: str,
         | 
| 111 | 
            -
                    operator: str = 'AND'
         | 
| 112 | 
            -
                ) -> Set[str]:
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def search(self, query: str, operator: str = "AND") -> Set[str]:
         | 
| 113 111 | 
             
                    """Search index for matching entries.
         | 
| 114 | 
            -
             | 
| 112 | 
            +
             | 
| 115 113 | 
             
                    Args:
         | 
| 116 114 | 
             
                        query: Search query
         | 
| 117 115 | 
             
                        operator: Boolean operator (AND, OR, NOT)
         | 
| 118 | 
            -
             | 
| 116 | 
            +
             | 
| 119 117 | 
             
                    Returns:
         | 
| 120 118 | 
             
                        Set of matching entry IDs
         | 
| 121 119 | 
             
                    """
         | 
| 122 120 | 
             
                    words = self._tokenize(query)
         | 
| 123 121 | 
             
                    if not words:
         | 
| 124 122 | 
             
                        return set()
         | 
| 125 | 
            -
             | 
| 123 | 
            +
             | 
| 126 124 | 
             
                    # Get entry sets for each word
         | 
| 127 125 | 
             
                    entry_sets = [self.index.get(word, set()) for word in words]
         | 
| 128 | 
            -
             | 
| 126 | 
            +
             | 
| 129 127 | 
             
                    if not entry_sets:
         | 
| 130 128 | 
             
                        return set()
         | 
| 131 | 
            -
             | 
| 129 | 
            +
             | 
| 132 130 | 
             
                    # Apply boolean operator
         | 
| 133 | 
            -
                    if operator ==  | 
| 131 | 
            +
                    if operator == "AND":
         | 
| 134 132 | 
             
                        result = entry_sets[0]
         | 
| 135 133 | 
             
                        for s in entry_sets[1:]:
         | 
| 136 134 | 
             
                            result = result.intersection(s)
         | 
| 137 | 
            -
                    elif operator ==  | 
| 135 | 
            +
                    elif operator == "OR":
         | 
| 138 136 | 
             
                        result = set()
         | 
| 139 137 | 
             
                        for s in entry_sets:
         | 
| 140 138 | 
             
                            result = result.union(s)
         | 
| 141 | 
            -
                    elif operator ==  | 
| 139 | 
            +
                    elif operator == "NOT":
         | 
| 142 140 | 
             
                        # Return entries that don't contain any query words
         | 
| 143 141 | 
             
                        all_entries = set(self.doc_freqs.keys())
         | 
| 144 142 | 
             
                        excluded = set()
         | 
| @@ -147,149 +145,154 @@ class InvertedIndex: | |
| 147 145 | 
             
                        result = all_entries - excluded
         | 
| 148 146 | 
             
                    else:
         | 
| 149 147 | 
             
                        result = entry_sets[0]
         | 
| 150 | 
            -
             | 
| 148 | 
            +
             | 
| 151 149 | 
             
                    return result
         | 
| 152 | 
            -
             | 
| 153 | 
            -
                def calculate_relevance(
         | 
| 154 | 
            -
                    self,
         | 
| 155 | 
            -
                    entry_id: str,
         | 
| 156 | 
            -
                    query: str
         | 
| 157 | 
            -
                ) -> float:
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                def calculate_relevance(self, entry_id: str, query: str) -> float:
         | 
| 158 152 | 
             
                    """Calculate TF-IDF relevance score.
         | 
| 159 | 
            -
             | 
| 153 | 
            +
             | 
| 160 154 | 
             
                    Args:
         | 
| 161 155 | 
             
                        entry_id: Memory entry ID
         | 
| 162 156 | 
             
                        query: Search query
         | 
| 163 | 
            -
             | 
| 157 | 
            +
             | 
| 164 158 | 
             
                    Returns:
         | 
| 165 159 | 
             
                        Relevance score (0.0 to 1.0)
         | 
| 166 160 | 
             
                    """
         | 
| 167 161 | 
             
                    if entry_id not in self.doc_freqs:
         | 
| 168 162 | 
             
                        return 0.0
         | 
| 169 | 
            -
             | 
| 163 | 
            +
             | 
| 170 164 | 
             
                    query_words = self._tokenize(query)
         | 
| 171 165 | 
             
                    if not query_words:
         | 
| 172 166 | 
             
                        return 0.0
         | 
| 173 | 
            -
             | 
| 167 | 
            +
             | 
| 174 168 | 
             
                    score = 0.0
         | 
| 175 169 | 
             
                    doc_freq = self.doc_freqs[entry_id]
         | 
| 176 | 
            -
             | 
| 170 | 
            +
             | 
| 177 171 | 
             
                    for word in query_words:
         | 
| 178 172 | 
             
                        if word not in doc_freq:
         | 
| 179 173 | 
             
                            continue
         | 
| 180 | 
            -
             | 
| 174 | 
            +
             | 
| 181 175 | 
             
                        # Term frequency
         | 
| 182 176 | 
             
                        tf = doc_freq[word] / sum(doc_freq.values())
         | 
| 183 | 
            -
             | 
| 177 | 
            +
             | 
| 184 178 | 
             
                        # Inverse document frequency
         | 
| 185 179 | 
             
                        if word in self.index:
         | 
| 186 180 | 
             
                            idf = 1.0 + (self.doc_count / len(self.index[word]))
         | 
| 187 181 | 
             
                        else:
         | 
| 188 182 | 
             
                            idf = 1.0
         | 
| 189 | 
            -
             | 
| 183 | 
            +
             | 
| 190 184 | 
             
                        score += tf * idf
         | 
| 191 | 
            -
             | 
| 185 | 
            +
             | 
| 192 186 | 
             
                    # Normalize score
         | 
| 193 187 | 
             
                    return min(1.0, score / len(query_words))
         | 
| 194 | 
            -
             | 
| 188 | 
            +
             | 
| 195 189 | 
             
                def _tokenize(self, text: str) -> List[str]:
         | 
| 196 190 | 
             
                    """Tokenize text into words."""
         | 
| 197 191 | 
             
                    # Convert to lowercase and split on non-alphanumeric
         | 
| 198 192 | 
             
                    text = text.lower()
         | 
| 199 | 
            -
                    words = re.findall(r | 
| 200 | 
            -
             | 
| 193 | 
            +
                    words = re.findall(r"\b[a-z0-9]+\b", text)
         | 
| 194 | 
            +
             | 
| 201 195 | 
             
                    # Remove stop words (simplified list)
         | 
| 202 | 
            -
                    stop_words = { | 
| 196 | 
            +
                    stop_words = {
         | 
| 197 | 
            +
                        "the",
         | 
| 198 | 
            +
                        "a",
         | 
| 199 | 
            +
                        "an",
         | 
| 200 | 
            +
                        "and",
         | 
| 201 | 
            +
                        "or",
         | 
| 202 | 
            +
                        "but",
         | 
| 203 | 
            +
                        "in",
         | 
| 204 | 
            +
                        "on",
         | 
| 205 | 
            +
                        "at",
         | 
| 206 | 
            +
                        "to",
         | 
| 207 | 
            +
                        "for",
         | 
| 208 | 
            +
                    }
         | 
| 203 209 | 
             
                    return [w for w in words if w not in stop_words and len(w) > 2]
         | 
| 204 | 
            -
             | 
| 210 | 
            +
             | 
| 205 211 | 
             
                def save(self, path: Path):
         | 
| 206 212 | 
             
                    """Persist index to disk."""
         | 
| 207 213 | 
             
                    data = {
         | 
| 208 | 
            -
                         | 
| 209 | 
            -
                         | 
| 210 | 
            -
                         | 
| 214 | 
            +
                        "index": dict(self.index),
         | 
| 215 | 
            +
                        "doc_freqs": dict(self.doc_freqs),
         | 
| 216 | 
            +
                        "doc_count": self.doc_count,
         | 
| 211 217 | 
             
                    }
         | 
| 212 | 
            -
                    with open(path,  | 
| 218 | 
            +
                    with open(path, "wb") as f:
         | 
| 213 219 | 
             
                        pickle.dump(data, f)
         | 
| 214 | 
            -
             | 
| 220 | 
            +
             | 
| 215 221 | 
             
                def load(self, path: Path):
         | 
| 216 222 | 
             
                    """Load index from disk."""
         | 
| 217 223 | 
             
                    if not path.exists():
         | 
| 218 224 | 
             
                        return
         | 
| 219 | 
            -
             | 
| 220 | 
            -
                    with open(path,  | 
| 225 | 
            +
             | 
| 226 | 
            +
                    with open(path, "rb") as f:
         | 
| 221 227 | 
             
                        data = pickle.load(f)
         | 
| 222 | 
            -
             | 
| 223 | 
            -
                    self.index = defaultdict(set, {k: set(v) for k, v in data[ | 
| 224 | 
            -
                    self.doc_freqs = data[ | 
| 225 | 
            -
                    self.doc_count = data[ | 
| 228 | 
            +
             | 
| 229 | 
            +
                    self.index = defaultdict(set, {k: set(v) for k, v in data["index"].items()})
         | 
| 230 | 
            +
                    self.doc_freqs = data["doc_freqs"]
         | 
| 231 | 
            +
                    self.doc_count = data["doc_count"]
         | 
| 226 232 |  | 
| 227 233 |  | 
| 228 234 | 
             
            class BTreeIndex:
         | 
| 229 235 | 
             
                """B-tree index for sorted queries.
         | 
| 230 | 
            -
             | 
| 236 | 
            +
             | 
| 231 237 | 
             
                WHY B-tree index:
         | 
| 232 238 | 
             
                - Maintains sorted order for range queries
         | 
| 233 239 | 
             
                - O(log n) search, insert, delete
         | 
| 234 240 | 
             
                - Efficient for timestamp-based queries
         | 
| 235 241 | 
             
                - Supports pagination
         | 
| 236 242 | 
             
                """
         | 
| 237 | 
            -
             | 
| 243 | 
            +
             | 
| 238 244 | 
             
                def __init__(self, key_func=None):
         | 
| 239 245 | 
             
                    # Sorted list of (key, entry_id) tuples
         | 
| 240 246 | 
             
                    self.index: List[Tuple[Any, str]] = []
         | 
| 241 247 | 
             
                    self.key_func = key_func or (lambda x: x)
         | 
| 242 248 | 
             
                    self.logger = get_logger("btree_index")
         | 
| 243 | 
            -
             | 
| 249 | 
            +
             | 
| 244 250 | 
             
                def add_entry(self, entry_id: str, key: Any):
         | 
| 245 251 | 
             
                    """Add entry to B-tree index."""
         | 
| 246 252 | 
             
                    bisect.insort(self.index, (self.key_func(key), entry_id))
         | 
| 247 | 
            -
             | 
| 253 | 
            +
             | 
| 248 254 | 
             
                def remove_entry(self, entry_id: str):
         | 
| 249 255 | 
             
                    """Remove entry from index."""
         | 
| 250 256 | 
             
                    self.index = [(k, id) for k, id in self.index if id != entry_id]
         | 
| 251 | 
            -
             | 
| 257 | 
            +
             | 
| 252 258 | 
             
                def range_search(
         | 
| 253 | 
            -
                    self,
         | 
| 254 | 
            -
                    min_key: Any = None,
         | 
| 255 | 
            -
                    max_key: Any = None,
         | 
| 256 | 
            -
                    limit: int = None
         | 
| 259 | 
            +
                    self, min_key: Any = None, max_key: Any = None, limit: int = None
         | 
| 257 260 | 
             
                ) -> List[str]:
         | 
| 258 261 | 
             
                    """Search for entries in key range.
         | 
| 259 | 
            -
             | 
| 262 | 
            +
             | 
| 260 263 | 
             
                    Args:
         | 
| 261 264 | 
             
                        min_key: Minimum key value (inclusive)
         | 
| 262 265 | 
             
                        max_key: Maximum key value (inclusive)
         | 
| 263 266 | 
             
                        limit: Maximum results to return
         | 
| 264 | 
            -
             | 
| 267 | 
            +
             | 
| 265 268 | 
             
                    Returns:
         | 
| 266 269 | 
             
                        List of matching entry IDs
         | 
| 267 270 | 
             
                    """
         | 
| 268 271 | 
             
                    # Find range boundaries
         | 
| 269 272 | 
             
                    if min_key is not None:
         | 
| 270 273 | 
             
                        min_key = self.key_func(min_key)
         | 
| 271 | 
            -
                        start = bisect.bisect_left(self.index, (min_key,  | 
| 274 | 
            +
                        start = bisect.bisect_left(self.index, (min_key, ""))
         | 
| 272 275 | 
             
                    else:
         | 
| 273 276 | 
             
                        start = 0
         | 
| 274 | 
            -
             | 
| 277 | 
            +
             | 
| 275 278 | 
             
                    if max_key is not None:
         | 
| 276 279 | 
             
                        max_key = self.key_func(max_key)
         | 
| 277 | 
            -
                        end = bisect.bisect_right(self.index, (max_key,  | 
| 280 | 
            +
                        end = bisect.bisect_right(self.index, (max_key, "\xff"))
         | 
| 278 281 | 
             
                    else:
         | 
| 279 282 | 
             
                        end = len(self.index)
         | 
| 280 | 
            -
             | 
| 283 | 
            +
             | 
| 281 284 | 
             
                    # Extract entry IDs
         | 
| 282 285 | 
             
                    results = [entry_id for _, entry_id in self.index[start:end]]
         | 
| 283 | 
            -
             | 
| 286 | 
            +
             | 
| 284 287 | 
             
                    if limit:
         | 
| 285 288 | 
             
                        results = results[:limit]
         | 
| 286 | 
            -
             | 
| 289 | 
            +
             | 
| 287 290 | 
             
                    return results
         | 
| 288 | 
            -
             | 
| 291 | 
            +
             | 
| 289 292 | 
             
                def get_recent(self, n: int = 10) -> List[str]:
         | 
| 290 293 | 
             
                    """Get n most recent entries."""
         | 
| 291 294 | 
             
                    return [entry_id for _, entry_id in self.index[-n:]]
         | 
| 292 | 
            -
             | 
| 295 | 
            +
             | 
| 293 296 | 
             
                def get_oldest(self, n: int = 10) -> List[str]:
         | 
| 294 297 | 
             
                    """Get n oldest entries."""
         | 
| 295 298 | 
             
                    return [entry_id for _, entry_id in self.index[:n]]
         | 
| @@ -297,38 +300,38 @@ class BTreeIndex: | |
| 297 300 |  | 
| 298 301 | 
             
            class IndexedMemoryService:
         | 
| 299 302 | 
             
                """High-performance memory service with multiple indexes.
         | 
| 300 | 
            -
             | 
| 303 | 
            +
             | 
| 301 304 | 
             
                WHY this design:
         | 
| 302 305 | 
             
                - Multiple specialized indexes for different query types
         | 
| 303 306 | 
             
                - LRU cache for frequent queries
         | 
| 304 307 | 
             
                - Incremental index updates for efficiency
         | 
| 305 308 | 
             
                - Memory-mapped files for large datasets
         | 
| 306 | 
            -
             | 
| 309 | 
            +
             | 
| 307 310 | 
             
                Example:
         | 
| 308 311 | 
             
                    memory = IndexedMemoryService()
         | 
| 309 | 
            -
             | 
| 312 | 
            +
             | 
| 310 313 | 
             
                    # Add memory entry
         | 
| 311 314 | 
             
                    memory.add_memory(
         | 
| 312 315 | 
             
                        agent_id='engineer',
         | 
| 313 316 | 
             
                        content='Use dependency injection for testability',
         | 
| 314 317 | 
             
                        category='pattern'
         | 
| 315 318 | 
             
                    )
         | 
| 316 | 
            -
             | 
| 319 | 
            +
             | 
| 317 320 | 
             
                    # Fast text search
         | 
| 318 321 | 
             
                    results = memory.search('dependency injection')
         | 
| 319 | 
            -
             | 
| 322 | 
            +
             | 
| 320 323 | 
             
                    # Range query by timestamp
         | 
| 321 324 | 
             
                    recent = memory.get_recent_memories(hours=24)
         | 
| 322 325 | 
             
                """
         | 
| 323 | 
            -
             | 
| 326 | 
            +
             | 
| 324 327 | 
             
                def __init__(
         | 
| 325 328 | 
             
                    self,
         | 
| 326 329 | 
             
                    data_dir: Optional[Path] = None,
         | 
| 327 330 | 
             
                    cache_size_mb: int = 50,
         | 
| 328 | 
            -
                    enable_mmap: bool = False
         | 
| 331 | 
            +
                    enable_mmap: bool = False,
         | 
| 329 332 | 
             
                ):
         | 
| 330 333 | 
             
                    """Initialize indexed memory service.
         | 
| 331 | 
            -
             | 
| 334 | 
            +
             | 
| 332 335 | 
             
                    Args:
         | 
| 333 336 | 
             
                        data_dir: Directory for persisting indexes
         | 
| 334 337 | 
             
                        cache_size_mb: Cache size for query results
         | 
| @@ -336,51 +339,51 @@ class IndexedMemoryService: | |
| 336 339 | 
             
                    """
         | 
| 337 340 | 
             
                    self.data_dir = data_dir or Path.home() / ".claude-mpm" / "memory"
         | 
| 338 341 | 
             
                    self.data_dir.mkdir(parents=True, exist_ok=True)
         | 
| 339 | 
            -
             | 
| 342 | 
            +
             | 
| 340 343 | 
             
                    self.enable_mmap = enable_mmap
         | 
| 341 | 
            -
             | 
| 344 | 
            +
             | 
| 342 345 | 
             
                    # Memory storage
         | 
| 343 346 | 
             
                    self.memories: Dict[str, MemoryEntry] = {}
         | 
| 344 | 
            -
             | 
| 347 | 
            +
             | 
| 345 348 | 
             
                    # Indexes
         | 
| 346 349 | 
             
                    self.text_index = InvertedIndex()
         | 
| 347 350 | 
             
                    self.time_index = BTreeIndex(key_func=lambda dt: dt.timestamp())
         | 
| 348 351 | 
             
                    self.agent_index: Dict[str, Set[str]] = defaultdict(set)
         | 
| 349 352 | 
             
                    self.category_index: Dict[str, Set[str]] = defaultdict(set)
         | 
| 350 353 | 
             
                    self.tag_index: Dict[str, Set[str]] = defaultdict(set)
         | 
| 351 | 
            -
             | 
| 354 | 
            +
             | 
| 352 355 | 
             
                    # Query cache
         | 
| 353 356 | 
             
                    self.cache = get_file_cache(max_size_mb=cache_size_mb, default_ttl=300)
         | 
| 354 | 
            -
             | 
| 357 | 
            +
             | 
| 355 358 | 
             
                    # Logger
         | 
| 356 359 | 
             
                    self.logger = get_logger("indexed_memory")
         | 
| 357 | 
            -
             | 
| 360 | 
            +
             | 
| 358 361 | 
             
                    # Load existing data
         | 
| 359 362 | 
             
                    self._load_indexes()
         | 
| 360 | 
            -
             | 
| 363 | 
            +
             | 
| 361 364 | 
             
                def add_memory(
         | 
| 362 365 | 
             
                    self,
         | 
| 363 366 | 
             
                    agent_id: str,
         | 
| 364 367 | 
             
                    content: str,
         | 
| 365 | 
            -
                    category: str =  | 
| 368 | 
            +
                    category: str = "general",
         | 
| 366 369 | 
             
                    tags: List[str] = None,
         | 
| 367 | 
            -
                    metadata: Dict[str, Any] = None
         | 
| 370 | 
            +
                    metadata: Dict[str, Any] = None,
         | 
| 368 371 | 
             
                ) -> str:
         | 
| 369 372 | 
             
                    """Add a new memory entry.
         | 
| 370 | 
            -
             | 
| 373 | 
            +
             | 
| 371 374 | 
             
                    Args:
         | 
| 372 375 | 
             
                        agent_id: Agent that owns this memory
         | 
| 373 376 | 
             
                        content: Memory content
         | 
| 374 377 | 
             
                        category: Memory category
         | 
| 375 378 | 
             
                        tags: Optional tags
         | 
| 376 379 | 
             
                        metadata: Optional metadata
         | 
| 377 | 
            -
             | 
| 380 | 
            +
             | 
| 378 381 | 
             
                    Returns:
         | 
| 379 382 | 
             
                        Memory entry ID
         | 
| 380 383 | 
             
                    """
         | 
| 381 384 | 
             
                    # Generate ID
         | 
| 382 385 | 
             
                    entry_id = self._generate_id(agent_id, content)
         | 
| 383 | 
            -
             | 
| 386 | 
            +
             | 
| 384 387 | 
             
                    # Create entry
         | 
| 385 388 | 
             
                    entry = MemoryEntry(
         | 
| 386 389 | 
             
                        id=entry_id,
         | 
| @@ -389,12 +392,12 @@ class IndexedMemoryService: | |
| 389 392 | 
             
                        category=category,
         | 
| 390 393 | 
             
                        timestamp=datetime.now(),
         | 
| 391 394 | 
             
                        tags=tags or [],
         | 
| 392 | 
            -
                        metadata=metadata or {}
         | 
| 395 | 
            +
                        metadata=metadata or {},
         | 
| 393 396 | 
             
                    )
         | 
| 394 | 
            -
             | 
| 397 | 
            +
             | 
| 395 398 | 
             
                    # Store entry
         | 
| 396 399 | 
             
                    self.memories[entry_id] = entry
         | 
| 397 | 
            -
             | 
| 400 | 
            +
             | 
| 398 401 | 
             
                    # Update indexes
         | 
| 399 402 | 
             
                    self.text_index.add_entry(entry_id, content)
         | 
| 400 403 | 
             
                    self.time_index.add_entry(entry_id, entry.timestamp)
         | 
| @@ -402,13 +405,13 @@ class IndexedMemoryService: | |
| 402 405 | 
             
                    self.category_index[category].add(entry_id)
         | 
| 403 406 | 
             
                    for tag in entry.tags:
         | 
| 404 407 | 
             
                        self.tag_index[tag].add(entry_id)
         | 
| 405 | 
            -
             | 
| 408 | 
            +
             | 
| 406 409 | 
             
                    # Invalidate cache
         | 
| 407 410 | 
             
                    self.cache.invalidate_pattern("query:*")
         | 
| 408 | 
            -
             | 
| 411 | 
            +
             | 
| 409 412 | 
             
                    self.logger.debug(f"Added memory {entry_id} for agent {agent_id}")
         | 
| 410 413 | 
             
                    return entry_id
         | 
| 411 | 
            -
             | 
| 414 | 
            +
             | 
| 412 415 | 
             
                def search(
         | 
| 413 416 | 
             
                    self,
         | 
| 414 417 | 
             
                    query: str,
         | 
| @@ -416,10 +419,10 @@ class IndexedMemoryService: | |
| 416 419 | 
             
                    category: Optional[str] = None,
         | 
| 417 420 | 
             
                    tags: Optional[List[str]] = None,
         | 
| 418 421 | 
             
                    limit: int = 50,
         | 
| 419 | 
            -
                    operator: str =  | 
| 422 | 
            +
                    operator: str = "AND",
         | 
| 420 423 | 
             
                ) -> QueryResult:
         | 
| 421 424 | 
             
                    """Search memories with multiple filters.
         | 
| 422 | 
            -
             | 
| 425 | 
            +
             | 
| 423 426 | 
             
                    Args:
         | 
| 424 427 | 
             
                        query: Text search query
         | 
| 425 428 | 
             
                        agent_id: Filter by agent
         | 
| @@ -427,91 +430,87 @@ class IndexedMemoryService: | |
| 427 430 | 
             
                        tags: Filter by tags
         | 
| 428 431 | 
             
                        limit: Maximum results
         | 
| 429 432 | 
             
                        operator: Boolean operator for text search
         | 
| 430 | 
            -
             | 
| 433 | 
            +
             | 
| 431 434 | 
             
                    Returns:
         | 
| 432 435 | 
             
                        Query results with metadata
         | 
| 433 436 | 
             
                    """
         | 
| 434 437 | 
             
                    start_time = time.time()
         | 
| 435 | 
            -
             | 
| 438 | 
            +
             | 
| 436 439 | 
             
                    # Generate cache key
         | 
| 437 440 | 
             
                    cache_key = f"query:{hashlib.md5(f'{query}:{agent_id}:{category}:{tags}:{limit}:{operator}'.encode()).hexdigest()}"
         | 
| 438 | 
            -
             | 
| 441 | 
            +
             | 
| 439 442 | 
             
                    # Check cache
         | 
| 440 443 | 
             
                    cached = self.cache.get(cache_key)
         | 
| 441 444 | 
             
                    if cached:
         | 
| 442 445 | 
             
                        return QueryResult(
         | 
| 443 | 
            -
                            entries=cached[ | 
| 444 | 
            -
                            total_count=cached[ | 
| 446 | 
            +
                            entries=cached["entries"],
         | 
| 447 | 
            +
                            total_count=cached["total_count"],
         | 
| 445 448 | 
             
                            query_time=0.001,
         | 
| 446 | 
            -
                            index_used= | 
| 447 | 
            -
                            cache_hit=True
         | 
| 449 | 
            +
                            index_used="cache",
         | 
| 450 | 
            +
                            cache_hit=True,
         | 
| 448 451 | 
             
                        )
         | 
| 449 | 
            -
             | 
| 452 | 
            +
             | 
| 450 453 | 
             
                    # Text search
         | 
| 451 454 | 
             
                    if query:
         | 
| 452 455 | 
             
                        matching_ids = self.text_index.search(query, operator)
         | 
| 453 456 | 
             
                    else:
         | 
| 454 457 | 
             
                        matching_ids = set(self.memories.keys())
         | 
| 455 | 
            -
             | 
| 458 | 
            +
             | 
| 456 459 | 
             
                    # Apply filters
         | 
| 457 460 | 
             
                    if agent_id:
         | 
| 458 461 | 
             
                        matching_ids &= self.agent_index.get(agent_id, set())
         | 
| 459 | 
            -
             | 
| 462 | 
            +
             | 
| 460 463 | 
             
                    if category:
         | 
| 461 464 | 
             
                        matching_ids &= self.category_index.get(category, set())
         | 
| 462 | 
            -
             | 
| 465 | 
            +
             | 
| 463 466 | 
             
                    if tags:
         | 
| 464 467 | 
             
                        for tag in tags:
         | 
| 465 468 | 
             
                            matching_ids &= self.tag_index.get(tag, set())
         | 
| 466 | 
            -
             | 
| 469 | 
            +
             | 
| 467 470 | 
             
                    # Get entries and calculate relevance
         | 
| 468 471 | 
             
                    entries = []
         | 
| 469 472 | 
             
                    for entry_id in matching_ids:
         | 
| 470 473 | 
             
                        if entry_id in self.memories:
         | 
| 471 474 | 
             
                            entry = self.memories[entry_id]
         | 
| 472 475 | 
             
                            if query:
         | 
| 473 | 
            -
                                entry.relevance_score = self.text_index.calculate_relevance( | 
| 476 | 
            +
                                entry.relevance_score = self.text_index.calculate_relevance(
         | 
| 477 | 
            +
                                    entry_id, query
         | 
| 478 | 
            +
                                )
         | 
| 474 479 | 
             
                            entries.append(entry)
         | 
| 475 | 
            -
             | 
| 480 | 
            +
             | 
| 476 481 | 
             
                    # Sort by relevance and timestamp
         | 
| 477 482 | 
             
                    entries.sort(key=lambda e: (-e.relevance_score, -e.timestamp.timestamp()))
         | 
| 478 | 
            -
             | 
| 483 | 
            +
             | 
| 479 484 | 
             
                    # Apply limit
         | 
| 480 485 | 
             
                    limited_entries = entries[:limit]
         | 
| 481 | 
            -
             | 
| 486 | 
            +
             | 
| 482 487 | 
             
                    # Cache result
         | 
| 483 | 
            -
                    cache_data = {
         | 
| 484 | 
            -
                        'entries': limited_entries,
         | 
| 485 | 
            -
                        'total_count': len(entries)
         | 
| 486 | 
            -
                    }
         | 
| 488 | 
            +
                    cache_data = {"entries": limited_entries, "total_count": len(entries)}
         | 
| 487 489 | 
             
                    self.cache.put(cache_key, cache_data, ttl=300)
         | 
| 488 | 
            -
             | 
| 490 | 
            +
             | 
| 489 491 | 
             
                    # Return result
         | 
| 490 492 | 
             
                    return QueryResult(
         | 
| 491 493 | 
             
                        entries=limited_entries,
         | 
| 492 494 | 
             
                        total_count=len(entries),
         | 
| 493 495 | 
             
                        query_time=time.time() - start_time,
         | 
| 494 | 
            -
                        index_used= | 
| 496 | 
            +
                        index_used="text_index" if query else "full_scan",
         | 
| 495 497 | 
             
                    )
         | 
| 496 | 
            -
             | 
| 498 | 
            +
             | 
| 497 499 | 
             
                def get_recent_memories(
         | 
| 498 | 
            -
                    self,
         | 
| 499 | 
            -
                    hours: Optional[int] = None,
         | 
| 500 | 
            -
                    days: Optional[int] = None,
         | 
| 501 | 
            -
                    limit: int = 50
         | 
| 500 | 
            +
                    self, hours: Optional[int] = None, days: Optional[int] = None, limit: int = 50
         | 
| 502 501 | 
             
                ) -> QueryResult:
         | 
| 503 502 | 
             
                    """Get recent memories within time range.
         | 
| 504 | 
            -
             | 
| 503 | 
            +
             | 
| 505 504 | 
             
                    Args:
         | 
| 506 505 | 
             
                        hours: Hours to look back
         | 
| 507 506 | 
             
                        days: Days to look back
         | 
| 508 507 | 
             
                        limit: Maximum results
         | 
| 509 | 
            -
             | 
| 508 | 
            +
             | 
| 510 509 | 
             
                    Returns:
         | 
| 511 510 | 
             
                        Recent memories
         | 
| 512 511 | 
             
                    """
         | 
| 513 512 | 
             
                    start_time = time.time()
         | 
| 514 | 
            -
             | 
| 513 | 
            +
             | 
| 515 514 | 
             
                    # Calculate time range
         | 
| 516 515 | 
             
                    now = datetime.now()
         | 
| 517 516 | 
             
                    if hours:
         | 
| @@ -520,117 +519,120 @@ class IndexedMemoryService: | |
| 520 519 | 
             
                        min_time = now - timedelta(days=days)
         | 
| 521 520 | 
             
                    else:
         | 
| 522 521 | 
             
                        min_time = None
         | 
| 523 | 
            -
             | 
| 522 | 
            +
             | 
| 524 523 | 
             
                    # Use time index for range search
         | 
| 525 524 | 
             
                    entry_ids = self.time_index.range_search(
         | 
| 526 | 
            -
                        min_key=min_time,
         | 
| 527 | 
            -
                        max_key=now,
         | 
| 528 | 
            -
                        limit=limit
         | 
| 525 | 
            +
                        min_key=min_time, max_key=now, limit=limit
         | 
| 529 526 | 
             
                    )
         | 
| 530 | 
            -
             | 
| 527 | 
            +
             | 
| 531 528 | 
             
                    # Get entries
         | 
| 532 529 | 
             
                    entries = [self.memories[id] for id in entry_ids if id in self.memories]
         | 
| 533 | 
            -
             | 
| 530 | 
            +
             | 
| 534 531 | 
             
                    return QueryResult(
         | 
| 535 532 | 
             
                        entries=entries,
         | 
| 536 533 | 
             
                        total_count=len(entries),
         | 
| 537 534 | 
             
                        query_time=time.time() - start_time,
         | 
| 538 | 
            -
                        index_used= | 
| 535 | 
            +
                        index_used="time_index",
         | 
| 539 536 | 
             
                    )
         | 
| 540 | 
            -
             | 
| 541 | 
            -
                def get_agent_memories(
         | 
| 542 | 
            -
                    self,
         | 
| 543 | 
            -
                    agent_id: str,
         | 
| 544 | 
            -
                    limit: int = 50
         | 
| 545 | 
            -
                ) -> QueryResult:
         | 
| 537 | 
            +
             | 
| 538 | 
            +
                def get_agent_memories(self, agent_id: str, limit: int = 50) -> QueryResult:
         | 
| 546 539 | 
             
                    """Get all memories for a specific agent.
         | 
| 547 | 
            -
             | 
| 540 | 
            +
             | 
| 548 541 | 
             
                    Args:
         | 
| 549 542 | 
             
                        agent_id: Agent ID
         | 
| 550 543 | 
             
                        limit: Maximum results
         | 
| 551 | 
            -
             | 
| 544 | 
            +
             | 
| 552 545 | 
             
                    Returns:
         | 
| 553 546 | 
             
                        Agent's memories
         | 
| 554 547 | 
             
                    """
         | 
| 555 548 | 
             
                    start_time = time.time()
         | 
| 556 | 
            -
             | 
| 549 | 
            +
             | 
| 557 550 | 
             
                    # Use agent index
         | 
| 558 551 | 
             
                    entry_ids = list(self.agent_index.get(agent_id, set()))[:limit]
         | 
| 559 | 
            -
             | 
| 552 | 
            +
             | 
| 560 553 | 
             
                    # Get entries
         | 
| 561 554 | 
             
                    entries = [self.memories[id] for id in entry_ids if id in self.memories]
         | 
| 562 | 
            -
             | 
| 555 | 
            +
             | 
| 563 556 | 
             
                    # Sort by timestamp
         | 
| 564 557 | 
             
                    entries.sort(key=lambda e: -e.timestamp.timestamp())
         | 
| 565 | 
            -
             | 
| 558 | 
            +
             | 
| 566 559 | 
             
                    return QueryResult(
         | 
| 567 560 | 
             
                        entries=entries,
         | 
| 568 561 | 
             
                        total_count=len(self.agent_index.get(agent_id, set())),
         | 
| 569 562 | 
             
                        query_time=time.time() - start_time,
         | 
| 570 | 
            -
                        index_used= | 
| 563 | 
            +
                        index_used="agent_index",
         | 
| 571 564 | 
             
                    )
         | 
| 572 | 
            -
             | 
| 565 | 
            +
             | 
| 573 566 | 
             
                def _generate_id(self, agent_id: str, content: str) -> str:
         | 
| 574 567 | 
             
                    """Generate unique ID for memory entry."""
         | 
| 575 568 | 
             
                    timestamp = datetime.now().isoformat()
         | 
| 576 569 | 
             
                    hash_input = f"{agent_id}:{content[:100]}:{timestamp}"
         | 
| 577 570 | 
             
                    return hashlib.md5(hash_input.encode()).hexdigest()[:12]
         | 
| 578 | 
            -
             | 
| 571 | 
            +
             | 
| 579 572 | 
             
                def _save_indexes(self):
         | 
| 580 573 | 
             
                    """Persist all indexes to disk."""
         | 
| 581 574 | 
             
                    # Save text index
         | 
| 582 575 | 
             
                    self.text_index.save(self.data_dir / "text_index.pkl")
         | 
| 583 | 
            -
             | 
| 576 | 
            +
             | 
| 584 577 | 
             
                    # Save other indexes
         | 
| 585 | 
            -
                    with open(self.data_dir / "indexes.pkl",  | 
| 586 | 
            -
                        pickle.dump( | 
| 587 | 
            -
                             | 
| 588 | 
            -
             | 
| 589 | 
            -
             | 
| 590 | 
            -
             | 
| 591 | 
            -
             | 
| 592 | 
            -
             | 
| 593 | 
            -
             | 
| 578 | 
            +
                    with open(self.data_dir / "indexes.pkl", "wb") as f:
         | 
| 579 | 
            +
                        pickle.dump(
         | 
| 580 | 
            +
                            {
         | 
| 581 | 
            +
                                "memories": self.memories,
         | 
| 582 | 
            +
                                "time_index": self.time_index.index,
         | 
| 583 | 
            +
                                "agent_index": dict(self.agent_index),
         | 
| 584 | 
            +
                                "category_index": dict(self.category_index),
         | 
| 585 | 
            +
                                "tag_index": dict(self.tag_index),
         | 
| 586 | 
            +
                            },
         | 
| 587 | 
            +
                            f,
         | 
| 588 | 
            +
                        )
         | 
| 589 | 
            +
             | 
| 594 590 | 
             
                    self.logger.info(f"Saved {len(self.memories)} memories to disk")
         | 
| 595 | 
            -
             | 
| 591 | 
            +
             | 
| 596 592 | 
             
                def _load_indexes(self):
         | 
| 597 593 | 
             
                    """Load indexes from disk."""
         | 
| 598 594 | 
             
                    # Load text index
         | 
| 599 595 | 
             
                    text_index_path = self.data_dir / "text_index.pkl"
         | 
| 600 596 | 
             
                    if text_index_path.exists():
         | 
| 601 597 | 
             
                        self.text_index.load(text_index_path)
         | 
| 602 | 
            -
             | 
| 598 | 
            +
             | 
| 603 599 | 
             
                    # Load other indexes
         | 
| 604 600 | 
             
                    indexes_path = self.data_dir / "indexes.pkl"
         | 
| 605 601 | 
             
                    if indexes_path.exists():
         | 
| 606 | 
            -
                        with open(indexes_path,  | 
| 602 | 
            +
                        with open(indexes_path, "rb") as f:
         | 
| 607 603 | 
             
                            data = pickle.load(f)
         | 
| 608 | 
            -
             | 
| 609 | 
            -
                        self.memories = data.get( | 
| 610 | 
            -
                        self.time_index.index = data.get( | 
| 611 | 
            -
                        self.agent_index = defaultdict( | 
| 612 | 
            -
             | 
| 613 | 
            -
                         | 
| 614 | 
            -
                        
         | 
| 604 | 
            +
             | 
| 605 | 
            +
                        self.memories = data.get("memories", {})
         | 
| 606 | 
            +
                        self.time_index.index = data.get("time_index", [])
         | 
| 607 | 
            +
                        self.agent_index = defaultdict(
         | 
| 608 | 
            +
                            set, {k: set(v) for k, v in data.get("agent_index", {}).items()}
         | 
| 609 | 
            +
                        )
         | 
| 610 | 
            +
                        self.category_index = defaultdict(
         | 
| 611 | 
            +
                            set, {k: set(v) for k, v in data.get("category_index", {}).items()}
         | 
| 612 | 
            +
                        )
         | 
| 613 | 
            +
                        self.tag_index = defaultdict(
         | 
| 614 | 
            +
                            set, {k: set(v) for k, v in data.get("tag_index", {}).items()}
         | 
| 615 | 
            +
                        )
         | 
| 616 | 
            +
             | 
| 615 617 | 
             
                        self.logger.info(f"Loaded {len(self.memories)} memories from disk")
         | 
| 616 | 
            -
             | 
| 618 | 
            +
             | 
| 617 619 | 
             
                def get_stats(self) -> Dict[str, Any]:
         | 
| 618 620 | 
             
                    """Get memory service statistics."""
         | 
| 619 621 | 
             
                    return {
         | 
| 620 | 
            -
                         | 
| 621 | 
            -
                         | 
| 622 | 
            -
                         | 
| 623 | 
            -
                         | 
| 624 | 
            -
                         | 
| 625 | 
            -
                             | 
| 626 | 
            -
                             | 
| 627 | 
            -
                             | 
| 628 | 
            -
                             | 
| 629 | 
            -
                             | 
| 622 | 
            +
                        "total_memories": len(self.memories),
         | 
| 623 | 
            +
                        "agents": len(self.agent_index),
         | 
| 624 | 
            +
                        "categories": len(self.category_index),
         | 
| 625 | 
            +
                        "tags": len(self.tag_index),
         | 
| 626 | 
            +
                        "index_size": {
         | 
| 627 | 
            +
                            "text": len(self.text_index.index),
         | 
| 628 | 
            +
                            "time": len(self.time_index.index),
         | 
| 629 | 
            +
                            "agent": sum(len(v) for v in self.agent_index.values()),
         | 
| 630 | 
            +
                            "category": sum(len(v) for v in self.category_index.values()),
         | 
| 631 | 
            +
                            "tag": sum(len(v) for v in self.tag_index.values()),
         | 
| 630 632 | 
             
                        },
         | 
| 631 | 
            -
                         | 
| 633 | 
            +
                        "cache_stats": self.cache.get_stats(),
         | 
| 632 634 | 
             
                    }
         | 
| 633 | 
            -
             | 
| 635 | 
            +
             | 
| 634 636 | 
             
                def cleanup(self):
         | 
| 635 637 | 
             
                    """Save indexes and cleanup resources."""
         | 
| 636 638 | 
             
                    self._save_indexes()
         | 
| @@ -645,4 +647,4 @@ def get_indexed_memory() -> IndexedMemoryService: | |
| 645 647 | 
             
                global _memory_service
         | 
| 646 648 | 
             
                if _memory_service is None:
         | 
| 647 649 | 
             
                    _memory_service = IndexedMemoryService()
         | 
| 648 | 
            -
                return _memory_service
         | 
| 650 | 
            +
                return _memory_service
         |