devsper 2.1.6__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 (375) hide show
  1. devsper/__init__.py +14 -0
  2. devsper/agents/a2a/__init__.py +27 -0
  3. devsper/agents/a2a/client.py +126 -0
  4. devsper/agents/a2a/discovery.py +24 -0
  5. devsper/agents/a2a/server.py +128 -0
  6. devsper/agents/a2a/tool_adapter.py +68 -0
  7. devsper/agents/a2a/types.py +49 -0
  8. devsper/agents/agent.py +602 -0
  9. devsper/agents/critic.py +80 -0
  10. devsper/agents/message_bus.py +124 -0
  11. devsper/agents/roles.py +181 -0
  12. devsper/agents/run_agent.py +78 -0
  13. devsper/analytics/__init__.py +5 -0
  14. devsper/analytics/tool_analytics.py +78 -0
  15. devsper/audit/__init__.py +5 -0
  16. devsper/audit/logger.py +214 -0
  17. devsper/bus/__init__.py +29 -0
  18. devsper/bus/backends/__init__.py +5 -0
  19. devsper/bus/backends/base.py +38 -0
  20. devsper/bus/backends/memory.py +55 -0
  21. devsper/bus/backends/redis.py +146 -0
  22. devsper/bus/message.py +56 -0
  23. devsper/bus/schema_version.py +3 -0
  24. devsper/bus/topics.py +19 -0
  25. devsper/cache/__init__.py +6 -0
  26. devsper/cache/embedding_index.py +98 -0
  27. devsper/cache/hashing.py +24 -0
  28. devsper/cache/store.py +153 -0
  29. devsper/cache/task_cache.py +191 -0
  30. devsper/cli/__init__.py +6 -0
  31. devsper/cli/commands/reg.py +733 -0
  32. devsper/cli/github_oauth.py +157 -0
  33. devsper/cli/init.py +637 -0
  34. devsper/cli/main.py +2956 -0
  35. devsper/cli/run_progress.py +103 -0
  36. devsper/cli/ui/__init__.py +65 -0
  37. devsper/cli/ui/components.py +94 -0
  38. devsper/cli/ui/errors.py +104 -0
  39. devsper/cli/ui/logging.py +120 -0
  40. devsper/cli/ui/onboarding.py +102 -0
  41. devsper/cli/ui/progress.py +43 -0
  42. devsper/cli/ui/run_view.py +308 -0
  43. devsper/cli/ui/theme.py +40 -0
  44. devsper/cluster/__init__.py +29 -0
  45. devsper/cluster/election.py +84 -0
  46. devsper/cluster/local.py +97 -0
  47. devsper/cluster/node_info.py +77 -0
  48. devsper/cluster/registry.py +71 -0
  49. devsper/cluster/router.py +117 -0
  50. devsper/cluster/state_backend.py +105 -0
  51. devsper/compliance/__init__.py +5 -0
  52. devsper/compliance/pii.py +147 -0
  53. devsper/config/__init__.py +52 -0
  54. devsper/config/config_loader.py +121 -0
  55. devsper/config/defaults.py +77 -0
  56. devsper/config/resolver.py +342 -0
  57. devsper/config/schema.py +237 -0
  58. devsper/credentials/__init__.py +19 -0
  59. devsper/credentials/cli.py +197 -0
  60. devsper/credentials/migration.py +124 -0
  61. devsper/credentials/store.py +142 -0
  62. devsper/dashboard/__init__.py +9 -0
  63. devsper/dashboard/dashboard.py +87 -0
  64. devsper/dev/__init__.py +25 -0
  65. devsper/dev/builder.py +195 -0
  66. devsper/dev/debugger.py +95 -0
  67. devsper/dev/repo_index.py +138 -0
  68. devsper/dev/sandbox.py +203 -0
  69. devsper/dev/scaffold.py +122 -0
  70. devsper/embeddings/__init__.py +5 -0
  71. devsper/embeddings/service.py +36 -0
  72. devsper/explainability/__init__.py +14 -0
  73. devsper/explainability/decision_tree.py +104 -0
  74. devsper/explainability/rationale.py +38 -0
  75. devsper/explainability/simulation.py +56 -0
  76. devsper/hitl/__init__.py +13 -0
  77. devsper/hitl/approval.py +160 -0
  78. devsper/hitl/escalation.py +95 -0
  79. devsper/intelligence/__init__.py +9 -0
  80. devsper/intelligence/adaptation.py +88 -0
  81. devsper/intelligence/analysis/__init__.py +19 -0
  82. devsper/intelligence/analysis/analyzer.py +71 -0
  83. devsper/intelligence/analysis/cost_estimator.py +66 -0
  84. devsper/intelligence/analysis/formatter.py +103 -0
  85. devsper/intelligence/analysis/run_report.py +402 -0
  86. devsper/intelligence/learning_engine.py +92 -0
  87. devsper/intelligence/strategies/__init__.py +23 -0
  88. devsper/intelligence/strategies/base.py +14 -0
  89. devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
  90. devsper/intelligence/strategies/data_science_strategy.py +33 -0
  91. devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
  92. devsper/intelligence/strategies/experiment_strategy.py +33 -0
  93. devsper/intelligence/strategies/research_strategy.py +34 -0
  94. devsper/intelligence/strategy_selector.py +84 -0
  95. devsper/intelligence/synthesis.py +132 -0
  96. devsper/intelligence/task_optimizer.py +92 -0
  97. devsper/knowledge/__init__.py +5 -0
  98. devsper/knowledge/extractor.py +204 -0
  99. devsper/knowledge/knowledge_graph.py +184 -0
  100. devsper/knowledge/query.py +285 -0
  101. devsper/memory/__init__.py +35 -0
  102. devsper/memory/consolidation.py +138 -0
  103. devsper/memory/embeddings.py +60 -0
  104. devsper/memory/memory_index.py +97 -0
  105. devsper/memory/memory_router.py +62 -0
  106. devsper/memory/memory_store.py +221 -0
  107. devsper/memory/memory_types.py +54 -0
  108. devsper/memory/namespaces.py +45 -0
  109. devsper/memory/scoring.py +77 -0
  110. devsper/memory/summarizer.py +52 -0
  111. devsper/nodes/__init__.py +5 -0
  112. devsper/nodes/controller.py +449 -0
  113. devsper/nodes/rpc.py +127 -0
  114. devsper/nodes/single.py +161 -0
  115. devsper/nodes/worker.py +506 -0
  116. devsper/orchestration/__init__.py +19 -0
  117. devsper/orchestration/meta_planner.py +239 -0
  118. devsper/orchestration/priority_queue.py +61 -0
  119. devsper/plugins/__init__.py +19 -0
  120. devsper/plugins/marketplace/__init__.py +0 -0
  121. devsper/plugins/plugin_loader.py +70 -0
  122. devsper/plugins/plugin_registry.py +34 -0
  123. devsper/plugins/registry.py +83 -0
  124. devsper/protocols/__init__.py +6 -0
  125. devsper/providers/__init__.py +17 -0
  126. devsper/providers/anthropic.py +84 -0
  127. devsper/providers/base.py +75 -0
  128. devsper/providers/complexity_router.py +94 -0
  129. devsper/providers/gemini.py +36 -0
  130. devsper/providers/github.py +180 -0
  131. devsper/providers/model_router.py +40 -0
  132. devsper/providers/openai.py +105 -0
  133. devsper/providers/router/__init__.py +21 -0
  134. devsper/providers/router/backends/__init__.py +19 -0
  135. devsper/providers/router/backends/anthropic_backend.py +111 -0
  136. devsper/providers/router/backends/custom_backend.py +138 -0
  137. devsper/providers/router/backends/gemini_backend.py +89 -0
  138. devsper/providers/router/backends/github_backend.py +165 -0
  139. devsper/providers/router/backends/ollama_backend.py +104 -0
  140. devsper/providers/router/backends/openai_backend.py +142 -0
  141. devsper/providers/router/backends/vllm_backend.py +35 -0
  142. devsper/providers/router/base.py +60 -0
  143. devsper/providers/router/factory.py +92 -0
  144. devsper/providers/router/legacy.py +101 -0
  145. devsper/providers/router/router.py +135 -0
  146. devsper/reasoning/__init__.py +12 -0
  147. devsper/reasoning/graph.py +59 -0
  148. devsper/reasoning/nodes.py +20 -0
  149. devsper/reasoning/store.py +67 -0
  150. devsper/runtime/__init__.py +12 -0
  151. devsper/runtime/health.py +88 -0
  152. devsper/runtime/replay.py +53 -0
  153. devsper/runtime/replay_engine.py +142 -0
  154. devsper/runtime/run_history.py +204 -0
  155. devsper/runtime/telemetry.py +116 -0
  156. devsper/runtime/visualize.py +58 -0
  157. devsper/sandbox/__init__.py +13 -0
  158. devsper/sandbox/sandbox.py +161 -0
  159. devsper/swarm/checkpointer.py +65 -0
  160. devsper/swarm/executor.py +558 -0
  161. devsper/swarm/map_reduce.py +44 -0
  162. devsper/swarm/planner.py +197 -0
  163. devsper/swarm/prefetcher.py +91 -0
  164. devsper/swarm/scheduler.py +153 -0
  165. devsper/swarm/speculation.py +47 -0
  166. devsper/swarm/swarm.py +562 -0
  167. devsper/tools/__init__.py +33 -0
  168. devsper/tools/base.py +29 -0
  169. devsper/tools/code_intelligence/__init__.py +13 -0
  170. devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
  171. devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
  172. devsper/tools/code_intelligence/codebase_indexer.py +71 -0
  173. devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
  174. devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
  175. devsper/tools/code_intelligence/large_function_detector.py +68 -0
  176. devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
  177. devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
  178. devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
  179. devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
  180. devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
  181. devsper/tools/coding/__init__.py +12 -0
  182. devsper/tools/coding/analyze_code_complexity.py +48 -0
  183. devsper/tools/coding/dependency_analyzer.py +42 -0
  184. devsper/tools/coding/extract_functions.py +38 -0
  185. devsper/tools/coding/format_python.py +50 -0
  186. devsper/tools/coding/generate_docstrings.py +40 -0
  187. devsper/tools/coding/generate_unit_tests.py +42 -0
  188. devsper/tools/coding/lint_python.py +51 -0
  189. devsper/tools/coding/refactor_function.py +41 -0
  190. devsper/tools/coding/repo_structure_map.py +54 -0
  191. devsper/tools/coding/run_python.py +53 -0
  192. devsper/tools/data/__init__.py +12 -0
  193. devsper/tools/data/column_type_detection.py +64 -0
  194. devsper/tools/data/csv_summary.py +52 -0
  195. devsper/tools/data/dataframe_filter.py +51 -0
  196. devsper/tools/data/dataframe_groupby.py +47 -0
  197. devsper/tools/data/dataframe_stats.py +38 -0
  198. devsper/tools/data/dataset_sampling.py +55 -0
  199. devsper/tools/data/dataset_schema.py +45 -0
  200. devsper/tools/data/json_pretty_print.py +37 -0
  201. devsper/tools/data/json_query.py +46 -0
  202. devsper/tools/data/missing_value_report.py +47 -0
  203. devsper/tools/data_science/__init__.py +13 -0
  204. devsper/tools/data_science/correlation_heatmap.py +72 -0
  205. devsper/tools/data_science/dataset_bias_detector.py +49 -0
  206. devsper/tools/data_science/dataset_distribution_report.py +64 -0
  207. devsper/tools/data_science/dataset_drift_detector.py +64 -0
  208. devsper/tools/data_science/dataset_outlier_detector.py +65 -0
  209. devsper/tools/data_science/dataset_profile.py +76 -0
  210. devsper/tools/data_science/distributed_dataset_processor.py +54 -0
  211. devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
  212. devsper/tools/data_science/feature_importance_estimator.py +82 -0
  213. devsper/tools/data_science/model_input_validator.py +59 -0
  214. devsper/tools/data_science/time_series_analyzer.py +57 -0
  215. devsper/tools/documents/__init__.py +11 -0
  216. devsper/tools/documents/_docproc.py +56 -0
  217. devsper/tools/documents/document_to_markdown.py +29 -0
  218. devsper/tools/documents/extract_document_images.py +39 -0
  219. devsper/tools/documents/extract_document_text.py +29 -0
  220. devsper/tools/documents/extract_equations.py +36 -0
  221. devsper/tools/documents/extract_tables.py +47 -0
  222. devsper/tools/documents/summarize_document.py +42 -0
  223. devsper/tools/documents/write_latex_document.py +133 -0
  224. devsper/tools/documents/write_markdown_document.py +89 -0
  225. devsper/tools/documents/write_word_document.py +149 -0
  226. devsper/tools/experiments/__init__.py +13 -0
  227. devsper/tools/experiments/bootstrap_estimator.py +54 -0
  228. devsper/tools/experiments/experiment_report_generator.py +50 -0
  229. devsper/tools/experiments/experiment_tracker.py +36 -0
  230. devsper/tools/experiments/grid_search_runner.py +50 -0
  231. devsper/tools/experiments/model_benchmark_runner.py +45 -0
  232. devsper/tools/experiments/monte_carlo_experiment.py +38 -0
  233. devsper/tools/experiments/parameter_sweep_runner.py +51 -0
  234. devsper/tools/experiments/result_comparator.py +58 -0
  235. devsper/tools/experiments/simulation_runner.py +43 -0
  236. devsper/tools/experiments/statistical_significance_test.py +56 -0
  237. devsper/tools/experiments/swarm_map_reduce.py +42 -0
  238. devsper/tools/filesystem/__init__.py +12 -0
  239. devsper/tools/filesystem/append_file.py +42 -0
  240. devsper/tools/filesystem/file_hash.py +40 -0
  241. devsper/tools/filesystem/file_line_count.py +36 -0
  242. devsper/tools/filesystem/file_metadata.py +38 -0
  243. devsper/tools/filesystem/file_preview.py +55 -0
  244. devsper/tools/filesystem/find_large_files.py +50 -0
  245. devsper/tools/filesystem/list_directory.py +39 -0
  246. devsper/tools/filesystem/read_file.py +35 -0
  247. devsper/tools/filesystem/search_files.py +60 -0
  248. devsper/tools/filesystem/write_file.py +41 -0
  249. devsper/tools/flagship/__init__.py +15 -0
  250. devsper/tools/flagship/distributed_document_analysis.py +77 -0
  251. devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
  252. devsper/tools/flagship/repository_semantic_map.py +99 -0
  253. devsper/tools/flagship/research_graph_builder.py +111 -0
  254. devsper/tools/flagship/swarm_experiment_runner.py +86 -0
  255. devsper/tools/knowledge/__init__.py +10 -0
  256. devsper/tools/knowledge/citation_graph_builder.py +69 -0
  257. devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
  258. devsper/tools/knowledge/corpus_builder.py +66 -0
  259. devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
  260. devsper/tools/knowledge/document_corpus_summary.py +68 -0
  261. devsper/tools/knowledge/document_topic_extractor.py +58 -0
  262. devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
  263. devsper/tools/knowledge/timeline_extractor.py +59 -0
  264. devsper/tools/math/__init__.py +12 -0
  265. devsper/tools/math/calculate_expression.py +52 -0
  266. devsper/tools/math/correlation.py +44 -0
  267. devsper/tools/math/distribution_summary.py +39 -0
  268. devsper/tools/math/histogram.py +53 -0
  269. devsper/tools/math/linear_regression.py +47 -0
  270. devsper/tools/math/matrix_multiply.py +38 -0
  271. devsper/tools/math/mean_std.py +35 -0
  272. devsper/tools/math/monte_carlo_simulation.py +43 -0
  273. devsper/tools/math/polynomial_fit.py +40 -0
  274. devsper/tools/math/random_sample.py +36 -0
  275. devsper/tools/mcp/__init__.py +23 -0
  276. devsper/tools/mcp/adapter.py +53 -0
  277. devsper/tools/mcp/client.py +235 -0
  278. devsper/tools/mcp/discovery.py +53 -0
  279. devsper/tools/memory/__init__.py +16 -0
  280. devsper/tools/memory/delete_memory.py +25 -0
  281. devsper/tools/memory/list_memory.py +34 -0
  282. devsper/tools/memory/search_memory.py +36 -0
  283. devsper/tools/memory/store_memory.py +47 -0
  284. devsper/tools/memory/summarize_memory.py +41 -0
  285. devsper/tools/memory/tag_memory.py +47 -0
  286. devsper/tools/pipelines.py +92 -0
  287. devsper/tools/registry.py +39 -0
  288. devsper/tools/research/__init__.py +12 -0
  289. devsper/tools/research/arxiv_download.py +55 -0
  290. devsper/tools/research/arxiv_search.py +58 -0
  291. devsper/tools/research/citation_extractor.py +35 -0
  292. devsper/tools/research/duckduckgo_search.py +42 -0
  293. devsper/tools/research/paper_metadata_extractor.py +45 -0
  294. devsper/tools/research/paper_summarizer.py +41 -0
  295. devsper/tools/research/research_question_generator.py +39 -0
  296. devsper/tools/research/topic_cluster.py +46 -0
  297. devsper/tools/research/web_search.py +47 -0
  298. devsper/tools/research/wikipedia_lookup.py +50 -0
  299. devsper/tools/research_advanced/__init__.py +14 -0
  300. devsper/tools/research_advanced/citation_context_extractor.py +60 -0
  301. devsper/tools/research_advanced/literature_review_generator.py +79 -0
  302. devsper/tools/research_advanced/methodology_extractor.py +58 -0
  303. devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
  304. devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
  305. devsper/tools/research_advanced/paper_method_comparator.py +62 -0
  306. devsper/tools/research_advanced/paper_similarity_search.py +69 -0
  307. devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
  308. devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
  309. devsper/tools/research_advanced/research_gap_finder.py +71 -0
  310. devsper/tools/research_advanced/research_topic_mapper.py +69 -0
  311. devsper/tools/research_advanced/swarm_literature_review.py +58 -0
  312. devsper/tools/scoring/__init__.py +52 -0
  313. devsper/tools/scoring/report.py +44 -0
  314. devsper/tools/scoring/scorer.py +39 -0
  315. devsper/tools/scoring/selector.py +61 -0
  316. devsper/tools/scoring/store.py +267 -0
  317. devsper/tools/selector.py +130 -0
  318. devsper/tools/system/__init__.py +12 -0
  319. devsper/tools/system/cpu_usage.py +22 -0
  320. devsper/tools/system/disk_usage.py +35 -0
  321. devsper/tools/system/environment_variables.py +29 -0
  322. devsper/tools/system/memory_usage.py +23 -0
  323. devsper/tools/system/pip_install.py +44 -0
  324. devsper/tools/system/pip_search.py +29 -0
  325. devsper/tools/system/process_list.py +34 -0
  326. devsper/tools/system/python_package_list.py +40 -0
  327. devsper/tools/system/run_shell_command.py +51 -0
  328. devsper/tools/system/system_info.py +26 -0
  329. devsper/tools/tool_runner.py +122 -0
  330. devsper/tui/__init__.py +5 -0
  331. devsper/tui/activity_feed_view.py +73 -0
  332. devsper/tui/adaptive_tasks_view.py +75 -0
  333. devsper/tui/agent_role_view.py +35 -0
  334. devsper/tui/app.py +395 -0
  335. devsper/tui/dashboard_screen.py +290 -0
  336. devsper/tui/dev_view.py +99 -0
  337. devsper/tui/inject_screen.py +73 -0
  338. devsper/tui/knowledge_graph_view.py +46 -0
  339. devsper/tui/layout.py +43 -0
  340. devsper/tui/logs_view.py +83 -0
  341. devsper/tui/memory_view.py +58 -0
  342. devsper/tui/performance_view.py +33 -0
  343. devsper/tui/reasoning_graph_view.py +39 -0
  344. devsper/tui/results_view.py +139 -0
  345. devsper/tui/swarm_view.py +37 -0
  346. devsper/tui/task_detail_screen.py +55 -0
  347. devsper/tui/task_view.py +103 -0
  348. devsper/types/event.py +97 -0
  349. devsper/types/exceptions.py +21 -0
  350. devsper/types/swarm.py +41 -0
  351. devsper/types/task.py +80 -0
  352. devsper/upgrade/__init__.py +21 -0
  353. devsper/upgrade/changelog.py +124 -0
  354. devsper/upgrade/cli.py +145 -0
  355. devsper/upgrade/installer.py +103 -0
  356. devsper/upgrade/notifier.py +52 -0
  357. devsper/upgrade/version_check.py +121 -0
  358. devsper/utils/event_logger.py +88 -0
  359. devsper/utils/http.py +43 -0
  360. devsper/utils/models.py +54 -0
  361. devsper/visualization/__init__.py +5 -0
  362. devsper/visualization/dag_export.py +67 -0
  363. devsper/workflow/__init__.py +18 -0
  364. devsper/workflow/conditions.py +157 -0
  365. devsper/workflow/context.py +108 -0
  366. devsper/workflow/loader.py +156 -0
  367. devsper/workflow/resolver.py +109 -0
  368. devsper/workflow/runner.py +562 -0
  369. devsper/workflow/schema.py +63 -0
  370. devsper/workflow/validator.py +128 -0
  371. devsper-2.1.6.dist-info/METADATA +346 -0
  372. devsper-2.1.6.dist-info/RECORD +375 -0
  373. devsper-2.1.6.dist-info/WHEEL +4 -0
  374. devsper-2.1.6.dist-info/entry_points.txt +3 -0
  375. devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
