claude-mpm 4.1.1__py3-none-any.whl → 4.1.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/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +1 -1
- claude_mpm/agents/BASE_PM.md +74 -46
- claude_mpm/agents/INSTRUCTIONS.md +11 -153
- claude_mpm/agents/WORKFLOW.md +61 -321
- claude_mpm/agents/__init__.py +11 -11
- claude_mpm/agents/agent_loader.py +23 -20
- claude_mpm/agents/agent_loader_integration.py +1 -1
- claude_mpm/agents/agents_metadata.py +27 -0
- claude_mpm/agents/async_agent_loader.py +5 -8
- claude_mpm/agents/base_agent_loader.py +36 -25
- claude_mpm/agents/frontmatter_validator.py +6 -6
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +9 -9
- claude_mpm/agents/templates/api_qa.json +47 -2
- claude_mpm/agents/templates/engineer.json +33 -11
- claude_mpm/agents/templates/imagemagick.json +256 -0
- claude_mpm/agents/templates/qa.json +41 -2
- claude_mpm/agents/templates/ticketing.json +5 -5
- claude_mpm/agents/templates/web_qa.json +50 -2
- claude_mpm/cli/__init__.py +51 -46
- claude_mpm/cli/__main__.py +1 -1
- claude_mpm/cli/commands/__init__.py +10 -12
- claude_mpm/cli/commands/agent_manager.py +186 -181
- claude_mpm/cli/commands/agents.py +648 -1098
- claude_mpm/cli/commands/aggregate.py +30 -29
- claude_mpm/cli/commands/cleanup.py +50 -44
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
- claude_mpm/cli/commands/config.py +162 -127
- claude_mpm/cli/commands/doctor.py +52 -62
- claude_mpm/cli/commands/info.py +37 -25
- claude_mpm/cli/commands/mcp.py +3 -7
- claude_mpm/cli/commands/mcp_command_router.py +14 -18
- claude_mpm/cli/commands/mcp_install_commands.py +28 -23
- claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
- claude_mpm/cli/commands/mcp_server_commands.py +23 -17
- claude_mpm/cli/commands/memory.py +339 -967
- claude_mpm/cli/commands/monitor.py +117 -88
- claude_mpm/cli/commands/run.py +233 -542
- claude_mpm/cli/commands/socketio_monitor.py +17 -19
- claude_mpm/cli/commands/tickets.py +92 -92
- claude_mpm/cli/parser.py +1 -5
- claude_mpm/cli/parsers/__init__.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
- claude_mpm/cli/parsers/agents_parser.py +2 -3
- claude_mpm/cli/parsers/base_parser.py +7 -5
- claude_mpm/cli/parsers/mcp_parser.py +4 -2
- claude_mpm/cli/parsers/monitor_parser.py +26 -18
- claude_mpm/cli/shared/__init__.py +10 -10
- claude_mpm/cli/shared/argument_patterns.py +57 -71
- claude_mpm/cli/shared/base_command.py +61 -53
- claude_mpm/cli/shared/error_handling.py +62 -58
- claude_mpm/cli/shared/output_formatters.py +78 -77
- claude_mpm/cli/startup_logging.py +280 -172
- claude_mpm/cli/utils.py +10 -11
- claude_mpm/cli_module/__init__.py +1 -1
- claude_mpm/cli_module/args.py +1 -1
- claude_mpm/cli_module/migration_example.py +5 -5
- claude_mpm/config/__init__.py +9 -9
- claude_mpm/config/agent_config.py +15 -14
- claude_mpm/config/experimental_features.py +4 -4
- claude_mpm/config/paths.py +0 -1
- claude_mpm/config/socketio_config.py +5 -6
- claude_mpm/constants.py +1 -2
- claude_mpm/core/__init__.py +8 -8
- claude_mpm/core/agent_name_normalizer.py +1 -1
- claude_mpm/core/agent_registry.py +22 -29
- claude_mpm/core/agent_session_manager.py +3 -3
- claude_mpm/core/base_service.py +7 -15
- claude_mpm/core/cache.py +4 -6
- claude_mpm/core/claude_runner.py +85 -113
- claude_mpm/core/config.py +43 -28
- claude_mpm/core/config_aliases.py +0 -9
- claude_mpm/core/config_constants.py +52 -30
- claude_mpm/core/constants.py +0 -1
- claude_mpm/core/container.py +18 -27
- claude_mpm/core/exceptions.py +2 -2
- claude_mpm/core/factories.py +10 -12
- claude_mpm/core/framework_loader.py +500 -680
- claude_mpm/core/hook_manager.py +26 -22
- claude_mpm/core/hook_performance_config.py +58 -47
- claude_mpm/core/injectable_service.py +1 -1
- claude_mpm/core/interactive_session.py +61 -152
- claude_mpm/core/interfaces.py +1 -100
- claude_mpm/core/lazy.py +5 -5
- claude_mpm/core/log_manager.py +587 -0
- claude_mpm/core/logger.py +125 -8
- claude_mpm/core/logging_config.py +15 -17
- claude_mpm/core/minimal_framework_loader.py +5 -8
- claude_mpm/core/oneshot_session.py +15 -33
- claude_mpm/core/optimized_agent_loader.py +4 -6
- claude_mpm/core/optimized_startup.py +2 -1
- claude_mpm/core/output_style_manager.py +147 -106
- claude_mpm/core/pm_hook_interceptor.py +0 -1
- claude_mpm/core/service_registry.py +11 -8
- claude_mpm/core/session_manager.py +1 -2
- claude_mpm/core/shared/__init__.py +1 -1
- claude_mpm/core/shared/config_loader.py +101 -97
- claude_mpm/core/shared/path_resolver.py +72 -68
- claude_mpm/core/shared/singleton_manager.py +56 -50
- claude_mpm/core/socketio_pool.py +26 -6
- claude_mpm/core/tool_access_control.py +4 -5
- claude_mpm/core/typing_utils.py +50 -59
- claude_mpm/core/unified_agent_registry.py +14 -19
- claude_mpm/core/unified_config.py +4 -6
- claude_mpm/core/unified_paths.py +197 -109
- claude_mpm/dashboard/open_dashboard.py +2 -4
- claude_mpm/experimental/cli_enhancements.py +51 -36
- claude_mpm/generators/agent_profile_generator.py +2 -4
- claude_mpm/hooks/base_hook.py +1 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
- claude_mpm/hooks/claude_hooks/event_handlers.py +99 -154
- claude_mpm/hooks/claude_hooks/hook_handler.py +110 -720
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
- claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
- claude_mpm/hooks/memory_integration_hook.py +5 -5
- claude_mpm/hooks/tool_call_interceptor.py +1 -1
- claude_mpm/hooks/validation_hooks.py +4 -4
- claude_mpm/init.py +4 -9
- claude_mpm/models/__init__.py +2 -2
- claude_mpm/models/agent_session.py +11 -14
- claude_mpm/scripts/mcp_server.py +20 -11
- claude_mpm/scripts/mcp_wrapper.py +5 -5
- claude_mpm/scripts/mpm_doctor.py +321 -0
- claude_mpm/scripts/socketio_daemon.py +28 -25
- claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
- claude_mpm/scripts/socketio_server_manager.py +116 -95
- claude_mpm/services/__init__.py +49 -49
- claude_mpm/services/agent_capabilities_service.py +12 -18
- claude_mpm/services/agents/__init__.py +22 -22
- claude_mpm/services/agents/agent_builder.py +140 -119
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
- claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +129 -511
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
- claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
- claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
- claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
- claude_mpm/services/agents/deployment/agent_validator.py +0 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
- claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
- claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/config/__init__.py +1 -1
- claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
- claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
- claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
- claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
- claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
- claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
- claude_mpm/services/agents/deployment/results/__init__.py +1 -1
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
- claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
- claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
- claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
- claude_mpm/services/agents/loading/__init__.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
- claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
- claude_mpm/services/agents/management/__init__.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
- claude_mpm/services/agents/management/agent_management_service.py +5 -9
- claude_mpm/services/agents/memory/__init__.py +4 -4
- claude_mpm/services/agents/memory/agent_memory_manager.py +157 -503
- claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
- claude_mpm/services/agents/memory/content_manager.py +44 -38
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/memory/template_generator.py +4 -6
- claude_mpm/services/agents/registry/__init__.py +11 -7
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
- claude_mpm/services/agents/registry/modification_tracker.py +3 -6
- claude_mpm/services/async_session_logger.py +1 -2
- claude_mpm/services/claude_session_logger.py +1 -2
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/command_deployment_service.py +173 -0
- claude_mpm/services/command_handler_service.py +20 -22
- claude_mpm/services/core/__init__.py +25 -25
- claude_mpm/services/core/base.py +0 -5
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/interfaces/__init__.py +32 -32
- claude_mpm/services/core/interfaces/agent.py +0 -21
- claude_mpm/services/core/interfaces/communication.py +0 -27
- claude_mpm/services/core/interfaces/infrastructure.py +0 -56
- claude_mpm/services/core/interfaces/service.py +0 -29
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/__init__.py +1 -1
- claude_mpm/services/diagnostics/checks/__init__.py +6 -6
- claude_mpm/services/diagnostics/checks/agent_check.py +152 -97
- claude_mpm/services/diagnostics/checks/base_check.py +12 -16
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
- claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
- claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
- claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
- claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
- claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
- claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
- claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
- claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
- claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
- claude_mpm/services/diagnostics/models.py +21 -19
- claude_mpm/services/event_aggregator.py +10 -17
- claude_mpm/services/event_bus/__init__.py +1 -1
- claude_mpm/services/event_bus/config.py +54 -35
- claude_mpm/services/event_bus/event_bus.py +76 -71
- claude_mpm/services/event_bus/relay.py +74 -64
- claude_mpm/services/events/__init__.py +11 -11
- claude_mpm/services/events/consumers/__init__.py +3 -3
- claude_mpm/services/events/consumers/dead_letter.py +71 -63
- claude_mpm/services/events/consumers/logging.py +39 -37
- claude_mpm/services/events/consumers/metrics.py +56 -57
- claude_mpm/services/events/consumers/socketio.py +82 -81
- claude_mpm/services/events/core.py +110 -99
- claude_mpm/services/events/interfaces.py +56 -72
- claude_mpm/services/events/producers/__init__.py +1 -1
- claude_mpm/services/events/producers/hook.py +38 -38
- claude_mpm/services/events/producers/system.py +46 -44
- claude_mpm/services/exceptions.py +81 -80
- claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
- claude_mpm/services/hook_service.py +6 -9
- claude_mpm/services/infrastructure/__init__.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +8 -12
- claude_mpm/services/infrastructure/monitoring.py +21 -23
- claude_mpm/services/mcp_gateway/__init__.py +37 -37
- claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
- claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
- claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
- claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
- claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
- claude_mpm/services/mcp_gateway/core/base.py +0 -3
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
- claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
- claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
- claude_mpm/services/mcp_gateway/main.py +2 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
- claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
- claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
- claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
- claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
- claude_mpm/services/memory/__init__.py +3 -3
- claude_mpm/services/memory/builder.py +3 -6
- claude_mpm/services/memory/cache/__init__.py +1 -1
- claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/memory/indexed_memory.py +5 -7
- claude_mpm/services/memory/optimizer.py +7 -10
- claude_mpm/services/memory/router.py +8 -9
- claude_mpm/services/memory_hook_service.py +48 -34
- claude_mpm/services/monitor_build_service.py +77 -73
- claude_mpm/services/port_manager.py +130 -108
- claude_mpm/services/project/analyzer.py +12 -10
- claude_mpm/services/project/registry.py +11 -11
- claude_mpm/services/recovery_manager.py +10 -19
- claude_mpm/services/response_tracker.py +0 -1
- claude_mpm/services/runner_configuration_service.py +19 -20
- claude_mpm/services/session_management_service.py +7 -11
- claude_mpm/services/shared/__init__.py +1 -1
- claude_mpm/services/shared/async_service_base.py +58 -50
- claude_mpm/services/shared/config_service_base.py +73 -67
- claude_mpm/services/shared/lifecycle_service_base.py +82 -78
- claude_mpm/services/shared/manager_base.py +94 -82
- claude_mpm/services/shared/service_factory.py +96 -98
- claude_mpm/services/socketio/__init__.py +3 -3
- claude_mpm/services/socketio/client_proxy.py +5 -5
- claude_mpm/services/socketio/event_normalizer.py +199 -181
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +5 -4
- claude_mpm/services/socketio/handlers/connection.py +163 -136
- claude_mpm/services/socketio/handlers/file.py +13 -14
- claude_mpm/services/socketio/handlers/git.py +12 -7
- claude_mpm/services/socketio/handlers/hook.py +49 -44
- claude_mpm/services/socketio/handlers/memory.py +0 -1
- claude_mpm/services/socketio/handlers/project.py +0 -1
- claude_mpm/services/socketio/handlers/registry.py +37 -19
- claude_mpm/services/socketio/migration_utils.py +98 -84
- claude_mpm/services/socketio/server/__init__.py +1 -1
- claude_mpm/services/socketio/server/broadcaster.py +81 -87
- claude_mpm/services/socketio/server/core.py +65 -54
- claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
- claude_mpm/services/socketio/server/main.py +64 -38
- claude_mpm/services/socketio_client_manager.py +10 -12
- claude_mpm/services/subprocess_launcher_service.py +4 -7
- claude_mpm/services/system_instructions_service.py +13 -14
- claude_mpm/services/ticket_manager.py +2 -2
- claude_mpm/services/utility_service.py +5 -13
- claude_mpm/services/version_control/__init__.py +16 -16
- claude_mpm/services/version_control/branch_strategy.py +5 -8
- claude_mpm/services/version_control/conflict_resolution.py +9 -23
- claude_mpm/services/version_control/git_operations.py +5 -7
- claude_mpm/services/version_control/semantic_versioning.py +16 -17
- claude_mpm/services/version_control/version_parser.py +13 -18
- claude_mpm/services/version_service.py +10 -11
- claude_mpm/storage/__init__.py +1 -1
- claude_mpm/storage/state_storage.py +22 -28
- claude_mpm/utils/__init__.py +6 -6
- claude_mpm/utils/agent_dependency_loader.py +47 -33
- claude_mpm/utils/config_manager.py +11 -14
- claude_mpm/utils/dependency_cache.py +1 -1
- claude_mpm/utils/dependency_manager.py +13 -17
- claude_mpm/utils/dependency_strategies.py +8 -10
- claude_mpm/utils/environment_context.py +3 -9
- claude_mpm/utils/error_handler.py +3 -13
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/path_operations.py +8 -12
- claude_mpm/utils/robust_installer.py +110 -33
- claude_mpm/utils/subprocess_utils.py +5 -6
- claude_mpm/validation/agent_validator.py +3 -6
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
- claude_mpm-4.1.3.dist-info/RECORD +528 -0
- claude_mpm/cli/commands/run_config_checker.py +0 -160
- claude_mpm-4.1.1.dist-info/RECORD +0 -494
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
|
|
4
5
|
"""
|
|
5
6
|
Agent Memory Manager Service
|
|
6
7
|
===========================
|
|
@@ -22,13 +23,17 @@ following the naming convention: {agent_id}_memories.md
|
|
|
22
23
|
|
|
23
24
|
import logging
|
|
24
25
|
import os
|
|
25
|
-
from datetime import datetime
|
|
26
26
|
from typing import Any, Dict, List, Optional, Tuple
|
|
27
27
|
|
|
28
28
|
from claude_mpm.core.config import Config
|
|
29
29
|
from claude_mpm.core.interfaces import MemoryServiceInterface
|
|
30
30
|
from claude_mpm.core.unified_paths import get_path_manager
|
|
31
|
+
|
|
31
32
|
from .content_manager import MemoryContentManager
|
|
33
|
+
from .memory_categorization_service import MemoryCategorizationService
|
|
34
|
+
from .memory_file_service import MemoryFileService
|
|
35
|
+
from .memory_format_service import MemoryFormatService
|
|
36
|
+
from .memory_limits_service import MemoryLimitsService
|
|
32
37
|
from .template_generator import MemoryTemplateGenerator
|
|
33
38
|
|
|
34
39
|
|
|
@@ -74,18 +79,22 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
74
79
|
self.project_root = get_path_manager().project_root
|
|
75
80
|
# Use current working directory by default, not project root
|
|
76
81
|
self.working_directory = working_directory or Path(os.getcwd())
|
|
77
|
-
|
|
82
|
+
|
|
78
83
|
# Use only project memory directory
|
|
79
84
|
self.project_memories_dir = self.working_directory / ".claude-mpm" / "memories"
|
|
80
|
-
|
|
85
|
+
|
|
81
86
|
# Primary memories_dir points to project
|
|
82
87
|
self.memories_dir = self.project_memories_dir
|
|
83
|
-
|
|
84
|
-
# Ensure project directory exists
|
|
85
|
-
self._ensure_memories_directory()
|
|
86
88
|
|
|
87
|
-
# Initialize
|
|
88
|
-
self.
|
|
89
|
+
# Initialize services
|
|
90
|
+
self.file_service = MemoryFileService(self.memories_dir)
|
|
91
|
+
self.limits_service = MemoryLimitsService(self.config)
|
|
92
|
+
self.memory_limits = self.limits_service.memory_limits
|
|
93
|
+
self.format_service = MemoryFormatService()
|
|
94
|
+
self.categorization_service = MemoryCategorizationService()
|
|
95
|
+
|
|
96
|
+
# Ensure project directory exists
|
|
97
|
+
self.file_service.ensure_memories_directory()
|
|
89
98
|
|
|
90
99
|
# Initialize component services
|
|
91
100
|
self.template_generator = MemoryTemplateGenerator(
|
|
@@ -112,124 +121,6 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
112
121
|
|
|
113
122
|
return self._logger_instance
|
|
114
123
|
|
|
115
|
-
def _init_memory_limits(self):
|
|
116
|
-
"""Initialize memory limits from configuration.
|
|
117
|
-
|
|
118
|
-
WHY: Allows configuration-driven memory limits instead of hardcoded values.
|
|
119
|
-
Supports agent-specific overrides for different memory requirements.
|
|
120
|
-
"""
|
|
121
|
-
# Check if memory system is enabled
|
|
122
|
-
self.memory_enabled = self.config.get("memory.enabled", True)
|
|
123
|
-
self.auto_learning = self.config.get(
|
|
124
|
-
"memory.auto_learning", True
|
|
125
|
-
) # Changed default to True
|
|
126
|
-
|
|
127
|
-
# Load default limits from configuration
|
|
128
|
-
config_limits = self.config.get("memory.limits", {})
|
|
129
|
-
self.memory_limits = {
|
|
130
|
-
"max_file_size_kb": config_limits.get(
|
|
131
|
-
"default_size_kb", self.DEFAULT_MEMORY_LIMITS["max_file_size_kb"]
|
|
132
|
-
),
|
|
133
|
-
"max_items": config_limits.get(
|
|
134
|
-
"max_items", self.DEFAULT_MEMORY_LIMITS["max_items"]
|
|
135
|
-
),
|
|
136
|
-
"max_line_length": config_limits.get(
|
|
137
|
-
"max_line_length", self.DEFAULT_MEMORY_LIMITS["max_line_length"]
|
|
138
|
-
),
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
# Load agent-specific overrides
|
|
142
|
-
self.agent_overrides = self.config.get("memory.agent_overrides", {})
|
|
143
|
-
|
|
144
|
-
def _get_agent_limits(self, agent_id: str) -> Dict[str, Any]:
|
|
145
|
-
"""Get memory limits for specific agent, including overrides.
|
|
146
|
-
|
|
147
|
-
WHY: Different agents may need different memory capacities. Research agents
|
|
148
|
-
might need larger memory for comprehensive findings, while simple agents
|
|
149
|
-
can work with smaller limits.
|
|
150
|
-
|
|
151
|
-
Args:
|
|
152
|
-
agent_id: The agent identifier
|
|
153
|
-
|
|
154
|
-
Returns:
|
|
155
|
-
Dict containing the effective limits for this agent
|
|
156
|
-
"""
|
|
157
|
-
# Start with default limits
|
|
158
|
-
limits = self.memory_limits.copy()
|
|
159
|
-
|
|
160
|
-
# Apply agent-specific overrides if they exist
|
|
161
|
-
if agent_id in self.agent_overrides:
|
|
162
|
-
overrides = self.agent_overrides[agent_id]
|
|
163
|
-
if "size_kb" in overrides:
|
|
164
|
-
limits["max_file_size_kb"] = overrides["size_kb"]
|
|
165
|
-
|
|
166
|
-
return limits
|
|
167
|
-
|
|
168
|
-
def _get_agent_auto_learning(self, agent_id: str) -> bool:
|
|
169
|
-
"""Check if auto-learning is enabled for specific agent.
|
|
170
|
-
|
|
171
|
-
Args:
|
|
172
|
-
agent_id: The agent identifier
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
bool: True if auto-learning is enabled for this agent
|
|
176
|
-
"""
|
|
177
|
-
# Check agent-specific override first
|
|
178
|
-
if agent_id in self.agent_overrides:
|
|
179
|
-
return self.agent_overrides[agent_id].get(
|
|
180
|
-
"auto_learning", self.auto_learning
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
# Fall back to global setting
|
|
184
|
-
return self.auto_learning
|
|
185
|
-
|
|
186
|
-
def _get_memory_file_with_migration(self, directory: Path, agent_id: str) -> Path:
|
|
187
|
-
"""Get memory file path, migrating from old naming if needed.
|
|
188
|
-
|
|
189
|
-
WHY: Supports backward compatibility by automatically migrating from
|
|
190
|
-
the old {agent_id}_agent.md and {agent_id}.md formats to the new {agent_id}_memories.md format.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
directory: Directory containing memory files
|
|
194
|
-
agent_id: The agent identifier
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
Path: Path to the memory file (may not exist)
|
|
198
|
-
"""
|
|
199
|
-
new_file = directory / f"{agent_id}_memories.md"
|
|
200
|
-
# Support migration from both old formats
|
|
201
|
-
old_file_agent = directory / f"{agent_id}_agent.md"
|
|
202
|
-
old_file_simple = directory / f"{agent_id}.md"
|
|
203
|
-
|
|
204
|
-
# Migrate from old formats if needed
|
|
205
|
-
if not new_file.exists():
|
|
206
|
-
# Try migrating from {agent_id}_agent.md first
|
|
207
|
-
if old_file_agent.exists():
|
|
208
|
-
try:
|
|
209
|
-
content = old_file_agent.read_text(encoding="utf-8")
|
|
210
|
-
new_file.write_text(content, encoding="utf-8")
|
|
211
|
-
|
|
212
|
-
# Delete old file for all agents
|
|
213
|
-
old_file_agent.unlink()
|
|
214
|
-
self.logger.info(f"Migrated memory file from {old_file_agent.name} to {new_file.name}")
|
|
215
|
-
except Exception as e:
|
|
216
|
-
self.logger.error(f"Failed to migrate memory file for {agent_id}: {e}")
|
|
217
|
-
return old_file_agent
|
|
218
|
-
# Try migrating from {agent_id}.md
|
|
219
|
-
elif old_file_simple.exists():
|
|
220
|
-
try:
|
|
221
|
-
content = old_file_simple.read_text(encoding="utf-8")
|
|
222
|
-
new_file.write_text(content, encoding="utf-8")
|
|
223
|
-
|
|
224
|
-
# Delete old file for all agents
|
|
225
|
-
old_file_simple.unlink()
|
|
226
|
-
self.logger.info(f"Migrated memory file from {old_file_simple.name} to {new_file.name}")
|
|
227
|
-
except Exception as e:
|
|
228
|
-
self.logger.error(f"Failed to migrate memory file for {agent_id}: {e}")
|
|
229
|
-
return old_file_simple
|
|
230
|
-
|
|
231
|
-
return new_file
|
|
232
|
-
|
|
233
124
|
def load_agent_memory(self, agent_id: str) -> str:
|
|
234
125
|
"""Load agent memory file content from project directory.
|
|
235
126
|
|
|
@@ -244,18 +135,24 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
244
135
|
str: The memory file content, creating default if doesn't exist
|
|
245
136
|
"""
|
|
246
137
|
# All agents use project directory
|
|
247
|
-
project_memory_file = self.
|
|
248
|
-
|
|
138
|
+
project_memory_file = self.file_service.get_memory_file_with_migration(
|
|
139
|
+
self.project_memories_dir, agent_id
|
|
140
|
+
)
|
|
141
|
+
|
|
249
142
|
# Load project-level memory if exists
|
|
250
143
|
if project_memory_file.exists():
|
|
251
144
|
try:
|
|
252
145
|
project_memory = project_memory_file.read_text(encoding="utf-8")
|
|
253
|
-
project_memory = self.content_manager.validate_and_repair(
|
|
146
|
+
project_memory = self.content_manager.validate_and_repair(
|
|
147
|
+
project_memory, agent_id
|
|
148
|
+
)
|
|
254
149
|
self.logger.debug(f"Loaded project-level memory for {agent_id}")
|
|
255
150
|
return project_memory
|
|
256
151
|
except Exception as e:
|
|
257
|
-
self.logger.error(
|
|
258
|
-
|
|
152
|
+
self.logger.error(
|
|
153
|
+
f"Error reading project memory file for {agent_id}: {e}"
|
|
154
|
+
)
|
|
155
|
+
|
|
259
156
|
# Memory doesn't exist - create default in project directory
|
|
260
157
|
self.logger.info(f"Creating default memory for agent: {agent_id}")
|
|
261
158
|
return self._create_default_memory(agent_id)
|
|
@@ -310,7 +207,7 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
310
207
|
str: The project-specific memory template content
|
|
311
208
|
"""
|
|
312
209
|
# Get limits for this agent
|
|
313
|
-
limits = self.
|
|
210
|
+
limits = self.limits_service.get_agent_limits(agent_id)
|
|
314
211
|
|
|
315
212
|
# Delegate to template generator
|
|
316
213
|
template = self.template_generator.create_default_memory(agent_id, limits)
|
|
@@ -327,35 +224,6 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
327
224
|
|
|
328
225
|
return template
|
|
329
226
|
|
|
330
|
-
def _save_memory_file(self, agent_id: str, content: str) -> bool:
|
|
331
|
-
"""Save memory content to file.
|
|
332
|
-
|
|
333
|
-
WHY: Memory updates need to be persisted atomically to prevent corruption
|
|
334
|
-
and ensure learnings are preserved across agent invocations.
|
|
335
|
-
|
|
336
|
-
Args:
|
|
337
|
-
agent_id: Agent identifier
|
|
338
|
-
content: Content to save
|
|
339
|
-
|
|
340
|
-
Returns:
|
|
341
|
-
bool: True if save succeeded
|
|
342
|
-
"""
|
|
343
|
-
try:
|
|
344
|
-
# All agents save to project directory
|
|
345
|
-
target_dir = self.project_memories_dir
|
|
346
|
-
|
|
347
|
-
# Ensure directory exists
|
|
348
|
-
target_dir.mkdir(parents=True, exist_ok=True)
|
|
349
|
-
|
|
350
|
-
memory_file = target_dir / f"{agent_id}_memories.md"
|
|
351
|
-
memory_file.write_text(content, encoding="utf-8")
|
|
352
|
-
|
|
353
|
-
self.logger.info(f"Saved {agent_id} memory to project directory: {memory_file}")
|
|
354
|
-
return True
|
|
355
|
-
except Exception as e:
|
|
356
|
-
self.logger.error(f"Error saving memory for {agent_id}: {e}")
|
|
357
|
-
return False
|
|
358
|
-
|
|
359
227
|
def optimize_memory(self, agent_id: Optional[str] = None) -> Dict[str, Any]:
|
|
360
228
|
"""Optimize agent memory by consolidating/cleaning memories.
|
|
361
229
|
|
|
@@ -459,25 +327,25 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
459
327
|
try:
|
|
460
328
|
import json
|
|
461
329
|
import re
|
|
462
|
-
|
|
330
|
+
|
|
463
331
|
# Log that we're processing memory for this agent
|
|
464
332
|
is_pm = agent_id.upper() == "PM"
|
|
465
333
|
self.logger.debug(f"Extracting memory for {agent_id} (is_pm={is_pm})")
|
|
466
|
-
|
|
334
|
+
|
|
467
335
|
# Look for JSON block in the response
|
|
468
336
|
# Pattern matches ```json ... ``` blocks
|
|
469
|
-
json_pattern = r
|
|
337
|
+
json_pattern = r"```json\s*(.*?)\s*```"
|
|
470
338
|
json_matches = re.findall(json_pattern, response, re.DOTALL)
|
|
471
|
-
|
|
339
|
+
|
|
472
340
|
if not json_matches:
|
|
473
341
|
# Also try to find inline JSON objects
|
|
474
342
|
json_pattern2 = r'\{[^{}]*"(?:remember|Remember|MEMORIES)"[^{}]*\}'
|
|
475
343
|
json_matches = re.findall(json_pattern2, response, re.DOTALL)
|
|
476
|
-
|
|
344
|
+
|
|
477
345
|
for json_str in json_matches:
|
|
478
346
|
try:
|
|
479
347
|
data = json.loads(json_str)
|
|
480
|
-
|
|
348
|
+
|
|
481
349
|
# Check for complete memory replacement in "MEMORIES" field
|
|
482
350
|
if "MEMORIES" in data and data["MEMORIES"] is not None:
|
|
483
351
|
memories = data["MEMORIES"]
|
|
@@ -491,26 +359,33 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
491
359
|
if not item_text.startswith("-"):
|
|
492
360
|
item_text = f"- {item_text}"
|
|
493
361
|
valid_items.append(item_text)
|
|
494
|
-
|
|
362
|
+
|
|
495
363
|
if valid_items:
|
|
496
|
-
self.logger.info(
|
|
497
|
-
|
|
364
|
+
self.logger.info(
|
|
365
|
+
f"Replacing all memories for {agent_id} with {len(valid_items)} items"
|
|
366
|
+
)
|
|
367
|
+
success = self.replace_agent_memory(
|
|
368
|
+
agent_id, valid_items
|
|
369
|
+
)
|
|
498
370
|
if success:
|
|
499
|
-
self.logger.info(
|
|
371
|
+
self.logger.info(
|
|
372
|
+
f"Successfully replaced memories for {agent_id}"
|
|
373
|
+
)
|
|
500
374
|
return True
|
|
501
|
-
|
|
502
|
-
|
|
375
|
+
self.logger.error(
|
|
376
|
+
f"Failed to replace memories for {agent_id}"
|
|
377
|
+
)
|
|
503
378
|
continue # Skip checking remember field if MEMORIES was processed
|
|
504
|
-
|
|
379
|
+
|
|
505
380
|
# Check for incremental memory updates in "remember" field
|
|
506
381
|
memory_items = None
|
|
507
|
-
|
|
382
|
+
|
|
508
383
|
# Check both "remember" and "Remember" fields
|
|
509
384
|
if "remember" in data:
|
|
510
385
|
memory_items = data["remember"]
|
|
511
386
|
elif "Remember" in data:
|
|
512
387
|
memory_items = data["Remember"]
|
|
513
|
-
|
|
388
|
+
|
|
514
389
|
# Process memory items if found and not null
|
|
515
390
|
if memory_items is not None and memory_items != "null":
|
|
516
391
|
# Skip if explicitly null or empty list
|
|
@@ -520,269 +395,120 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
520
395
|
for item in memory_items:
|
|
521
396
|
if item and isinstance(item, str) and item.strip():
|
|
522
397
|
valid_items.append(item.strip())
|
|
523
|
-
|
|
398
|
+
|
|
524
399
|
# Only proceed if we have valid items
|
|
525
400
|
if valid_items:
|
|
526
|
-
self.logger.info(
|
|
527
|
-
|
|
401
|
+
self.logger.info(
|
|
402
|
+
f"Found {len(valid_items)} memory items for {agent_id}: {valid_items[:2]}..."
|
|
403
|
+
)
|
|
404
|
+
success = self._add_learnings_to_memory(
|
|
405
|
+
agent_id, valid_items
|
|
406
|
+
)
|
|
528
407
|
if success:
|
|
529
|
-
self.logger.info(
|
|
408
|
+
self.logger.info(
|
|
409
|
+
f"Successfully saved {len(valid_items)} memories for {agent_id} to project directory"
|
|
410
|
+
)
|
|
530
411
|
return True
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
412
|
+
self.logger.error(
|
|
413
|
+
f"Failed to save memories for {agent_id}"
|
|
414
|
+
)
|
|
415
|
+
|
|
534
416
|
except json.JSONDecodeError as je:
|
|
535
417
|
# Not valid JSON, continue to next match
|
|
536
418
|
self.logger.debug(f"JSON decode error for {agent_id}: {je}")
|
|
537
419
|
continue
|
|
538
|
-
|
|
420
|
+
|
|
539
421
|
self.logger.debug(f"No memory items found in response for {agent_id}")
|
|
540
422
|
return False
|
|
541
|
-
|
|
423
|
+
|
|
542
424
|
except Exception as e:
|
|
543
|
-
self.logger.error(
|
|
425
|
+
self.logger.error(
|
|
426
|
+
f"Error extracting memory from response for {agent_id}: {e}"
|
|
427
|
+
)
|
|
544
428
|
return False
|
|
545
|
-
|
|
429
|
+
|
|
546
430
|
def _add_learnings_to_memory(self, agent_id: str, learnings: List[str]) -> bool:
|
|
547
431
|
"""Add new learnings to agent memory as a simple list.
|
|
548
|
-
|
|
432
|
+
|
|
549
433
|
WHY: Simplified memory system - all memories are stored as a simple list
|
|
550
434
|
without categorization, making it easier to manage and understand.
|
|
551
435
|
Updates timestamp on every update.
|
|
552
|
-
|
|
436
|
+
|
|
553
437
|
Args:
|
|
554
438
|
agent_id: The agent identifier
|
|
555
439
|
learnings: List of new learning strings to add
|
|
556
|
-
|
|
440
|
+
|
|
557
441
|
Returns:
|
|
558
442
|
bool: True if memory was successfully updated
|
|
559
443
|
"""
|
|
560
444
|
try:
|
|
561
445
|
# Load existing memory
|
|
562
446
|
current_memory = self.load_agent_memory(agent_id)
|
|
563
|
-
|
|
447
|
+
|
|
564
448
|
# Parse existing memory into a simple list
|
|
565
|
-
existing_items = self.
|
|
566
|
-
|
|
449
|
+
existing_items = self.format_service.parse_memory_list(current_memory)
|
|
450
|
+
|
|
567
451
|
# Clean template placeholders if this is a fresh memory
|
|
568
|
-
existing_items = self.
|
|
569
|
-
|
|
452
|
+
existing_items = self.format_service.clean_template_placeholders_list(
|
|
453
|
+
existing_items
|
|
454
|
+
)
|
|
455
|
+
|
|
570
456
|
# Add new learnings, avoiding duplicates
|
|
571
457
|
updated = False
|
|
572
458
|
for learning in learnings:
|
|
573
459
|
if not learning or not isinstance(learning, str):
|
|
574
460
|
continue
|
|
575
|
-
|
|
461
|
+
|
|
576
462
|
learning = learning.strip()
|
|
577
463
|
if not learning:
|
|
578
464
|
continue
|
|
579
|
-
|
|
465
|
+
|
|
580
466
|
# Check for duplicates (case-insensitive)
|
|
581
467
|
normalized_learning = learning.lower()
|
|
582
468
|
# Strip bullet points from existing items for comparison
|
|
583
|
-
existing_normalized = [
|
|
584
|
-
|
|
469
|
+
existing_normalized = [
|
|
470
|
+
item.lstrip("- ").strip().lower() for item in existing_items
|
|
471
|
+
]
|
|
472
|
+
|
|
585
473
|
if normalized_learning not in existing_normalized:
|
|
586
474
|
# Add bullet point if not present
|
|
587
475
|
if not learning.startswith("-"):
|
|
588
476
|
learning = f"- {learning}"
|
|
589
477
|
existing_items.append(learning)
|
|
590
|
-
self.logger.info(
|
|
478
|
+
self.logger.info(
|
|
479
|
+
f"Added new memory for {agent_id}: {learning[:50]}..."
|
|
480
|
+
)
|
|
591
481
|
updated = True
|
|
592
482
|
else:
|
|
593
|
-
self.logger.debug(
|
|
594
|
-
|
|
483
|
+
self.logger.debug(
|
|
484
|
+
f"Skipping duplicate memory for {agent_id}: {learning}"
|
|
485
|
+
)
|
|
486
|
+
|
|
595
487
|
# Only save if we actually added new items
|
|
596
488
|
if not updated:
|
|
597
489
|
self.logger.debug(f"No new memories to add for {agent_id}")
|
|
598
490
|
return True # Not an error, just nothing new to add
|
|
599
|
-
|
|
491
|
+
|
|
600
492
|
# Rebuild memory content as simple list with updated timestamp
|
|
601
|
-
new_content = self.
|
|
602
|
-
|
|
493
|
+
new_content = self.format_service.build_simple_memory_content(
|
|
494
|
+
agent_id, existing_items
|
|
495
|
+
)
|
|
496
|
+
|
|
603
497
|
# Validate and save
|
|
604
|
-
agent_limits = self.
|
|
498
|
+
agent_limits = self.limits_service.get_agent_limits(agent_id)
|
|
605
499
|
if self.content_manager.exceeds_limits(new_content, agent_limits):
|
|
606
500
|
self.logger.debug(f"Memory for {agent_id} exceeds limits, truncating")
|
|
607
|
-
new_content = self.content_manager.truncate_simple_list(
|
|
608
|
-
|
|
501
|
+
new_content = self.content_manager.truncate_simple_list(
|
|
502
|
+
new_content, agent_limits
|
|
503
|
+
)
|
|
504
|
+
|
|
609
505
|
# All memories go to project directory
|
|
610
|
-
return self.
|
|
611
|
-
|
|
506
|
+
return self._save_memory_file_wrapper(agent_id, new_content)
|
|
507
|
+
|
|
612
508
|
except Exception as e:
|
|
613
509
|
self.logger.error(f"Error adding learnings to memory for {agent_id}: {e}")
|
|
614
510
|
return False
|
|
615
|
-
|
|
616
|
-
def _parse_memory_list(self, memory_content: str) -> List[str]:
|
|
617
|
-
"""Parse memory content into a simple list.
|
|
618
|
-
|
|
619
|
-
Args:
|
|
620
|
-
memory_content: Raw memory file content
|
|
621
|
-
|
|
622
|
-
Returns:
|
|
623
|
-
List of memory items
|
|
624
|
-
"""
|
|
625
|
-
items = []
|
|
626
|
-
|
|
627
|
-
for line in memory_content.split('\n'):
|
|
628
|
-
line = line.strip()
|
|
629
|
-
# Skip metadata lines and headers
|
|
630
|
-
if line.startswith('<!-- ') or line.startswith('#') or not line:
|
|
631
|
-
continue
|
|
632
|
-
# Collect items (with or without bullet points)
|
|
633
|
-
if line.startswith('- '):
|
|
634
|
-
items.append(line)
|
|
635
|
-
elif line and not line.startswith('##'): # Legacy format without bullets
|
|
636
|
-
items.append(f"- {line}")
|
|
637
|
-
|
|
638
|
-
return items
|
|
639
|
-
|
|
640
|
-
def _clean_template_placeholders_list(self, items: List[str]) -> List[str]:
|
|
641
|
-
"""Remove template placeholder text from item list.
|
|
642
|
-
|
|
643
|
-
Args:
|
|
644
|
-
items: List of memory items
|
|
645
|
-
|
|
646
|
-
Returns:
|
|
647
|
-
List with placeholder text removed
|
|
648
|
-
"""
|
|
649
|
-
# Template placeholder patterns to remove
|
|
650
|
-
placeholders = [
|
|
651
|
-
"Analyze project structure to understand architecture patterns",
|
|
652
|
-
"Observe codebase patterns and conventions during tasks",
|
|
653
|
-
"Extract implementation guidelines from project documentation",
|
|
654
|
-
"Learn from errors encountered during project work",
|
|
655
|
-
"Project analysis pending - gather context during tasks",
|
|
656
|
-
"claude-mpm: Software project requiring analysis"
|
|
657
|
-
]
|
|
658
|
-
|
|
659
|
-
cleaned = []
|
|
660
|
-
for item in items:
|
|
661
|
-
# Remove bullet point for comparison
|
|
662
|
-
item_text = item.lstrip("- ").strip()
|
|
663
|
-
# Keep item if it's not a placeholder
|
|
664
|
-
if item_text and item_text not in placeholders:
|
|
665
|
-
cleaned.append(item)
|
|
666
|
-
|
|
667
|
-
return cleaned
|
|
668
|
-
|
|
669
|
-
def _clean_template_placeholders(self, sections: Dict[str, List[str]]) -> Dict[str, List[str]]:
|
|
670
|
-
"""Remove template placeholder text from sections.
|
|
671
|
-
|
|
672
|
-
Args:
|
|
673
|
-
sections: Dict mapping section names to lists of items
|
|
674
|
-
|
|
675
|
-
Returns:
|
|
676
|
-
Dict with placeholder text removed
|
|
677
|
-
"""
|
|
678
|
-
# Template placeholder patterns to remove
|
|
679
|
-
placeholders = [
|
|
680
|
-
"Analyze project structure to understand architecture patterns",
|
|
681
|
-
"Observe codebase patterns and conventions during tasks",
|
|
682
|
-
"Extract implementation guidelines from project documentation",
|
|
683
|
-
"Learn from errors encountered during project work",
|
|
684
|
-
"Project analysis pending - gather context during tasks",
|
|
685
|
-
"claude-mpm: Software project requiring analysis"
|
|
686
|
-
]
|
|
687
|
-
|
|
688
|
-
cleaned = {}
|
|
689
|
-
for section_name, items in sections.items():
|
|
690
|
-
cleaned_items = []
|
|
691
|
-
for item in items:
|
|
692
|
-
# Remove bullet point for comparison
|
|
693
|
-
item_text = item.lstrip("- ").strip()
|
|
694
|
-
# Keep item if it's not a placeholder
|
|
695
|
-
if item_text and item_text not in placeholders:
|
|
696
|
-
cleaned_items.append(item)
|
|
697
|
-
|
|
698
|
-
# Only include section if it has real content
|
|
699
|
-
if cleaned_items:
|
|
700
|
-
cleaned[section_name] = cleaned_items
|
|
701
|
-
|
|
702
|
-
return cleaned
|
|
703
|
-
|
|
704
|
-
def _categorize_learning(self, learning: str) -> str:
|
|
705
|
-
"""Categorize a learning item into appropriate section.
|
|
706
|
-
|
|
707
|
-
Args:
|
|
708
|
-
learning: The learning string to categorize
|
|
709
|
-
|
|
710
|
-
Returns:
|
|
711
|
-
str: The section name for this learning
|
|
712
|
-
"""
|
|
713
|
-
learning_lower = learning.lower()
|
|
714
|
-
|
|
715
|
-
# Check for keywords to categorize with improved patterns
|
|
716
|
-
# Order matters - more specific patterns should come first
|
|
717
|
-
|
|
718
|
-
# Architecture keywords
|
|
719
|
-
if any(word in learning_lower for word in ["architecture", "structure", "design", "module", "component", "microservices", "service-oriented"]):
|
|
720
|
-
return "Project Architecture"
|
|
721
|
-
|
|
722
|
-
# Integration keywords (check before patterns to avoid "use" conflict)
|
|
723
|
-
elif any(word in learning_lower for word in ["integration", "interface", "api", "connection", "database", "pooling", "via"]):
|
|
724
|
-
return "Integration Points"
|
|
725
|
-
|
|
726
|
-
# Mistake keywords (check before patterns to avoid conflicts)
|
|
727
|
-
elif any(word in learning_lower for word in ["mistake", "error", "avoid", "don't", "never", "not"]):
|
|
728
|
-
return "Common Mistakes to Avoid"
|
|
729
|
-
|
|
730
|
-
# Context keywords (check before patterns to avoid "working", "version" conflicts)
|
|
731
|
-
elif any(word in learning_lower for word in ["context", "current", "currently", "working", "version", "release", "candidate"]):
|
|
732
|
-
return "Current Technical Context"
|
|
733
|
-
|
|
734
|
-
# Guideline keywords (check before patterns to avoid "must", "should" conflicts)
|
|
735
|
-
elif any(word in learning_lower for word in ["guideline", "rule", "standard", "practice", "docstring", "documentation", "must", "should", "include", "comprehensive"]):
|
|
736
|
-
return "Implementation Guidelines"
|
|
737
|
-
|
|
738
|
-
# Pattern keywords (including dependency injection, conventions)
|
|
739
|
-
elif any(word in learning_lower for word in ["pattern", "convention", "style", "format", "dependency injection", "instantiation", "use", "implement"]):
|
|
740
|
-
return "Coding Patterns Learned"
|
|
741
|
-
|
|
742
|
-
# Strategy keywords
|
|
743
|
-
elif any(word in learning_lower for word in ["strategy", "approach", "method", "technique", "effective"]):
|
|
744
|
-
return "Effective Strategies"
|
|
745
|
-
|
|
746
|
-
# Performance keywords
|
|
747
|
-
elif any(word in learning_lower for word in ["performance", "optimization", "speed", "efficiency"]):
|
|
748
|
-
return "Performance Considerations"
|
|
749
|
-
|
|
750
|
-
# Domain keywords
|
|
751
|
-
elif any(word in learning_lower for word in ["domain", "business", "specific"]):
|
|
752
|
-
return "Domain-Specific Knowledge"
|
|
753
|
-
|
|
754
|
-
else:
|
|
755
|
-
return "Recent Learnings"
|
|
756
|
-
|
|
757
|
-
def _build_simple_memory_content(self, agent_id: str, items: List[str]) -> str:
|
|
758
|
-
"""Build memory content as a simple list with updated timestamp.
|
|
759
|
-
|
|
760
|
-
Args:
|
|
761
|
-
agent_id: The agent identifier
|
|
762
|
-
items: List of memory items
|
|
763
|
-
|
|
764
|
-
Returns:
|
|
765
|
-
str: The formatted memory content
|
|
766
|
-
"""
|
|
767
|
-
lines = []
|
|
768
|
-
|
|
769
|
-
# Add header
|
|
770
|
-
lines.append(f"# Agent Memory: {agent_id}")
|
|
771
|
-
# Always update timestamp when building new content
|
|
772
|
-
lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
|
|
773
|
-
lines.append("")
|
|
774
|
-
|
|
775
|
-
# Add all items as a simple list
|
|
776
|
-
for item in items:
|
|
777
|
-
if item.strip():
|
|
778
|
-
# Ensure item has bullet point
|
|
779
|
-
if not item.strip().startswith("-"):
|
|
780
|
-
lines.append(f"- {item.strip()}")
|
|
781
|
-
else:
|
|
782
|
-
lines.append(item.strip())
|
|
783
|
-
|
|
784
|
-
return '\n'.join(lines)
|
|
785
|
-
|
|
511
|
+
|
|
786
512
|
def replace_agent_memory(self, agent_id: str, memory_items: List[str]) -> bool:
|
|
787
513
|
"""Replace agent's memory with new content as a simple list.
|
|
788
514
|
|
|
@@ -799,17 +525,21 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
799
525
|
"""
|
|
800
526
|
try:
|
|
801
527
|
# Build new memory content as simple list with updated timestamp
|
|
802
|
-
new_content = self.
|
|
803
|
-
|
|
528
|
+
new_content = self.format_service.build_simple_memory_content(
|
|
529
|
+
agent_id, memory_items
|
|
530
|
+
)
|
|
531
|
+
|
|
804
532
|
# Validate and save
|
|
805
|
-
agent_limits = self.
|
|
533
|
+
agent_limits = self.limits_service.get_agent_limits(agent_id)
|
|
806
534
|
if self.content_manager.exceeds_limits(new_content, agent_limits):
|
|
807
535
|
self.logger.debug(f"Memory for {agent_id} exceeds limits, truncating")
|
|
808
|
-
new_content = self.content_manager.truncate_simple_list(
|
|
809
|
-
|
|
536
|
+
new_content = self.content_manager.truncate_simple_list(
|
|
537
|
+
new_content, agent_limits
|
|
538
|
+
)
|
|
539
|
+
|
|
810
540
|
# Save the new memory
|
|
811
|
-
return self.
|
|
812
|
-
|
|
541
|
+
return self._save_memory_file_wrapper(agent_id, new_content)
|
|
542
|
+
|
|
813
543
|
except Exception as e:
|
|
814
544
|
self.logger.error(f"Error replacing memory for {agent_id}: {e}")
|
|
815
545
|
return False
|
|
@@ -832,13 +562,13 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
832
562
|
"total_agents": 0,
|
|
833
563
|
"total_size_kb": 0,
|
|
834
564
|
"agents": {},
|
|
835
|
-
"system_health": "healthy"
|
|
565
|
+
"system_health": "healthy",
|
|
836
566
|
}
|
|
837
|
-
|
|
567
|
+
|
|
838
568
|
if self.memories_dir.exists():
|
|
839
569
|
memory_files = list(self.memories_dir.glob("*_memories.md"))
|
|
840
570
|
status["total_agents"] = len(memory_files)
|
|
841
|
-
|
|
571
|
+
|
|
842
572
|
for file_path in memory_files:
|
|
843
573
|
if file_path.name != "README.md":
|
|
844
574
|
size_kb = file_path.stat().st_size / 1024
|
|
@@ -846,9 +576,9 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
846
576
|
agent_id = file_path.stem.replace("_memories", "")
|
|
847
577
|
status["agents"][agent_id] = {
|
|
848
578
|
"file": file_path.name,
|
|
849
|
-
"size_kb": round(size_kb, 2)
|
|
579
|
+
"size_kb": round(size_kb, 2),
|
|
850
580
|
}
|
|
851
|
-
|
|
581
|
+
|
|
852
582
|
return status
|
|
853
583
|
|
|
854
584
|
def cross_reference_memories(self, query: Optional[str] = None) -> Dict[str, Any]:
|
|
@@ -868,7 +598,7 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
868
598
|
return {
|
|
869
599
|
"status": "deprecated",
|
|
870
600
|
"message": "Cross-reference analysis has been deprecated in favor of simplified memory management",
|
|
871
|
-
"suggestion": "Use get_memory_status() for memory overview"
|
|
601
|
+
"suggestion": "Use get_memory_status() for memory overview",
|
|
872
602
|
}
|
|
873
603
|
|
|
874
604
|
def get_all_memories_raw(self) -> Dict[str, Any]:
|
|
@@ -883,110 +613,25 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
883
613
|
"""
|
|
884
614
|
# Deprecated - return informative message
|
|
885
615
|
return {
|
|
886
|
-
"status": "deprecated",
|
|
616
|
+
"status": "deprecated",
|
|
887
617
|
"message": "Raw memory access has been deprecated in favor of simplified memory management",
|
|
888
|
-
"suggestion": "Use load_agent_memory() for specific agent memories"
|
|
618
|
+
"suggestion": "Use load_agent_memory() for specific agent memories",
|
|
889
619
|
}
|
|
890
620
|
|
|
891
|
-
def
|
|
892
|
-
"""
|
|
893
|
-
|
|
894
|
-
WHY: The memories directory needs clear documentation so developers
|
|
895
|
-
understand the purpose of these files and how to interact with them.
|
|
896
|
-
"""
|
|
897
|
-
try:
|
|
898
|
-
self.memories_dir.mkdir(parents=True, exist_ok=True)
|
|
899
|
-
self.logger.debug(f"Ensured memories directory exists: {self.memories_dir}")
|
|
900
|
-
|
|
901
|
-
readme_path = self.memories_dir / "README.md"
|
|
902
|
-
if not readme_path.exists():
|
|
903
|
-
readme_content = """# Agent Memory System
|
|
904
|
-
|
|
905
|
-
## Purpose
|
|
906
|
-
Each agent maintains project-specific knowledge in these files. Agents read their memory file before tasks and update it when they learn something new.
|
|
907
|
-
|
|
908
|
-
## Manual Editing
|
|
909
|
-
Feel free to edit these files to:
|
|
910
|
-
- Add project-specific guidelines
|
|
911
|
-
- Remove outdated information
|
|
912
|
-
- Reorganize for better clarity
|
|
913
|
-
- Add domain-specific knowledge
|
|
914
|
-
|
|
915
|
-
## Memory Limits
|
|
916
|
-
- Max file size: 80KB (~20k tokens)
|
|
917
|
-
- Max sections: 10
|
|
918
|
-
- Max items per section: 15
|
|
919
|
-
- Files auto-truncate when limits exceeded
|
|
920
|
-
|
|
921
|
-
## File Format
|
|
922
|
-
Standard markdown with structured sections. Agents expect:
|
|
923
|
-
- Project Architecture
|
|
924
|
-
- Implementation Guidelines
|
|
925
|
-
- Common Mistakes to Avoid
|
|
926
|
-
- Current Technical Context
|
|
927
|
-
|
|
928
|
-
## How It Works
|
|
929
|
-
1. Agents read their memory file before starting tasks
|
|
930
|
-
2. Agents add learnings during or after task completion
|
|
931
|
-
3. Files automatically enforce size limits
|
|
932
|
-
4. Developers can manually edit for accuracy
|
|
933
|
-
|
|
934
|
-
## Memory File Lifecycle
|
|
935
|
-
- Created automatically when agent first runs
|
|
936
|
-
- Updated through hook system after delegations
|
|
937
|
-
- Manually editable by developers
|
|
938
|
-
- Version controlled with project
|
|
939
|
-
"""
|
|
940
|
-
readme_path.write_text(readme_content, encoding="utf-8")
|
|
941
|
-
self.logger.info("Created README.md in memories directory")
|
|
942
|
-
|
|
943
|
-
except Exception as e:
|
|
944
|
-
self.logger.error(f"Error ensuring memories directory: {e}")
|
|
945
|
-
# Continue anyway - memory system should not block operations
|
|
946
|
-
|
|
621
|
+
def _save_memory_file_wrapper(self, agent_id: str, content: str) -> bool:
|
|
622
|
+
"""Wrapper for save_memory_file that handles agent_id.
|
|
947
623
|
|
|
948
|
-
|
|
949
|
-
def _parse_memory_sections(self, memory_content: str) -> Dict[str, List[str]]:
|
|
950
|
-
"""Parse memory content into sections and items.
|
|
951
|
-
|
|
952
624
|
Args:
|
|
953
|
-
|
|
954
|
-
|
|
625
|
+
agent_id: Agent identifier
|
|
626
|
+
content: Content to save
|
|
627
|
+
|
|
955
628
|
Returns:
|
|
956
|
-
|
|
629
|
+
True if saved successfully
|
|
957
630
|
"""
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
for line in memory_content.split('\n'):
|
|
963
|
-
# Skip metadata lines
|
|
964
|
-
if line.startswith('<!-- ') and line.endswith(' -->'):
|
|
965
|
-
continue
|
|
966
|
-
# Check for section headers (## Level 2 headers)
|
|
967
|
-
elif line.startswith('## '):
|
|
968
|
-
# Save previous section if exists
|
|
969
|
-
if current_section and current_items:
|
|
970
|
-
sections[current_section] = current_items
|
|
971
|
-
|
|
972
|
-
# Start new section
|
|
973
|
-
current_section = line[3:].strip() # Remove "## " prefix
|
|
974
|
-
current_items = []
|
|
975
|
-
# Collect non-empty lines as items (but not HTML comments)
|
|
976
|
-
elif line.strip() and current_section and not line.strip().startswith('<!--'):
|
|
977
|
-
# Keep the full line with its formatting
|
|
978
|
-
current_items.append(line.strip())
|
|
979
|
-
|
|
980
|
-
# Save last section
|
|
981
|
-
if current_section and current_items:
|
|
982
|
-
sections[current_section] = current_items
|
|
983
|
-
|
|
984
|
-
return sections
|
|
985
|
-
|
|
986
|
-
# ================================================================================
|
|
987
|
-
# Interface Adapter Methods
|
|
988
|
-
# ================================================================================
|
|
989
|
-
# These methods adapt the existing implementation to comply with MemoryServiceInterface
|
|
631
|
+
file_path = self.file_service.get_memory_file_with_migration(
|
|
632
|
+
self.memories_dir, agent_id
|
|
633
|
+
)
|
|
634
|
+
return self.file_service.save_memory_file(file_path, content)
|
|
990
635
|
|
|
991
636
|
def load_memory(self, agent_id: str) -> Optional[str]:
|
|
992
637
|
"""Load memory for a specific agent.
|
|
@@ -1066,12 +711,8 @@ Standard markdown with structured sections. Agents expect:
|
|
|
1066
711
|
Dictionary with memory metrics
|
|
1067
712
|
"""
|
|
1068
713
|
# Minimal implementation for interface compliance
|
|
1069
|
-
metrics = {
|
|
1070
|
-
|
|
1071
|
-
"agent_count": 0,
|
|
1072
|
-
"agents": {}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
714
|
+
metrics = {"total_memory_kb": 0, "agent_count": 0, "agents": {}}
|
|
715
|
+
|
|
1075
716
|
if self.memories_dir.exists():
|
|
1076
717
|
if agent_id:
|
|
1077
718
|
# Metrics for specific agent
|
|
@@ -1080,8 +721,19 @@ Standard markdown with structured sections. Agents expect:
|
|
|
1080
721
|
size_kb = memory_file.stat().st_size / 1024
|
|
1081
722
|
metrics["agents"][agent_id] = {
|
|
1082
723
|
"size_kb": round(size_kb, 2),
|
|
1083
|
-
"limit_kb": self.
|
|
1084
|
-
|
|
724
|
+
"limit_kb": self.limits_service.get_agent_limits(agent_id)[
|
|
725
|
+
"max_file_size_kb"
|
|
726
|
+
],
|
|
727
|
+
"usage_percent": round(
|
|
728
|
+
(
|
|
729
|
+
size_kb
|
|
730
|
+
/ self.limits_service.get_agent_limits(agent_id)[
|
|
731
|
+
"max_file_size_kb"
|
|
732
|
+
]
|
|
733
|
+
)
|
|
734
|
+
* 100,
|
|
735
|
+
1,
|
|
736
|
+
),
|
|
1085
737
|
}
|
|
1086
738
|
metrics["total_memory_kb"] = round(size_kb, 2)
|
|
1087
739
|
metrics["agent_count"] = 1
|
|
@@ -1092,17 +744,19 @@ Standard markdown with structured sections. Agents expect:
|
|
|
1092
744
|
if file_path.name != "README.md":
|
|
1093
745
|
agent_name = file_path.stem.replace("_memories", "")
|
|
1094
746
|
size_kb = file_path.stat().st_size / 1024
|
|
1095
|
-
limit_kb = self.
|
|
747
|
+
limit_kb = self.limits_service.get_agent_limits(agent_name)[
|
|
748
|
+
"max_file_size_kb"
|
|
749
|
+
]
|
|
1096
750
|
metrics["agents"][agent_name] = {
|
|
1097
751
|
"size_kb": round(size_kb, 2),
|
|
1098
752
|
"limit_kb": limit_kb,
|
|
1099
|
-
"usage_percent": round((size_kb / limit_kb) * 100, 1)
|
|
753
|
+
"usage_percent": round((size_kb / limit_kb) * 100, 1),
|
|
1100
754
|
}
|
|
1101
755
|
metrics["total_memory_kb"] += size_kb
|
|
1102
|
-
|
|
756
|
+
|
|
1103
757
|
metrics["total_memory_kb"] = round(metrics["total_memory_kb"], 2)
|
|
1104
758
|
metrics["agent_count"] = len(metrics["agents"])
|
|
1105
|
-
|
|
759
|
+
|
|
1106
760
|
return metrics
|
|
1107
761
|
|
|
1108
762
|
|