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,409 @@
1
+ """Real-Time Event Streaming using Redis Streams.
2
+
3
+ Pattern 4 from Agent Coordination Architecture - Publish agent events
4
+ to Redis Streams for real-time monitoring and WebSocket consumption.
5
+
6
+ Events types:
7
+ - agent_heartbeat: Agent liveness updates
8
+ - coordination_signal: Inter-agent coordination messages
9
+ - workflow_progress: Workflow stage progress
10
+ - agent_error: Agent failures and errors
11
+
12
+ Usage:
13
+ # Publish events
14
+ streamer = EventStreamer()
15
+ streamer.publish_event(
16
+ event_type="agent_heartbeat",
17
+ data={"agent_id": "worker-1", "status": "running", "progress": 0.5}
18
+ )
19
+
20
+ # Consume events (blocking)
21
+ for event in streamer.consume_events(event_types=["agent_heartbeat"]):
22
+ print(f"Received: {event}")
23
+
24
+ # Get recent events (non-blocking)
25
+ recent = streamer.get_recent_events(event_type="agent_heartbeat", count=100)
26
+
27
+ Copyright 2025 Smart-AI-Memory
28
+ Licensed under Fair Source License 0.9
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import logging
35
+ from collections.abc import Iterator
36
+ from dataclasses import dataclass
37
+ from datetime import datetime
38
+ from typing import Any
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ @dataclass
44
+ class StreamEvent:
45
+ """Event published to Redis Stream."""
46
+
47
+ event_id: str # Redis stream entry ID (e.g., "1706356800000-0")
48
+ event_type: str # "agent_heartbeat", "coordination_signal", etc.
49
+ timestamp: datetime
50
+ data: dict[str, Any]
51
+ source: str = "attune" # Source system
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ """Convert to dictionary for serialization."""
55
+ return {
56
+ "event_id": self.event_id,
57
+ "event_type": self.event_type,
58
+ "timestamp": self.timestamp.isoformat() if isinstance(self.timestamp, datetime) else self.timestamp,
59
+ "data": self.data,
60
+ "source": self.source,
61
+ }
62
+
63
+ @classmethod
64
+ def from_redis_entry(cls, event_id: str, entry_data: dict[bytes, bytes]) -> StreamEvent:
65
+ """Create from Redis stream entry.
66
+
67
+ Args:
68
+ event_id: Redis stream entry ID
69
+ entry_data: Raw entry data from Redis (bytes dict or str dict)
70
+
71
+ Returns:
72
+ StreamEvent instance
73
+ """
74
+ # Decode bytes to strings (handle both bytes and str)
75
+ decoded = {}
76
+ for k, v in entry_data.items():
77
+ key = k.decode("utf-8") if isinstance(k, bytes) else k
78
+ value = v.decode("utf-8") if isinstance(v, bytes) else v
79
+ decoded[key] = value
80
+
81
+ # Parse timestamp
82
+ timestamp_str = decoded.get("timestamp", "")
83
+ try:
84
+ timestamp = datetime.fromisoformat(timestamp_str)
85
+ except (ValueError, AttributeError):
86
+ timestamp = datetime.utcnow()
87
+
88
+ # Parse data field (JSON)
89
+ data_str = decoded.get("data", "{}")
90
+ try:
91
+ data = json.loads(data_str)
92
+ except json.JSONDecodeError:
93
+ data = {}
94
+
95
+ return cls(
96
+ event_id=event_id,
97
+ event_type=decoded.get("event_type", "unknown"),
98
+ timestamp=timestamp,
99
+ data=data,
100
+ source=decoded.get("source", "attune"),
101
+ )
102
+
103
+
104
+ class EventStreamer:
105
+ """Real-time event streaming using Redis Streams.
106
+
107
+ Publishes events to Redis Streams and provides methods for consuming
108
+ events via polling or blocking reads.
109
+
110
+ Stream naming: stream:{event_type}
111
+ Examples:
112
+ - stream:agent_heartbeat
113
+ - stream:coordination_signal
114
+ - stream:workflow_progress
115
+ """
116
+
117
+ STREAM_PREFIX = "stream:"
118
+ MAX_STREAM_LENGTH = 10000 # Trim streams to last 10K events
119
+ DEFAULT_BLOCK_MS = 5000 # 5 seconds blocking read timeout
120
+
121
+ def __init__(self, memory=None):
122
+ """Initialize event streamer.
123
+
124
+ Args:
125
+ memory: Memory backend with Redis connection
126
+ """
127
+ self.memory = memory
128
+
129
+ if self.memory is None:
130
+ try:
131
+ from attune.telemetry import UsageTracker
132
+
133
+ tracker = UsageTracker.get_instance()
134
+ if hasattr(tracker, "_memory"):
135
+ self.memory = tracker._memory
136
+ except (ImportError, AttributeError):
137
+ pass
138
+
139
+ if self.memory is None:
140
+ logger.warning("No memory backend available for event streaming")
141
+
142
+ def _get_stream_key(self, event_type: str) -> str:
143
+ """Get Redis stream key for an event type.
144
+
145
+ Args:
146
+ event_type: Type of event
147
+
148
+ Returns:
149
+ Stream key (e.g., "stream:agent_heartbeat")
150
+ """
151
+ return f"{self.STREAM_PREFIX}{event_type}"
152
+
153
+ def publish_event(
154
+ self,
155
+ event_type: str,
156
+ data: dict[str, Any],
157
+ source: str = "attune",
158
+ ) -> str:
159
+ """Publish an event to Redis Stream.
160
+
161
+ Args:
162
+ event_type: Type of event (e.g., "agent_heartbeat", "coordination_signal")
163
+ data: Event payload data
164
+ source: Source system (default "attune")
165
+
166
+ Returns:
167
+ Event ID (Redis stream entry ID) if successful, empty string otherwise
168
+ """
169
+ if not self.memory or not hasattr(self.memory, "_client") or not self.memory._client:
170
+ logger.debug("Cannot publish event: no Redis backend")
171
+ return ""
172
+
173
+ stream_key = self._get_stream_key(event_type)
174
+
175
+ # Prepare entry data
176
+ entry = {
177
+ "event_type": event_type,
178
+ "timestamp": datetime.utcnow().isoformat(),
179
+ "data": json.dumps(data),
180
+ "source": source,
181
+ }
182
+
183
+ try:
184
+ # Add to stream with automatic trimming (MAXLEN)
185
+ event_id = self.memory._client.xadd(
186
+ stream_key,
187
+ entry,
188
+ maxlen=self.MAX_STREAM_LENGTH,
189
+ approximate=True, # Use ~ for performance
190
+ )
191
+
192
+ # Decode event_id if bytes
193
+ if isinstance(event_id, bytes):
194
+ event_id = event_id.decode("utf-8")
195
+
196
+ logger.debug(f"Published event {event_type}: {event_id}")
197
+ return event_id
198
+
199
+ except Exception as e:
200
+ logger.error(f"Failed to publish event {event_type}: {e}")
201
+ return ""
202
+
203
+ def consume_events(
204
+ self,
205
+ event_types: list[str] | None = None,
206
+ block_ms: int | None = None,
207
+ count: int = 10,
208
+ start_id: str = "$",
209
+ ) -> Iterator[StreamEvent]:
210
+ """Consume events from Redis Streams (blocking iterator).
211
+
212
+ Args:
213
+ event_types: List of event types to consume (None = all types)
214
+ block_ms: Blocking timeout in milliseconds (None = DEFAULT_BLOCK_MS)
215
+ count: Number of events to read per batch
216
+ start_id: Stream position to start from ("$" = new events only, "0" = all events)
217
+
218
+ Yields:
219
+ StreamEvent instances as they arrive
220
+
221
+ Example:
222
+ >>> streamer = EventStreamer()
223
+ >>> for event in streamer.consume_events(event_types=["agent_heartbeat"]):
224
+ ... print(f"Agent {event.data['agent_id']} status: {event.data['status']}")
225
+ """
226
+ if not self.memory or not hasattr(self.memory, "_client") or not self.memory._client:
227
+ logger.warning("Cannot consume events: no Redis backend")
228
+ return
229
+
230
+ block_ms = block_ms if block_ms is not None else self.DEFAULT_BLOCK_MS
231
+
232
+ # Determine streams to read
233
+ if event_types:
234
+ streams = {self._get_stream_key(et): start_id for et in event_types}
235
+ else:
236
+ # Subscribe to all event streams (expensive - requires KEYS scan)
237
+ all_streams = self.memory._client.keys(f"{self.STREAM_PREFIX}*")
238
+ streams = {s.decode("utf-8") if isinstance(s, bytes) else s: start_id for s in all_streams}
239
+
240
+ if not streams:
241
+ logger.debug("No streams to consume")
242
+ return
243
+
244
+ # Track last IDs for each stream
245
+ last_ids = streams.copy()
246
+
247
+ try:
248
+ while True:
249
+ # XREAD: blocking read from multiple streams
250
+ results = self.memory._client.xread(
251
+ last_ids,
252
+ count=count,
253
+ block=block_ms,
254
+ )
255
+
256
+ if not results:
257
+ # Timeout - no new events
258
+ continue
259
+
260
+ # Process results
261
+ for stream_key, entries in results:
262
+ # Decode stream key if bytes
263
+ if isinstance(stream_key, bytes):
264
+ stream_key = stream_key.decode("utf-8")
265
+
266
+ for event_id, entry_data in entries:
267
+ # Decode event_id if bytes
268
+ if isinstance(event_id, bytes):
269
+ event_id = event_id.decode("utf-8")
270
+
271
+ # Parse event
272
+ event = StreamEvent.from_redis_entry(event_id, entry_data)
273
+ yield event
274
+
275
+ # Update last_id for this stream
276
+ last_ids[stream_key] = event_id
277
+
278
+ except KeyboardInterrupt:
279
+ logger.info("Event consumption interrupted")
280
+ except Exception as e:
281
+ logger.error(f"Error consuming events: {e}")
282
+
283
+ def get_recent_events(
284
+ self,
285
+ event_type: str,
286
+ count: int = 100,
287
+ start_id: str = "-",
288
+ end_id: str = "+",
289
+ ) -> list[StreamEvent]:
290
+ """Get recent events from a stream (non-blocking).
291
+
292
+ Args:
293
+ event_type: Type of event to retrieve
294
+ count: Maximum number of events to return
295
+ start_id: Start position ("-" = oldest, specific ID = from that point)
296
+ end_id: End position ("+" = newest, specific ID = up to that point)
297
+
298
+ Returns:
299
+ List of recent events (newest first)
300
+ """
301
+ if not self.memory or not hasattr(self.memory, "_client") or not self.memory._client:
302
+ logger.debug("Cannot get recent events: no Redis backend")
303
+ return []
304
+
305
+ stream_key = self._get_stream_key(event_type)
306
+
307
+ try:
308
+ # XREVRANGE: get events in reverse chronological order
309
+ results = self.memory._client.xrevrange(
310
+ stream_key,
311
+ max=end_id,
312
+ min=start_id,
313
+ count=count,
314
+ )
315
+
316
+ events = []
317
+ for event_id, entry_data in results:
318
+ # Decode event_id if bytes
319
+ if isinstance(event_id, bytes):
320
+ event_id = event_id.decode("utf-8")
321
+
322
+ event = StreamEvent.from_redis_entry(event_id, entry_data)
323
+ events.append(event)
324
+
325
+ return events
326
+
327
+ except Exception as e:
328
+ logger.error(f"Failed to get recent events for {event_type}: {e}")
329
+ return []
330
+
331
+ def get_stream_info(self, event_type: str) -> dict[str, Any]:
332
+ """Get information about a stream.
333
+
334
+ Args:
335
+ event_type: Type of event stream
336
+
337
+ Returns:
338
+ Dictionary with stream info (length, first_entry, last_entry, etc.)
339
+ """
340
+ if not self.memory or not hasattr(self.memory, "_client") or not self.memory._client:
341
+ return {}
342
+
343
+ stream_key = self._get_stream_key(event_type)
344
+
345
+ try:
346
+ info = self.memory._client.xinfo_stream(stream_key)
347
+
348
+ # Decode bytes keys/values
349
+ decoded_info = {}
350
+ for key, value in info.items():
351
+ if isinstance(key, bytes):
352
+ key = key.decode("utf-8")
353
+ if isinstance(value, bytes):
354
+ value = value.decode("utf-8")
355
+ decoded_info[key] = value
356
+
357
+ return decoded_info
358
+
359
+ except Exception as e:
360
+ logger.debug(f"Failed to get stream info for {event_type}: {e}")
361
+ return {}
362
+
363
+ def delete_stream(self, event_type: str) -> bool:
364
+ """Delete an event stream.
365
+
366
+ Args:
367
+ event_type: Type of event stream to delete
368
+
369
+ Returns:
370
+ True if deleted, False otherwise
371
+ """
372
+ if not self.memory or not hasattr(self.memory, "_client") or not self.memory._client:
373
+ return False
374
+
375
+ stream_key = self._get_stream_key(event_type)
376
+
377
+ try:
378
+ result = self.memory._client.delete(stream_key)
379
+ return result > 0
380
+ except Exception as e:
381
+ logger.error(f"Failed to delete stream {event_type}: {e}")
382
+ return False
383
+
384
+ def trim_stream(self, event_type: str, max_length: int = 1000) -> int:
385
+ """Trim a stream to a maximum length.
386
+
387
+ Args:
388
+ event_type: Type of event stream
389
+ max_length: Maximum number of events to keep
390
+
391
+ Returns:
392
+ Number of events trimmed
393
+ """
394
+ if not self.memory or not hasattr(self.memory, "_client") or not self.memory._client:
395
+ return 0
396
+
397
+ stream_key = self._get_stream_key(event_type)
398
+
399
+ try:
400
+ # XTRIM: trim to approximate max length
401
+ trimmed = self.memory._client.xtrim(
402
+ stream_key,
403
+ maxlen=max_length,
404
+ approximate=True,
405
+ )
406
+ return trimmed
407
+ except Exception as e:
408
+ logger.error(f"Failed to trim stream {event_type}: {e}")
409
+ return 0