@@ -0,0 +1,558 @@
1
+ """
2
+ Executor: runtime engine that runs tasks via agents while respecting scheduler dependencies.
3
+
4
+ Lifecycle: EXECUTOR_STARTED → (agent/task events per task) → EXECUTOR_FINISHED.
5
+ Uses asyncio and a worker pool (Semaphore) to run up to worker_count tasks concurrently.
6
+ Supports speculative execution and task cache lookup.
7
+
8
+ v1.6: Semantic task cache, model complexity routing, streaming DAG unblocking.
9
+ v1.7: Critic loop, speculative prefetching.
10
+ v1.9: Stateless — no task state stored in executor; all state in Scheduler. Publishes to bus.
11
+ """
12
+
13
+ import os
14
+ import asyncio
15
+ import threading
16
+ from datetime import datetime, timezone
17
+ from typing import TYPE_CHECKING
18
+
19
+ from devsper.types.task import Task, TaskStatus
20
+ from devsper.types.event import Event, events
21
+ from devsper.utils.event_logger import EventLog
22
+
23
+ from devsper.agents.agent import Agent
24
+ from devsper.swarm.scheduler import Scheduler
25
+ from devsper.swarm.planner import Planner
26
+ from devsper.intelligence.adaptation import create_alternative_subtasks_for_failed
27
+
28
+ if TYPE_CHECKING:
29
+ from devsper.agents.critic import CriticAgent
30
+ from devsper.swarm.prefetcher import TaskPrefetcher
31
+
32
+
33
+ def _get_tools_for_task(task: Task) -> list:
34
+ """Get tools that would be selected for this task (for complexity routing)."""
35
+ try:
36
+ from devsper.tools.selector import get_tools_for_task as _get_tools
37
+ from devsper.tools.scoring import get_default_score_store
38
+ score_store = get_default_score_store()
39
+ except Exception:
40
+ score_store = None
41
+ return _get_tools(
42
+ task.description or "",
43
+ role=getattr(task, "role", None),
44
+ score_store=score_store,
45
+ )
46
+
47
+
48
+ class Executor:
49
+ """Executes tasks using an agent, respecting the scheduler DAG and worker limit."""
50
+
51
+ def __init__(
52
+ self,
53
+ scheduler: Scheduler,
54
+ agent: Agent,
55
+ worker_count: int = 4,
56
+ event_log: EventLog | None = None,
57
+ planner: Planner | None = None,
58
+ adaptive: bool = False,
59
+ speculative_execution: bool = False,
60
+ task_cache: object = None,
61
+ pause_event: threading.Event | None = None,
62
+ semantic_cache: object = None,
63
+ complexity_router: object = None,
64
+ models_config: object = None,
65
+ streaming_dag: bool = True,
66
+ critic_agent: "CriticAgent | None" = None,
67
+ critic_enabled: bool = False,
68
+ critic_roles: list[str] | None = None,
69
+ fast_model: str = "mock",
70
+ prefetcher: "TaskPrefetcher | None" = None,
71
+ bus: object = None,
72
+ checkpointer: object = None,
73
+ sandbox_config: object = None,
74
+ audit_logger: object = None,
75
+ hitl_enabled: bool = False,
76
+ hitl_escalation_checker: object = None,
77
+ hitl_approval_store: object = None,
78
+ hitl_notifier: object = None,
79
+ hitl_resolver: object = None,
80
+ ) -> None:
81
+ self.scheduler = scheduler
82
+ self.agent = agent
83
+ self.worker_count = worker_count
84
+ self.event_log = event_log or EventLog()
85
+ self.planner = planner
86
+ self.adaptive = adaptive
87
+ self.speculative_execution = speculative_execution
88
+ self.task_cache = task_cache
89
+ self.pause_event = pause_event
90
+ self.semantic_cache = semantic_cache
91
+ self.complexity_router = complexity_router
92
+ self.models_config = models_config or getattr(agent, "model_name", "mock")
93
+ self.streaming_dag = streaming_dag
94
+ self.critic_agent = critic_agent
95
+ self.critic_enabled = critic_enabled
96
+ self.critic_roles = critic_roles or []
97
+ self.fast_model = fast_model
98
+ self.prefetcher = prefetcher
99
+ self.bus = bus
100
+ self.checkpointer = checkpointer
101
+ self.sandbox_config = sandbox_config
102
+ self.audit_logger = audit_logger
103
+ self.hitl_enabled = hitl_enabled
104
+ self.hitl_escalation_checker = hitl_escalation_checker
105
+ self.hitl_approval_store = hitl_approval_store
106
+ self.hitl_notifier = hitl_notifier
107
+ self.hitl_resolver = hitl_resolver
108
+
109
+ def run_sync(self) -> None:
110
+ """Run the execution loop to completion (synchronous entry point)."""
111
+ asyncio.run(self.run())
112
+
113
+ def _semantic_cache_disabled(self) -> bool:
114
+ return os.environ.get("DEVSPER_DISABLE_SEMANTIC_CACHE", "").strip() == "1"
115
+
116
+ def _get_cached_result(self, task: Task) -> tuple[str | None, dict | None]:
117
+ """
118
+ Return (cached_result, hit_info). hit_info is None or {similarity, original_description}
119
+ for semantic hits. Uses semantic cache first if enabled, then exact task_cache.
120
+ """
121
+ if self.semantic_cache is not None and not self._semantic_cache_disabled():
122
+ try:
123
+ hit = self.semantic_cache.lookup(task.description or "")
124
+ if hit is not None:
125
+ return (
126
+ hit.result,
127
+ {
128
+ "similarity": hit.similarity,
129
+ "original_description": hit.original_description,
130
+ },
131
+ )
132
+ except Exception:
133
+ pass
134
+ self._emit(events.TASK_CACHE_MISS, {"task_id": task.id})
135
+ if self.task_cache is None:
136
+ return (None, None)
137
+ try:
138
+ out = self.task_cache.get(task)
139
+ return (out, None) if out is not None else (None, None)
140
+ except Exception:
141
+ return (None, None)
142
+
143
+ def _set_cached_result(self, task: Task, result: str) -> None:
144
+ """Store result in exact and/or semantic cache."""
145
+ if self.task_cache is not None:
146
+ try:
147
+ self.task_cache.set(task, result)
148
+ except Exception:
149
+ pass
150
+ if self.semantic_cache is not None and not self._semantic_cache_disabled():
151
+ try:
152
+ self.semantic_cache.store_result(
153
+ task.description or "",
154
+ result,
155
+ getattr(task, "role", None) or "general",
156
+ )
157
+ except Exception:
158
+ pass
159
+
160
+ def _model_for_task(self, task: Task) -> str:
161
+ """Return model name for this task (complexity routing or agent default)."""
162
+ if self.complexity_router is None or self.models_config is None:
163
+ return getattr(self.agent, "model_name", "mock")
164
+ tools = _get_tools_for_task(task)
165
+ tier = self.complexity_router.classify(task, tools)
166
+ model = self.complexity_router.select_model(tier, self.models_config)
167
+ self._emit(
168
+ events.TASK_MODEL_SELECTED,
169
+ {"task_id": task.id, "tier": tier, "model": model},
170
+ )
171
+ return model
172
+
173
+ def _publish_bus(self, topic: str, payload: dict) -> None:
174
+ """Publish to bus if configured. Fire-and-forget."""
175
+ if self.bus is None:
176
+ return
177
+ try:
178
+ from devsper.bus.message import create_bus_message
179
+ run_id = getattr(self.scheduler, "run_id", "") or ""
180
+ msg = create_bus_message(topic=topic, payload=payload, run_id=run_id)
181
+ try:
182
+ loop = asyncio.get_running_loop()
183
+ loop.create_task(self.bus.publish(msg))
184
+ except RuntimeError:
185
+ asyncio.run(self.bus.publish(msg))
186
+ except Exception:
187
+ pass
188
+
189
+ async def _execute_task(self, task: Task, is_speculative: bool) -> str:
190
+ """Run one task (cache lookup, agent run, cache store). Returns task.id. No state stored on self."""
191
+ loop = asyncio.get_running_loop()
192
+ cached, hit_info = self._get_cached_result(task)
193
+ if cached is not None:
194
+ if not is_speculative:
195
+ self.scheduler.mark_completed(task.id, cached)
196
+ self.scheduler.confirm_speculative_for(task.id)
197
+ if hit_info:
198
+ self._emit(
199
+ events.TASK_CACHE_HIT,
200
+ {
201
+ "task_id": task.id,
202
+ "similarity": hit_info["similarity"],
203
+ "original_description": hit_info["original_description"],
204
+ },
205
+ )
206
+ return task.id
207
+
208
+ self._publish_bus("task.started", task.to_dict())
209
+ if self.audit_logger is not None and self.scheduler is not None:
210
+ try:
211
+ from devsper.audit.logger import make_audit_record
212
+ run_id = getattr(self.scheduler, "run_id", "") or ""
213
+ rec = make_audit_record(
214
+ run_id=run_id,
215
+ task_id=task.id,
216
+ event_type="TASK_STARTED",
217
+ actor=run_id,
218
+ resource=getattr(task, "role", "") or "agent",
219
+ input_text=task.description or "",
220
+ output_text="",
221
+ )
222
+ self.audit_logger.log(rec)
223
+ except Exception:
224
+ pass
225
+
226
+ prefetch_result = None
227
+ if self.prefetcher:
228
+ prefetch_result = self.prefetcher.consume(task.id)
229
+ if prefetch_result is not None:
230
+ age_seconds = (
231
+ datetime.now(timezone.utc) - prefetch_result.computed_at
232
+ ).total_seconds()
233
+ self._emit(
234
+ events.PREFETCH_HIT,
235
+ {"task_id": task.id, "age_seconds": round(age_seconds, 2)},
236
+ )
237
+ else:
238
+ self._emit(
239
+ events.PREFETCH_MISS,
240
+ {"task_id": task.id, "reason": "stale_or_missing"},
241
+ )
242
+
243
+ model_override = None
244
+ if self.complexity_router and self.models_config:
245
+ model_override = self._model_for_task(task)
246
+ try:
247
+ use_sandbox = (
248
+ self.sandbox_config is not None
249
+ and getattr(self.sandbox_config, "enabled", False)
250
+ )
251
+ if use_sandbox:
252
+ from devsper.sandbox.sandbox import AgentSandbox, get_quota_for_role
253
+ request = self.agent.build_request(
254
+ task, model_override=model_override, prefetch_result=prefetch_result
255
+ )
256
+ quota = get_quota_for_role(self.sandbox_config, getattr(task, "role", None))
257
+ sandbox = AgentSandbox(self.agent)
258
+ response = await loop.run_in_executor(
259
+ None,
260
+ lambda: sandbox.run(request, quota),
261
+ )
262
+ self.agent.apply_response(task, response)
263
+ else:
264
+ await loop.run_in_executor(
265
+ None,
266
+ lambda t=task, m=model_override, p=prefetch_result: self.agent.run_task(
267
+ t, model_override=m, prefetch_result=p
268
+ ),
269
+ )
270
+ except Exception as err:
271
+ if self.audit_logger is not None and self.scheduler is not None:
272
+ try:
273
+ from devsper.audit.logger import make_audit_record
274
+ run_id = getattr(self.scheduler, "run_id", "") or ""
275
+ rec = make_audit_record(
276
+ run_id=run_id,
277
+ task_id=task.id,
278
+ event_type="TASK_FAILED",
279
+ actor=run_id,
280
+ resource="",
281
+ input_text=task.description or "",
282
+ output_text=str(err),
283
+ success=False,
284
+ )
285
+ self.audit_logger.log(rec)
286
+ except Exception:
287
+ pass
288
+ self.scheduler.mark_failed(task.id, str(err))
289
+ if is_speculative:
290
+ self.scheduler.discard_speculative_for(task.id)
291
+ else:
292
+ self._emit(
293
+ events.TASK_FAILED,
294
+ {"task_id": task.id, "error": str(err)},
295
+ )
296
+ self._publish_bus("task.failed", {"task_id": task.id, "error": str(err)})
297
+ if self.adaptive and self.planner:
298
+ alt = create_alternative_subtasks_for_failed(
299
+ task, self.planner, self.scheduler
300
+ )
301
+ if alt:
302
+ self.scheduler.add_tasks(alt)
303
+ return task.id
304
+
305
+ result = task.result or ""
306
+ self._set_cached_result(task, result)
307
+
308
+ last_critic_score: float | None = None
309
+ # v1.7: critic loop — only for non-speculative, eligible roles, not already retried
310
+ role = getattr(task, "role", None) or ""
311
+ retry_count = getattr(task, "retry_count", 0)
312
+ if (
313
+ not is_speculative
314
+ and self.critic_enabled
315
+ and self.critic_agent
316
+ and role in self.critic_roles
317
+ and retry_count < 1
318
+ ):
319
+ from devsper.agents.critic import CriticAgent
320
+
321
+ critique = await self.critic_agent.critique(
322
+ task, result, model=self.fast_model
323
+ )
324
+ last_critic_score = critique.score
325
+ self._emit(
326
+ events.TASK_CRITIQUED,
327
+ {
328
+ "task_id": task.id,
329
+ "score": critique.score,
330
+ "issues": critique.issues,
331
+ "retry_requested": critique.retry,
332
+ },
333
+ )
334
+ if critique.retry and retry_count < 1:
335
+ retry_prompt = await self.critic_agent.get_retry_prompt(
336
+ task, result, critique
337
+ )
338
+ task.description = retry_prompt
339
+ task.retry_count = retry_count + 1
340
+ task.result = None
341
+ task.status = TaskStatus.PENDING
342
+ return await self._execute_task(task, is_speculative)
343
+
344
+ # v2.1: HITL — if escalation triggered, create approval request and wait
345
+ if not is_speculative and self.hitl_enabled and self.hitl_escalation_checker:
346
+ from devsper.agents.agent import AgentResponse
347
+ from devsper.explainability.decision_tree import DecisionRecord
348
+ from devsper.hitl.escalation import EscalationTrigger
349
+ from devsper.hitl.approval import ApprovalRequest, ApprovalStore
350
+ from datetime import timedelta
351
+
352
+ fake_response = AgentResponse(
353
+ task_id=task.id,
354
+ result=result,
355
+ tools_called=getattr(task, "tools_called", []) or [],
356
+ broadcasts=[],
357
+ tokens_used=None,
358
+ duration_seconds=0.0,
359
+ error=None,
360
+ success=True,
361
+ )
362
+ fake_decision = DecisionRecord(
363
+ task_id=task.id,
364
+ task_description=task.description or "",
365
+ strategy_selected="",
366
+ strategy_reason="",
367
+ critic_score=last_critic_score,
368
+ confidence=float(last_critic_score) if last_critic_score is not None else 0.0,
369
+ )
370
+ match = self.hitl_escalation_checker.evaluate(task, fake_response, fake_decision)
371
+ if match is not None:
372
+ trigger, hitl_policy = match
373
+ request_id = str(__import__("uuid").uuid4())
374
+ now = datetime.now(timezone.utc)
375
+ timeout_sec = getattr(hitl_policy, "timeout_seconds", 3600)
376
+ expires = now + timedelta(seconds=timeout_sec)
377
+ approval = ApprovalRequest(
378
+ request_id=request_id,
379
+ task=task,
380
+ proposed_result=result,
381
+ decision_record=fake_decision,
382
+ trigger=trigger,
383
+ created_at=now.isoformat(),
384
+ expires_at=expires.isoformat(),
385
+ status="pending",
386
+ )
387
+ store = self.hitl_approval_store
388
+ if store is not None:
389
+ store.save(approval)
390
+ if self.hitl_notifier is not None:
391
+ await self.hitl_notifier.notify(approval, hitl_policy)
392
+ # In-process resolver or poll store
393
+ approved_result = True
394
+ if self.hitl_resolver is not None:
395
+ resolver = self.hitl_resolver
396
+ try:
397
+ if asyncio.iscoroutinefunction(resolver):
398
+ approved_result = await resolver(approval, hitl_policy)
399
+ else:
400
+ approved_result = await asyncio.to_thread(resolver, approval, hitl_policy)
401
+ except Exception:
402
+ approved_result = False
403
+ if store is not None:
404
+ store.resolve(request_id, approved_result, "")
405
+ if not approved_result:
406
+ self.scheduler.mark_failed(task.id, "Rejected by human reviewer")
407
+ self._emit(
408
+ events.TASK_REJECTED_BY_HUMAN,
409
+ {"task_id": task.id, "request_id": request_id},
410
+ )
411
+ return task.id
412
+ # approved: fall through to mark_completed
413
+ else:
414
+ # Poll for resolution every 5s up to timeout
415
+ poll_interval = 5
416
+ elapsed = 0
417
+ while store is not None and elapsed < timeout_sec:
418
+ await asyncio.sleep(min(poll_interval, timeout_sec - elapsed))
419
+ elapsed += poll_interval
420
+ req = store.get(request_id)
421
+ if req is None:
422
+ break
423
+ if req.status == "approved":
424
+ break
425
+ if req.status == "rejected":
426
+ self.scheduler.mark_failed(task.id, "Rejected by human reviewer")
427
+ self._emit(
428
+ events.TASK_REJECTED_BY_HUMAN,
429
+ {"task_id": task.id, "request_id": request_id},
430
+ )
431
+ return task.id
432
+ # After loop: approved or timeout
433
+ req = store.get(request_id) if store else None
434
+ if req is not None and req.status == "rejected":
435
+ self.scheduler.mark_failed(task.id, "Rejected by human reviewer")
436
+ self._emit(
437
+ events.TASK_REJECTED_BY_HUMAN,
438
+ {"task_id": task.id, "request_id": request_id},
439
+ )
440
+ return task.id
441
+ if req is not None and req.status == "pending" and elapsed >= timeout_sec:
442
+ on_timeout = getattr(hitl_policy, "on_timeout", "auto_approve")
443
+ if on_timeout == "auto_reject":
444
+ self.scheduler.mark_failed(task.id, "Approval timeout (auto_reject)")
445
+ if store:
446
+ approval.status = "timeout"
447
+ store.save(approval)
448
+ return task.id
449
+ # auto_approve or escalate_further: treat as approve for now
450
+ # approved or auto_approve: fall through to mark_completed
451
+
452
+ if not is_speculative:
453
+ self.scheduler.mark_completed(task.id, result)
454
+ self.scheduler.confirm_speculative_for(task.id)
455
+ if self.checkpointer is not None:
456
+ self.checkpointer.on_task_completed(self.scheduler)
457
+ if self.audit_logger is not None and self.scheduler is not None:
458
+ try:
459
+ from devsper.audit.logger import make_audit_record
460
+ run_id = getattr(self.scheduler, "run_id", "") or ""
461
+ rec = make_audit_record(
462
+ run_id=run_id,
463
+ task_id=task.id,
464
+ event_type="TASK_COMPLETED",
465
+ actor=run_id,
466
+ resource=getattr(task, "role", "") or "agent",
467
+ input_text=task.description or "",
468
+ output_text=(result or "")[:5000],
469
+ duration_ms=int((task.result and 0) or 0),
470
+ success=True,
471
+ )
472
+ self.audit_logger.log(rec)
473
+ except Exception:
474
+ pass
475
+ self._publish_bus(
476
+ "task.completed",
477
+ {
478
+ "task_id": task.id,
479
+ "result": result,
480
+ "tokens_used": None,
481
+ "duration_seconds": 0.0,
482
+ },
483
+ )
484
+ if self.adaptive and self.planner:
485
+ new_tasks = self.planner.expand_tasks(task)
486
+ if new_tasks:
487
+ self.scheduler.add_tasks(new_tasks)
488
+ return task.id
489
+
490
+ async def run(self) -> None:
491
+ """Run the execution loop until all tasks are completed."""
492
+ self._emit(events.EXECUTOR_STARTED, {})
493
+
494
+ sem = asyncio.Semaphore(self.worker_count)
495
+ running: dict[str, tuple[Task, bool, asyncio.Task]] = {} # task_id -> (task, is_speculative, future)
496
+
497
+ async def run_with_sem(task: Task, is_spec: bool) -> str:
498
+ async with sem:
499
+ return await self._execute_task(task, is_spec)
500
+
501
+ while not self.scheduler.is_finished():
502
+ if self.pause_event is not None and not self.pause_event.is_set():
503
+ await asyncio.sleep(0.2)
504
+ continue
505
+
506
+ ready = self.scheduler.get_ready_tasks()
507
+ speculative: list[Task] = []
508
+ if self.speculative_execution:
509
+ speculative = self.scheduler.get_speculative_tasks()
510
+ if self.prefetcher:
511
+ for t in speculative:
512
+ if t.id not in running:
513
+ asyncio.create_task(self.prefetcher.prefetch(t))
514
+
515
+ if self.streaming_dag:
516
+ for task in ready + speculative:
517
+ if task.id in running:
518
+ continue
519
+ if len(running) >= self.worker_count:
520
+ break
521
+ task.status = TaskStatus.RUNNING
522
+ is_spec = task in speculative
523
+ fut = asyncio.create_task(run_with_sem(task, is_spec))
524
+ running[task.id] = (task, is_spec, fut)
525
+ if not running:
526
+ if not ready and not speculative:
527
+ await asyncio.sleep(0.01)
528
+ continue
529
+ done, _ = await asyncio.wait(
530
+ [f for _, _, f in running.values()],
531
+ return_when=asyncio.FIRST_COMPLETED,
532
+ )
533
+ for f in done:
534
+ try:
535
+ tid = f.result()
536
+ except Exception:
537
+ tid = None
538
+ if tid and tid in running:
539
+ del running[tid]
540
+ continue
541
+
542
+ for task in ready:
543
+ task.status = TaskStatus.RUNNING
544
+ for task in speculative:
545
+ task.status = TaskStatus.RUNNING
546
+ await asyncio.gather(
547
+ *[run_with_sem(t, False) for t in ready],
548
+ *[run_with_sem(t, True) for t in speculative],
549
+ )
550
+
551
+ self._emit(events.EXECUTOR_FINISHED, {})
552
+
553
+ def _emit(self, event_type: events, payload: dict) -> None:
554
+ self.event_log.append_event(
555
+ Event(
556
+ timestamp=datetime.now(timezone.utc), type=event_type, payload=payload
557
+ )
558
+ )
@@ -0,0 +1,44 @@
1
+ """
2
+ Map-reduce primitive: partition dataset, parallel map, single reduce.
3
+ Uses asyncio + worker pool (same pattern as Executor).
4
+ """
5
+
6
+ from collections.abc import Callable
7
+ from typing import TypeVar
8
+
9
+ T = TypeVar("T")
10
+ R = TypeVar("R")
11
+ A = TypeVar("A")
12
+
13
+
14
+ def map_reduce(
15
+ dataset: list[T],
16
+ map_fn: Callable[[T], R],
17
+ reduce_fn: Callable[[list[R]], A],
18
+ worker_count: int = 4,
19
+ ) -> A:
20
+ """
21
+ Run map_fn on each item in parallel (up to worker_count concurrent), then reduce_fn on results.
22
+
23
+ - dataset: list of items to process
24
+ - map_fn: item -> result (called once per item)
25
+ - reduce_fn: list of results -> aggregated value
26
+ - worker_count: max concurrent map operations
27
+
28
+ Returns the result of reduce_fn(map_results).
29
+ """
30
+ import asyncio
31
+
32
+ async def _run() -> A:
33
+ sem = asyncio.Semaphore(worker_count)
34
+ loop = asyncio.get_running_loop()
35
+ results: list[R] = [None] * len(dataset) # type: ignore
36
+
37
+ async def _map_one(i: int, item: T) -> None:
38
+ async with sem:
39
+ results[i] = await loop.run_in_executor(None, lambda: map_fn(item))
40
+
41
+ await asyncio.gather(*[_map_one(i, item) for i, item in enumerate(dataset)])
42
+ return reduce_fn(results)
43
+
44
+ return asyncio.run(_run())