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,161 @@
1
+ """
2
+ Single-node mode: one process runs controller + worker with InMemoryBus and filesystem state.
3
+ Zero-config, no Redis. Behaviorally identical to pre-v1.10 Swarm.
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ from pathlib import Path
9
+
10
+ from devsper.types.task import Task
11
+ from devsper.bus.backends.memory import InMemoryBus
12
+ from devsper.cluster.state_backend import FilesystemStateBackend
13
+ from devsper.cluster.local import InMemoryRegistry, LocalLeaderElector
14
+ from devsper.cluster.router import TaskRouter
15
+ from devsper.swarm.scheduler import Scheduler
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ class SingleNode:
21
+ """Controller + worker in one process; InMemoryBus; no RPC, no Redis."""
22
+
23
+ def __init__(
24
+ self,
25
+ config: object,
26
+ scheduler: Scheduler,
27
+ bus: object,
28
+ state_backend: object,
29
+ registry: object,
30
+ elector: object,
31
+ router: TaskRouter,
32
+ controller_node: object,
33
+ worker_node: object,
34
+ event_log: object,
35
+ ) -> None:
36
+ self.config = config
37
+ self.scheduler = scheduler
38
+ self.bus = bus
39
+ self.state_backend = state_backend
40
+ self.registry = registry
41
+ self.elector = elector
42
+ self.router = router
43
+ self.controller_node = controller_node
44
+ self.worker_node = worker_node
45
+ self.event_log = event_log
46
+ self.run_id = getattr(scheduler, "run_id", "") or ""
47
+
48
+ async def start(self) -> None:
49
+ await self.bus.start()
50
+ await asyncio.gather(
51
+ self.controller_node.start(),
52
+ self.worker_node.start(),
53
+ )
54
+
55
+ async def run_until_finished(self) -> dict[str, str]:
56
+ """Wait until scheduler is finished; return results."""
57
+ # Yield so the event loop runs elector.watch -> _become_leader -> dispatch_loop
58
+ await asyncio.sleep(0.3)
59
+ while not self.scheduler.is_finished():
60
+ await asyncio.sleep(0.05)
61
+ return self.scheduler.get_results()
62
+
63
+
64
+ def create_single_node(
65
+ config: object,
66
+ scheduler: Scheduler,
67
+ event_log: object,
68
+ memory_router: object,
69
+ agent_factory: object,
70
+ user_task: str = "",
71
+ message_bus: object = None,
72
+ hitl_enabled: bool = False,
73
+ hitl_escalation_checker: object = None,
74
+ hitl_approval_store: object = None,
75
+ hitl_notifier: object = None,
76
+ hitl_resolver: object = None,
77
+ ) -> SingleNode:
78
+ """Build SingleNode with InMemoryBus, filesystem state, in-memory registry/elector."""
79
+ run_id = getattr(scheduler, "run_id", "") or ""
80
+ bus = InMemoryBus()
81
+ events_dir = getattr(config, "events_dir", ".devsper/events")
82
+ state_backend = FilesystemStateBackend(events_dir)
83
+ registry = InMemoryRegistry(run_id)
84
+ elector = LocalLeaderElector(run_id)
85
+ try:
86
+ import devsper
87
+ version = getattr(devsper, "__version__", "1.10.0")
88
+ except Exception:
89
+ version = "1.10.0"
90
+ router = TaskRouter(controller_version=version)
91
+ from devsper.nodes.controller import ControllerNode
92
+ from devsper.nodes.worker import WorkerNode
93
+ try:
94
+ from devsper.tools.selector import get_tools_for_task
95
+ tool_selector = lambda desc, role=None, score_store=None: get_tools_for_task(
96
+ desc or "", role=role, score_store=score_store
97
+ )
98
+ except Exception:
99
+ tool_selector = lambda desc, role=None, score_store=None: []
100
+ try:
101
+ from devsper.tools.scoring import get_default_score_store
102
+ score_store = get_default_score_store()
103
+ except Exception:
104
+ score_store = None
105
+ prefetcher = None
106
+ try:
107
+ from devsper.swarm.prefetcher import TaskPrefetcher
108
+ prefetcher = TaskPrefetcher(
109
+ memory_router=memory_router,
110
+ tool_selector=tool_selector,
111
+ score_store=score_store,
112
+ max_age_seconds=30.0,
113
+ )
114
+ except Exception:
115
+ pass
116
+ controller = ControllerNode(
117
+ config=config,
118
+ scheduler=scheduler,
119
+ bus=bus,
120
+ state_backend=state_backend,
121
+ registry=registry,
122
+ elector=elector,
123
+ router=router,
124
+ event_log=event_log,
125
+ )
126
+ worker = WorkerNode(
127
+ config=config,
128
+ bus=bus,
129
+ registry=registry,
130
+ memory_router=memory_router,
131
+ tool_selector=tool_selector,
132
+ score_store=score_store,
133
+ prefetcher=prefetcher,
134
+ agent_factory=agent_factory,
135
+ event_log=event_log,
136
+ run_id=run_id,
137
+ user_task=user_task,
138
+ message_bus=message_bus,
139
+ hitl_enabled=hitl_enabled,
140
+ hitl_escalation_checker=hitl_escalation_checker,
141
+ hitl_approval_store=hitl_approval_store,
142
+ hitl_notifier=hitl_notifier,
143
+ hitl_resolver=hitl_resolver,
144
+ )
145
+ return SingleNode(
146
+ config=config,
147
+ scheduler=scheduler,
148
+ bus=bus,
149
+ state_backend=state_backend,
150
+ registry=registry,
151
+ elector=elector,
152
+ router=router,
153
+ controller_node=controller,
154
+ worker_node=worker,
155
+ event_log=event_log,
156
+ )
157
+
158
+
159
+ def _make_node_id() -> str:
160
+ from uuid import uuid4
161
+ return str(uuid4())
@@ -0,0 +1,506 @@
1
+ """
2
+ Worker node: executes tasks, reports heartbeats, claims tasks.
3
+ Distributed mode only; requires redis.
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ import time
9
+ from datetime import datetime, timezone
10
+
11
+ from devsper.types.task import Task, TaskStatus
12
+ from devsper.agents.agent import Agent, AgentRequest
13
+ from devsper.bus.message import create_bus_message
14
+ from devsper.bus.topics import (
15
+ TASK_READY,
16
+ TASK_CLAIMED,
17
+ TASK_CLAIM_GRANTED,
18
+ TASK_CLAIM_REJECTED,
19
+ TASK_COMPLETED,
20
+ TASK_FAILED,
21
+ NODE_HEARTBEAT,
22
+ NODE_JOINED,
23
+ SWARM_SNAPSHOT,
24
+ SWARM_CONTROL,
25
+ )
26
+ from devsper.cluster.node_info import NodeInfo, NodeRole
27
+ from devsper.cluster.registry import ClusterRegistry
28
+ from devsper.swarm.prefetcher import PrefetchResult
29
+
30
+ log = logging.getLogger(__name__)
31
+
32
+
33
+ def _require_distributed() -> None:
34
+ try:
35
+ import redis.asyncio # noqa: F401
36
+ except ImportError as e:
37
+ raise ImportError(
38
+ "Distributed mode requires: pip install devsper[distributed]"
39
+ ) from e
40
+
41
+
42
+ def _node_info_from_config(config: object, node_id: str, role: NodeRole, run_id: str) -> NodeInfo:
43
+ now = datetime.now(timezone.utc).isoformat()
44
+ nodes_cfg = getattr(config, "nodes", None)
45
+ rpc_port = getattr(nodes_cfg, "rpc_port", 7701)
46
+ host = "localhost"
47
+ try:
48
+ import socket
49
+ host = socket.gethostname() or host
50
+ except Exception:
51
+ pass
52
+ rpc_url = f"http://{host}:{rpc_port}"
53
+ tags = list(getattr(nodes_cfg, "node_tags", []) or [])
54
+ max_workers = getattr(nodes_cfg, "max_workers_per_node", 8)
55
+ try:
56
+ import devsper
57
+ version = getattr(devsper, "__version__", "1.10.0")
58
+ except Exception:
59
+ version = "1.10.0"
60
+ return NodeInfo(
61
+ node_id=node_id,
62
+ role=role,
63
+ host=host,
64
+ rpc_port=rpc_port,
65
+ rpc_url=rpc_url,
66
+ tags=tags,
67
+ max_workers=max_workers,
68
+ joined_at=now,
69
+ last_heartbeat=now,
70
+ version=version,
71
+ )
72
+
73
+
74
+ def _build_agent_request(
75
+ task: Task,
76
+ memory_router: object,
77
+ tool_selector: object,
78
+ prefetch: PrefetchResult | None,
79
+ user_task: str = "",
80
+ message_bus: object = None,
81
+ score_store: object = None,
82
+ ) -> AgentRequest:
83
+ """Build AgentRequest for a task (mirrors Agent.run_task context)."""
84
+ memory_section = ""
85
+ if prefetch and getattr(prefetch, "memory_context", None):
86
+ memory_section = prefetch.memory_context or ""
87
+ elif memory_router and task.description:
88
+ try:
89
+ query = f"{user_task} {task.description}".strip() if user_task else task.description
90
+ memory_section = memory_router.get_memory_context(query) or ""
91
+ except Exception:
92
+ pass
93
+ message_bus_section = ""
94
+ if message_bus:
95
+ try:
96
+ message_bus_section = message_bus.get_context_sync(task.id) or ""
97
+ except Exception:
98
+ pass
99
+ if message_bus_section:
100
+ memory_section = (memory_section + "\n\n" + message_bus_section).strip()
101
+ from devsper.agents.roles import get_role_config
102
+ role_config = get_role_config(getattr(task, "role", None))
103
+ from devsper.agents.agent import BROADCAST_INSTRUCTION
104
+ broadcast_instruction = BROADCAST_INSTRUCTION if message_bus_section else ""
105
+ system_prompt = role_config.prompt_prefix + broadcast_instruction
106
+ tools_names: list[str] = []
107
+ if tool_selector:
108
+ try:
109
+ if prefetch and getattr(prefetch, "tools", None):
110
+ tools_names = [t.name for t in prefetch.tools]
111
+ else:
112
+ tools = tool_selector(
113
+ task.description or "",
114
+ role=getattr(task, "role", None),
115
+ score_store=score_store,
116
+ )
117
+ tools_names = [t.name for t in tools] if tools else []
118
+ except Exception:
119
+ pass
120
+ return AgentRequest(
121
+ task=task,
122
+ memory_context=memory_section,
123
+ tools=tools_names,
124
+ model="mock",
125
+ system_prompt=system_prompt,
126
+ prefetch_used=prefetch is not None,
127
+ )
128
+
129
+
130
+ class WorkerNode:
131
+ """Executes tasks, owns local memory/tool cache, reports results."""
132
+
133
+ def __init__(
134
+ self,
135
+ config: object,
136
+ bus: object,
137
+ registry: ClusterRegistry,
138
+ memory_router: object,
139
+ tool_selector: object,
140
+ score_store: object,
141
+ prefetcher: object,
142
+ agent_factory: object,
143
+ event_log: object,
144
+ run_id: str,
145
+ user_task: str = "",
146
+ message_bus: object = None,
147
+ hitl_enabled: bool = False,
148
+ hitl_escalation_checker: object = None,
149
+ hitl_approval_store: object = None,
150
+ hitl_notifier: object = None,
151
+ hitl_resolver: object = None,
152
+ ) -> None:
153
+ self.config = config
154
+ self.bus = bus
155
+ self.registry = registry
156
+ self.memory_router = memory_router
157
+ self.tool_selector = tool_selector
158
+ self.score_store = score_store
159
+ self.prefetcher = prefetcher
160
+ self.agent_factory = agent_factory
161
+ self.event_log = event_log
162
+ self._run_id = run_id
163
+ self.user_task = user_task or ""
164
+ self.message_bus = message_bus
165
+ self.hitl_enabled = hitl_enabled
166
+ self.hitl_escalation_checker = hitl_escalation_checker
167
+ self.hitl_approval_store = hitl_approval_store
168
+ self.hitl_notifier = hitl_notifier
169
+ self.hitl_resolver = hitl_resolver
170
+ self.node_id = _make_node_id()
171
+ nodes_cfg = getattr(config, "nodes", None)
172
+ role = NodeRole.WORKER
173
+ self.node_info = _node_info_from_config(config, self.node_id, role, run_id)
174
+ self._active_tasks = 0
175
+ self._pending_grants: dict[str, asyncio.Event] = {}
176
+ self._current_tasks: dict[str, Task] = {}
177
+ self._task_durations: list[float] = []
178
+ self._last_completed_ids: list[str] = []
179
+ self._paused = False
180
+ self._draining = False
181
+ self._snapshot: dict | None = None
182
+
183
+ async def start(self) -> None:
184
+ await self.registry.register(self.node_info)
185
+ await self.bus.subscribe(TASK_READY, self._on_task_ready, run_id=self._run_id)
186
+ await self.bus.subscribe(TASK_CLAIM_GRANTED, self._on_claim_granted, run_id=self._run_id)
187
+ await self.bus.subscribe(TASK_CLAIM_REJECTED, self._on_claim_rejected, run_id=self._run_id)
188
+ await self.bus.subscribe(SWARM_SNAPSHOT, self._on_snapshot, run_id=self._run_id)
189
+ await self.bus.subscribe(SWARM_CONTROL, self._on_control, run_id=self._run_id)
190
+ asyncio.create_task(self.heartbeat_loop())
191
+ await self.bus.publish(
192
+ create_bus_message(
193
+ topic=NODE_JOINED,
194
+ payload=self.node_info.to_dict(),
195
+ sender_id=self.node_id,
196
+ run_id=self._run_id,
197
+ )
198
+ )
199
+
200
+ async def _on_task_ready(self, msg: object) -> None:
201
+ payload = getattr(msg, "payload", {}) or {}
202
+ target = payload.get("target_worker_id")
203
+ if target is not None and target != self.node_id:
204
+ return
205
+ if self._paused or self._draining:
206
+ return
207
+ if self._active_tasks >= self.node_info.max_workers:
208
+ return
209
+ try:
210
+ task = Task.from_dict(payload)
211
+ except Exception:
212
+ return
213
+ task_id_short = (task.id or "?")[:12]
214
+ log.info("Worker %s received TASK_READY for %s", self.node_id[:8], task_id_short)
215
+ self._pending_grants[task.id] = asyncio.Event()
216
+ await self.bus.publish(
217
+ create_bus_message(
218
+ topic=TASK_CLAIMED,
219
+ payload={"task_id": task.id, "worker_id": self.node_id},
220
+ sender_id=self.node_id,
221
+ run_id=self._run_id,
222
+ )
223
+ )
224
+ grant_wait = 15.0
225
+ nodes_cfg = getattr(self.config, "nodes", None)
226
+ if nodes_cfg:
227
+ grant_wait = getattr(nodes_cfg, "claim_grant_wait_seconds", 15.0)
228
+ try:
229
+ await asyncio.wait_for(self._pending_grants[task.id].wait(), timeout=grant_wait)
230
+ except asyncio.TimeoutError:
231
+ log.warning("Worker %s did not receive TASK_CLAIM_GRANTED for %s within %.0fs", self.node_id[:8], task_id_short, grant_wait)
232
+ self._pending_grants.pop(task.id, None)
233
+ return
234
+ if task.id not in self._pending_grants:
235
+ return
236
+ self._pending_grants.pop(task.id, None)
237
+ self._active_tasks += 1
238
+ log.info("Worker %s executing task %s", self.node_id[:8], task_id_short)
239
+ asyncio.create_task(self._execute_task(task))
240
+
241
+ async def _on_claim_granted(self, msg: object) -> None:
242
+ payload = getattr(msg, "payload", {}) or {}
243
+ if payload.get("worker_id") != self.node_id:
244
+ return
245
+ task_id = payload.get("task_id")
246
+ if task_id and task_id in self._pending_grants:
247
+ self._pending_grants[task_id].set()
248
+
249
+ async def _on_claim_rejected(self, msg: object) -> None:
250
+ payload = getattr(msg, "payload", {}) or {}
251
+ if payload.get("worker_id") != self.node_id:
252
+ return
253
+ task_id = payload.get("task_id")
254
+ if task_id:
255
+ self._pending_grants.pop(task_id, None)
256
+
257
+ async def _execute_task(self, task: Task) -> None:
258
+ self._current_tasks[task.id] = task
259
+ start = time.monotonic()
260
+ task_id_short = (task.id or "?")[:12]
261
+ try:
262
+ prefetch = self.prefetcher.consume(task.id) if self.prefetcher else None
263
+ request = _build_agent_request(
264
+ task,
265
+ self.memory_router,
266
+ self.tool_selector,
267
+ prefetch,
268
+ user_task=self.user_task,
269
+ message_bus=self.message_bus,
270
+ score_store=self.score_store,
271
+ )
272
+ agent = self.agent_factory(self.config)
273
+ if hasattr(agent, "model_name") and hasattr(self.config, "models"):
274
+ request = AgentRequest(
275
+ task=request.task,
276
+ memory_context=request.memory_context,
277
+ tools=request.tools,
278
+ model=getattr(self.config.models, "worker", "mock"),
279
+ system_prompt=request.system_prompt,
280
+ prefetch_used=request.prefetch_used,
281
+ )
282
+ exec_timeout = 90
283
+ nodes_cfg = getattr(self.config, "nodes", None)
284
+ if nodes_cfg:
285
+ exec_timeout = getattr(nodes_cfg, "task_execution_timeout_seconds", 90)
286
+ if exec_timeout > 0:
287
+ response = await asyncio.wait_for(
288
+ asyncio.to_thread(agent.run, request),
289
+ timeout=float(exec_timeout),
290
+ )
291
+ else:
292
+ response = await asyncio.to_thread(agent.run, request)
293
+ self._task_durations.append(time.monotonic() - start)
294
+ if len(self._task_durations) > 10:
295
+ self._task_durations.pop(0)
296
+ self._last_completed_ids.append(task.id)
297
+ if len(self._last_completed_ids) > 50:
298
+ self._last_completed_ids.pop(0)
299
+ # Apply response to task for HITL evaluation
300
+ task.result = response.result
301
+ task.status = TaskStatus.COMPLETED if response.success else TaskStatus.FAILED
302
+ task.error = response.error
303
+ result = response.result or ""
304
+ # HITL: escalation check before publishing TASK_COMPLETED
305
+ should_publish_completed = True
306
+ if self.hitl_enabled and self.hitl_escalation_checker and response.success:
307
+ from devsper.explainability.decision_tree import DecisionRecord
308
+ from devsper.hitl.approval import ApprovalRequest
309
+ from datetime import datetime, timezone, timedelta
310
+ last_critic_score = None
311
+ fake_decision = DecisionRecord(
312
+ task_id=task.id,
313
+ task_description=task.description or "",
314
+ strategy_selected="",
315
+ strategy_reason="",
316
+ critic_score=last_critic_score,
317
+ confidence=float(last_critic_score) if last_critic_score is not None else 0.0,
318
+ )
319
+ match = self.hitl_escalation_checker.evaluate(task, response, fake_decision)
320
+ if match is not None:
321
+ trigger, hitl_policy = match
322
+ request_id = str(__import__("uuid").uuid4())
323
+ now = datetime.now(timezone.utc)
324
+ timeout_sec = getattr(hitl_policy, "timeout_seconds", 3600)
325
+ expires = now + timedelta(seconds=timeout_sec)
326
+ approval = ApprovalRequest(
327
+ request_id=request_id,
328
+ task=task,
329
+ proposed_result=result,
330
+ decision_record=fake_decision,
331
+ trigger=trigger,
332
+ created_at=now.isoformat(),
333
+ expires_at=expires.isoformat(),
334
+ status="pending",
335
+ )
336
+ store = self.hitl_approval_store
337
+ if store is not None:
338
+ store.save(approval)
339
+ if self.hitl_notifier is not None:
340
+ await self.hitl_notifier.notify(approval, hitl_policy)
341
+ approved_result = True
342
+ if self.hitl_resolver is not None:
343
+ resolver = self.hitl_resolver
344
+ try:
345
+ import asyncio
346
+ if asyncio.iscoroutinefunction(resolver):
347
+ approved_result = await resolver(approval, hitl_policy)
348
+ else:
349
+ approved_result = await asyncio.to_thread(resolver, approval, hitl_policy)
350
+ except Exception:
351
+ approved_result = False
352
+ if store is not None:
353
+ store.resolve(request_id, approved_result, "")
354
+ else:
355
+ poll_interval = 5
356
+ elapsed = 0
357
+ approved_result = True
358
+ while store is not None and elapsed < timeout_sec:
359
+ await asyncio.sleep(min(poll_interval, timeout_sec - elapsed))
360
+ elapsed += poll_interval
361
+ req = store.get(request_id)
362
+ if req is None:
363
+ break
364
+ if req.status == "approved":
365
+ break
366
+ if req.status == "rejected":
367
+ approved_result = False
368
+ break
369
+ if store is not None:
370
+ req = store.get(request_id)
371
+ if req is not None and req.status == "rejected":
372
+ approved_result = False
373
+ elif req is not None and req.status == "pending" and elapsed >= timeout_sec:
374
+ on_timeout = getattr(hitl_policy, "on_timeout", "auto_approve")
375
+ approved_result = on_timeout != "auto_reject"
376
+ if not approved_result and store:
377
+ approval.status = "timeout"
378
+ store.save(approval)
379
+ if not approved_result:
380
+ should_publish_completed = False
381
+ await self.bus.publish(
382
+ create_bus_message(
383
+ topic=TASK_FAILED,
384
+ payload={
385
+ "task_id": task.id,
386
+ "error": "Rejected by human reviewer",
387
+ "error_type": "HITLRejected",
388
+ "worker_id": self.node_id,
389
+ },
390
+ sender_id=self.node_id,
391
+ run_id=self._run_id,
392
+ )
393
+ )
394
+ if should_publish_completed:
395
+ log.info("Worker %s completed task %s (%.1fs)", self.node_id[:8], task_id_short, time.monotonic() - start)
396
+ await self.bus.publish(
397
+ create_bus_message(
398
+ topic=TASK_COMPLETED,
399
+ payload=response.to_dict(),
400
+ sender_id=self.node_id,
401
+ run_id=self._run_id,
402
+ )
403
+ )
404
+ except asyncio.TimeoutError:
405
+ err_msg = f"Execution timeout after {exec_timeout}s"
406
+ log.warning("Worker %s failed task %s: %s", self.node_id[:8], task_id_short, err_msg)
407
+ await self.bus.publish(
408
+ create_bus_message(
409
+ topic=TASK_FAILED,
410
+ payload={
411
+ "task_id": task.id,
412
+ "error": err_msg,
413
+ "error_type": "TimeoutError",
414
+ "worker_id": self.node_id,
415
+ },
416
+ sender_id=self.node_id,
417
+ run_id=self._run_id,
418
+ )
419
+ )
420
+ except Exception as e:
421
+ log.warning("Worker %s failed task %s: %s", self.node_id[:8], task_id_short, e)
422
+ await self.bus.publish(
423
+ create_bus_message(
424
+ topic=TASK_FAILED,
425
+ payload={
426
+ "task_id": task.id,
427
+ "error": str(e),
428
+ "error_type": type(e).__name__,
429
+ "worker_id": self.node_id,
430
+ },
431
+ sender_id=self.node_id,
432
+ run_id=self._run_id,
433
+ )
434
+ )
435
+ finally:
436
+ self._active_tasks -= 1
437
+ self._current_tasks.pop(task.id, None)
438
+
439
+ async def heartbeat_loop(self) -> None:
440
+ interval = 10.0
441
+ nodes_cfg = getattr(self.config, "nodes", None)
442
+ if nodes_cfg:
443
+ interval = getattr(nodes_cfg, "heartbeat_interval_seconds", 10.0)
444
+ while True:
445
+ await asyncio.sleep(interval)
446
+ if self._paused:
447
+ continue
448
+ avg_duration = (
449
+ sum(self._task_durations) / len(self._task_durations)
450
+ if self._task_durations else 0.0
451
+ )
452
+ cached_tools: list[str] = []
453
+ if self.score_store and hasattr(self.score_store, "get_cached_tool_names"):
454
+ try:
455
+ cached_tools = list(self.score_store.get_cached_tool_names())
456
+ except Exception:
457
+ pass
458
+ await self.bus.publish(
459
+ create_bus_message(
460
+ topic=NODE_HEARTBEAT,
461
+ payload={
462
+ "node_id": self.node_id,
463
+ "active_tasks": self._active_tasks,
464
+ "max_workers": self.node_info.max_workers,
465
+ "avg_task_duration_seconds": avg_duration,
466
+ "load": self._active_tasks / max(1, self.node_info.max_workers),
467
+ "cached_tools": cached_tools,
468
+ "completed_task_ids": self._last_completed_ids[-50:],
469
+ "tags": self.node_info.tags,
470
+ "rpc_url": self.node_info.rpc_url,
471
+ },
472
+ sender_id=self.node_id,
473
+ run_id=self._run_id,
474
+ )
475
+ )
476
+ try:
477
+ await self.registry.heartbeat(
478
+ self.node_id,
479
+ {"last_heartbeat": datetime.now(timezone.utc).isoformat()},
480
+ )
481
+ except Exception:
482
+ pass
483
+
484
+ async def _on_control(self, msg: object) -> None:
485
+ payload = getattr(msg, "payload", {}) or {}
486
+ command = payload.get("command")
487
+ target = payload.get("target", "all")
488
+ if target != "all" and target != self.node_id:
489
+ return
490
+ if command == "pause":
491
+ self._paused = True
492
+ elif command == "resume":
493
+ self._paused = False
494
+ elif command == "drain":
495
+ self._draining = True
496
+
497
+ async def _on_snapshot(self, msg: object) -> None:
498
+ self._snapshot = getattr(msg, "payload", None)
499
+
500
+ def get_current_tasks(self) -> list[dict]:
501
+ return [t.to_dict() for t in self._current_tasks.values()]
502
+
503
+
504
+ def _make_node_id() -> str:
505
+ from uuid import uuid4
506
+ return str(uuid4())
@@ -0,0 +1,19 @@
1
+ """Hierarchical orchestration: meta-planner, sub-swarms, SLAs, priority scheduling."""
2
+
3
+ from devsper.orchestration.meta_planner import (
4
+ MetaPlanner,
5
+ MetaRunResult,
6
+ SLAConfig,
7
+ SLABreach,
8
+ SubSwarmSpec,
9
+ )
10
+ from devsper.orchestration.priority_queue import PriorityScheduler
11
+
12
+ __all__ = [
13
+ "MetaPlanner",
14
+ "MetaRunResult",
15
+ "PriorityScheduler",
16
+ "SLAConfig",
17
+ "SLABreach",
18
+ "SubSwarmSpec",
19
+ ]