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,579 @@
|
|
|
1
|
+
"""Trust Circuit Breaker
|
|
2
|
+
|
|
3
|
+
A cross-domain transfer of the circuit breaker pattern from reliability engineering
|
|
4
|
+
to human-AI trust management. Just as circuit breakers protect systems from cascading
|
|
5
|
+
failures, trust circuit breakers protect the AI-user relationship from trust erosion.
|
|
6
|
+
|
|
7
|
+
Pattern Origin: src/attune/resilience/circuit_breaker.py
|
|
8
|
+
Transfer Documentation: patterns/cross-domain/circuit-breaker-to-trust.md
|
|
9
|
+
|
|
10
|
+
Level: 5 (Systems Thinking) - Applying patterns across domains
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Trust States (mapped from circuit breaker states)
|
|
27
|
+
# =============================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TrustState(Enum):
|
|
31
|
+
"""Trust states mapped from circuit breaker states.
|
|
32
|
+
|
|
33
|
+
Circuit Breaker → Trust Mapping:
|
|
34
|
+
- CLOSED (normal) → FULL_AUTONOMY (AI acts freely)
|
|
35
|
+
- OPEN (failing fast) → REDUCED_AUTONOMY (require confirmation)
|
|
36
|
+
- HALF_OPEN (testing) → SUPERVISED (monitored recovery)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
FULL_AUTONOMY = "full_autonomy"
|
|
40
|
+
"""AI can act without confirmation. User trusts AI decisions."""
|
|
41
|
+
|
|
42
|
+
REDUCED_AUTONOMY = "reduced_autonomy"
|
|
43
|
+
"""AI must confirm significant actions. Trust has been damaged."""
|
|
44
|
+
|
|
45
|
+
SUPERVISED = "supervised"
|
|
46
|
+
"""AI is being tested for trust recovery. Partial confirmation needed."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# =============================================================================
|
|
50
|
+
# Trust Events
|
|
51
|
+
# =============================================================================
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TrustDamageType(Enum):
|
|
55
|
+
"""Types of events that damage trust."""
|
|
56
|
+
|
|
57
|
+
WRONG_ANSWER = "wrong_answer"
|
|
58
|
+
"""AI provided incorrect information."""
|
|
59
|
+
|
|
60
|
+
IGNORED_PREFERENCE = "ignored_preference"
|
|
61
|
+
"""AI acted against user's stated preferences."""
|
|
62
|
+
|
|
63
|
+
UNEXPECTED_ACTION = "unexpected_action"
|
|
64
|
+
"""AI did something user didn't expect or want."""
|
|
65
|
+
|
|
66
|
+
SLOW_RESPONSE = "slow_response"
|
|
67
|
+
"""AI was too slow, breaking flow."""
|
|
68
|
+
|
|
69
|
+
MISUNDERSTOOD_INTENT = "misunderstood_intent"
|
|
70
|
+
"""AI misinterpreted what user wanted."""
|
|
71
|
+
|
|
72
|
+
REPETITIVE_ERROR = "repetitive_error"
|
|
73
|
+
"""AI made the same mistake again."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class TrustDamageEvent:
|
|
78
|
+
"""Record of an event that damaged trust."""
|
|
79
|
+
|
|
80
|
+
event_type: TrustDamageType
|
|
81
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
82
|
+
context: str = ""
|
|
83
|
+
severity: float = 1.0 # 0.0 to 1.0, higher = more damage
|
|
84
|
+
user_explicit: bool = False # User explicitly indicated damage
|
|
85
|
+
|
|
86
|
+
def __post_init__(self):
|
|
87
|
+
if isinstance(self.event_type, str):
|
|
88
|
+
self.event_type = TrustDamageType(self.event_type)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class TrustRecoveryEvent:
|
|
93
|
+
"""Record of an event that builds trust."""
|
|
94
|
+
|
|
95
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
96
|
+
context: str = ""
|
|
97
|
+
user_explicit: bool = False # User explicitly praised AI
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# =============================================================================
|
|
101
|
+
# Configuration
|
|
102
|
+
# =============================================================================
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class TrustConfig:
|
|
107
|
+
"""Configuration for trust circuit breaker.
|
|
108
|
+
|
|
109
|
+
Mapped from circuit breaker config with trust-specific adaptations.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# Damage thresholds (mapped from failure_threshold)
|
|
113
|
+
damage_threshold: int = 3
|
|
114
|
+
"""Number of trust-damaging events before reducing autonomy."""
|
|
115
|
+
|
|
116
|
+
damage_window_hours: float = 24.0
|
|
117
|
+
"""Time window for counting damage events (recent damage matters more)."""
|
|
118
|
+
|
|
119
|
+
# Recovery settings (mapped from reset_timeout)
|
|
120
|
+
recovery_period_hours: float = 24.0
|
|
121
|
+
"""Minimum time in reduced autonomy before testing recovery."""
|
|
122
|
+
|
|
123
|
+
supervised_successes_required: int = 5
|
|
124
|
+
"""Successful interactions in supervised mode before full recovery."""
|
|
125
|
+
|
|
126
|
+
# Severity weights
|
|
127
|
+
severity_weights: dict[TrustDamageType, float] = field(
|
|
128
|
+
default_factory=lambda: {
|
|
129
|
+
TrustDamageType.WRONG_ANSWER: 1.0,
|
|
130
|
+
TrustDamageType.IGNORED_PREFERENCE: 1.5, # Preferences are important
|
|
131
|
+
TrustDamageType.UNEXPECTED_ACTION: 1.2,
|
|
132
|
+
TrustDamageType.SLOW_RESPONSE: 0.3, # Minor
|
|
133
|
+
TrustDamageType.MISUNDERSTOOD_INTENT: 0.8,
|
|
134
|
+
TrustDamageType.REPETITIVE_ERROR: 2.0, # Very damaging
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Domain-specific settings
|
|
139
|
+
domain_isolation: bool = True
|
|
140
|
+
"""If True, trust is tracked per-domain (code review vs documentation)."""
|
|
141
|
+
|
|
142
|
+
# Actions requiring confirmation in each state
|
|
143
|
+
high_impact_actions: list[str] = field(
|
|
144
|
+
default_factory=lambda: [
|
|
145
|
+
"file_write",
|
|
146
|
+
"file_delete",
|
|
147
|
+
"git_commit",
|
|
148
|
+
"external_api_call",
|
|
149
|
+
"code_execution",
|
|
150
|
+
],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# =============================================================================
|
|
155
|
+
# Trust Circuit Breaker
|
|
156
|
+
# =============================================================================
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TrustCircuitBreaker:
|
|
160
|
+
"""Circuit breaker for AI autonomy based on user trust.
|
|
161
|
+
|
|
162
|
+
Cross-domain transfer: Uses the same state machine as reliability
|
|
163
|
+
circuit breakers, but applied to trust management.
|
|
164
|
+
|
|
165
|
+
State Transitions:
|
|
166
|
+
```
|
|
167
|
+
FULL_AUTONOMY ──(damage threshold)──→ REDUCED_AUTONOMY
|
|
168
|
+
↑ │
|
|
169
|
+
│ (recovery period)
|
|
170
|
+
│ ↓
|
|
171
|
+
└──(supervised successes)─── SUPERVISED
|
|
172
|
+
```
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
user_id: str,
|
|
178
|
+
config: TrustConfig | None = None,
|
|
179
|
+
domain: str = "general",
|
|
180
|
+
):
|
|
181
|
+
self.user_id = user_id
|
|
182
|
+
self.config = config or TrustConfig()
|
|
183
|
+
self.domain = domain
|
|
184
|
+
|
|
185
|
+
# State tracking
|
|
186
|
+
self._state = TrustState.FULL_AUTONOMY
|
|
187
|
+
self._damage_events: list[TrustDamageEvent] = []
|
|
188
|
+
self._recovery_events: list[TrustRecoveryEvent] = []
|
|
189
|
+
self._state_changed_at: datetime = datetime.now()
|
|
190
|
+
self._supervised_successes: int = 0
|
|
191
|
+
|
|
192
|
+
# Callbacks
|
|
193
|
+
self._on_state_change: Callable[[TrustState, TrustState], None] | None = None
|
|
194
|
+
|
|
195
|
+
# =========================================================================
|
|
196
|
+
# State Properties
|
|
197
|
+
# =========================================================================
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def state(self) -> TrustState:
|
|
201
|
+
"""Get current trust state, checking for recovery eligibility."""
|
|
202
|
+
self._check_recovery_eligibility()
|
|
203
|
+
return self._state
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def damage_score(self) -> float:
|
|
207
|
+
"""Calculate current damage score (weighted sum of recent events).
|
|
208
|
+
|
|
209
|
+
Higher score = more damage. Threshold triggers state change.
|
|
210
|
+
"""
|
|
211
|
+
window_start = datetime.now() - timedelta(hours=self.config.damage_window_hours)
|
|
212
|
+
recent_events = [e for e in self._damage_events if e.timestamp > window_start]
|
|
213
|
+
|
|
214
|
+
score = 0.0
|
|
215
|
+
for event in recent_events:
|
|
216
|
+
weight = self.config.severity_weights.get(event.event_type, 1.0)
|
|
217
|
+
# More recent events count more (time decay)
|
|
218
|
+
age_hours = (datetime.now() - event.timestamp).total_seconds() / 3600
|
|
219
|
+
recency_factor = max(0.5, 1.0 - (age_hours / self.config.damage_window_hours))
|
|
220
|
+
score += event.severity * weight * recency_factor
|
|
221
|
+
|
|
222
|
+
return score
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def can_act_freely(self) -> bool:
|
|
226
|
+
"""Check if AI can act without confirmation."""
|
|
227
|
+
return self.state == TrustState.FULL_AUTONOMY
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def time_in_current_state(self) -> timedelta:
|
|
231
|
+
"""How long we've been in the current state."""
|
|
232
|
+
return datetime.now() - self._state_changed_at
|
|
233
|
+
|
|
234
|
+
# =========================================================================
|
|
235
|
+
# Decision Methods
|
|
236
|
+
# =========================================================================
|
|
237
|
+
|
|
238
|
+
def should_require_confirmation(self, action: str) -> bool:
|
|
239
|
+
"""Check if an action requires user confirmation.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
action: The action being considered (e.g., "file_write", "suggest")
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if confirmation should be requested
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
current_state = self.state
|
|
249
|
+
|
|
250
|
+
if current_state == TrustState.FULL_AUTONOMY:
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
if current_state == TrustState.REDUCED_AUTONOMY:
|
|
254
|
+
return True # All actions need confirmation
|
|
255
|
+
|
|
256
|
+
if current_state == TrustState.SUPERVISED:
|
|
257
|
+
# Only high-impact actions need confirmation
|
|
258
|
+
return action in self.config.high_impact_actions
|
|
259
|
+
|
|
260
|
+
return True # Default to safe
|
|
261
|
+
|
|
262
|
+
def get_autonomy_level(self) -> dict[str, Any]:
|
|
263
|
+
"""Get detailed autonomy level information for UI display.
|
|
264
|
+
|
|
265
|
+
Returns dict with state, allowed actions, and recovery progress.
|
|
266
|
+
"""
|
|
267
|
+
state = self.state
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"state": state.value,
|
|
271
|
+
"can_act_freely": state == TrustState.FULL_AUTONOMY,
|
|
272
|
+
"damage_score": round(self.damage_score, 2),
|
|
273
|
+
"damage_threshold": self.config.damage_threshold,
|
|
274
|
+
"time_in_state_hours": round(self.time_in_current_state.total_seconds() / 3600, 1),
|
|
275
|
+
"recovery_progress": self._get_recovery_progress(),
|
|
276
|
+
"recent_damage_count": len(
|
|
277
|
+
[
|
|
278
|
+
e
|
|
279
|
+
for e in self._damage_events
|
|
280
|
+
if e.timestamp > datetime.now() - timedelta(hours=24)
|
|
281
|
+
],
|
|
282
|
+
),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
def _get_recovery_progress(self) -> dict[str, Any]:
|
|
286
|
+
"""Get progress toward trust recovery."""
|
|
287
|
+
if self._state == TrustState.FULL_AUTONOMY:
|
|
288
|
+
return {"status": "full_trust", "progress": 1.0}
|
|
289
|
+
|
|
290
|
+
if self._state == TrustState.REDUCED_AUTONOMY:
|
|
291
|
+
time_remaining = (
|
|
292
|
+
timedelta(hours=self.config.recovery_period_hours) - self.time_in_current_state
|
|
293
|
+
)
|
|
294
|
+
if time_remaining.total_seconds() > 0:
|
|
295
|
+
return {
|
|
296
|
+
"status": "cooling_off",
|
|
297
|
+
"progress": self.time_in_current_state.total_seconds()
|
|
298
|
+
/ (self.config.recovery_period_hours * 3600),
|
|
299
|
+
"time_remaining_hours": round(time_remaining.total_seconds() / 3600, 1),
|
|
300
|
+
}
|
|
301
|
+
return {"status": "ready_for_supervised", "progress": 0.5}
|
|
302
|
+
|
|
303
|
+
if self._state == TrustState.SUPERVISED:
|
|
304
|
+
return {
|
|
305
|
+
"status": "supervised_testing",
|
|
306
|
+
"progress": 0.5
|
|
307
|
+
+ (0.5 * self._supervised_successes / self.config.supervised_successes_required),
|
|
308
|
+
"successes": self._supervised_successes,
|
|
309
|
+
"required": self.config.supervised_successes_required,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {"status": "unknown", "progress": 0.0}
|
|
313
|
+
|
|
314
|
+
# =========================================================================
|
|
315
|
+
# Event Recording
|
|
316
|
+
# =========================================================================
|
|
317
|
+
|
|
318
|
+
def record_damage(
|
|
319
|
+
self,
|
|
320
|
+
event_type: TrustDamageType | str,
|
|
321
|
+
context: str = "",
|
|
322
|
+
severity: float = 1.0,
|
|
323
|
+
user_explicit: bool = False,
|
|
324
|
+
) -> TrustState:
|
|
325
|
+
"""Record an event that damaged trust.
|
|
326
|
+
|
|
327
|
+
This is analogous to recording a failure in the reliability circuit breaker.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
event_type: Type of trust damage
|
|
331
|
+
context: Description of what happened
|
|
332
|
+
severity: 0.0-1.0, how severe the damage was
|
|
333
|
+
user_explicit: True if user explicitly indicated damage
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
The new trust state after recording
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
if isinstance(event_type, str):
|
|
340
|
+
event_type = TrustDamageType(event_type)
|
|
341
|
+
|
|
342
|
+
event = TrustDamageEvent(
|
|
343
|
+
event_type=event_type,
|
|
344
|
+
context=context,
|
|
345
|
+
severity=severity,
|
|
346
|
+
user_explicit=user_explicit,
|
|
347
|
+
)
|
|
348
|
+
self._damage_events.append(event)
|
|
349
|
+
|
|
350
|
+
logger.info(
|
|
351
|
+
f"Trust damage recorded for user {self.user_id}: "
|
|
352
|
+
f"{event_type.value} (severity={severity})",
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Check if we should transition to reduced autonomy
|
|
356
|
+
# Use small epsilon for floating point comparison (recency factor can cause tiny errors)
|
|
357
|
+
if self._state == TrustState.FULL_AUTONOMY:
|
|
358
|
+
if self.damage_score >= (self.config.damage_threshold - 0.01):
|
|
359
|
+
self._transition_to_reduced_autonomy()
|
|
360
|
+
|
|
361
|
+
# If in supervised mode, a damage event resets progress
|
|
362
|
+
elif self._state == TrustState.SUPERVISED:
|
|
363
|
+
self._supervised_successes = max(0, self._supervised_successes - 2)
|
|
364
|
+
logger.info(
|
|
365
|
+
f"Trust damage in supervised mode, successes reset to {self._supervised_successes}",
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
return self._state
|
|
369
|
+
|
|
370
|
+
def record_success(self, context: str = "", user_explicit: bool = False) -> TrustState:
|
|
371
|
+
"""Record a successful/positive interaction.
|
|
372
|
+
|
|
373
|
+
This is analogous to recording a success in the reliability circuit breaker.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
context: Description of the positive interaction
|
|
377
|
+
user_explicit: True if user explicitly praised the AI
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
The new trust state after recording
|
|
381
|
+
|
|
382
|
+
"""
|
|
383
|
+
event = TrustRecoveryEvent(
|
|
384
|
+
context=context,
|
|
385
|
+
user_explicit=user_explicit,
|
|
386
|
+
)
|
|
387
|
+
self._recovery_events.append(event)
|
|
388
|
+
|
|
389
|
+
# In supervised mode, successes count toward recovery
|
|
390
|
+
if self._state == TrustState.SUPERVISED:
|
|
391
|
+
self._supervised_successes += 1
|
|
392
|
+
logger.info(
|
|
393
|
+
f"Trust success in supervised mode: "
|
|
394
|
+
f"{self._supervised_successes}/{self.config.supervised_successes_required}",
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
if self._supervised_successes >= self.config.supervised_successes_required:
|
|
398
|
+
self._transition_to_full_autonomy()
|
|
399
|
+
|
|
400
|
+
return self._state
|
|
401
|
+
|
|
402
|
+
# =========================================================================
|
|
403
|
+
# State Transitions
|
|
404
|
+
# =========================================================================
|
|
405
|
+
|
|
406
|
+
def _check_recovery_eligibility(self) -> None:
|
|
407
|
+
"""Check if we should transition from reduced to supervised."""
|
|
408
|
+
if self._state != TrustState.REDUCED_AUTONOMY:
|
|
409
|
+
return
|
|
410
|
+
|
|
411
|
+
time_in_state = self.time_in_current_state
|
|
412
|
+
recovery_period = timedelta(hours=self.config.recovery_period_hours)
|
|
413
|
+
|
|
414
|
+
if time_in_state >= recovery_period:
|
|
415
|
+
self._transition_to_supervised()
|
|
416
|
+
|
|
417
|
+
def _transition_to_reduced_autonomy(self) -> None:
|
|
418
|
+
"""Transition to reduced autonomy (circuit opens)."""
|
|
419
|
+
old_state = self._state
|
|
420
|
+
self._state = TrustState.REDUCED_AUTONOMY
|
|
421
|
+
self._state_changed_at = datetime.now()
|
|
422
|
+
self._supervised_successes = 0
|
|
423
|
+
|
|
424
|
+
logger.warning(
|
|
425
|
+
f"Trust circuit opened for user {self.user_id}: {old_state.value} → {self._state.value}",
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if self._on_state_change:
|
|
429
|
+
self._on_state_change(old_state, self._state)
|
|
430
|
+
|
|
431
|
+
def _transition_to_supervised(self) -> None:
|
|
432
|
+
"""Transition to supervised mode (circuit half-opens)."""
|
|
433
|
+
old_state = self._state
|
|
434
|
+
self._state = TrustState.SUPERVISED
|
|
435
|
+
self._state_changed_at = datetime.now()
|
|
436
|
+
self._supervised_successes = 0
|
|
437
|
+
|
|
438
|
+
logger.info(
|
|
439
|
+
f"Trust circuit half-opened for user {self.user_id}: "
|
|
440
|
+
f"{old_state.value} → {self._state.value}",
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
if self._on_state_change:
|
|
444
|
+
self._on_state_change(old_state, self._state)
|
|
445
|
+
|
|
446
|
+
def _transition_to_full_autonomy(self) -> None:
|
|
447
|
+
"""Transition to full autonomy (circuit closes)."""
|
|
448
|
+
old_state = self._state
|
|
449
|
+
self._state = TrustState.FULL_AUTONOMY
|
|
450
|
+
self._state_changed_at = datetime.now()
|
|
451
|
+
|
|
452
|
+
# Clear old damage events
|
|
453
|
+
cutoff = datetime.now() - timedelta(hours=self.config.damage_window_hours * 2)
|
|
454
|
+
self._damage_events = [e for e in self._damage_events if e.timestamp > cutoff]
|
|
455
|
+
|
|
456
|
+
logger.info(
|
|
457
|
+
f"Trust circuit closed for user {self.user_id}: {old_state.value} → {self._state.value}",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if self._on_state_change:
|
|
461
|
+
self._on_state_change(old_state, self._state)
|
|
462
|
+
|
|
463
|
+
# =========================================================================
|
|
464
|
+
# Manual Controls
|
|
465
|
+
# =========================================================================
|
|
466
|
+
|
|
467
|
+
def reset(self) -> None:
|
|
468
|
+
"""Manually reset trust to full autonomy.
|
|
469
|
+
|
|
470
|
+
Use with caution - this skips the normal recovery process.
|
|
471
|
+
"""
|
|
472
|
+
old_state = self._state
|
|
473
|
+
self._state = TrustState.FULL_AUTONOMY
|
|
474
|
+
self._state_changed_at = datetime.now()
|
|
475
|
+
self._damage_events.clear()
|
|
476
|
+
self._supervised_successes = 0
|
|
477
|
+
|
|
478
|
+
logger.info(f"Trust manually reset for user {self.user_id}")
|
|
479
|
+
|
|
480
|
+
if self._on_state_change:
|
|
481
|
+
self._on_state_change(old_state, self._state)
|
|
482
|
+
|
|
483
|
+
def on_state_change(self, callback: Callable[[TrustState, TrustState], None]) -> None:
|
|
484
|
+
"""Register a callback for state changes."""
|
|
485
|
+
self._on_state_change = callback
|
|
486
|
+
|
|
487
|
+
# =========================================================================
|
|
488
|
+
# Serialization
|
|
489
|
+
# =========================================================================
|
|
490
|
+
|
|
491
|
+
def to_dict(self) -> dict[str, Any]:
|
|
492
|
+
"""Serialize state for persistence."""
|
|
493
|
+
return {
|
|
494
|
+
"user_id": self.user_id,
|
|
495
|
+
"domain": self.domain,
|
|
496
|
+
"state": self._state.value,
|
|
497
|
+
"state_changed_at": self._state_changed_at.isoformat(),
|
|
498
|
+
"supervised_successes": self._supervised_successes,
|
|
499
|
+
"damage_events": [
|
|
500
|
+
{
|
|
501
|
+
"event_type": e.event_type.value,
|
|
502
|
+
"timestamp": e.timestamp.isoformat(),
|
|
503
|
+
"context": e.context,
|
|
504
|
+
"severity": e.severity,
|
|
505
|
+
"user_explicit": e.user_explicit,
|
|
506
|
+
}
|
|
507
|
+
for e in self._damage_events
|
|
508
|
+
],
|
|
509
|
+
"config": {
|
|
510
|
+
"damage_threshold": self.config.damage_threshold,
|
|
511
|
+
"recovery_period_hours": self.config.recovery_period_hours,
|
|
512
|
+
"supervised_successes_required": self.config.supervised_successes_required,
|
|
513
|
+
},
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@classmethod
|
|
517
|
+
def from_dict(cls, data: dict[str, Any]) -> TrustCircuitBreaker:
|
|
518
|
+
"""Restore from serialized state."""
|
|
519
|
+
config = TrustConfig(
|
|
520
|
+
damage_threshold=data["config"]["damage_threshold"],
|
|
521
|
+
recovery_period_hours=data["config"]["recovery_period_hours"],
|
|
522
|
+
supervised_successes_required=data["config"]["supervised_successes_required"],
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
instance = cls(
|
|
526
|
+
user_id=data["user_id"],
|
|
527
|
+
config=config,
|
|
528
|
+
domain=data.get("domain", "general"),
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
instance._state = TrustState(data["state"])
|
|
532
|
+
instance._state_changed_at = datetime.fromisoformat(data["state_changed_at"])
|
|
533
|
+
instance._supervised_successes = data.get("supervised_successes", 0)
|
|
534
|
+
|
|
535
|
+
for e in data.get("damage_events", []):
|
|
536
|
+
instance._damage_events.append(
|
|
537
|
+
TrustDamageEvent(
|
|
538
|
+
event_type=TrustDamageType(e["event_type"]),
|
|
539
|
+
timestamp=datetime.fromisoformat(e["timestamp"]),
|
|
540
|
+
context=e.get("context", ""),
|
|
541
|
+
severity=e.get("severity", 1.0),
|
|
542
|
+
user_explicit=e.get("user_explicit", False),
|
|
543
|
+
),
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
return instance
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# =============================================================================
|
|
550
|
+
# Convenience Functions
|
|
551
|
+
# =============================================================================
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def create_trust_breaker(
|
|
555
|
+
user_id: str,
|
|
556
|
+
domain: str = "general",
|
|
557
|
+
strict: bool = False,
|
|
558
|
+
) -> TrustCircuitBreaker:
|
|
559
|
+
"""Create a trust circuit breaker with preset configurations.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
user_id: User identifier
|
|
563
|
+
domain: Domain for trust tracking (if domain_isolation enabled)
|
|
564
|
+
strict: If True, use stricter thresholds (fewer mistakes allowed)
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
Configured TrustCircuitBreaker instance
|
|
568
|
+
|
|
569
|
+
"""
|
|
570
|
+
if strict:
|
|
571
|
+
config = TrustConfig(
|
|
572
|
+
damage_threshold=2,
|
|
573
|
+
recovery_period_hours=48.0,
|
|
574
|
+
supervised_successes_required=10,
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
config = TrustConfig() # Defaults
|
|
578
|
+
|
|
579
|
+
return TrustCircuitBreaker(user_id=user_id, config=config, domain=domain)
|