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.
Files changed (457) hide show
  1. attune/__init__.py +358 -0
  2. attune/adaptive/__init__.py +13 -0
  3. attune/adaptive/task_complexity.py +127 -0
  4. attune/agent_monitoring.py +414 -0
  5. attune/cache/__init__.py +117 -0
  6. attune/cache/base.py +166 -0
  7. attune/cache/dependency_manager.py +256 -0
  8. attune/cache/hash_only.py +251 -0
  9. attune/cache/hybrid.py +457 -0
  10. attune/cache/storage.py +285 -0
  11. attune/cache_monitor.py +356 -0
  12. attune/cache_stats.py +298 -0
  13. attune/cli/__init__.py +152 -0
  14. attune/cli/__main__.py +12 -0
  15. attune/cli/commands/__init__.py +1 -0
  16. attune/cli/commands/batch.py +264 -0
  17. attune/cli/commands/cache.py +248 -0
  18. attune/cli/commands/help.py +331 -0
  19. attune/cli/commands/info.py +140 -0
  20. attune/cli/commands/inspect.py +436 -0
  21. attune/cli/commands/inspection.py +57 -0
  22. attune/cli/commands/memory.py +48 -0
  23. attune/cli/commands/metrics.py +92 -0
  24. attune/cli/commands/orchestrate.py +184 -0
  25. attune/cli/commands/patterns.py +207 -0
  26. attune/cli/commands/profiling.py +202 -0
  27. attune/cli/commands/provider.py +98 -0
  28. attune/cli/commands/routing.py +285 -0
  29. attune/cli/commands/setup.py +96 -0
  30. attune/cli/commands/status.py +235 -0
  31. attune/cli/commands/sync.py +166 -0
  32. attune/cli/commands/tier.py +121 -0
  33. attune/cli/commands/utilities.py +114 -0
  34. attune/cli/commands/workflow.py +579 -0
  35. attune/cli/core.py +32 -0
  36. attune/cli/parsers/__init__.py +68 -0
  37. attune/cli/parsers/batch.py +118 -0
  38. attune/cli/parsers/cache.py +65 -0
  39. attune/cli/parsers/help.py +41 -0
  40. attune/cli/parsers/info.py +26 -0
  41. attune/cli/parsers/inspect.py +66 -0
  42. attune/cli/parsers/metrics.py +42 -0
  43. attune/cli/parsers/orchestrate.py +61 -0
  44. attune/cli/parsers/patterns.py +54 -0
  45. attune/cli/parsers/provider.py +40 -0
  46. attune/cli/parsers/routing.py +110 -0
  47. attune/cli/parsers/setup.py +42 -0
  48. attune/cli/parsers/status.py +47 -0
  49. attune/cli/parsers/sync.py +31 -0
  50. attune/cli/parsers/tier.py +33 -0
  51. attune/cli/parsers/workflow.py +77 -0
  52. attune/cli/utils/__init__.py +1 -0
  53. attune/cli/utils/data.py +242 -0
  54. attune/cli/utils/helpers.py +68 -0
  55. attune/cli_legacy.py +3957 -0
  56. attune/cli_minimal.py +1159 -0
  57. attune/cli_router.py +437 -0
  58. attune/cli_unified.py +814 -0
  59. attune/config/__init__.py +66 -0
  60. attune/config/xml_config.py +286 -0
  61. attune/config.py +545 -0
  62. attune/coordination.py +870 -0
  63. attune/core.py +1511 -0
  64. attune/core_modules/__init__.py +15 -0
  65. attune/cost_tracker.py +626 -0
  66. attune/dashboard/__init__.py +41 -0
  67. attune/dashboard/app.py +512 -0
  68. attune/dashboard/simple_server.py +435 -0
  69. attune/dashboard/standalone_server.py +547 -0
  70. attune/discovery.py +306 -0
  71. attune/emergence.py +306 -0
  72. attune/exceptions.py +123 -0
  73. attune/feedback_loops.py +373 -0
  74. attune/hot_reload/README.md +473 -0
  75. attune/hot_reload/__init__.py +62 -0
  76. attune/hot_reload/config.py +83 -0
  77. attune/hot_reload/integration.py +229 -0
  78. attune/hot_reload/reloader.py +298 -0
  79. attune/hot_reload/watcher.py +183 -0
  80. attune/hot_reload/websocket.py +177 -0
  81. attune/levels.py +577 -0
  82. attune/leverage_points.py +441 -0
  83. attune/logging_config.py +261 -0
  84. attune/mcp/__init__.py +10 -0
  85. attune/mcp/server.py +506 -0
  86. attune/memory/__init__.py +237 -0
  87. attune/memory/claude_memory.py +469 -0
  88. attune/memory/config.py +224 -0
  89. attune/memory/control_panel.py +1290 -0
  90. attune/memory/control_panel_support.py +145 -0
  91. attune/memory/cross_session.py +845 -0
  92. attune/memory/edges.py +179 -0
  93. attune/memory/encryption.py +159 -0
  94. attune/memory/file_session.py +770 -0
  95. attune/memory/graph.py +570 -0
  96. attune/memory/long_term.py +913 -0
  97. attune/memory/long_term_types.py +99 -0
  98. attune/memory/mixins/__init__.py +25 -0
  99. attune/memory/mixins/backend_init_mixin.py +249 -0
  100. attune/memory/mixins/capabilities_mixin.py +208 -0
  101. attune/memory/mixins/handoff_mixin.py +208 -0
  102. attune/memory/mixins/lifecycle_mixin.py +49 -0
  103. attune/memory/mixins/long_term_mixin.py +352 -0
  104. attune/memory/mixins/promotion_mixin.py +109 -0
  105. attune/memory/mixins/short_term_mixin.py +182 -0
  106. attune/memory/nodes.py +179 -0
  107. attune/memory/redis_bootstrap.py +540 -0
  108. attune/memory/security/__init__.py +31 -0
  109. attune/memory/security/audit_logger.py +932 -0
  110. attune/memory/security/pii_scrubber.py +640 -0
  111. attune/memory/security/secrets_detector.py +678 -0
  112. attune/memory/short_term.py +2192 -0
  113. attune/memory/simple_storage.py +302 -0
  114. attune/memory/storage/__init__.py +15 -0
  115. attune/memory/storage_backend.py +167 -0
  116. attune/memory/summary_index.py +583 -0
  117. attune/memory/types.py +446 -0
  118. attune/memory/unified.py +182 -0
  119. attune/meta_workflows/__init__.py +74 -0
  120. attune/meta_workflows/agent_creator.py +248 -0
  121. attune/meta_workflows/builtin_templates.py +567 -0
  122. attune/meta_workflows/cli_commands/__init__.py +56 -0
  123. attune/meta_workflows/cli_commands/agent_commands.py +321 -0
  124. attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
  125. attune/meta_workflows/cli_commands/config_commands.py +232 -0
  126. attune/meta_workflows/cli_commands/memory_commands.py +182 -0
  127. attune/meta_workflows/cli_commands/template_commands.py +354 -0
  128. attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
  129. attune/meta_workflows/cli_meta_workflows.py +59 -0
  130. attune/meta_workflows/form_engine.py +292 -0
  131. attune/meta_workflows/intent_detector.py +409 -0
  132. attune/meta_workflows/models.py +569 -0
  133. attune/meta_workflows/pattern_learner.py +738 -0
  134. attune/meta_workflows/plan_generator.py +384 -0
  135. attune/meta_workflows/session_context.py +397 -0
  136. attune/meta_workflows/template_registry.py +229 -0
  137. attune/meta_workflows/workflow.py +984 -0
  138. attune/metrics/__init__.py +12 -0
  139. attune/metrics/collector.py +31 -0
  140. attune/metrics/prompt_metrics.py +194 -0
  141. attune/models/__init__.py +172 -0
  142. attune/models/__main__.py +13 -0
  143. attune/models/adaptive_routing.py +437 -0
  144. attune/models/auth_cli.py +444 -0
  145. attune/models/auth_strategy.py +450 -0
  146. attune/models/cli.py +655 -0
  147. attune/models/empathy_executor.py +354 -0
  148. attune/models/executor.py +257 -0
  149. attune/models/fallback.py +762 -0
  150. attune/models/provider_config.py +282 -0
  151. attune/models/registry.py +472 -0
  152. attune/models/tasks.py +359 -0
  153. attune/models/telemetry/__init__.py +71 -0
  154. attune/models/telemetry/analytics.py +594 -0
  155. attune/models/telemetry/backend.py +196 -0
  156. attune/models/telemetry/data_models.py +431 -0
  157. attune/models/telemetry/storage.py +489 -0
  158. attune/models/token_estimator.py +420 -0
  159. attune/models/validation.py +280 -0
  160. attune/monitoring/__init__.py +52 -0
  161. attune/monitoring/alerts.py +946 -0
  162. attune/monitoring/alerts_cli.py +448 -0
  163. attune/monitoring/multi_backend.py +271 -0
  164. attune/monitoring/otel_backend.py +362 -0
  165. attune/optimization/__init__.py +19 -0
  166. attune/optimization/context_optimizer.py +272 -0
  167. attune/orchestration/__init__.py +67 -0
  168. attune/orchestration/agent_templates.py +707 -0
  169. attune/orchestration/config_store.py +499 -0
  170. attune/orchestration/execution_strategies.py +2111 -0
  171. attune/orchestration/meta_orchestrator.py +1168 -0
  172. attune/orchestration/pattern_learner.py +696 -0
  173. attune/orchestration/real_tools.py +931 -0
  174. attune/pattern_cache.py +187 -0
  175. attune/pattern_library.py +542 -0
  176. attune/patterns/debugging/all_patterns.json +81 -0
  177. attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
  178. attune/patterns/refactoring_memory.json +89 -0
  179. attune/persistence.py +564 -0
  180. attune/platform_utils.py +265 -0
  181. attune/plugins/__init__.py +28 -0
  182. attune/plugins/base.py +361 -0
  183. attune/plugins/registry.py +268 -0
  184. attune/project_index/__init__.py +32 -0
  185. attune/project_index/cli.py +335 -0
  186. attune/project_index/index.py +667 -0
  187. attune/project_index/models.py +504 -0
  188. attune/project_index/reports.py +474 -0
  189. attune/project_index/scanner.py +777 -0
  190. attune/project_index/scanner_parallel.py +291 -0
  191. attune/prompts/__init__.py +61 -0
  192. attune/prompts/config.py +77 -0
  193. attune/prompts/context.py +177 -0
  194. attune/prompts/parser.py +285 -0
  195. attune/prompts/registry.py +313 -0
  196. attune/prompts/templates.py +208 -0
  197. attune/redis_config.py +302 -0
  198. attune/redis_memory.py +799 -0
  199. attune/resilience/__init__.py +56 -0
  200. attune/resilience/circuit_breaker.py +256 -0
  201. attune/resilience/fallback.py +179 -0
  202. attune/resilience/health.py +300 -0
  203. attune/resilience/retry.py +209 -0
  204. attune/resilience/timeout.py +135 -0
  205. attune/routing/__init__.py +43 -0
  206. attune/routing/chain_executor.py +433 -0
  207. attune/routing/classifier.py +217 -0
  208. attune/routing/smart_router.py +234 -0
  209. attune/routing/workflow_registry.py +343 -0
  210. attune/scaffolding/README.md +589 -0
  211. attune/scaffolding/__init__.py +35 -0
  212. attune/scaffolding/__main__.py +14 -0
  213. attune/scaffolding/cli.py +240 -0
  214. attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
  215. attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
  216. attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
  217. attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
  218. attune/socratic/__init__.py +256 -0
  219. attune/socratic/ab_testing.py +958 -0
  220. attune/socratic/blueprint.py +533 -0
  221. attune/socratic/cli.py +703 -0
  222. attune/socratic/collaboration.py +1114 -0
  223. attune/socratic/domain_templates.py +924 -0
  224. attune/socratic/embeddings.py +738 -0
  225. attune/socratic/engine.py +794 -0
  226. attune/socratic/explainer.py +682 -0
  227. attune/socratic/feedback.py +772 -0
  228. attune/socratic/forms.py +629 -0
  229. attune/socratic/generator.py +732 -0
  230. attune/socratic/llm_analyzer.py +637 -0
  231. attune/socratic/mcp_server.py +702 -0
  232. attune/socratic/session.py +312 -0
  233. attune/socratic/storage.py +667 -0
  234. attune/socratic/success.py +730 -0
  235. attune/socratic/visual_editor.py +860 -0
  236. attune/socratic/web_ui.py +958 -0
  237. attune/telemetry/__init__.py +39 -0
  238. attune/telemetry/agent_coordination.py +475 -0
  239. attune/telemetry/agent_tracking.py +367 -0
  240. attune/telemetry/approval_gates.py +545 -0
  241. attune/telemetry/cli.py +1231 -0
  242. attune/telemetry/commands/__init__.py +14 -0
  243. attune/telemetry/commands/dashboard_commands.py +696 -0
  244. attune/telemetry/event_streaming.py +409 -0
  245. attune/telemetry/feedback_loop.py +567 -0
  246. attune/telemetry/usage_tracker.py +591 -0
  247. attune/templates.py +754 -0
  248. attune/test_generator/__init__.py +38 -0
  249. attune/test_generator/__main__.py +14 -0
  250. attune/test_generator/cli.py +234 -0
  251. attune/test_generator/generator.py +355 -0
  252. attune/test_generator/risk_analyzer.py +216 -0
  253. attune/test_generator/templates/unit_test.py.jinja2 +272 -0
  254. attune/tier_recommender.py +384 -0
  255. attune/tools.py +183 -0
  256. attune/trust/__init__.py +28 -0
  257. attune/trust/circuit_breaker.py +579 -0
  258. attune/trust_building.py +527 -0
  259. attune/validation/__init__.py +19 -0
  260. attune/validation/xml_validator.py +281 -0
  261. attune/vscode_bridge.py +173 -0
  262. attune/workflow_commands.py +780 -0
  263. attune/workflow_patterns/__init__.py +33 -0
  264. attune/workflow_patterns/behavior.py +249 -0
  265. attune/workflow_patterns/core.py +76 -0
  266. attune/workflow_patterns/output.py +99 -0
  267. attune/workflow_patterns/registry.py +255 -0
  268. attune/workflow_patterns/structural.py +288 -0
  269. attune/workflows/__init__.py +539 -0
  270. attune/workflows/autonomous_test_gen.py +1268 -0
  271. attune/workflows/base.py +2667 -0
  272. attune/workflows/batch_processing.py +342 -0
  273. attune/workflows/bug_predict.py +1084 -0
  274. attune/workflows/builder.py +273 -0
  275. attune/workflows/caching.py +253 -0
  276. attune/workflows/code_review.py +1048 -0
  277. attune/workflows/code_review_adapters.py +312 -0
  278. attune/workflows/code_review_pipeline.py +722 -0
  279. attune/workflows/config.py +645 -0
  280. attune/workflows/dependency_check.py +644 -0
  281. attune/workflows/document_gen/__init__.py +25 -0
  282. attune/workflows/document_gen/config.py +30 -0
  283. attune/workflows/document_gen/report_formatter.py +162 -0
  284. attune/workflows/document_gen/workflow.py +1426 -0
  285. attune/workflows/document_manager.py +216 -0
  286. attune/workflows/document_manager_README.md +134 -0
  287. attune/workflows/documentation_orchestrator.py +1205 -0
  288. attune/workflows/history.py +510 -0
  289. attune/workflows/keyboard_shortcuts/__init__.py +39 -0
  290. attune/workflows/keyboard_shortcuts/generators.py +391 -0
  291. attune/workflows/keyboard_shortcuts/parsers.py +416 -0
  292. attune/workflows/keyboard_shortcuts/prompts.py +295 -0
  293. attune/workflows/keyboard_shortcuts/schema.py +193 -0
  294. attune/workflows/keyboard_shortcuts/workflow.py +509 -0
  295. attune/workflows/llm_base.py +363 -0
  296. attune/workflows/manage_docs.py +87 -0
  297. attune/workflows/manage_docs_README.md +134 -0
  298. attune/workflows/manage_documentation.py +821 -0
  299. attune/workflows/new_sample_workflow1.py +149 -0
  300. attune/workflows/new_sample_workflow1_README.md +150 -0
  301. attune/workflows/orchestrated_health_check.py +849 -0
  302. attune/workflows/orchestrated_release_prep.py +600 -0
  303. attune/workflows/output.py +413 -0
  304. attune/workflows/perf_audit.py +863 -0
  305. attune/workflows/pr_review.py +762 -0
  306. attune/workflows/progress.py +785 -0
  307. attune/workflows/progress_server.py +322 -0
  308. attune/workflows/progressive/README 2.md +454 -0
  309. attune/workflows/progressive/README.md +454 -0
  310. attune/workflows/progressive/__init__.py +82 -0
  311. attune/workflows/progressive/cli.py +219 -0
  312. attune/workflows/progressive/core.py +488 -0
  313. attune/workflows/progressive/orchestrator.py +723 -0
  314. attune/workflows/progressive/reports.py +520 -0
  315. attune/workflows/progressive/telemetry.py +274 -0
  316. attune/workflows/progressive/test_gen.py +495 -0
  317. attune/workflows/progressive/workflow.py +589 -0
  318. attune/workflows/refactor_plan.py +694 -0
  319. attune/workflows/release_prep.py +895 -0
  320. attune/workflows/release_prep_crew.py +969 -0
  321. attune/workflows/research_synthesis.py +404 -0
  322. attune/workflows/routing.py +168 -0
  323. attune/workflows/secure_release.py +593 -0
  324. attune/workflows/security_adapters.py +297 -0
  325. attune/workflows/security_audit.py +1329 -0
  326. attune/workflows/security_audit_phase3.py +355 -0
  327. attune/workflows/seo_optimization.py +633 -0
  328. attune/workflows/step_config.py +234 -0
  329. attune/workflows/telemetry_mixin.py +269 -0
  330. attune/workflows/test5.py +125 -0
  331. attune/workflows/test5_README.md +158 -0
  332. attune/workflows/test_coverage_boost_crew.py +849 -0
  333. attune/workflows/test_gen/__init__.py +52 -0
  334. attune/workflows/test_gen/ast_analyzer.py +249 -0
  335. attune/workflows/test_gen/config.py +88 -0
  336. attune/workflows/test_gen/data_models.py +38 -0
  337. attune/workflows/test_gen/report_formatter.py +289 -0
  338. attune/workflows/test_gen/test_templates.py +381 -0
  339. attune/workflows/test_gen/workflow.py +655 -0
  340. attune/workflows/test_gen.py +54 -0
  341. attune/workflows/test_gen_behavioral.py +477 -0
  342. attune/workflows/test_gen_parallel.py +341 -0
  343. attune/workflows/test_lifecycle.py +526 -0
  344. attune/workflows/test_maintenance.py +627 -0
  345. attune/workflows/test_maintenance_cli.py +590 -0
  346. attune/workflows/test_maintenance_crew.py +840 -0
  347. attune/workflows/test_runner.py +622 -0
  348. attune/workflows/tier_tracking.py +531 -0
  349. attune/workflows/xml_enhanced_crew.py +285 -0
  350. attune_ai-2.0.0.dist-info/METADATA +1026 -0
  351. attune_ai-2.0.0.dist-info/RECORD +457 -0
  352. attune_ai-2.0.0.dist-info/WHEEL +5 -0
  353. attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
  354. attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
  355. attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  356. attune_ai-2.0.0.dist-info/top_level.txt +5 -0
  357. attune_healthcare/__init__.py +13 -0
  358. attune_healthcare/monitors/__init__.py +9 -0
  359. attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
  360. attune_healthcare/monitors/monitoring/__init__.py +44 -0
  361. attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
  362. attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
  363. attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
  364. attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
  365. attune_llm/README.md +553 -0
  366. attune_llm/__init__.py +28 -0
  367. attune_llm/agent_factory/__init__.py +53 -0
  368. attune_llm/agent_factory/adapters/__init__.py +85 -0
  369. attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
  370. attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
  371. attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
  372. attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
  373. attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
  374. attune_llm/agent_factory/adapters/native.py +228 -0
  375. attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
  376. attune_llm/agent_factory/base.py +305 -0
  377. attune_llm/agent_factory/crews/__init__.py +67 -0
  378. attune_llm/agent_factory/crews/code_review.py +1113 -0
  379. attune_llm/agent_factory/crews/health_check.py +1262 -0
  380. attune_llm/agent_factory/crews/refactoring.py +1128 -0
  381. attune_llm/agent_factory/crews/security_audit.py +1018 -0
  382. attune_llm/agent_factory/decorators.py +287 -0
  383. attune_llm/agent_factory/factory.py +558 -0
  384. attune_llm/agent_factory/framework.py +193 -0
  385. attune_llm/agent_factory/memory_integration.py +328 -0
  386. attune_llm/agent_factory/resilient.py +320 -0
  387. attune_llm/agents_md/__init__.py +22 -0
  388. attune_llm/agents_md/loader.py +218 -0
  389. attune_llm/agents_md/parser.py +271 -0
  390. attune_llm/agents_md/registry.py +307 -0
  391. attune_llm/claude_memory.py +466 -0
  392. attune_llm/cli/__init__.py +8 -0
  393. attune_llm/cli/sync_claude.py +487 -0
  394. attune_llm/code_health.py +1313 -0
  395. attune_llm/commands/__init__.py +51 -0
  396. attune_llm/commands/context.py +375 -0
  397. attune_llm/commands/loader.py +301 -0
  398. attune_llm/commands/models.py +231 -0
  399. attune_llm/commands/parser.py +371 -0
  400. attune_llm/commands/registry.py +429 -0
  401. attune_llm/config/__init__.py +29 -0
  402. attune_llm/config/unified.py +291 -0
  403. attune_llm/context/__init__.py +22 -0
  404. attune_llm/context/compaction.py +455 -0
  405. attune_llm/context/manager.py +434 -0
  406. attune_llm/contextual_patterns.py +361 -0
  407. attune_llm/core.py +907 -0
  408. attune_llm/git_pattern_extractor.py +435 -0
  409. attune_llm/hooks/__init__.py +24 -0
  410. attune_llm/hooks/config.py +306 -0
  411. attune_llm/hooks/executor.py +289 -0
  412. attune_llm/hooks/registry.py +302 -0
  413. attune_llm/hooks/scripts/__init__.py +39 -0
  414. attune_llm/hooks/scripts/evaluate_session.py +201 -0
  415. attune_llm/hooks/scripts/first_time_init.py +285 -0
  416. attune_llm/hooks/scripts/pre_compact.py +207 -0
  417. attune_llm/hooks/scripts/session_end.py +183 -0
  418. attune_llm/hooks/scripts/session_start.py +163 -0
  419. attune_llm/hooks/scripts/suggest_compact.py +225 -0
  420. attune_llm/learning/__init__.py +30 -0
  421. attune_llm/learning/evaluator.py +438 -0
  422. attune_llm/learning/extractor.py +514 -0
  423. attune_llm/learning/storage.py +560 -0
  424. attune_llm/levels.py +227 -0
  425. attune_llm/pattern_confidence.py +414 -0
  426. attune_llm/pattern_resolver.py +272 -0
  427. attune_llm/pattern_summary.py +350 -0
  428. attune_llm/providers.py +967 -0
  429. attune_llm/routing/__init__.py +32 -0
  430. attune_llm/routing/model_router.py +362 -0
  431. attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
  432. attune_llm/security/PHASE2_COMPLETE.md +384 -0
  433. attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  434. attune_llm/security/QUICK_REFERENCE.md +316 -0
  435. attune_llm/security/README.md +262 -0
  436. attune_llm/security/__init__.py +62 -0
  437. attune_llm/security/audit_logger.py +929 -0
  438. attune_llm/security/audit_logger_example.py +152 -0
  439. attune_llm/security/pii_scrubber.py +640 -0
  440. attune_llm/security/secrets_detector.py +678 -0
  441. attune_llm/security/secrets_detector_example.py +304 -0
  442. attune_llm/security/secure_memdocs.py +1192 -0
  443. attune_llm/security/secure_memdocs_example.py +278 -0
  444. attune_llm/session_status.py +745 -0
  445. attune_llm/state.py +246 -0
  446. attune_llm/utils/__init__.py +5 -0
  447. attune_llm/utils/tokens.py +349 -0
  448. attune_software/SOFTWARE_PLUGIN_README.md +57 -0
  449. attune_software/__init__.py +13 -0
  450. attune_software/cli/__init__.py +120 -0
  451. attune_software/cli/inspect.py +362 -0
  452. attune_software/cli.py +574 -0
  453. attune_software/plugin.py +188 -0
  454. workflow_scaffolding/__init__.py +11 -0
  455. workflow_scaffolding/__main__.py +12 -0
  456. workflow_scaffolding/cli.py +206 -0
  457. workflow_scaffolding/generator.py +265 -0
