claude-mpm 3.9.9__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 +155 -0
- 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 +90 -49
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +21 -18
- 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 +143 -762
- 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 -1150
- 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 +217 -0
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +36 -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 +571 -0
- 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 +40 -23
- 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 +14 -21
- 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 +97 -93
- claude_mpm/services/mcp_gateway/main.py +307 -127
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
- claude_mpm/services/mcp_gateway/server/{mcp_server.py ā mcp_gateway.py} +149 -153
- 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 +110 -121
- 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 +20 -534
- 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 +9 -0
- claude_mpm/storage/state_storage.py +552 -0
- 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.9.dist-info ā claude_mpm-4.0.3.dist-info}/METADATA +51 -2
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.9.dist-info ā claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.9.dist-info ā claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/core/config_paths.py +0 -150
- 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/memory_guardian.py +0 -770
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
- 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.9.dist-info/RECORD +0 -293
- {claude_mpm-3.9.9.dist-info ā claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.9.dist-info ā claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -22,24 +22,23 @@ Performance Impact: | |
| 22 22 | 
             
            - Reduces redundant file I/O operations
         | 
| 23 23 |  | 
| 24 24 | 
             
            Usage:
         | 
| 25 | 
            -
                from  | 
| 26 | 
            -
             | 
| 25 | 
            +
                from claude_mpm.services.shared_prompt_cache import SharedPromptCache
         | 
| 26 | 
            +
             | 
| 27 27 | 
             
                # Get singleton instance
         | 
| 28 28 | 
             
                cache = SharedPromptCache.get_instance()
         | 
| 29 | 
            -
             | 
| 29 | 
            +
             | 
| 30 30 | 
             
                # Cache prompt data
         | 
| 31 31 | 
             
                cache.set("engineer:profile", prompt_data, ttl=300)
         | 
| 32 | 
            -
             | 
| 32 | 
            +
             | 
| 33 33 | 
             
                # Retrieve cached data
         | 
| 34 34 | 
             
                prompt_data = cache.get("engineer:profile")
         | 
| 35 | 
            -
             | 
| 35 | 
            +
             | 
| 36 36 | 
             
                # Invalidate specific cache entries
         | 
| 37 37 | 
             
                cache.invalidate("engineer:profile")
         | 
| 38 38 | 
             
            """
         | 
| 39 39 |  | 
| 40 40 | 
             
            import asyncio
         | 
| 41 41 | 
             
            import json
         | 
| 42 | 
            -
            import logging
         | 
| 43 42 | 
             
            import threading
         | 
| 44 43 | 
             
            import time
         | 
| 45 44 | 
             
            import weakref
         | 
| @@ -47,7 +46,6 @@ from collections import OrderedDict | |
| 47 46 | 
             
            from dataclasses import dataclass, field
         | 
| 48 47 | 
             
            from datetime import datetime, timedelta
         | 
| 49 48 | 
             
            from functools import wraps
         | 
| 50 | 
            -
            from pathlib import Path
         | 
| 51 49 | 
             
            from threading import RLock
         | 
| 52 50 | 
             
            from typing import Any, Dict, List, Optional, Set, Tuple, Union
         | 
| 53 51 |  | 
| @@ -57,7 +55,7 @@ from claude_mpm.core.base_service import BaseService, ServiceHealth, ServiceMetr | |
| 57 55 | 
             
            @dataclass
         | 
| 58 56 | 
             
            class CacheEntry:
         | 
| 59 57 | 
             
                """Cache entry with TTL and metadata."""
         | 
| 60 | 
            -
             | 
| 58 | 
            +
             | 
| 61 59 | 
             
                key: str
         | 
| 62 60 | 
             
                value: Any
         | 
| 63 61 | 
             
                created_at: float
         | 
| @@ -66,19 +64,19 @@ class CacheEntry: | |
| 66 64 | 
             
                last_accessed: float = field(default_factory=time.time)
         | 
| 67 65 | 
             
                size_bytes: int = 0
         | 
| 68 66 | 
             
                metadata: Dict[str, Any] = field(default_factory=dict)
         | 
| 69 | 
            -
             | 
| 67 | 
            +
             | 
| 70 68 | 
             
                @property
         | 
| 71 69 | 
             
                def is_expired(self) -> bool:
         | 
| 72 70 | 
             
                    """Check if cache entry has expired."""
         | 
| 73 71 | 
             
                    if self.ttl is None:
         | 
| 74 72 | 
             
                        return False
         | 
| 75 73 | 
             
                    return time.time() > (self.created_at + self.ttl)
         | 
| 76 | 
            -
             | 
| 74 | 
            +
             | 
| 77 75 | 
             
                @property
         | 
| 78 76 | 
             
                def age_seconds(self) -> float:
         | 
| 79 77 | 
             
                    """Get age of cache entry in seconds."""
         | 
| 80 78 | 
             
                    return time.time() - self.created_at
         | 
| 81 | 
            -
             | 
| 79 | 
            +
             | 
| 82 80 | 
             
                def touch(self) -> None:
         | 
| 83 81 | 
             
                    """Update access metrics."""
         | 
| 84 82 | 
             
                    self.access_count += 1
         | 
| @@ -88,7 +86,7 @@ class CacheEntry: | |
| 88 86 | 
             
            @dataclass
         | 
| 89 87 | 
             
            class CacheMetrics:
         | 
| 90 88 | 
             
                """Cache performance metrics."""
         | 
| 91 | 
            -
             | 
| 89 | 
            +
             | 
| 92 90 | 
             
                hits: int = 0
         | 
| 93 91 | 
             
                misses: int = 0
         | 
| 94 92 | 
             
                sets: int = 0
         | 
| @@ -98,13 +96,13 @@ class CacheMetrics: | |
| 98 96 | 
             
                entry_count: int = 0
         | 
| 99 97 | 
             
                evictions: int = 0
         | 
| 100 98 | 
             
                expired_removals: int = 0
         | 
| 101 | 
            -
             | 
| 99 | 
            +
             | 
| 102 100 | 
             
                @property
         | 
| 103 101 | 
             
                def hit_rate(self) -> float:
         | 
| 104 102 | 
             
                    """Calculate cache hit rate."""
         | 
| 105 103 | 
             
                    total = self.hits + self.misses
         | 
| 106 104 | 
             
                    return self.hits / total if total > 0 else 0.0
         | 
| 107 | 
            -
             | 
| 105 | 
            +
             | 
| 108 106 | 
             
                @property
         | 
| 109 107 | 
             
                def miss_rate(self) -> float:
         | 
| 110 108 | 
             
                    """Calculate cache miss rate."""
         | 
| @@ -114,59 +112,75 @@ class CacheMetrics: | |
| 114 112 | 
             
            class SharedPromptCache(BaseService):
         | 
| 115 113 | 
             
                """
         | 
| 116 114 | 
             
                Shared Prompt Cache Service with Singleton Pattern
         | 
| 117 | 
            -
             | 
| 115 | 
            +
             | 
| 118 116 | 
             
                Thread-safe, high-performance caching service for subprocess agent prompts.
         | 
| 119 117 | 
             
                Implements LRU eviction with TTL support and comprehensive metrics.
         | 
| 120 118 | 
             
                """
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                _instance: Optional[ | 
| 119 | 
            +
             | 
| 120 | 
            +
                _instance: Optional["SharedPromptCache"] = None
         | 
| 123 121 | 
             
                _lock = threading.Lock()
         | 
| 124 | 
            -
             | 
| 122 | 
            +
             | 
| 125 123 | 
             
                def __init__(self, config: Optional[Dict[str, Any]] = None):
         | 
| 126 124 | 
             
                    """Initialize the shared cache service."""
         | 
| 127 125 | 
             
                    # Singleton pattern enforcement
         | 
| 128 126 | 
             
                    if SharedPromptCache._instance is not None:
         | 
| 129 | 
            -
                        raise RuntimeError( | 
| 130 | 
            -
             | 
| 127 | 
            +
                        raise RuntimeError(
         | 
| 128 | 
            +
                            "SharedPromptCache is a singleton. Use get_instance() instead."
         | 
| 129 | 
            +
                        )
         | 
| 130 | 
            +
             | 
| 131 131 | 
             
                    super().__init__("shared_prompt_cache", config)
         | 
| 132 | 
            -
             | 
| 132 | 
            +
             | 
| 133 133 | 
             
                    # Cache configuration
         | 
| 134 | 
            -
                    self.max_size = self.get_config( | 
| 135 | 
            -
             | 
| 136 | 
            -
                     | 
| 137 | 
            -
                    self. | 
| 134 | 
            +
                    self.max_size = self.get_config(
         | 
| 135 | 
            +
                        "max_size", 500
         | 
| 136 | 
            +
                    )  # Reduced maximum cache entries
         | 
| 137 | 
            +
                    self.max_memory_mb = self.get_config(
         | 
| 138 | 
            +
                        "max_memory_mb", 50
         | 
| 139 | 
            +
                    )  # Reduced maximum memory usage
         | 
| 140 | 
            +
                    self.default_ttl = self.get_config(
         | 
| 141 | 
            +
                        "default_ttl", 300
         | 
| 142 | 
            +
                    )  # 5 minutes default TTL (was 30)
         | 
| 143 | 
            +
                    self.cleanup_interval = self.get_config(
         | 
| 144 | 
            +
                        "cleanup_interval", 60
         | 
| 145 | 
            +
                    )  # 1 minute cleanup (was 5)
         | 
| 138 146 | 
             
                    self.enable_metrics = self.get_config("enable_metrics", True)
         | 
| 139 | 
            -
             | 
| 147 | 
            +
             | 
| 140 148 | 
             
                    # Memory pressure handling
         | 
| 141 | 
            -
                    self.memory_pressure_threshold =  | 
| 149 | 
            +
                    self.memory_pressure_threshold = (
         | 
| 150 | 
            +
                        0.8  # 80% of max memory triggers aggressive cleanup
         | 
| 151 | 
            +
                    )
         | 
| 142 152 | 
             
                    self.aggressive_cleanup_active = False
         | 
| 143 | 
            -
             | 
| 153 | 
            +
             | 
| 144 154 | 
             
                    # Cache storage - OrderedDict for LRU behavior
         | 
| 145 155 | 
             
                    self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
         | 
| 146 156 | 
             
                    self._cache_lock = RLock()  # Reentrant lock for nested operations
         | 
| 147 | 
            -
             | 
| 157 | 
            +
             | 
| 148 158 | 
             
                    # Metrics and monitoring
         | 
| 149 159 | 
             
                    self._metrics = CacheMetrics()
         | 
| 150 160 | 
             
                    self._metrics_lock = threading.Lock()
         | 
| 151 | 
            -
             | 
| 161 | 
            +
             | 
| 152 162 | 
             
                    # Background task tracking
         | 
| 153 163 | 
             
                    self._cleanup_task: Optional[asyncio.Task] = None
         | 
| 154 | 
            -
             | 
| 164 | 
            +
             | 
| 155 165 | 
             
                    # Cache invalidation tracking
         | 
| 156 166 | 
             
                    self._invalidation_callbacks: Dict[str, List[callable]] = {}
         | 
| 157 167 | 
             
                    self._namespace_dependencies: Dict[str, Set[str]] = {}
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                    self.logger.info( | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 168 | 
            +
             | 
| 169 | 
            +
                    self.logger.info(
         | 
| 170 | 
            +
                        f"SharedPromptCache initialized with max_size={self.max_size}, "
         | 
| 171 | 
            +
                        f"max_memory_mb={self.max_memory_mb}, default_ttl={self.default_ttl}s"
         | 
| 172 | 
            +
                    )
         | 
| 173 | 
            +
             | 
| 162 174 | 
             
                @classmethod
         | 
| 163 | 
            -
                def get_instance( | 
| 175 | 
            +
                def get_instance(
         | 
| 176 | 
            +
                    cls, config: Optional[Dict[str, Any]] = None
         | 
| 177 | 
            +
                ) -> "SharedPromptCache":
         | 
| 164 178 | 
             
                    """
         | 
| 165 179 | 
             
                    Get the singleton instance of SharedPromptCache.
         | 
| 166 | 
            -
             | 
| 180 | 
            +
             | 
| 167 181 | 
             
                    Args:
         | 
| 168 182 | 
             
                        config: Optional configuration (only used on first call)
         | 
| 169 | 
            -
             | 
| 183 | 
            +
             | 
| 170 184 | 
             
                    Returns:
         | 
| 171 185 | 
             
                        Singleton instance of SharedPromptCache
         | 
| 172 186 | 
             
                    """
         | 
| @@ -175,7 +189,7 @@ class SharedPromptCache(BaseService): | |
| 175 189 | 
             
                            if cls._instance is None:
         | 
| 176 190 | 
             
                                cls._instance = cls(config)
         | 
| 177 191 | 
             
                    return cls._instance
         | 
| 178 | 
            -
             | 
| 192 | 
            +
             | 
| 179 193 | 
             
                @classmethod
         | 
| 180 194 | 
             
                def reset_instance(cls) -> None:
         | 
| 181 195 | 
             
                    """Reset singleton instance (for testing purposes)."""
         | 
| @@ -184,85 +198,95 @@ class SharedPromptCache(BaseService): | |
| 184 198 | 
             
                            if cls._instance.running:
         | 
| 185 199 | 
             
                                asyncio.create_task(cls._instance.stop())
         | 
| 186 200 | 
             
                            cls._instance = None
         | 
| 187 | 
            -
             | 
| 201 | 
            +
             | 
| 188 202 | 
             
                async def _initialize(self) -> None:
         | 
| 189 203 | 
             
                    """Initialize the cache service."""
         | 
| 190 204 | 
             
                    self.logger.info("Initializing SharedPromptCache service...")
         | 
| 191 | 
            -
             | 
| 205 | 
            +
             | 
| 192 206 | 
             
                    # Start cleanup task
         | 
| 193 207 | 
             
                    self._cleanup_task = asyncio.create_task(self._cleanup_expired_entries())
         | 
| 194 | 
            -
             | 
| 208 | 
            +
             | 
| 195 209 | 
             
                    # Register with memory pressure coordinator
         | 
| 196 210 | 
             
                    try:
         | 
| 197 211 | 
             
                        from .memory_pressure_coordinator import register_service_cleanup
         | 
| 198 | 
            -
             | 
| 212 | 
            +
             | 
| 213 | 
            +
                        await register_service_cleanup(
         | 
| 214 | 
            +
                            "shared_prompt_cache", self.handle_memory_pressure
         | 
| 215 | 
            +
                        )
         | 
| 199 216 | 
             
                        self.logger.info("Registered with memory pressure coordinator")
         | 
| 200 217 | 
             
                    except Exception as e:
         | 
| 201 | 
            -
                        self.logger.warning( | 
| 202 | 
            -
             | 
| 218 | 
            +
                        self.logger.warning(
         | 
| 219 | 
            +
                            f"Failed to register with memory pressure coordinator: {e}"
         | 
| 220 | 
            +
                        )
         | 
| 221 | 
            +
             | 
| 203 222 | 
             
                    # Note: Metrics collection is handled by parent class
         | 
| 204 223 | 
             
                    # Custom metrics are collected in _collect_custom_metrics()
         | 
| 205 | 
            -
             | 
| 224 | 
            +
             | 
| 206 225 | 
             
                    self.logger.info("SharedPromptCache service initialized successfully")
         | 
| 207 | 
            -
             | 
| 226 | 
            +
             | 
| 208 227 | 
             
                async def _cleanup(self) -> None:
         | 
| 209 228 | 
             
                    """Cleanup cache service resources."""
         | 
| 210 229 | 
             
                    self.logger.info("Cleaning up SharedPromptCache service...")
         | 
| 211 | 
            -
             | 
| 230 | 
            +
             | 
| 212 231 | 
             
                    # Cancel background tasks
         | 
| 213 232 | 
             
                    if self._cleanup_task:
         | 
| 214 233 | 
             
                        self._cleanup_task.cancel()
         | 
| 215 | 
            -
             | 
| 234 | 
            +
             | 
| 216 235 | 
             
                    # Clear cache
         | 
| 217 236 | 
             
                    with self._cache_lock:
         | 
| 218 237 | 
             
                        self._cache.clear()
         | 
| 219 | 
            -
             | 
| 238 | 
            +
             | 
| 220 239 | 
             
                    self.logger.info("SharedPromptCache service cleaned up")
         | 
| 221 | 
            -
             | 
| 240 | 
            +
             | 
| 222 241 | 
             
                async def _health_check(self) -> Dict[str, bool]:
         | 
| 223 242 | 
             
                    """Perform cache-specific health checks."""
         | 
| 224 243 | 
             
                    checks = {}
         | 
| 225 | 
            -
             | 
| 244 | 
            +
             | 
| 226 245 | 
             
                    try:
         | 
| 227 246 | 
             
                        # Test cache operations
         | 
| 228 247 | 
             
                        test_key = f"__health_check_{time.time()}"
         | 
| 229 248 | 
             
                        test_value = {"test": True, "timestamp": time.time()}
         | 
| 230 | 
            -
             | 
| 249 | 
            +
             | 
| 231 250 | 
             
                        # Test set operation
         | 
| 232 251 | 
             
                        self.set(test_key, test_value, ttl=5)
         | 
| 233 252 | 
             
                        checks["cache_set"] = True
         | 
| 234 | 
            -
             | 
| 253 | 
            +
             | 
| 235 254 | 
             
                        # Test get operation
         | 
| 236 255 | 
             
                        retrieved = self.get(test_key)
         | 
| 237 256 | 
             
                        checks["cache_get"] = retrieved is not None and retrieved["test"] is True
         | 
| 238 | 
            -
             | 
| 257 | 
            +
             | 
| 239 258 | 
             
                        # Test delete operation
         | 
| 240 259 | 
             
                        self.delete(test_key)
         | 
| 241 260 | 
             
                        checks["cache_delete"] = self.get(test_key) is None
         | 
| 242 | 
            -
             | 
| 261 | 
            +
             | 
| 243 262 | 
             
                        # Check memory usage
         | 
| 244 263 | 
             
                        checks["memory_usage_ok"] = self._get_memory_usage_mb() < self.max_memory_mb
         | 
| 245 | 
            -
             | 
| 264 | 
            +
             | 
| 246 265 | 
             
                        # Check cache size
         | 
| 247 266 | 
             
                        checks["cache_size_ok"] = len(self._cache) <= self.max_size
         | 
| 248 | 
            -
             | 
| 267 | 
            +
             | 
| 249 268 | 
             
                    except Exception as e:
         | 
| 250 269 | 
             
                        self.logger.error(f"Cache health check failed: {e}")
         | 
| 251 270 | 
             
                        checks["cache_operations"] = False
         | 
| 252 | 
            -
             | 
| 271 | 
            +
             | 
| 253 272 | 
             
                    return checks
         | 
| 254 | 
            -
             | 
| 255 | 
            -
                def set( | 
| 256 | 
            -
             | 
| 273 | 
            +
             | 
| 274 | 
            +
                def set(
         | 
| 275 | 
            +
                    self,
         | 
| 276 | 
            +
                    key: str,
         | 
| 277 | 
            +
                    value: Any,
         | 
| 278 | 
            +
                    ttl: Optional[float] = None,
         | 
| 279 | 
            +
                    metadata: Optional[Dict[str, Any]] = None,
         | 
| 280 | 
            +
                ) -> bool:
         | 
| 257 281 | 
             
                    """
         | 
| 258 282 | 
             
                    Set a cache entry with optional TTL.
         | 
| 259 | 
            -
             | 
| 283 | 
            +
             | 
| 260 284 | 
             
                    Args:
         | 
| 261 285 | 
             
                        key: Cache key
         | 
| 262 286 | 
             
                        value: Value to cache
         | 
| 263 287 | 
             
                        ttl: Time to live in seconds (uses default_ttl if None)
         | 
| 264 288 | 
             
                        metadata: Optional metadata for the cache entry
         | 
| 265 | 
            -
             | 
| 289 | 
            +
             | 
| 266 290 | 
             
                    Returns:
         | 
| 267 291 | 
             
                        True if successful, False otherwise
         | 
| 268 292 | 
             
                    """
         | 
| @@ -271,10 +295,10 @@ class SharedPromptCache(BaseService): | |
| 271 295 | 
             
                            # Use default TTL if not specified
         | 
| 272 296 | 
             
                            if ttl is None:
         | 
| 273 297 | 
             
                                ttl = self.default_ttl
         | 
| 274 | 
            -
             | 
| 298 | 
            +
             | 
| 275 299 | 
             
                            # Calculate entry size
         | 
| 276 300 | 
             
                            size_bytes = self._calculate_size(value)
         | 
| 277 | 
            -
             | 
| 301 | 
            +
             | 
| 278 302 | 
             
                            # Create cache entry
         | 
| 279 303 | 
             
                            entry = CacheEntry(
         | 
| 280 304 | 
             
                                key=key,
         | 
| @@ -282,54 +306,56 @@ class SharedPromptCache(BaseService): | |
| 282 306 | 
             
                                created_at=time.time(),
         | 
| 283 307 | 
             
                                ttl=ttl,
         | 
| 284 308 | 
             
                                size_bytes=size_bytes,
         | 
| 285 | 
            -
                                metadata=metadata or {}
         | 
| 309 | 
            +
                                metadata=metadata or {},
         | 
| 286 310 | 
             
                            )
         | 
| 287 | 
            -
             | 
| 311 | 
            +
             | 
| 288 312 | 
             
                            # Check if we need to evict entries
         | 
| 289 313 | 
             
                            self._ensure_cache_capacity(size_bytes)
         | 
| 290 | 
            -
             | 
| 314 | 
            +
             | 
| 291 315 | 
             
                            # Remove existing entry if present
         | 
| 292 316 | 
             
                            if key in self._cache:
         | 
| 293 317 | 
             
                                old_entry = self._cache.pop(key)
         | 
| 294 318 | 
             
                                with self._metrics_lock:
         | 
| 295 319 | 
             
                                    self._metrics.size_bytes -= old_entry.size_bytes
         | 
| 296 | 
            -
             | 
| 320 | 
            +
             | 
| 297 321 | 
             
                            # Add new entry (to end for LRU)
         | 
| 298 322 | 
             
                            self._cache[key] = entry
         | 
| 299 | 
            -
             | 
| 323 | 
            +
             | 
| 300 324 | 
             
                            # Update metrics
         | 
| 301 325 | 
             
                            with self._metrics_lock:
         | 
| 302 326 | 
             
                                self._metrics.sets += 1
         | 
| 303 327 | 
             
                                self._metrics.size_bytes += size_bytes
         | 
| 304 328 | 
             
                                self._metrics.entry_count = len(self._cache)
         | 
| 305 | 
            -
             | 
| 306 | 
            -
                        self.logger.debug( | 
| 329 | 
            +
             | 
| 330 | 
            +
                        self.logger.debug(
         | 
| 331 | 
            +
                            f"Cached key '{key}' with TTL {ttl}s, size {size_bytes} bytes"
         | 
| 332 | 
            +
                        )
         | 
| 307 333 | 
             
                        return True
         | 
| 308 | 
            -
             | 
| 334 | 
            +
             | 
| 309 335 | 
             
                    except Exception as e:
         | 
| 310 336 | 
             
                        self.logger.error(f"Failed to set cache key '{key}': {e}")
         | 
| 311 337 | 
             
                        return False
         | 
| 312 | 
            -
             | 
| 338 | 
            +
             | 
| 313 339 | 
             
                def get(self, key: str) -> Optional[Any]:
         | 
| 314 340 | 
             
                    """
         | 
| 315 341 | 
             
                    Get a cache entry by key.
         | 
| 316 | 
            -
             | 
| 342 | 
            +
             | 
| 317 343 | 
             
                    Args:
         | 
| 318 344 | 
             
                        key: Cache key to retrieve
         | 
| 319 | 
            -
             | 
| 345 | 
            +
             | 
| 320 346 | 
             
                    Returns:
         | 
| 321 347 | 
             
                        Cached value if found and not expired, None otherwise
         | 
| 322 348 | 
             
                    """
         | 
| 323 349 | 
             
                    try:
         | 
| 324 350 | 
             
                        with self._cache_lock:
         | 
| 325 351 | 
             
                            entry = self._cache.get(key)
         | 
| 326 | 
            -
             | 
| 352 | 
            +
             | 
| 327 353 | 
             
                            if entry is None:
         | 
| 328 354 | 
             
                                # Cache miss
         | 
| 329 355 | 
             
                                with self._metrics_lock:
         | 
| 330 356 | 
             
                                    self._metrics.misses += 1
         | 
| 331 357 | 
             
                                return None
         | 
| 332 | 
            -
             | 
| 358 | 
            +
             | 
| 333 359 | 
             
                            if entry.is_expired:
         | 
| 334 360 | 
             
                                # Entry expired, remove it
         | 
| 335 361 | 
             
                                self._remove_entry(key, entry)
         | 
| @@ -337,118 +363,124 @@ class SharedPromptCache(BaseService): | |
| 337 363 | 
             
                                    self._metrics.misses += 1
         | 
| 338 364 | 
             
                                    self._metrics.expired_removals += 1
         | 
| 339 365 | 
             
                                return None
         | 
| 340 | 
            -
             | 
| 366 | 
            +
             | 
| 341 367 | 
             
                            # Cache hit - update access metrics and move to end (LRU)
         | 
| 342 368 | 
             
                            entry.touch()
         | 
| 343 369 | 
             
                            self._cache.move_to_end(key)
         | 
| 344 | 
            -
             | 
| 370 | 
            +
             | 
| 345 371 | 
             
                            with self._metrics_lock:
         | 
| 346 372 | 
             
                                self._metrics.hits += 1
         | 
| 347 | 
            -
             | 
| 348 | 
            -
                            self.logger.debug( | 
| 373 | 
            +
             | 
| 374 | 
            +
                            self.logger.debug(
         | 
| 375 | 
            +
                                f"Cache hit for key '{key}' (age: {entry.age_seconds:.1f}s)"
         | 
| 376 | 
            +
                            )
         | 
| 349 377 | 
             
                            return entry.value
         | 
| 350 | 
            -
             | 
| 378 | 
            +
             | 
| 351 379 | 
             
                    except Exception as e:
         | 
| 352 380 | 
             
                        self.logger.error(f"Failed to get cache key '{key}': {e}")
         | 
| 353 381 | 
             
                        with self._metrics_lock:
         | 
| 354 382 | 
             
                            self._metrics.misses += 1
         | 
| 355 383 | 
             
                        return None
         | 
| 356 | 
            -
             | 
| 384 | 
            +
             | 
| 357 385 | 
             
                def delete(self, key: str) -> bool:
         | 
| 358 386 | 
             
                    """
         | 
| 359 387 | 
             
                    Delete a cache entry.
         | 
| 360 | 
            -
             | 
| 388 | 
            +
             | 
| 361 389 | 
             
                    Args:
         | 
| 362 390 | 
             
                        key: Cache key to delete
         | 
| 363 | 
            -
             | 
| 391 | 
            +
             | 
| 364 392 | 
             
                    Returns:
         | 
| 365 393 | 
             
                        True if deleted, False if not found
         | 
| 366 394 | 
             
                    """
         | 
| 367 395 | 
             
                    try:
         | 
| 368 396 | 
             
                        with self._cache_lock:
         | 
| 369 397 | 
             
                            entry = self._cache.pop(key, None)
         | 
| 370 | 
            -
             | 
| 398 | 
            +
             | 
| 371 399 | 
             
                            if entry is not None:
         | 
| 372 400 | 
             
                                with self._metrics_lock:
         | 
| 373 401 | 
             
                                    self._metrics.deletes += 1
         | 
| 374 402 | 
             
                                    self._metrics.size_bytes -= entry.size_bytes
         | 
| 375 403 | 
             
                                    self._metrics.entry_count = len(self._cache)
         | 
| 376 | 
            -
             | 
| 404 | 
            +
             | 
| 377 405 | 
             
                                self.logger.debug(f"Deleted cache key '{key}'")
         | 
| 378 406 | 
             
                                return True
         | 
| 379 | 
            -
             | 
| 407 | 
            +
             | 
| 380 408 | 
             
                            return False
         | 
| 381 | 
            -
             | 
| 409 | 
            +
             | 
| 382 410 | 
             
                    except Exception as e:
         | 
| 383 411 | 
             
                        self.logger.error(f"Failed to delete cache key '{key}': {e}")
         | 
| 384 412 | 
             
                        return False
         | 
| 385 | 
            -
             | 
| 413 | 
            +
             | 
| 386 414 | 
             
                def invalidate(self, pattern: str) -> int:
         | 
| 387 415 | 
             
                    """
         | 
| 388 416 | 
             
                    Invalidate cache entries matching a pattern.
         | 
| 389 | 
            -
             | 
| 417 | 
            +
             | 
| 390 418 | 
             
                    Args:
         | 
| 391 419 | 
             
                        pattern: Pattern to match keys (supports wildcards *)
         | 
| 392 | 
            -
             | 
| 420 | 
            +
             | 
| 393 421 | 
             
                    Returns:
         | 
| 394 422 | 
             
                        Number of entries invalidated
         | 
| 395 423 | 
             
                    """
         | 
| 396 424 | 
             
                    try:
         | 
| 397 425 | 
             
                        import fnmatch
         | 
| 398 | 
            -
             | 
| 426 | 
            +
             | 
| 399 427 | 
             
                        invalidated = 0
         | 
| 400 | 
            -
             | 
| 428 | 
            +
             | 
| 401 429 | 
             
                        with self._cache_lock:
         | 
| 402 430 | 
             
                            keys_to_remove = []
         | 
| 403 | 
            -
             | 
| 431 | 
            +
             | 
| 404 432 | 
             
                            for key in self._cache.keys():
         | 
| 405 433 | 
             
                                if fnmatch.fnmatch(key, pattern):
         | 
| 406 434 | 
             
                                    keys_to_remove.append(key)
         | 
| 407 | 
            -
             | 
| 435 | 
            +
             | 
| 408 436 | 
             
                            for key in keys_to_remove:
         | 
| 409 437 | 
             
                                entry = self._cache.pop(key)
         | 
| 410 438 | 
             
                                with self._metrics_lock:
         | 
| 411 439 | 
             
                                    self._metrics.size_bytes -= entry.size_bytes
         | 
| 412 440 | 
             
                                invalidated += 1
         | 
| 413 | 
            -
             | 
| 441 | 
            +
             | 
| 414 442 | 
             
                            with self._metrics_lock:
         | 
| 415 443 | 
             
                                self._metrics.invalidations += invalidated
         | 
| 416 444 | 
             
                                self._metrics.entry_count = len(self._cache)
         | 
| 417 | 
            -
             | 
| 418 | 
            -
                        self.logger.info( | 
| 419 | 
            -
             | 
| 445 | 
            +
             | 
| 446 | 
            +
                        self.logger.info(
         | 
| 447 | 
            +
                            f"Invalidated {invalidated} cache entries matching pattern '{pattern}'"
         | 
| 448 | 
            +
                        )
         | 
| 449 | 
            +
             | 
| 420 450 | 
             
                        # Trigger invalidation callbacks
         | 
| 421 451 | 
             
                        self._trigger_invalidation_callbacks(pattern)
         | 
| 422 | 
            -
             | 
| 452 | 
            +
             | 
| 423 453 | 
             
                        return invalidated
         | 
| 424 | 
            -
             | 
| 454 | 
            +
             | 
| 425 455 | 
             
                    except Exception as e:
         | 
| 426 456 | 
             
                        self.logger.error(f"Failed to invalidate pattern '{pattern}': {e}")
         | 
| 427 457 | 
             
                        return 0
         | 
| 428 | 
            -
             | 
| 458 | 
            +
             | 
| 429 459 | 
             
                def clear(self) -> None:
         | 
| 430 460 | 
             
                    """Clear all cache entries."""
         | 
| 431 461 | 
             
                    try:
         | 
| 432 462 | 
             
                        with self._cache_lock:
         | 
| 433 463 | 
             
                            entry_count = len(self._cache)
         | 
| 434 464 | 
             
                            self._cache.clear()
         | 
| 435 | 
            -
             | 
| 465 | 
            +
             | 
| 436 466 | 
             
                            with self._metrics_lock:
         | 
| 437 467 | 
             
                                self._metrics.size_bytes = 0
         | 
| 438 468 | 
             
                                self._metrics.entry_count = 0
         | 
| 439 469 | 
             
                                self._metrics.invalidations += entry_count
         | 
| 440 | 
            -
             | 
| 470 | 
            +
             | 
| 441 471 | 
             
                        self.logger.info(f"Cleared all {entry_count} cache entries")
         | 
| 442 | 
            -
             | 
| 472 | 
            +
             | 
| 443 473 | 
             
                    except Exception as e:
         | 
| 444 474 | 
             
                        self.logger.error(f"Failed to clear cache: {e}")
         | 
| 445 | 
            -
             | 
| 475 | 
            +
             | 
| 446 476 | 
             
                def get_metrics(self) -> Dict[str, Any]:
         | 
| 447 477 | 
             
                    """Get current cache metrics."""
         | 
| 448 478 | 
             
                    with self._metrics_lock:
         | 
| 449 479 | 
             
                        size_mb = self._metrics.size_bytes / (1024 * 1024)
         | 
| 450 | 
            -
                        memory_usage_percent = ( | 
| 451 | 
            -
             | 
| 480 | 
            +
                        memory_usage_percent = (
         | 
| 481 | 
            +
                            (size_mb / self.max_memory_mb * 100) if self.max_memory_mb > 0 else 0
         | 
| 482 | 
            +
                        )
         | 
| 483 | 
            +
             | 
| 452 484 | 
             
                        return {
         | 
| 453 485 | 
             
                            "hits": self._metrics.hits,
         | 
| 454 486 | 
             
                            "misses": self._metrics.misses,
         | 
| @@ -467,15 +499,15 @@ class SharedPromptCache(BaseService): | |
| 467 499 | 
             
                            "memory_usage_percent": memory_usage_percent,
         | 
| 468 500 | 
             
                            "memory_pressure": memory_usage_percent > 80,  # Flag high memory usage
         | 
| 469 501 | 
             
                            "ttl_default": self.default_ttl,
         | 
| 470 | 
            -
                            "cleanup_interval": self.cleanup_interval
         | 
| 502 | 
            +
                            "cleanup_interval": self.cleanup_interval,
         | 
| 471 503 | 
             
                        }
         | 
| 472 | 
            -
             | 
| 504 | 
            +
             | 
| 473 505 | 
             
                def get_cache_info(self) -> Dict[str, Any]:
         | 
| 474 506 | 
             
                    """Get detailed cache information."""
         | 
| 475 507 | 
             
                    with self._cache_lock:
         | 
| 476 508 | 
             
                        entries_info = []
         | 
| 477 509 | 
             
                        total_size = 0
         | 
| 478 | 
            -
             | 
| 510 | 
            +
             | 
| 479 511 | 
             
                        for key, entry in self._cache.items():
         | 
| 480 512 | 
             
                            entry_info = {
         | 
| 481 513 | 
             
                                "key": key,
         | 
| @@ -484,30 +516,30 @@ class SharedPromptCache(BaseService): | |
| 484 516 | 
             
                                "size_bytes": entry.size_bytes,
         | 
| 485 517 | 
             
                                "is_expired": entry.is_expired,
         | 
| 486 518 | 
             
                                "ttl": entry.ttl,
         | 
| 487 | 
            -
                                "metadata": entry.metadata
         | 
| 519 | 
            +
                                "metadata": entry.metadata,
         | 
| 488 520 | 
             
                            }
         | 
| 489 521 | 
             
                            entries_info.append(entry_info)
         | 
| 490 522 | 
             
                            total_size += entry.size_bytes
         | 
| 491 | 
            -
             | 
| 523 | 
            +
             | 
| 492 524 | 
             
                        return {
         | 
| 493 525 | 
             
                            "total_entries": len(self._cache),
         | 
| 494 526 | 
             
                            "total_size_bytes": total_size,
         | 
| 495 527 | 
             
                            "total_size_mb": total_size / (1024 * 1024),
         | 
| 496 528 | 
             
                            "entries": entries_info,
         | 
| 497 | 
            -
                            "metrics": self.get_metrics()
         | 
| 529 | 
            +
                            "metrics": self.get_metrics(),
         | 
| 498 530 | 
             
                        }
         | 
| 499 | 
            -
             | 
| 531 | 
            +
             | 
| 500 532 | 
             
                def register_invalidation_callback(self, pattern: str, callback: callable) -> None:
         | 
| 501 533 | 
             
                    """Register a callback for cache invalidation events."""
         | 
| 502 534 | 
             
                    if pattern not in self._invalidation_callbacks:
         | 
| 503 535 | 
             
                        self._invalidation_callbacks[pattern] = []
         | 
| 504 536 | 
             
                    self._invalidation_callbacks[pattern].append(callback)
         | 
| 505 | 
            -
             | 
| 537 | 
            +
             | 
| 506 538 | 
             
                def _ensure_cache_capacity(self, new_entry_size: int) -> None:
         | 
| 507 539 | 
             
                    """Ensure cache has capacity for new entry."""
         | 
| 508 540 | 
             
                    current_memory_mb = self._get_memory_usage_mb()
         | 
| 509 541 | 
             
                    max_memory_bytes = self.max_memory_mb * 1024 * 1024
         | 
| 510 | 
            -
             | 
| 542 | 
            +
             | 
| 511 543 | 
             
                    # Check if we're under memory pressure
         | 
| 512 544 | 
             
                    memory_usage_ratio = current_memory_mb / self.max_memory_mb
         | 
| 513 545 | 
             
                    if memory_usage_ratio > self.memory_pressure_threshold:
         | 
| @@ -516,61 +548,63 @@ class SharedPromptCache(BaseService): | |
| 516 548 | 
             
                        while self._metrics.size_bytes > target_memory_bytes:
         | 
| 517 549 | 
             
                            if not self._evict_lru_entry():
         | 
| 518 550 | 
             
                                break
         | 
| 519 | 
            -
                        self.logger.warning( | 
| 520 | 
            -
             | 
| 521 | 
            -
             | 
| 551 | 
            +
                        self.logger.warning(
         | 
| 552 | 
            +
                            f"Memory pressure detected ({memory_usage_ratio:.1%}), "
         | 
| 553 | 
            +
                            f"aggressively cleaned cache to {self._get_memory_usage_mb():.1f} MB"
         | 
| 554 | 
            +
                        )
         | 
| 555 | 
            +
             | 
| 522 556 | 
             
                    # Check memory limit
         | 
| 523 557 | 
             
                    while (self._metrics.size_bytes + new_entry_size) > max_memory_bytes:
         | 
| 524 558 | 
             
                        if not self._evict_lru_entry():
         | 
| 525 559 | 
             
                            break
         | 
| 526 | 
            -
             | 
| 560 | 
            +
             | 
| 527 561 | 
             
                    # Check size limit
         | 
| 528 562 | 
             
                    while len(self._cache) >= self.max_size:
         | 
| 529 563 | 
             
                        if not self._evict_lru_entry():
         | 
| 530 564 | 
             
                            break
         | 
| 531 | 
            -
             | 
| 565 | 
            +
             | 
| 532 566 | 
             
                def _evict_lru_entry(self) -> bool:
         | 
| 533 567 | 
             
                    """Evict least recently used entry."""
         | 
| 534 568 | 
             
                    if not self._cache:
         | 
| 535 569 | 
             
                        return False
         | 
| 536 | 
            -
             | 
| 570 | 
            +
             | 
| 537 571 | 
             
                    # Get LRU entry (first in OrderedDict)
         | 
| 538 572 | 
             
                    key, entry = next(iter(self._cache.items()))
         | 
| 539 573 | 
             
                    self._remove_entry(key, entry)
         | 
| 540 | 
            -
             | 
| 574 | 
            +
             | 
| 541 575 | 
             
                    with self._metrics_lock:
         | 
| 542 576 | 
             
                        self._metrics.evictions += 1
         | 
| 543 | 
            -
             | 
| 577 | 
            +
             | 
| 544 578 | 
             
                    self.logger.debug(f"Evicted LRU entry '{key}' (age: {entry.age_seconds:.1f}s)")
         | 
| 545 579 | 
             
                    return True
         | 
| 546 | 
            -
             | 
| 580 | 
            +
             | 
| 547 581 | 
             
                def _remove_entry(self, key: str, entry: CacheEntry) -> None:
         | 
| 548 582 | 
             
                    """Remove entry from cache and update metrics."""
         | 
| 549 583 | 
             
                    self._cache.pop(key, None)
         | 
| 550 584 | 
             
                    with self._metrics_lock:
         | 
| 551 585 | 
             
                        self._metrics.size_bytes -= entry.size_bytes
         | 
| 552 586 | 
             
                        self._metrics.entry_count = len(self._cache)
         | 
| 553 | 
            -
             | 
| 587 | 
            +
             | 
| 554 588 | 
             
                def _calculate_size(self, value: Any) -> int:
         | 
| 555 589 | 
             
                    """Calculate approximate size of value in bytes."""
         | 
| 556 590 | 
             
                    try:
         | 
| 557 591 | 
             
                        # Use JSON serialization as approximation
         | 
| 558 | 
            -
                        return len(json.dumps(value, default=str).encode( | 
| 592 | 
            +
                        return len(json.dumps(value, default=str).encode("utf-8"))
         | 
| 559 593 | 
             
                    except Exception:
         | 
| 560 594 | 
             
                        # Fallback to string representation
         | 
| 561 | 
            -
                        return len(str(value).encode( | 
| 562 | 
            -
             | 
| 595 | 
            +
                        return len(str(value).encode("utf-8"))
         | 
| 596 | 
            +
             | 
| 563 597 | 
             
                def _get_memory_usage_mb(self) -> float:
         | 
| 564 598 | 
             
                    """Get current memory usage in MB."""
         | 
| 565 599 | 
             
                    return self._metrics.size_bytes / (1024 * 1024)
         | 
| 566 | 
            -
             | 
| 600 | 
            +
             | 
| 567 601 | 
             
                async def handle_memory_pressure(self, severity: str = "warning") -> Dict[str, Any]:
         | 
| 568 602 | 
             
                    """
         | 
| 569 603 | 
             
                    Handle memory pressure by aggressively cleaning cache.
         | 
| 570 | 
            -
             | 
| 604 | 
            +
             | 
| 571 605 | 
             
                    Args:
         | 
| 572 606 | 
             
                        severity: "warning" or "critical" level of memory pressure
         | 
| 573 | 
            -
             | 
| 607 | 
            +
             | 
| 574 608 | 
             
                    Returns:
         | 
| 575 609 | 
             
                        Dict with cleanup statistics
         | 
| 576 610 | 
             
                    """
         | 
| @@ -578,9 +612,9 @@ class SharedPromptCache(BaseService): | |
| 578 612 | 
             
                        "entries_before": len(self._cache),
         | 
| 579 613 | 
             
                        "memory_before_mb": self._get_memory_usage_mb(),
         | 
| 580 614 | 
             
                        "entries_removed": 0,
         | 
| 581 | 
            -
                        "memory_freed_mb": 0
         | 
| 615 | 
            +
                        "memory_freed_mb": 0,
         | 
| 582 616 | 
             
                    }
         | 
| 583 | 
            -
             | 
| 617 | 
            +
             | 
| 584 618 | 
             
                    with self._cache_lock:
         | 
| 585 619 | 
             
                        if severity == "critical":
         | 
| 586 620 | 
             
                            # Critical: Clear 75% of cache
         | 
| @@ -588,21 +622,21 @@ class SharedPromptCache(BaseService): | |
| 588 622 | 
             
                        else:
         | 
| 589 623 | 
             
                            # Warning: Clear 50% of cache
         | 
| 590 624 | 
             
                            target_entries = int(len(self._cache) * 0.5)
         | 
| 591 | 
            -
             | 
| 625 | 
            +
             | 
| 592 626 | 
             
                        # Remove oldest entries first
         | 
| 593 627 | 
             
                        while len(self._cache) > target_entries:
         | 
| 594 628 | 
             
                            if not self._evict_lru_entry():
         | 
| 595 629 | 
             
                                break
         | 
| 596 630 | 
             
                            stats["entries_removed"] += 1
         | 
| 597 | 
            -
             | 
| 631 | 
            +
             | 
| 598 632 | 
             
                        # Force cleanup of expired entries
         | 
| 599 633 | 
             
                        expired_count = 0
         | 
| 600 634 | 
             
                        keys_to_remove = []
         | 
| 601 | 
            -
             | 
| 635 | 
            +
             | 
| 602 636 | 
             
                        for key, entry in self._cache.items():
         | 
| 603 637 | 
             
                            if entry.is_expired:
         | 
| 604 638 | 
             
                                keys_to_remove.append(key)
         | 
| 605 | 
            -
             | 
| 639 | 
            +
             | 
| 606 640 | 
             
                        for key in keys_to_remove:
         | 
| 607 641 | 
             
                            entry = self._cache.pop(key)
         | 
| 608 642 | 
             
                            with self._metrics_lock:
         | 
| @@ -610,24 +644,26 @@ class SharedPromptCache(BaseService): | |
| 610 644 | 
             
                                self._metrics.expired_removals += 1
         | 
| 611 645 | 
             
                            expired_count += 1
         | 
| 612 646 | 
             
                            stats["entries_removed"] += 1
         | 
| 613 | 
            -
             | 
| 647 | 
            +
             | 
| 614 648 | 
             
                        if expired_count > 0:
         | 
| 615 649 | 
             
                            with self._metrics_lock:
         | 
| 616 650 | 
             
                                self._metrics.entry_count = len(self._cache)
         | 
| 617 | 
            -
             | 
| 651 | 
            +
             | 
| 618 652 | 
             
                    stats["entries_after"] = len(self._cache)
         | 
| 619 653 | 
             
                    stats["memory_after_mb"] = self._get_memory_usage_mb()
         | 
| 620 654 | 
             
                    stats["memory_freed_mb"] = stats["memory_before_mb"] - stats["memory_after_mb"]
         | 
| 621 | 
            -
             | 
| 622 | 
            -
                    self.logger.info( | 
| 623 | 
            -
             | 
| 624 | 
            -
             | 
| 655 | 
            +
             | 
| 656 | 
            +
                    self.logger.info(
         | 
| 657 | 
            +
                        f"Memory pressure ({severity}): Removed {stats['entries_removed']} entries, "
         | 
| 658 | 
            +
                        f"freed {stats['memory_freed_mb']:.2f} MB"
         | 
| 659 | 
            +
                    )
         | 
| 660 | 
            +
             | 
| 625 661 | 
             
                    return stats
         | 
| 626 | 
            -
             | 
| 662 | 
            +
             | 
| 627 663 | 
             
                def _trigger_invalidation_callbacks(self, pattern: str) -> None:
         | 
| 628 664 | 
             
                    """Trigger invalidation callbacks for pattern."""
         | 
| 629 665 | 
             
                    import fnmatch
         | 
| 630 | 
            -
             | 
| 666 | 
            +
             | 
| 631 667 | 
             
                    for callback_pattern, callbacks in self._invalidation_callbacks.items():
         | 
| 632 668 | 
             
                        if fnmatch.fnmatch(pattern, callback_pattern):
         | 
| 633 669 | 
             
                            for callback in callbacks:
         | 
| @@ -635,43 +671,45 @@ class SharedPromptCache(BaseService): | |
| 635 671 | 
             
                                    callback(pattern)
         | 
| 636 672 | 
             
                                except Exception as e:
         | 
| 637 673 | 
             
                                    self.logger.error(f"Invalidation callback failed: {e}")
         | 
| 638 | 
            -
             | 
| 674 | 
            +
             | 
| 639 675 | 
             
                async def _cleanup_expired_entries(self) -> None:
         | 
| 640 676 | 
             
                    """Background task to clean up expired entries."""
         | 
| 641 677 | 
             
                    while not self._stop_event.is_set():
         | 
| 642 678 | 
             
                        try:
         | 
| 643 679 | 
             
                            expired_count = 0
         | 
| 644 | 
            -
             | 
| 680 | 
            +
             | 
| 645 681 | 
             
                            with self._cache_lock:
         | 
| 646 682 | 
             
                                keys_to_remove = []
         | 
| 647 | 
            -
             | 
| 683 | 
            +
             | 
| 648 684 | 
             
                                for key, entry in self._cache.items():
         | 
| 649 685 | 
             
                                    if entry.is_expired:
         | 
| 650 686 | 
             
                                        keys_to_remove.append(key)
         | 
| 651 | 
            -
             | 
| 687 | 
            +
             | 
| 652 688 | 
             
                                for key in keys_to_remove:
         | 
| 653 689 | 
             
                                    entry = self._cache.pop(key)
         | 
| 654 690 | 
             
                                    with self._metrics_lock:
         | 
| 655 691 | 
             
                                        self._metrics.size_bytes -= entry.size_bytes
         | 
| 656 692 | 
             
                                        self._metrics.expired_removals += 1
         | 
| 657 693 | 
             
                                    expired_count += 1
         | 
| 658 | 
            -
             | 
| 694 | 
            +
             | 
| 659 695 | 
             
                                if expired_count > 0:
         | 
| 660 696 | 
             
                                    with self._metrics_lock:
         | 
| 661 697 | 
             
                                        self._metrics.entry_count = len(self._cache)
         | 
| 662 | 
            -
             | 
| 698 | 
            +
             | 
| 663 699 | 
             
                            if expired_count > 0:
         | 
| 664 | 
            -
                                self.logger.debug( | 
| 665 | 
            -
             | 
| 700 | 
            +
                                self.logger.debug(
         | 
| 701 | 
            +
                                    f"Cleaned up {expired_count} expired cache entries"
         | 
| 702 | 
            +
                                )
         | 
| 703 | 
            +
             | 
| 666 704 | 
             
                            # Wait for next cleanup interval
         | 
| 667 705 | 
             
                            await asyncio.sleep(self.cleanup_interval)
         | 
| 668 | 
            -
             | 
| 706 | 
            +
             | 
| 669 707 | 
             
                        except asyncio.CancelledError:
         | 
| 670 708 | 
             
                            break
         | 
| 671 709 | 
             
                        except Exception as e:
         | 
| 672 710 | 
             
                            self.logger.error(f"Cache cleanup task error: {e}")
         | 
| 673 711 | 
             
                            await asyncio.sleep(self.cleanup_interval)
         | 
| 674 | 
            -
             | 
| 712 | 
            +
             | 
| 675 713 | 
             
                async def _collect_custom_metrics(self) -> None:
         | 
| 676 714 | 
             
                    """Collect custom metrics for the service."""
         | 
| 677 715 | 
             
                    try:
         | 
| @@ -682,58 +720,60 @@ class SharedPromptCache(BaseService): | |
| 682 720 | 
             
                            cache_misses=metrics["misses"],
         | 
| 683 721 | 
             
                            cache_hit_rate=metrics["hit_rate"],
         | 
| 684 722 | 
             
                            cache_size_mb=metrics["size_mb"],
         | 
| 685 | 
            -
                            cache_entries=metrics["entry_count"]
         | 
| 723 | 
            +
                            cache_entries=metrics["entry_count"],
         | 
| 686 724 | 
             
                        )
         | 
| 687 725 | 
             
                    except Exception as e:
         | 
| 688 726 | 
             
                        self.logger.warning(f"Failed to collect cache metrics: {e}")
         | 
| 689 727 |  | 
| 690 728 |  | 
| 691 729 | 
             
            # Decorator for caching function results
         | 
| 692 | 
            -
            def cache_result( | 
| 693 | 
            -
             | 
| 730 | 
            +
            def cache_result(
         | 
| 731 | 
            +
                key_pattern: str, ttl: Optional[float] = None, namespace: Optional[str] = None
         | 
| 732 | 
            +
            ):
         | 
| 694 733 | 
             
                """
         | 
| 695 734 | 
             
                Decorator to cache function results in SharedPromptCache.
         | 
| 696 | 
            -
             | 
| 735 | 
            +
             | 
| 697 736 | 
             
                Args:
         | 
| 698 737 | 
             
                    key_pattern: Pattern for cache key (can use {args} placeholders)
         | 
| 699 738 | 
             
                    ttl: Time to live for cached result
         | 
| 700 739 | 
             
                    namespace: Optional namespace for cache keys
         | 
| 701 | 
            -
             | 
| 740 | 
            +
             | 
| 702 741 | 
             
                Example:
         | 
| 703 742 | 
             
                    @cache_result("agent_profile:{agent_name}", ttl=300)
         | 
| 704 743 | 
             
                    def load_agent_profile(agent_name: str):
         | 
| 705 744 | 
             
                        # Load profile logic
         | 
| 706 745 | 
             
                        return profile_data
         | 
| 707 746 | 
             
                """
         | 
| 747 | 
            +
             | 
| 708 748 | 
             
                def decorator(func):
         | 
| 709 749 | 
             
                    @wraps(func)
         | 
| 710 750 | 
             
                    def wrapper(*args, **kwargs):
         | 
| 711 751 | 
             
                        # Generate cache key
         | 
| 712 752 | 
             
                        import hashlib
         | 
| 713 | 
            -
             | 
| 753 | 
            +
             | 
| 714 754 | 
             
                        # Create key from pattern and args
         | 
| 715 755 | 
             
                        cache_key = key_pattern.format(
         | 
| 716 | 
            -
                            **kwargs, 
         | 
| 717 | 
            -
                            args_hash=hashlib.md5(str(args).encode()).hexdigest()[:8]
         | 
| 756 | 
            +
                            **kwargs, args_hash=hashlib.md5(str(args).encode()).hexdigest()[:8]
         | 
| 718 757 | 
             
                        )
         | 
| 719 | 
            -
             | 
| 758 | 
            +
             | 
| 720 759 | 
             
                        if namespace:
         | 
| 721 760 | 
             
                            cache_key = f"{namespace}:{cache_key}"
         | 
| 722 | 
            -
             | 
| 761 | 
            +
             | 
| 723 762 | 
             
                        # Try to get from cache
         | 
| 724 763 | 
             
                        cache = SharedPromptCache.get_instance()
         | 
| 725 764 | 
             
                        result = cache.get(cache_key)
         | 
| 726 | 
            -
             | 
| 765 | 
            +
             | 
| 727 766 | 
             
                        if result is not None:
         | 
| 728 767 | 
             
                            return result
         | 
| 729 | 
            -
             | 
| 768 | 
            +
             | 
| 730 769 | 
             
                        # Call function and cache result
         | 
| 731 770 | 
             
                        result = func(*args, **kwargs)
         | 
| 732 771 | 
             
                        cache.set(cache_key, result, ttl=ttl)
         | 
| 733 | 
            -
             | 
| 772 | 
            +
             | 
| 734 773 | 
             
                        return result
         | 
| 735 | 
            -
             | 
| 774 | 
            +
             | 
| 736 775 | 
             
                    return wrapper
         | 
| 776 | 
            +
             | 
| 737 777 | 
             
                return decorator
         | 
| 738 778 |  | 
| 739 779 |  | 
| @@ -755,65 +795,63 @@ if __name__ == "__main__": | |
| 755 795 | 
             
                    """Demonstrate SharedPromptCache usage."""
         | 
| 756 796 | 
             
                    print("š SharedPromptCache Demo")
         | 
| 757 797 | 
             
                    print("=" * 50)
         | 
| 758 | 
            -
             | 
| 798 | 
            +
             | 
| 759 799 | 
             
                    # Get cache instance
         | 
| 760 | 
            -
                    cache = SharedPromptCache.get_instance( | 
| 761 | 
            -
                        "max_size": 100,
         | 
| 762 | 
            -
             | 
| 763 | 
            -
             | 
| 764 | 
            -
                    })
         | 
| 765 | 
            -
                    
         | 
| 800 | 
            +
                    cache = SharedPromptCache.get_instance(
         | 
| 801 | 
            +
                        {"max_size": 100, "max_memory_mb": 10, "default_ttl": 60}
         | 
| 802 | 
            +
                    )
         | 
| 803 | 
            +
             | 
| 766 804 | 
             
                    # Start the service
         | 
| 767 805 | 
             
                    await cache.start()
         | 
| 768 | 
            -
             | 
| 806 | 
            +
             | 
| 769 807 | 
             
                    try:
         | 
| 770 808 | 
             
                        # Test basic operations
         | 
| 771 809 | 
             
                        print("\nš Testing basic cache operations...")
         | 
| 772 | 
            -
             | 
| 810 | 
            +
             | 
| 773 811 | 
             
                        # Set some data
         | 
| 774 812 | 
             
                        cache.set("test:key1", {"data": "value1", "type": "test"})
         | 
| 775 813 | 
             
                        cache.set("test:key2", {"data": "value2", "type": "test"}, ttl=5)
         | 
| 776 | 
            -
             | 
| 814 | 
            +
             | 
| 777 815 | 
             
                        # Get data
         | 
| 778 816 | 
             
                        result1 = cache.get("test:key1")
         | 
| 779 817 | 
             
                        result2 = cache.get("test:key2")
         | 
| 780 818 | 
             
                        print(f"Retrieved: {result1}, {result2}")
         | 
| 781 | 
            -
             | 
| 819 | 
            +
             | 
| 782 820 | 
             
                        # Test metrics
         | 
| 783 821 | 
             
                        print("\nš Cache metrics:")
         | 
| 784 822 | 
             
                        metrics = cache.get_metrics()
         | 
| 785 823 | 
             
                        for key, value in metrics.items():
         | 
| 786 824 | 
             
                            print(f"  {key}: {value}")
         | 
| 787 | 
            -
             | 
| 825 | 
            +
             | 
| 788 826 | 
             
                        # Test invalidation
         | 
| 789 827 | 
             
                        print("\nšļø Testing invalidation...")
         | 
| 790 828 | 
             
                        cache.invalidate("test:*")
         | 
| 791 | 
            -
             | 
| 829 | 
            +
             | 
| 792 830 | 
             
                        # Test decorator
         | 
| 793 831 | 
             
                        print("\nšÆ Testing cache decorator...")
         | 
| 794 | 
            -
             | 
| 832 | 
            +
             | 
| 795 833 | 
             
                        @cache_result("demo:{name}", ttl=30)
         | 
| 796 834 | 
             
                        def get_demo_data(name: str):
         | 
| 797 835 | 
             
                            print(f"Computing data for {name}...")
         | 
| 798 836 | 
             
                            return {"name": name, "timestamp": time.time()}
         | 
| 799 | 
            -
             | 
| 837 | 
            +
             | 
| 800 838 | 
             
                        # First call (cache miss)
         | 
| 801 839 | 
             
                        data1 = get_demo_data("test")
         | 
| 802 840 | 
             
                        print(f"First call: {data1}")
         | 
| 803 | 
            -
             | 
| 841 | 
            +
             | 
| 804 842 | 
             
                        # Second call (cache hit)
         | 
| 805 843 | 
             
                        data2 = get_demo_data("test")
         | 
| 806 844 | 
             
                        print(f"Second call: {data2}")
         | 
| 807 | 
            -
             | 
| 845 | 
            +
             | 
| 808 846 | 
             
                        # Final metrics
         | 
| 809 847 | 
             
                        print("\nš Final metrics:")
         | 
| 810 848 | 
             
                        final_metrics = cache.get_metrics()
         | 
| 811 849 | 
             
                        for key, value in final_metrics.items():
         | 
| 812 850 | 
             
                            print(f"  {key}: {value}")
         | 
| 813 | 
            -
             | 
| 851 | 
            +
             | 
| 814 852 | 
             
                    finally:
         | 
| 815 853 | 
             
                        await cache.stop()
         | 
| 816 854 | 
             
                        print("\nā
 Demo completed")
         | 
| 817 | 
            -
             | 
| 855 | 
            +
             | 
| 818 856 | 
             
                # Run demo
         | 
| 819 | 
            -
                asyncio.run(demo())
         | 
| 857 | 
            +
                asyncio.run(demo())
         |