attune-ai 2.0.0__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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Session handoff and export mixin for UnifiedMemory.
|
|
2
|
+
|
|
3
|
+
Provides compact state generation and Claude Code integration.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
import structlog
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ..file_session import FileSessionMemory
|
|
17
|
+
|
|
18
|
+
logger = structlog.get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HandoffAndExportMixin:
|
|
22
|
+
"""Mixin providing session handoff and export capabilities for UnifiedMemory."""
|
|
23
|
+
|
|
24
|
+
# Type hints for attributes that will be provided by UnifiedMemory
|
|
25
|
+
_file_session: "FileSessionMemory | None"
|
|
26
|
+
config: Any # MemoryConfig
|
|
27
|
+
|
|
28
|
+
# Needs access to capabilities from CapabilitiesMixin
|
|
29
|
+
def get_capabilities(self) -> dict[str, bool]:
|
|
30
|
+
"""Get capabilities - provided by CapabilitiesMixin."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
# =========================================================================
|
|
34
|
+
# COMPACT STATE GENERATION
|
|
35
|
+
# =========================================================================
|
|
36
|
+
|
|
37
|
+
def generate_compact_state(self) -> str:
|
|
38
|
+
"""Generate SBAR-format compact state from current session.
|
|
39
|
+
|
|
40
|
+
Creates a human-readable summary of the current session state,
|
|
41
|
+
suitable for Claude Code's .claude/compact-state.md file.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Markdown-formatted compact state string
|
|
45
|
+
"""
|
|
46
|
+
lines = [
|
|
47
|
+
"# Compact State - Session Handoff",
|
|
48
|
+
"",
|
|
49
|
+
f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M')}",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Add session info
|
|
53
|
+
if self._file_session:
|
|
54
|
+
session = self._file_session._state
|
|
55
|
+
lines.extend(
|
|
56
|
+
[
|
|
57
|
+
f"**Session ID:** {session.session_id}",
|
|
58
|
+
f"**User ID:** {session.user_id}",
|
|
59
|
+
"",
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
lines.extend(
|
|
64
|
+
[
|
|
65
|
+
"## SBAR Handoff",
|
|
66
|
+
"",
|
|
67
|
+
"### Situation",
|
|
68
|
+
]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Get context from file session
|
|
72
|
+
context = {}
|
|
73
|
+
if self._file_session:
|
|
74
|
+
context = self._file_session.get_all_context()
|
|
75
|
+
|
|
76
|
+
situation = context.get("situation", "Session in progress.")
|
|
77
|
+
background = context.get("background", "No background information recorded.")
|
|
78
|
+
assessment = context.get("assessment", "No assessment recorded.")
|
|
79
|
+
recommendation = context.get("recommendation", "Continue with current task.")
|
|
80
|
+
|
|
81
|
+
lines.extend(
|
|
82
|
+
[
|
|
83
|
+
situation,
|
|
84
|
+
"",
|
|
85
|
+
"### Background",
|
|
86
|
+
background,
|
|
87
|
+
"",
|
|
88
|
+
"### Assessment",
|
|
89
|
+
assessment,
|
|
90
|
+
"",
|
|
91
|
+
"### Recommendation",
|
|
92
|
+
recommendation,
|
|
93
|
+
"",
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Add working memory summary
|
|
98
|
+
if self._file_session:
|
|
99
|
+
working_keys = list(self._file_session._state.working_memory.keys())
|
|
100
|
+
if working_keys:
|
|
101
|
+
lines.extend(
|
|
102
|
+
[
|
|
103
|
+
"## Working Memory",
|
|
104
|
+
"",
|
|
105
|
+
f"**Active keys:** {len(working_keys)}",
|
|
106
|
+
"",
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
for key in working_keys[:10]: # Show max 10
|
|
110
|
+
lines.append(f"- `{key}`")
|
|
111
|
+
if len(working_keys) > 10:
|
|
112
|
+
lines.append(f"- ... and {len(working_keys) - 10} more")
|
|
113
|
+
lines.append("")
|
|
114
|
+
|
|
115
|
+
# Add staged patterns summary
|
|
116
|
+
if self._file_session:
|
|
117
|
+
staged = list(self._file_session._state.staged_patterns.values())
|
|
118
|
+
if staged:
|
|
119
|
+
lines.extend(
|
|
120
|
+
[
|
|
121
|
+
"## Staged Patterns",
|
|
122
|
+
"",
|
|
123
|
+
f"**Pending validation:** {len(staged)}",
|
|
124
|
+
"",
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
for pattern in staged[:5]: # Show max 5
|
|
128
|
+
lines.append(
|
|
129
|
+
f"- {pattern.name} ({pattern.pattern_type}, conf: {pattern.confidence:.2f})"
|
|
130
|
+
)
|
|
131
|
+
if len(staged) > 5:
|
|
132
|
+
lines.append(f"- ... and {len(staged) - 5} more")
|
|
133
|
+
lines.append("")
|
|
134
|
+
|
|
135
|
+
# Add capabilities
|
|
136
|
+
caps = self.get_capabilities()
|
|
137
|
+
lines.extend(
|
|
138
|
+
[
|
|
139
|
+
"## Capabilities",
|
|
140
|
+
"",
|
|
141
|
+
f"- File session: {'Yes' if caps['file_session'] else 'No'}",
|
|
142
|
+
f"- Redis: {'Yes' if caps['redis'] else 'No'}",
|
|
143
|
+
f"- Long-term memory: {'Yes' if caps['long_term'] else 'No'}",
|
|
144
|
+
f"- Real-time sync: {'Yes' if caps['realtime'] else 'No'}",
|
|
145
|
+
"",
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return "\n".join(lines)
|
|
150
|
+
|
|
151
|
+
def export_to_claude_md(self, path: str | None = None) -> Path:
|
|
152
|
+
"""Export current session state to Claude Code's compact-state.md.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
path: Path to write to (defaults to config.compact_state_path)
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Path where state was written
|
|
159
|
+
"""
|
|
160
|
+
from attune.config import _validate_file_path
|
|
161
|
+
|
|
162
|
+
path = path or self.config.compact_state_path
|
|
163
|
+
validated_path = _validate_file_path(path)
|
|
164
|
+
|
|
165
|
+
# Ensure parent directory exists
|
|
166
|
+
validated_path.parent.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
|
|
168
|
+
# Generate and write compact state
|
|
169
|
+
content = self.generate_compact_state()
|
|
170
|
+
validated_path.write_text(content, encoding="utf-8")
|
|
171
|
+
|
|
172
|
+
logger.info("compact_state_exported", path=str(validated_path))
|
|
173
|
+
return validated_path
|
|
174
|
+
|
|
175
|
+
def set_handoff(
|
|
176
|
+
self,
|
|
177
|
+
situation: str,
|
|
178
|
+
background: str,
|
|
179
|
+
assessment: str,
|
|
180
|
+
recommendation: str,
|
|
181
|
+
**extra_context,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Set SBAR handoff context for session continuity.
|
|
184
|
+
|
|
185
|
+
This data is used by generate_compact_state() and export_to_claude_md().
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
situation: Current situation summary
|
|
189
|
+
background: Relevant background information
|
|
190
|
+
assessment: Assessment of progress/state
|
|
191
|
+
recommendation: Recommended next steps
|
|
192
|
+
**extra_context: Additional context key-value pairs
|
|
193
|
+
"""
|
|
194
|
+
if not self._file_session:
|
|
195
|
+
logger.warning("file_session_not_available")
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
self._file_session.set_context("situation", situation)
|
|
199
|
+
self._file_session.set_context("background", background)
|
|
200
|
+
self._file_session.set_context("assessment", assessment)
|
|
201
|
+
self._file_session.set_context("recommendation", recommendation)
|
|
202
|
+
|
|
203
|
+
for key, value in extra_context.items():
|
|
204
|
+
self._file_session.set_context(key, value)
|
|
205
|
+
|
|
206
|
+
# Auto-export if configured
|
|
207
|
+
if self.config.auto_generate_compact_state:
|
|
208
|
+
self.export_to_claude_md()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Lifecycle management mixin for UnifiedMemory.
|
|
2
|
+
|
|
3
|
+
Provides resource cleanup and context manager protocol.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import structlog
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..file_session import FileSessionMemory
|
|
15
|
+
from ..short_term import RedisShortTermMemory
|
|
16
|
+
|
|
17
|
+
logger = structlog.get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LifecycleMixin:
|
|
21
|
+
"""Mixin providing lifecycle management for UnifiedMemory."""
|
|
22
|
+
|
|
23
|
+
# Type hints for attributes that will be provided by UnifiedMemory
|
|
24
|
+
_file_session: "FileSessionMemory | None"
|
|
25
|
+
_short_term: "RedisShortTermMemory | None"
|
|
26
|
+
|
|
27
|
+
def save(self) -> None:
|
|
28
|
+
"""Explicitly save all memory state."""
|
|
29
|
+
if self._file_session:
|
|
30
|
+
self._file_session.save()
|
|
31
|
+
logger.debug("memory_saved")
|
|
32
|
+
|
|
33
|
+
def close(self) -> None:
|
|
34
|
+
"""Close all memory backends and save state."""
|
|
35
|
+
if self._file_session:
|
|
36
|
+
self._file_session.close()
|
|
37
|
+
|
|
38
|
+
if self._short_term and hasattr(self._short_term, "close"):
|
|
39
|
+
self._short_term.close()
|
|
40
|
+
|
|
41
|
+
logger.info("unified_memory_closed")
|
|
42
|
+
|
|
43
|
+
def __enter__(self):
|
|
44
|
+
"""Context manager entry."""
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
48
|
+
"""Context manager exit - close all backends."""
|
|
49
|
+
self.close()
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""Long-term memory operations mixin for UnifiedMemory.
|
|
2
|
+
|
|
3
|
+
Provides pattern persistence, retrieval, search, and caching.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import heapq
|
|
10
|
+
import json
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
import structlog
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..long_term import Classification
|
|
19
|
+
|
|
20
|
+
logger = structlog.get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LongTermOperationsMixin:
|
|
24
|
+
"""Mixin providing long-term memory operations for UnifiedMemory."""
|
|
25
|
+
|
|
26
|
+
# Type hints for attributes that will be provided by UnifiedMemory
|
|
27
|
+
user_id: str
|
|
28
|
+
_long_term: Any # SecureMemDocsIntegration | None
|
|
29
|
+
_pattern_cache: dict[str, dict[str, Any]]
|
|
30
|
+
_pattern_cache_max_size: int
|
|
31
|
+
|
|
32
|
+
# =========================================================================
|
|
33
|
+
# LONG-TERM MEMORY OPERATIONS
|
|
34
|
+
# =========================================================================
|
|
35
|
+
|
|
36
|
+
def persist_pattern(
|
|
37
|
+
self,
|
|
38
|
+
content: str,
|
|
39
|
+
pattern_type: str,
|
|
40
|
+
classification: "Classification | str | None" = None,
|
|
41
|
+
auto_classify: bool = True,
|
|
42
|
+
metadata: dict[str, Any] | None = None,
|
|
43
|
+
) -> dict[str, Any] | None:
|
|
44
|
+
"""Store a pattern in long-term memory with security controls.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
content: Pattern content
|
|
48
|
+
pattern_type: Type of pattern (algorithm, protocol, etc.)
|
|
49
|
+
classification: Security classification (PUBLIC/INTERNAL/SENSITIVE)
|
|
50
|
+
auto_classify: Auto-detect classification from content
|
|
51
|
+
metadata: Additional metadata to store
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Storage result with pattern_id and classification, or None if failed
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
from ..long_term import Classification
|
|
58
|
+
|
|
59
|
+
if not self._long_term:
|
|
60
|
+
logger.error("long_term_memory_unavailable")
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Convert string classification to enum if needed
|
|
65
|
+
explicit_class = None
|
|
66
|
+
if classification is not None:
|
|
67
|
+
if isinstance(classification, str):
|
|
68
|
+
explicit_class = Classification[classification.upper()]
|
|
69
|
+
else:
|
|
70
|
+
explicit_class = classification
|
|
71
|
+
|
|
72
|
+
result = self._long_term.store_pattern(
|
|
73
|
+
content=content,
|
|
74
|
+
pattern_type=pattern_type,
|
|
75
|
+
user_id=self.user_id,
|
|
76
|
+
explicit_classification=explicit_class,
|
|
77
|
+
auto_classify=auto_classify,
|
|
78
|
+
custom_metadata=metadata,
|
|
79
|
+
)
|
|
80
|
+
logger.info(
|
|
81
|
+
"pattern_persisted",
|
|
82
|
+
pattern_id=result.get("pattern_id"),
|
|
83
|
+
classification=result.get("classification"),
|
|
84
|
+
)
|
|
85
|
+
return result
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error("persist_pattern_failed", error=str(e))
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def _cache_pattern(self, pattern_id: str, pattern: dict[str, Any]) -> None:
|
|
91
|
+
"""Add pattern to LRU cache, evicting oldest if at capacity."""
|
|
92
|
+
# Simple LRU: remove oldest entry if at max size
|
|
93
|
+
if len(self._pattern_cache) >= self._pattern_cache_max_size:
|
|
94
|
+
# Remove first (oldest) item
|
|
95
|
+
oldest_key = next(iter(self._pattern_cache))
|
|
96
|
+
del self._pattern_cache[oldest_key]
|
|
97
|
+
|
|
98
|
+
self._pattern_cache[pattern_id] = pattern
|
|
99
|
+
|
|
100
|
+
def recall_pattern(
|
|
101
|
+
self,
|
|
102
|
+
pattern_id: str,
|
|
103
|
+
check_permissions: bool = True,
|
|
104
|
+
use_cache: bool = True,
|
|
105
|
+
) -> dict[str, Any] | None:
|
|
106
|
+
"""Retrieve a pattern from long-term memory.
|
|
107
|
+
|
|
108
|
+
Uses LRU cache for frequently accessed patterns to reduce I/O.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
pattern_id: ID of pattern to retrieve
|
|
112
|
+
check_permissions: Verify user has access to pattern
|
|
113
|
+
use_cache: Whether to use/update the pattern cache (default: True)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Pattern data with content and metadata, or None if not found
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
if not self._long_term:
|
|
120
|
+
logger.error("long_term_memory_unavailable")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
# Check cache first (if enabled)
|
|
124
|
+
if use_cache and pattern_id in self._pattern_cache:
|
|
125
|
+
logger.debug("pattern_cache_hit", pattern_id=pattern_id)
|
|
126
|
+
return self._pattern_cache[pattern_id]
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
pattern = self._long_term.retrieve_pattern(
|
|
130
|
+
pattern_id=pattern_id,
|
|
131
|
+
user_id=self.user_id,
|
|
132
|
+
check_permissions=check_permissions,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Cache the result (if enabled and pattern found)
|
|
136
|
+
if use_cache and pattern:
|
|
137
|
+
self._cache_pattern(pattern_id, pattern)
|
|
138
|
+
|
|
139
|
+
return pattern
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error("recall_pattern_failed", pattern_id=pattern_id, error=str(e))
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
def clear_pattern_cache(self) -> int:
|
|
145
|
+
"""Clear the pattern lookup cache.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Number of entries cleared
|
|
149
|
+
"""
|
|
150
|
+
count = len(self._pattern_cache)
|
|
151
|
+
self._pattern_cache.clear()
|
|
152
|
+
logger.debug("pattern_cache_cleared", entries=count)
|
|
153
|
+
return count
|
|
154
|
+
|
|
155
|
+
# =========================================================================
|
|
156
|
+
# PATTERN SEARCH
|
|
157
|
+
# =========================================================================
|
|
158
|
+
|
|
159
|
+
def _score_pattern(
|
|
160
|
+
self,
|
|
161
|
+
pattern: dict[str, Any],
|
|
162
|
+
query_lower: str,
|
|
163
|
+
query_words: list[str],
|
|
164
|
+
) -> float:
|
|
165
|
+
"""Calculate relevance score for a pattern.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
pattern: Pattern data dictionary
|
|
169
|
+
query_lower: Lowercase query string
|
|
170
|
+
query_words: Pre-split query words (length >= 3)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Relevance score (0.0 if no match)
|
|
174
|
+
"""
|
|
175
|
+
if not query_lower:
|
|
176
|
+
return 1.0 # No query - all patterns have equal score
|
|
177
|
+
|
|
178
|
+
content = str(pattern.get("content", "")).lower()
|
|
179
|
+
metadata_str = str(pattern.get("metadata", {})).lower()
|
|
180
|
+
|
|
181
|
+
score = 0.0
|
|
182
|
+
|
|
183
|
+
# Exact phrase match in content (highest score)
|
|
184
|
+
if query_lower in content:
|
|
185
|
+
score += 10.0
|
|
186
|
+
|
|
187
|
+
# Keyword matching (medium score)
|
|
188
|
+
for word in query_words:
|
|
189
|
+
if word in content:
|
|
190
|
+
score += 2.0
|
|
191
|
+
if word in metadata_str:
|
|
192
|
+
score += 1.0
|
|
193
|
+
|
|
194
|
+
return score
|
|
195
|
+
|
|
196
|
+
def _filter_and_score_patterns(
|
|
197
|
+
self,
|
|
198
|
+
query: str | None,
|
|
199
|
+
pattern_type: str | None,
|
|
200
|
+
classification: "Classification | None",
|
|
201
|
+
) -> Iterator[tuple[float, dict[str, Any]]]:
|
|
202
|
+
"""Generator that filters and scores patterns.
|
|
203
|
+
|
|
204
|
+
Memory-efficient: yields (score, pattern) tuples one at a time.
|
|
205
|
+
Use with heapq.nlargest() for efficient top-N selection.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
query: Search query (case-insensitive)
|
|
209
|
+
pattern_type: Filter by pattern type
|
|
210
|
+
classification: Filter by classification level
|
|
211
|
+
|
|
212
|
+
Yields:
|
|
213
|
+
Tuples of (score, pattern) for matching patterns
|
|
214
|
+
"""
|
|
215
|
+
from ..long_term import Classification
|
|
216
|
+
|
|
217
|
+
query_lower = query.lower() if query else ""
|
|
218
|
+
query_words = [w for w in query_lower.split() if len(w) >= 3] if query else []
|
|
219
|
+
|
|
220
|
+
for pattern in self._iter_all_patterns():
|
|
221
|
+
# Apply filters
|
|
222
|
+
if pattern_type and pattern.get("pattern_type") != pattern_type:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
if classification:
|
|
226
|
+
pattern_class = pattern.get("classification")
|
|
227
|
+
if isinstance(classification, Classification):
|
|
228
|
+
if pattern_class != classification.value:
|
|
229
|
+
continue
|
|
230
|
+
elif pattern_class != classification:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
# Calculate relevance score
|
|
234
|
+
score = self._score_pattern(pattern, query_lower, query_words)
|
|
235
|
+
|
|
236
|
+
# Skip if no matches found (when query is provided)
|
|
237
|
+
if query and score == 0.0:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
yield (score, pattern)
|
|
241
|
+
|
|
242
|
+
def search_patterns(
|
|
243
|
+
self,
|
|
244
|
+
query: str | None = None,
|
|
245
|
+
pattern_type: str | None = None,
|
|
246
|
+
classification: "Classification | None" = None,
|
|
247
|
+
limit: int = 10,
|
|
248
|
+
) -> list[dict[str, Any]]:
|
|
249
|
+
"""Search patterns in long-term memory with keyword matching and relevance scoring.
|
|
250
|
+
|
|
251
|
+
Implements keyword-based search with:
|
|
252
|
+
1. Full-text search in pattern content and metadata
|
|
253
|
+
2. Filter by pattern_type and classification
|
|
254
|
+
3. Relevance scoring (exact matches rank higher)
|
|
255
|
+
4. Results sorted by relevance
|
|
256
|
+
|
|
257
|
+
Memory-efficient: Uses generators and heapq.nlargest() to avoid
|
|
258
|
+
loading all patterns into memory. Only keeps top N results.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
query: Text to search for in pattern content (case-insensitive)
|
|
262
|
+
pattern_type: Filter by pattern type (e.g., "meta_workflow_execution")
|
|
263
|
+
classification: Filter by classification level
|
|
264
|
+
limit: Maximum results to return
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
List of matching patterns with metadata, sorted by relevance
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> patterns = memory.search_patterns(
|
|
271
|
+
... query="successful workflows",
|
|
272
|
+
... pattern_type="meta_workflow_execution",
|
|
273
|
+
... limit=5
|
|
274
|
+
... )
|
|
275
|
+
"""
|
|
276
|
+
if not self._long_term:
|
|
277
|
+
logger.debug("long_term_memory_unavailable")
|
|
278
|
+
return []
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
# Use heapq.nlargest for memory-efficient top-N selection
|
|
282
|
+
# This avoids loading all patterns into memory at once
|
|
283
|
+
scored_patterns = heapq.nlargest(
|
|
284
|
+
limit,
|
|
285
|
+
self._filter_and_score_patterns(query, pattern_type, classification),
|
|
286
|
+
key=lambda x: x[0],
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Return patterns without scores
|
|
290
|
+
return [pattern for _, pattern in scored_patterns]
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.error("pattern_search_failed", error=str(e))
|
|
294
|
+
return []
|
|
295
|
+
|
|
296
|
+
# =========================================================================
|
|
297
|
+
# PATTERN ITERATION (Internal Helpers)
|
|
298
|
+
# =========================================================================
|
|
299
|
+
|
|
300
|
+
def _get_storage_dir(self) -> Path | None:
|
|
301
|
+
"""Get the storage directory from long-term memory backend.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Path to storage directory, or None if unavailable.
|
|
305
|
+
"""
|
|
306
|
+
if not self._long_term:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
# Try different ways to access storage directory
|
|
310
|
+
if hasattr(self._long_term, "storage_dir"):
|
|
311
|
+
return Path(self._long_term.storage_dir)
|
|
312
|
+
elif hasattr(self._long_term, "storage"):
|
|
313
|
+
if hasattr(self._long_term.storage, "storage_dir"):
|
|
314
|
+
return Path(self._long_term.storage.storage_dir)
|
|
315
|
+
elif hasattr(self._long_term, "_storage"):
|
|
316
|
+
if hasattr(self._long_term._storage, "storage_dir"):
|
|
317
|
+
return Path(self._long_term._storage.storage_dir)
|
|
318
|
+
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
def _iter_all_patterns(self) -> Iterator[dict[str, Any]]:
|
|
322
|
+
"""Iterate over all patterns from long-term memory storage.
|
|
323
|
+
|
|
324
|
+
Memory-efficient generator that yields patterns one at a time,
|
|
325
|
+
avoiding loading all patterns into memory simultaneously.
|
|
326
|
+
|
|
327
|
+
Yields:
|
|
328
|
+
Pattern data dictionaries
|
|
329
|
+
|
|
330
|
+
Note:
|
|
331
|
+
This is O(1) memory vs O(n) for _get_all_patterns().
|
|
332
|
+
Use this for large datasets or when streaming is acceptable.
|
|
333
|
+
"""
|
|
334
|
+
storage_dir = self._get_storage_dir()
|
|
335
|
+
if not storage_dir:
|
|
336
|
+
logger.warning("cannot_access_storage_directory")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
if not storage_dir.exists():
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
# Yield patterns one at a time (memory-efficient)
|
|
343
|
+
for pattern_file in storage_dir.rglob("*.json"):
|
|
344
|
+
try:
|
|
345
|
+
with pattern_file.open("r", encoding="utf-8") as f:
|
|
346
|
+
yield json.load(f)
|
|
347
|
+
except json.JSONDecodeError as e:
|
|
348
|
+
logger.debug("pattern_json_decode_failed", file=str(pattern_file), error=str(e))
|
|
349
|
+
continue
|
|
350
|
+
except Exception as e:
|
|
351
|
+
logger.debug("pattern_load_failed", file=str(pattern_file), error=str(e))
|
|
352
|
+
continue
|