@@ -0,0 +1,545 @@
1
+ """Human Approval Gates for Workflow Control.
2
+
3
+ Pattern 5 from Agent Coordination Architecture - Pause workflow execution
4
+ for human approval on critical decisions.
5
+
6
+ Usage:
7
+ # In workflow: Request approval
8
+ gate = ApprovalGate(agent_id="code-review-workflow")
9
+ approval = gate.request_approval(
10
+ approval_type="deploy_to_production",
11
+ context={
12
+ "deployment": "v2.0.0",
13
+ "changes": ["feature-x", "bugfix-y"],
14
+ "risk_level": "medium"
15
+ },
16
+ timeout=300.0 # 5 minutes
17
+ )
18
+
19
+ if approval.approved:
20
+ deploy_to_production()
21
+ else:
22
+ logger.info(f"Deployment rejected: {approval.reason}")
23
+
24
+ # From UI: Respond to approval request
25
+ gate = ApprovalGate()
26
+ pending = gate.get_pending_approvals()
27
+ for request in pending:
28
+ # Display to user, get decision
29
+ gate.respond_to_approval(
30
+ request_id=request.request_id,
31
+ approved=True,
32
+ responder="user@example.com",
33
+ reason="Looks good to deploy"
34
+ )
35
+
36
+ Copyright 2025 Smart-AI-Memory
37
+ Licensed under Fair Source License 0.9
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ import logging
43
+ import time
44
+ from dataclasses import dataclass, field
45
+ from datetime import datetime
46
+ from typing import Any
47
+ from uuid import uuid4
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ @dataclass
53
+ class ApprovalRequest:
54
+ """Approval request with context for human decision.
55
+
56
+ Represents a pending approval request from a workflow.
57
+ """
58
+
59
+ request_id: str
60
+ approval_type: str # "deploy", "delete", "refactor", etc.
61
+ agent_id: str # Requesting agent/workflow
62
+ context: dict[str, Any] # Decision context
63
+ timestamp: datetime
64
+ timeout_seconds: float
65
+ status: str = "pending" # "pending", "approved", "rejected", "timeout"
66
+
67
+ def to_dict(self) -> dict[str, Any]:
68
+ """Convert to dictionary for serialization."""
69
+ return {
70
+ "request_id": self.request_id,
71
+ "approval_type": self.approval_type,
72
+ "agent_id": self.agent_id,
73
+ "context": self.context,
74
+ "timestamp": self.timestamp.isoformat() if isinstance(self.timestamp, datetime) else self.timestamp,
75
+ "timeout_seconds": self.timeout_seconds,
76
+ "status": self.status,
77
+ }
78
+
79
+ @classmethod
80
+ def from_dict(cls, data: dict[str, Any]) -> ApprovalRequest:
81
+ """Create from dictionary."""
82
+ timestamp = data.get("timestamp")
83
+ if isinstance(timestamp, str):
84
+ timestamp = datetime.fromisoformat(timestamp)
85
+ elif not isinstance(timestamp, datetime):
86
+ timestamp = datetime.utcnow()
87
+
88
+ return cls(
89
+ request_id=data["request_id"],
90
+ approval_type=data["approval_type"],
91
+ agent_id=data["agent_id"],
92
+ context=data.get("context", {}),
93
+ timestamp=timestamp,
94
+ timeout_seconds=data.get("timeout_seconds", 300.0),
95
+ status=data.get("status", "pending"),
96
+ )
97
+
98
+
99
+ @dataclass
100
+ class ApprovalResponse:
101
+ """Response to an approval request.
102
+
103
+ Represents a human's decision on an approval request.
104
+ """
105
+
106
+ request_id: str
107
+ approved: bool
108
+ responder: str # User who approved/rejected
109
+ reason: str = ""
110
+ timestamp: datetime = field(default_factory=datetime.utcnow)
111
+
112
+ def to_dict(self) -> dict[str, Any]:
113
+ """Convert to dictionary for serialization."""
114
+ return {
115
+ "request_id": self.request_id,
116
+ "approved": self.approved,
117
+ "responder": self.responder,
118
+ "reason": self.reason,
119
+ "timestamp": self.timestamp.isoformat() if isinstance(self.timestamp, datetime) else self.timestamp,
120
+ }
121
+
122
+ @classmethod
123
+ def from_dict(cls, data: dict[str, Any]) -> ApprovalResponse:
124
+ """Create from dictionary."""
125
+ timestamp = data.get("timestamp")
126
+ if isinstance(timestamp, str):
127
+ timestamp = datetime.fromisoformat(timestamp)
128
+ elif not isinstance(timestamp, datetime):
129
+ timestamp = datetime.utcnow()
130
+
131
+ return cls(
132
+ request_id=data["request_id"],
133
+ approved=data.get("approved", False),
134
+ responder=data.get("responder", "unknown"),
135
+ reason=data.get("reason", ""),
136
+ timestamp=timestamp,
137
+ )
138
+
139
+
140
+ class ApprovalGate:
141
+ """Human approval gates for workflow control.
142
+
143
+ Workflows can pause execution and wait for human approval before
144
+ proceeding with critical actions.
145
+
146
+ Uses coordination signals under the hood:
147
+ - "approval_request" signal: Workflow → Human
148
+ - "approval_response" signal: Human → Workflow
149
+
150
+ Attributes:
151
+ DEFAULT_TIMEOUT: Default approval timeout (300s = 5 minutes)
152
+ POLL_INTERVAL: Poll interval when waiting for approval (1s)
153
+ """
154
+
155
+ DEFAULT_TIMEOUT = 300.0 # 5 minutes default timeout
156
+ POLL_INTERVAL = 1.0 # Check for response every 1 second
157
+
158
+ def __init__(self, memory=None, agent_id: str | None = None):
159
+ """Initialize approval gate.
160
+
161
+ Args:
162
+ memory: Memory instance for storing approval requests/responses
163
+ agent_id: This agent's ID (for workflow requesting approval)
164
+ """
165
+ self.memory = memory
166
+ self.agent_id = agent_id
167
+
168
+ if self.memory is None:
169
+ try:
170
+ from attune.telemetry import UsageTracker
171
+
172
+ tracker = UsageTracker.get_instance()
173
+ if hasattr(tracker, "_memory"):
174
+ self.memory = tracker._memory
175
+ except (ImportError, AttributeError):
176
+ pass
177
+
178
+ if self.memory is None:
179
+ logger.warning("No memory backend available for approval gates")
180
+
181
+ def request_approval(
182
+ self,
183
+ approval_type: str,
184
+ context: dict[str, Any] | None = None,
185
+ timeout: float | None = None,
186
+ ) -> ApprovalResponse:
187
+ """Request human approval and wait for response.
188
+
189
+ This is a blocking operation that waits for human approval with timeout.
190
+
191
+ Args:
192
+ approval_type: Type of approval needed (e.g., "deploy", "delete")
193
+ context: Context information for decision making
194
+ timeout: Maximum wait time in seconds (default: DEFAULT_TIMEOUT)
195
+
196
+ Returns:
197
+ ApprovalResponse with decision (approved or rejected)
198
+
199
+ Raises:
200
+ ValueError: If approval times out
201
+
202
+ Example:
203
+ >>> gate = ApprovalGate(agent_id="my-workflow")
204
+ >>> approval = gate.request_approval(
205
+ ... approval_type="deploy_to_production",
206
+ ... context={"version": "2.0.0", "risk": "medium"},
207
+ ... timeout=300.0
208
+ ... )
209
+ >>> if approval.approved:
210
+ ... deploy()
211
+ """
212
+ if not self.memory or not self.agent_id:
213
+ logger.warning("Cannot request approval: no memory backend or agent_id")
214
+ # Return auto-rejected response
215
+ return ApprovalResponse(
216
+ request_id="",
217
+ approved=False,
218
+ responder="system",
219
+ reason="Approval gates not available (no memory backend)",
220
+ )
221
+
222
+ timeout = timeout if timeout is not None else self.DEFAULT_TIMEOUT
223
+ request_id = f"approval_{uuid4().hex[:8]}"
224
+
225
+ # Create approval request
226
+ request = ApprovalRequest(
227
+ request_id=request_id,
228
+ approval_type=approval_type,
229
+ agent_id=self.agent_id,
230
+ context=context or {},
231
+ timestamp=datetime.utcnow(),
232
+ timeout_seconds=timeout,
233
+ status="pending",
234
+ )
235
+
236
+ # Store approval request (for UI to retrieve)
237
+ request_key = f"approval_request:{request_id}"
238
+ try:
239
+ # Use direct Redis access for custom TTL
240
+ if hasattr(self.memory, "_client") and self.memory._client:
241
+ import json
242
+
243
+ self.memory._client.setex(request_key, int(timeout) + 60, json.dumps(request.to_dict()))
244
+ else:
245
+ logger.warning("Cannot store approval request: no Redis backend available")
246
+ except Exception as e:
247
+ logger.error(f"Failed to store approval request: {e}")
248
+ return ApprovalResponse(
249
+ request_id=request_id, approved=False, responder="system", reason=f"Storage error: {e}"
250
+ )
251
+
252
+ # Send approval_request signal (for notifications)
253
+ try:
254
+ from attune.telemetry import CoordinationSignals
255
+
256
+ signals = CoordinationSignals(memory=self.memory, agent_id=self.agent_id)
257
+ signals.signal(
258
+ signal_type="approval_request",
259
+ source_agent=self.agent_id,
260
+ target_agent="*", # Broadcast to UI/monitoring systems
261
+ payload=request.to_dict(),
262
+ ttl_seconds=int(timeout) + 60,
263
+ )
264
+ except Exception as e:
265
+ logger.warning(f"Failed to send approval_request signal: {e}")
266
+
267
+ # Wait for approval response (blocking with timeout)
268
+ logger.info(
269
+ f"Waiting for approval: {approval_type} (request_id={request_id}, timeout={timeout}s)"
270
+ )
271
+
272
+ start_time = time.time()
273
+ while time.time() - start_time < timeout:
274
+ # Check for response
275
+ response = self._check_for_response(request_id)
276
+ if response:
277
+ logger.info(
278
+ f"Approval received: {approval_type} → {'APPROVED' if response.approved else 'REJECTED'}"
279
+ )
280
+ return response
281
+
282
+ # Sleep before next check
283
+ time.sleep(self.POLL_INTERVAL)
284
+
285
+ # Timeout - no response received
286
+ logger.warning(f"Approval timeout: {approval_type} (request_id={request_id})")
287
+
288
+ # Update request status to timeout
289
+ request.status = "timeout"
290
+ try:
291
+ # Use direct Redis access
292
+ if hasattr(self.memory, "_client") and self.memory._client:
293
+ import json
294
+
295
+ self.memory._client.setex(request_key, 60, json.dumps(request.to_dict()))
296
+ except Exception:
297
+ pass
298
+
299
+ return ApprovalResponse(
300
+ request_id=request_id,
301
+ approved=False,
302
+ responder="system",
303
+ reason=f"Approval timeout after {timeout}s",
304
+ )
305
+
306
+ def _check_for_response(self, request_id: str) -> ApprovalResponse | None:
307
+ """Check if approval response has been received."""
308
+ if not self.memory:
309
+ return None
310
+
311
+ response_key = f"approval_response:{request_id}"
312
+
313
+ try:
314
+ # Try retrieve method first (UnifiedMemory)
315
+ if hasattr(self.memory, "retrieve"):
316
+ data = self.memory.retrieve(response_key, credentials=None)
317
+ # Try direct Redis access
318
+ elif hasattr(self.memory, "_client"):
319
+ import json
320
+
321
+ raw_data = self.memory._client.get(response_key)
322
+ if raw_data:
323
+ if isinstance(raw_data, bytes):
324
+ raw_data = raw_data.decode("utf-8")
325
+ data = json.loads(raw_data)
326
+ else:
327
+ data = None
328
+ else:
329
+ data = None
330
+
331
+ if data:
332
+ return ApprovalResponse.from_dict(data)
333
+ return None
334
+ except Exception as e:
335
+ logger.debug(f"Failed to check for approval response: {e}")
336
+ return None
337
+
338
+ def respond_to_approval(
339
+ self, request_id: str, approved: bool, responder: str, reason: str = ""
340
+ ) -> bool:
341
+ """Respond to an approval request (called from UI/human).
342
+
343
+ Args:
344
+ request_id: ID of approval request to respond to
345
+ approved: Whether to approve or reject
346
+ responder: User/system responding (e.g., email, username)
347
+ reason: Optional reason for decision
348
+
349
+ Returns:
350
+ True if response was stored successfully, False otherwise
351
+
352
+ Example:
353
+ >>> gate = ApprovalGate()
354
+ >>> success = gate.respond_to_approval(
355
+ ... request_id="approval_abc123",
356
+ ... approved=True,
357
+ ... responder="user@example.com",
358
+ ... reason="Looks good to deploy"
359
+ ... )
360
+ """
361
+ if not self.memory:
362
+ logger.warning("Cannot respond to approval: no memory backend")
363
+ return False
364
+
365
+ response = ApprovalResponse(
366
+ request_id=request_id, approved=approved, responder=responder, reason=reason, timestamp=datetime.utcnow()
367
+ )
368
+
369
+ # Store approval response (for workflow to retrieve)
370
+ response_key = f"approval_response:{request_id}"
371
+ try:
372
+ # Use direct Redis access
373
+ if hasattr(self.memory, "_client") and self.memory._client:
374
+ import json
375
+
376
+ self.memory._client.setex(response_key, 300, json.dumps(response.to_dict()))
377
+ else:
378
+ logger.warning("Cannot store approval response: no Redis backend available")
379
+ return False
380
+ except Exception as e:
381
+ logger.error(f"Failed to store approval response: {e}")
382
+ return False
383
+
384
+ # Update request status
385
+ request_key = f"approval_request:{request_id}"
386
+ try:
387
+ if hasattr(self.memory, "retrieve"):
388
+ request_data = self.memory.retrieve(request_key, credentials=None)
389
+ elif hasattr(self.memory, "_client"):
390
+ import json
391
+
392
+ raw_data = self.memory._client.get(request_key)
393
+ if raw_data:
394
+ if isinstance(raw_data, bytes):
395
+ raw_data = raw_data.decode("utf-8")
396
+ request_data = json.loads(raw_data)
397
+ else:
398
+ request_data = None
399
+ else:
400
+ request_data = None
401
+
402
+ if request_data:
403
+ request = ApprovalRequest.from_dict(request_data)
404
+ request.status = "approved" if approved else "rejected"
405
+
406
+ # Use direct Redis access
407
+ if hasattr(self.memory, "_client") and self.memory._client:
408
+ import json
409
+
410
+ self.memory._client.setex(request_key, 300, json.dumps(request.to_dict()))
411
+ except Exception as e:
412
+ logger.debug(f"Failed to update request status: {e}")
413
+
414
+ # Send approval_response signal (for notifications)
415
+ try:
416
+ from attune.telemetry import CoordinationSignals
417
+
418
+ signals = CoordinationSignals(memory=self.memory, agent_id=responder)
419
+ signals.signal(
420
+ signal_type="approval_response",
421
+ source_agent=responder,
422
+ target_agent="*", # Broadcast
423
+ payload=response.to_dict(),
424
+ ttl_seconds=300,
425
+ )
426
+ except Exception as e:
427
+ logger.debug(f"Failed to send approval_response signal: {e}")
428
+
429
+ logger.info(
430
+ f"Approval response recorded: {request_id} → {'APPROVED' if approved else 'REJECTED'} by {responder}"
431
+ )
432
+ return True
433
+
434
+ def get_pending_approvals(self, approval_type: str | None = None) -> list[ApprovalRequest]:
435
+ """Get all pending approval requests (called from UI).
436
+
437
+ Args:
438
+ approval_type: Optional filter by approval type
439
+
440
+ Returns:
441
+ List of pending approval requests
442
+
443
+ Example:
444
+ >>> gate = ApprovalGate()
445
+ >>> pending = gate.get_pending_approvals()
446
+ >>> for request in pending:
447
+ ... print(f"{request.approval_type}: {request.context}")
448
+ """
449
+ if not self.memory or not hasattr(self.memory, "_client"):
450
+ return []
451
+
452
+ try:
453
+ # Scan for approval_request:* keys
454
+ keys = self.memory._client.keys("approval_request:*")
455
+
456
+ requests = []
457
+ for key in keys:
458
+ if isinstance(key, bytes):
459
+ key = key.decode("utf-8")
460
+
461
+ # Retrieve request - use direct Redis access (approval keys are stored without prefix)
462
+ import json
463
+
464
+ raw_data = self.memory._client.get(key)
465
+ if raw_data:
466
+ if isinstance(raw_data, bytes):
467
+ raw_data = raw_data.decode("utf-8")
468
+ data = json.loads(raw_data)
469
+ else:
470
+ data = None
471
+
472
+ if not data:
473
+ continue
474
+
475
+ request = ApprovalRequest.from_dict(data)
476
+
477
+ # Filter by status (only pending)
478
+ if request.status != "pending":
479
+ continue
480
+
481
+ # Filter by type if specified
482
+ if approval_type and request.approval_type != approval_type:
483
+ continue
484
+
485
+ requests.append(request)
486
+
487
+ # Sort by timestamp (oldest first)
488
+ requests.sort(key=lambda r: r.timestamp)
489
+
490
+ return requests
491
+ except Exception as e:
492
+ logger.error(f"Failed to get pending approvals: {e}")
493
+ return []
494
+
495
+ def clear_expired_requests(self) -> int:
496
+ """Clear approval requests that have timed out.
497
+
498
+ Returns:
499
+ Number of requests cleared
500
+ """
501
+ if not self.memory or not hasattr(self.memory, "_client"):
502
+ return 0
503
+
504
+ try:
505
+ keys = self.memory._client.keys("approval_request:*")
506
+ now = datetime.utcnow()
507
+ cleared = 0
508
+
509
+ for key in keys:
510
+ if isinstance(key, bytes):
511
+ key = key.decode("utf-8")
512
+
513
+ # Retrieve request - use direct Redis access (approval keys are stored without prefix)
514
+ import json
515
+
516
+ raw_data = self.memory._client.get(key)
517
+ if raw_data:
518
+ if isinstance(raw_data, bytes):
519
+ raw_data = raw_data.decode("utf-8")
520
+ data = json.loads(raw_data)
521
+ else:
522
+ data = None
523
+
524
+ if not data:
525
+ continue
526
+
527
+ request = ApprovalRequest.from_dict(data)
528
+
529
+ # Check if expired
530
+ elapsed = (now - request.timestamp).total_seconds()
531
+ if elapsed > request.timeout_seconds and request.status == "pending":
532
+ # Update to timeout status
533
+ request.status = "timeout"
534
+ # Use direct Redis access
535
+ if hasattr(self.memory, "_client") and self.memory._client:
536
+ import json
537
+
538
+ self.memory._client.setex(key, 60, json.dumps(request.to_dict()))
539
+
540
+ cleared += 1
541
+
542
+ return cleared
543
+ except Exception as e:
544
+ logger.error(f"Failed to clear expired requests: {e}")
545
+ return 0