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,547 @@
1
+ """Standalone Dashboard Server - Reads Directly from Redis.
2
+
3
+ This version bypasses the telemetry API layer and reads directly from Redis.
4
+ Works with data populated by scripts/populate_redis_direct.py.
5
+
6
+ Zero external dependencies (uses Python stdlib only).
7
+
8
+ Copyright 2025 Smart-AI-Memory
9
+ Licensed under Fair Source License 0.9
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ from datetime import datetime
17
+ from http.server import BaseHTTPRequestHandler, HTTPServer
18
+ from pathlib import Path
19
+ from urllib.parse import parse_qs, urlparse
20
+
21
+ try:
22
+ import redis
23
+
24
+ REDIS_AVAILABLE = True
25
+ except ImportError:
26
+ REDIS_AVAILABLE = False
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class StandaloneDashboardHandler(BaseHTTPRequestHandler):
32
+ """HTTP handler that reads directly from Redis."""
33
+
34
+ # Class variable for Redis connection (shared across requests)
35
+ _redis_client = None
36
+
37
+ @classmethod
38
+ def get_redis(cls):
39
+ """Get or create Redis connection."""
40
+ if not REDIS_AVAILABLE:
41
+ return None
42
+
43
+ if cls._redis_client is None:
44
+ try:
45
+ cls._redis_client = redis.Redis(host="localhost", port=6379, decode_responses=False)
46
+ cls._redis_client.ping() # Test connection
47
+ except Exception as e:
48
+ logger.error(f"Failed to connect to Redis: {e}")
49
+ cls._redis_client = None
50
+
51
+ return cls._redis_client
52
+
53
+ def do_GET(self):
54
+ """Handle GET requests."""
55
+ parsed = urlparse(self.path)
56
+ path = parsed.path
57
+ query = parse_qs(parsed.query)
58
+
59
+ # Route requests
60
+ if path == "/" or path == "/index.html":
61
+ self.serve_file("index.html", "text/html")
62
+ elif path == "/static/style.css":
63
+ self.serve_file("style.css", "text/css")
64
+ elif path == "/static/app.js":
65
+ self.serve_file("app.js", "application/javascript")
66
+ elif path == "/api/health":
67
+ self.api_health()
68
+ elif path == "/api/agents":
69
+ self.api_agents()
70
+ elif path.startswith("/api/agents/"):
71
+ agent_id = path.split("/")[-1]
72
+ self.api_agent_detail(agent_id)
73
+ elif path == "/api/signals":
74
+ limit = int(query.get("limit", [50])[0])
75
+ self.api_signals(limit)
76
+ elif path == "/api/events":
77
+ event_type = query.get("event_type", [None])[0]
78
+ limit = int(query.get("limit", [100])[0])
79
+ self.api_events(event_type, limit)
80
+ elif path == "/api/approvals":
81
+ self.api_approvals()
82
+ elif path == "/api/feedback/workflows":
83
+ self.api_feedback_workflows()
84
+ elif path == "/api/feedback/underperforming":
85
+ threshold = float(query.get("threshold", [0.7])[0])
86
+ self.api_underperforming(threshold)
87
+ else:
88
+ self.send_error(404, "Not Found")
89
+
90
+ def do_POST(self):
91
+ """Handle POST requests."""
92
+ parsed = urlparse(self.path)
93
+ path = parsed.path
94
+
95
+ # Get request body
96
+ content_length = int(self.headers.get("Content-Length", 0))
97
+ body = self.rfile.read(content_length) if content_length > 0 else b"{}"
98
+ data = json.loads(body.decode("utf-8")) if body else {}
99
+
100
+ # Route requests
101
+ if "/approve" in path:
102
+ request_id = path.split("/")[-2]
103
+ self.api_approve(request_id, data.get("reason", "Approved via dashboard"))
104
+ elif "/reject" in path:
105
+ request_id = path.split("/")[-2]
106
+ self.api_reject(request_id, data.get("reason", "Rejected via dashboard"))
107
+ else:
108
+ self.send_error(404, "Not Found")
109
+
110
+ def serve_file(self, filename: str, content_type: str):
111
+ """Serve static file."""
112
+ try:
113
+ static_dir = Path(__file__).parent / "static"
114
+ file_path = static_dir / filename
115
+
116
+ if not file_path.exists():
117
+ self.send_error(404, f"File not found: {filename}")
118
+ return
119
+
120
+ content = file_path.read_bytes()
121
+
122
+ self.send_response(200)
123
+ self.send_header("Content-Type", content_type)
124
+ self.send_header("Content-Length", str(len(content)))
125
+ self.end_headers()
126
+ self.wfile.write(content)
127
+
128
+ except Exception as e:
129
+ logger.error(f"Failed to serve file {filename}: {e}")
130
+ self.send_error(500, str(e))
131
+
132
+ def send_json(self, data: dict | list, status: int = 200):
133
+ """Send JSON response."""
134
+ try:
135
+ content = json.dumps(data).encode("utf-8")
136
+
137
+ self.send_response(status)
138
+ self.send_header("Content-Type", "application/json")
139
+ self.send_header("Content-Length", str(len(content)))
140
+ self.send_header("Access-Control-Allow-Origin", "*") # CORS
141
+ self.end_headers()
142
+ self.wfile.write(content)
143
+
144
+ except Exception as e:
145
+ logger.error(f"Failed to send JSON: {e}")
146
+ self.send_error(500, str(e))
147
+
148
+ # ========================================================================
149
+ # API Endpoints - Read Directly from Redis
150
+ # ========================================================================
151
+
152
+ def api_health(self):
153
+ """System health endpoint."""
154
+ try:
155
+ r = self.get_redis()
156
+ has_redis = r is not None
157
+
158
+ if has_redis:
159
+ # Count keys directly
160
+ heartbeat_count = len(r.keys(b"heartbeat:*"))
161
+ approval_count = len(r.keys(b"approval:pending:*"))
162
+ else:
163
+ heartbeat_count = 0
164
+ approval_count = 0
165
+
166
+ self.send_json(
167
+ {
168
+ "status": "healthy" if has_redis else "degraded",
169
+ "redis_available": has_redis,
170
+ "active_agents": heartbeat_count,
171
+ "pending_approvals": approval_count,
172
+ "timestamp": datetime.utcnow().isoformat(),
173
+ }
174
+ )
175
+ except Exception as e:
176
+ self.send_json({"status": "error", "error": str(e)}, status=500)
177
+
178
+ def api_agents(self):
179
+ """List active agents."""
180
+ try:
181
+ r = self.get_redis()
182
+ if not r:
183
+ self.send_json([])
184
+ return
185
+
186
+ result = []
187
+ for key in r.keys(b"heartbeat:*"):
188
+ try:
189
+ data = r.get(key)
190
+ if data:
191
+ heartbeat = json.loads(data.decode("utf-8"))
192
+ result.append(
193
+ {
194
+ "agent_id": heartbeat.get("agent_id"),
195
+ "status": heartbeat.get("status"),
196
+ "last_seen": heartbeat.get("timestamp"),
197
+ "progress": heartbeat.get("progress", 0.0),
198
+ "current_task": heartbeat.get("current_task", "Unknown"),
199
+ }
200
+ )
201
+ except Exception as e:
202
+ logger.error(f"Failed to parse heartbeat {key}: {e}")
203
+
204
+ self.send_json(result)
205
+ except Exception as e:
206
+ logger.error(f"Failed to get agents: {e}")
207
+ self.send_json([], status=500)
208
+
209
+ def api_agent_detail(self, agent_id: str):
210
+ """Get specific agent details."""
211
+ try:
212
+ r = self.get_redis()
213
+ if not r:
214
+ self.send_json({"error": "Redis not available"}, status=503)
215
+ return
216
+
217
+ key = f"heartbeat:{agent_id}".encode()
218
+ data = r.get(key)
219
+
220
+ if not data:
221
+ self.send_json({"error": f"Agent {agent_id} not found"}, status=404)
222
+ return
223
+
224
+ heartbeat = json.loads(data.decode("utf-8"))
225
+ self.send_json(
226
+ {
227
+ "agent_id": heartbeat.get("agent_id"),
228
+ "status": heartbeat.get("status"),
229
+ "last_seen": heartbeat.get("timestamp"),
230
+ "progress": heartbeat.get("progress", 0.0),
231
+ "current_task": heartbeat.get("current_task"),
232
+ "metadata": heartbeat.get("metadata", {}),
233
+ }
234
+ )
235
+ except Exception as e:
236
+ self.send_json({"error": str(e)}, status=500)
237
+
238
+ def api_signals(self, limit: int):
239
+ """Get recent coordination signals."""
240
+ try:
241
+ r = self.get_redis()
242
+ if not r:
243
+ self.send_json([])
244
+ return
245
+
246
+ result = []
247
+ for key in r.keys(b"empathy:signal:*")[:limit]:
248
+ try:
249
+ data = r.get(key)
250
+ if data:
251
+ signal = json.loads(data.decode("utf-8"))
252
+ result.append(
253
+ {
254
+ "signal_type": signal.get("signal_type"),
255
+ "source_agent": signal.get("source_agent"),
256
+ "target_agent": signal.get("target_agent"),
257
+ "timestamp": signal.get("timestamp"),
258
+ "payload": signal.get("payload", {}),
259
+ }
260
+ )
261
+ except Exception as e:
262
+ logger.error(f"Failed to parse signal {key}: {e}")
263
+
264
+ # Sort by timestamp (newest first)
265
+ result.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
266
+ self.send_json(result[:limit])
267
+ except Exception as e:
268
+ logger.error(f"Failed to get signals: {e}")
269
+ self.send_json([])
270
+
271
+ def api_events(self, event_type: str | None, limit: int):
272
+ """Get recent events."""
273
+ try:
274
+ r = self.get_redis()
275
+ if not r:
276
+ self.send_json([])
277
+ return
278
+
279
+ result = []
280
+
281
+ # Get from streams
282
+ stream_patterns = [
283
+ b"stream:workflow_progress",
284
+ b"stream:agent_heartbeat",
285
+ b"stream:coordination_signal",
286
+ ]
287
+
288
+ for stream_key in stream_patterns:
289
+ try:
290
+ # Get last N entries from stream
291
+ entries = r.xrevrange(stream_key, count=limit)
292
+ for entry_id, fields in entries:
293
+ # Parse event structure: top-level fields + data payload
294
+ event_type = fields.get(b"event_type", b"unknown").decode("utf-8")
295
+ timestamp = fields.get(b"timestamp", b"").decode("utf-8")
296
+ source = fields.get(b"source", b"attune").decode("utf-8")
297
+
298
+ # Parse the data payload (JSON-encoded)
299
+ data = {}
300
+ if b"data" in fields:
301
+ try:
302
+ data = json.loads(fields[b"data"].decode("utf-8"))
303
+ except json.JSONDecodeError:
304
+ data = {}
305
+
306
+ result.append(
307
+ {
308
+ "event_id": entry_id.decode("utf-8") if isinstance(entry_id, bytes) else entry_id,
309
+ "event_type": event_type,
310
+ "timestamp": timestamp,
311
+ "data": data,
312
+ "source": source,
313
+ }
314
+ )
315
+ except Exception as e:
316
+ logger.debug(f"Stream {stream_key} not found or empty: {e}")
317
+
318
+ # Sort by timestamp
319
+ result.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
320
+ self.send_json(result[:limit])
321
+ except Exception as e:
322
+ logger.error(f"Failed to get events: {e}")
323
+ self.send_json([])
324
+
325
+ def api_approvals(self):
326
+ """Get pending approvals."""
327
+ try:
328
+ r = self.get_redis()
329
+ if not r:
330
+ self.send_json([])
331
+ return
332
+
333
+ result = []
334
+ for key in r.keys(b"approval_request:*"):
335
+ try:
336
+ data = r.get(key)
337
+ if data:
338
+ approval = json.loads(data.decode("utf-8"))
339
+ result.append(
340
+ {
341
+ "request_id": approval.get("request_id"),
342
+ "approval_type": approval.get("approval_type"),
343
+ "agent_id": approval.get("agent_id"),
344
+ "context": approval.get("context", {}),
345
+ "timestamp": approval.get("timestamp"),
346
+ "timeout_seconds": approval.get("timeout_seconds", 300),
347
+ }
348
+ )
349
+ except Exception as e:
350
+ logger.error(f"Failed to parse approval {key}: {e}")
351
+
352
+ self.send_json(result)
353
+ except Exception as e:
354
+ logger.error(f"Failed to get approvals: {e}")
355
+ self.send_json([])
356
+
357
+ def api_approve(self, request_id: str, reason: str):
358
+ """Approve request."""
359
+ try:
360
+ r = self.get_redis()
361
+ if not r:
362
+ self.send_json({"error": "Redis not available"}, status=503)
363
+ return
364
+
365
+ # Delete from pending
366
+ key = f"approval:pending:{request_id}".encode()
367
+ if r.delete(key):
368
+ self.send_json({"status": "approved", "request_id": request_id})
369
+ else:
370
+ self.send_json({"error": "Request not found"}, status=404)
371
+ except Exception as e:
372
+ self.send_json({"error": str(e)}, status=500)
373
+
374
+ def api_reject(self, request_id: str, reason: str):
375
+ """Reject request."""
376
+ try:
377
+ r = self.get_redis()
378
+ if not r:
379
+ self.send_json({"error": "Redis not available"}, status=503)
380
+ return
381
+
382
+ # Delete from pending
383
+ key = f"approval:pending:{request_id}".encode()
384
+ if r.delete(key):
385
+ self.send_json({"status": "rejected", "request_id": request_id})
386
+ else:
387
+ self.send_json({"error": "Request not found"}, status=404)
388
+ except Exception as e:
389
+ self.send_json({"error": str(e)}, status=500)
390
+
391
+ def api_feedback_workflows(self):
392
+ """Get workflow quality metrics."""
393
+ try:
394
+ r = self.get_redis()
395
+ if not r:
396
+ self.send_json([])
397
+ return
398
+
399
+ # Group feedback by workflow/stage/tier
400
+ feedback_groups = {}
401
+
402
+ for key in r.keys(b"feedback:*"):
403
+ try:
404
+ data = r.get(key)
405
+ if data:
406
+ feedback = json.loads(data.decode("utf-8"))
407
+ workflow = feedback.get("workflow_name")
408
+ stage = feedback.get("stage_name")
409
+ tier = feedback.get("tier")
410
+ quality = feedback.get("quality_score")
411
+
412
+ group_key = f"{workflow}/{stage}/{tier}"
413
+ if group_key not in feedback_groups:
414
+ feedback_groups[group_key] = {
415
+ "workflow_name": workflow,
416
+ "stage_name": stage,
417
+ "tier": tier,
418
+ "qualities": [],
419
+ }
420
+ feedback_groups[group_key]["qualities"].append(quality)
421
+ except Exception as e:
422
+ logger.error(f"Failed to parse feedback {key}: {e}")
423
+
424
+ # Calculate stats
425
+ result = []
426
+ for group_key, group in feedback_groups.items():
427
+ qualities = group["qualities"]
428
+ if qualities:
429
+ avg_quality = sum(qualities) / len(qualities)
430
+ result.append(
431
+ {
432
+ "workflow_name": group["workflow_name"],
433
+ "stage_name": group["stage_name"],
434
+ "tier": group["tier"],
435
+ "avg_quality": avg_quality,
436
+ "sample_count": len(qualities),
437
+ "trend": 0, # Simplified - no trend calculation
438
+ }
439
+ )
440
+
441
+ self.send_json(result)
442
+ except Exception as e:
443
+ logger.error(f"Failed to get quality metrics: {e}")
444
+ self.send_json([])
445
+
446
+ def api_underperforming(self, threshold: float):
447
+ """Get underperforming stages."""
448
+ try:
449
+ r = self.get_redis()
450
+ if not r:
451
+ self.send_json([])
452
+ return
453
+
454
+ # Get all feedback and group by workflow/stage
455
+ feedback_groups = {}
456
+
457
+ for key in r.keys(b"feedback:*"):
458
+ try:
459
+ data = r.get(key)
460
+ if data:
461
+ feedback = json.loads(data.decode("utf-8"))
462
+ workflow = feedback.get("workflow_name")
463
+ stage = feedback.get("stage_name")
464
+ quality = feedback.get("quality_score")
465
+
466
+ group_key = f"{workflow}/{stage}"
467
+ if group_key not in feedback_groups:
468
+ feedback_groups[group_key] = {
469
+ "workflow_name": workflow,
470
+ "stage_name": stage,
471
+ "qualities": [],
472
+ }
473
+ feedback_groups[group_key]["qualities"].append(quality)
474
+ except Exception as e:
475
+ logger.error(f"Failed to parse feedback {key}: {e}")
476
+
477
+ # Find underperforming stages
478
+ result = []
479
+ for group_key, group in feedback_groups.items():
480
+ qualities = group["qualities"]
481
+ if qualities:
482
+ avg_quality = sum(qualities) / len(qualities)
483
+ if avg_quality < threshold:
484
+ result.append(
485
+ {
486
+ "workflow_name": group["workflow_name"],
487
+ "stage_name": group["stage_name"],
488
+ "avg_quality": avg_quality,
489
+ "sample_count": len(qualities),
490
+ "min_quality": min(qualities),
491
+ "max_quality": max(qualities),
492
+ "trend": 0,
493
+ }
494
+ )
495
+
496
+ # Sort by quality (worst first)
497
+ result.sort(key=lambda x: x["avg_quality"])
498
+ self.send_json(result)
499
+ except Exception as e:
500
+ logger.error(f"Failed to get underperforming: {e}")
501
+ self.send_json([])
502
+
503
+ def log_message(self, format, *args):
504
+ """Suppress default logging."""
505
+ # Override to reduce noise - only log errors
506
+ if args[1][0] in ("4", "5"): # 4xx or 5xx errors
507
+ logger.warning(f"{self.address_string()} - {format % args}")
508
+
509
+
510
+ def run_standalone_dashboard(host: str = "127.0.0.1", port: int = 8000):
511
+ """Run standalone dashboard that reads directly from Redis.
512
+
513
+ This version bypasses the telemetry API layer and works with
514
+ data populated by scripts/populate_redis_direct.py.
515
+
516
+ Args:
517
+ host: Host to bind to (default: 127.0.0.1)
518
+ port: Port to bind to (default: 8000)
519
+
520
+ Example:
521
+ >>> from attune.dashboard.standalone_server import run_standalone_dashboard
522
+ >>> run_standalone_dashboard(host="0.0.0.0", port=8080)
523
+ """
524
+ if not REDIS_AVAILABLE:
525
+ print("āš ļø Warning: redis-py not installed. Install with: pip install redis")
526
+ print(" Dashboard will start but won't show data.")
527
+ print()
528
+
529
+ server = HTTPServer((host, port), StandaloneDashboardHandler)
530
+
531
+ print(f"šŸš€ Agent Coordination Dashboard (Standalone) running at http://{host}:{port}")
532
+ print(f"šŸ“Š Open in browser: http://{host}:{port}")
533
+ print()
534
+ print("šŸ’” This version reads directly from Redis")
535
+ print(" Populate data with: python scripts/populate_redis_direct.py")
536
+ print()
537
+ print("Press Ctrl+C to stop")
538
+
539
+ try:
540
+ server.serve_forever()
541
+ except KeyboardInterrupt:
542
+ print("\n\nšŸ›‘ Shutting down dashboard...")
543
+ server.shutdown()
544
+
545
+
546
+ if __name__ == "__main__":
547
+ run_standalone_dashboard()