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,124 @@
1
+ """
2
+ Per-run pub/sub channel for agent-to-agent messaging. v1.7.
3
+ Agents broadcast discoveries; subsequent agents receive them via memory context.
4
+ Not persistent — lives only for the duration of a run.
5
+ """
6
+
7
+ import asyncio
8
+ from dataclasses import dataclass
9
+ from datetime import datetime, timezone
10
+
11
+ from devsper.types.event import Event, events
12
+
13
+
14
+ @dataclass
15
+ class AgentMessage:
16
+ sender_task_id: str
17
+ content: str
18
+ tags: list[str]
19
+ timestamp: str
20
+
21
+
22
+ class SwarmMessageBus:
23
+ """
24
+ Per-run pub/sub channel. Agents broadcast discoveries;
25
+ all subsequent agents receive them via memory context.
26
+ Not persistent — lives only for the duration of a run.
27
+ """
28
+
29
+ def __init__(self, event_log=None):
30
+ self._messages: list[AgentMessage] = []
31
+ self._lock = asyncio.Lock()
32
+ self._event_log = event_log
33
+
34
+ def _emit(self, event_type: events, payload: dict) -> None:
35
+ if self._event_log:
36
+ self._event_log.append_event(
37
+ Event(
38
+ timestamp=datetime.now(timezone.utc),
39
+ type=event_type,
40
+ payload=payload,
41
+ )
42
+ )
43
+
44
+ async def broadcast(
45
+ self, sender_task_id: str, content: str, tags: list[str] | None = None
46
+ ) -> None:
47
+ """Agent calls this when it discovers something worth sharing."""
48
+ tags = tags or []
49
+ async with self._lock:
50
+ self._messages.append(
51
+ AgentMessage(
52
+ sender_task_id=sender_task_id,
53
+ content=content,
54
+ tags=tags,
55
+ timestamp=datetime.now(timezone.utc).isoformat(),
56
+ )
57
+ )
58
+ self._emit(
59
+ events.AGENT_BROADCAST,
60
+ {
61
+ "sender_task_id": sender_task_id,
62
+ "content_preview": (content or "")[:100],
63
+ },
64
+ )
65
+
66
+ def broadcast_sync(
67
+ self, sender_task_id: str, content: str, tags: list[str] | None = None
68
+ ) -> None:
69
+ """Synchronous broadcast for use from sync agent.run()."""
70
+ tags = tags or []
71
+ self._messages.append(
72
+ AgentMessage(
73
+ sender_task_id=sender_task_id,
74
+ content=content,
75
+ tags=tags,
76
+ timestamp=datetime.now(timezone.utc).isoformat(),
77
+ )
78
+ )
79
+ self._emit(
80
+ events.AGENT_BROADCAST,
81
+ {
82
+ "sender_task_id": sender_task_id,
83
+ "content_preview": (content or "")[:100],
84
+ },
85
+ )
86
+
87
+ async def get_context(
88
+ self, requesting_task_id: str, max_messages: int = 5
89
+ ) -> str:
90
+ """
91
+ Return formatted string of recent broadcasts for injection into agent prompt.
92
+ Exclude messages from requesting_task_id itself.
93
+ Return most recent max_messages.
94
+ """
95
+ async with self._lock:
96
+ eligible = [
97
+ m
98
+ for m in self._messages
99
+ if m.sender_task_id != requesting_task_id
100
+ ]
101
+ recent = eligible[-max_messages:] if len(eligible) > max_messages else eligible
102
+ if not recent:
103
+ return ""
104
+ lines = ["Shared Discoveries (from other agents in this run):"]
105
+ for m in recent:
106
+ lines.append(f"- [{m.sender_task_id}]: {m.content[:500]}{'...' if len(m.content) > 500 else ''}")
107
+ return "\n".join(lines)
108
+
109
+ def get_context_sync(
110
+ self, requesting_task_id: str, max_messages: int = 5
111
+ ) -> str:
112
+ """Synchronous get_context for use from sync agent.run()."""
113
+ eligible = [
114
+ m
115
+ for m in self._messages
116
+ if m.sender_task_id != requesting_task_id
117
+ ]
118
+ recent = eligible[-max_messages:] if len(eligible) > max_messages else eligible
119
+ if not recent:
120
+ return ""
121
+ lines = ["Shared Discoveries (from other agents in this run):"]
122
+ for m in recent:
123
+ lines.append(f"- [{m.sender_task_id}]: {m.content[:500]}{'...' if len(m.content) > 500 else ''}")
124
+ return "\n".join(lines)
@@ -0,0 +1,181 @@
1
+ """
2
+ Specialized agent roles: research, code, analysis, critic.
3
+
4
+ Role determines tool access, prompt template, and model selection.
5
+ Planner assigns roles based on task type.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+
10
+ RESEARCH_AGENT = "research_agent"
11
+ CODE_AGENT = "code_agent"
12
+ ANALYSIS_AGENT = "analysis_agent"
13
+ CRITIC_AGENT = "critic_agent"
14
+
15
+ # Dev / autonomous builder roles
16
+ ARCHITECT_AGENT = "architect_agent"
17
+ BACKEND_AGENT = "backend_agent"
18
+ FRONTEND_AGENT = "frontend_agent"
19
+ TEST_AGENT = "test_agent"
20
+ REVIEW_AGENT = "review_agent"
21
+
22
+ DEFAULT_ROLE = "general"
23
+
24
+
25
+ @dataclass
26
+ class RoleConfig:
27
+ """Configuration for an agent role."""
28
+
29
+ name: str
30
+ tool_categories: list[str] # e.g. ["research", "documents"]
31
+ prompt_prefix: str
32
+ model_hint: str # e.g. "analysis", "planning" for model router
33
+
34
+
35
+ ROLE_CONFIGS: dict[str, RoleConfig] = {
36
+ RESEARCH_AGENT: RoleConfig(
37
+ name=RESEARCH_AGENT,
38
+ tool_categories=[
39
+ "research",
40
+ "research_advanced",
41
+ "documents",
42
+ "knowledge",
43
+ "memory",
44
+ "mcp",
45
+ ],
46
+ prompt_prefix="You are a research specialist. Focus on literature, citations, methodology, and evidence.",
47
+ model_hint="analysis",
48
+ ),
49
+ CODE_AGENT: RoleConfig(
50
+ name=CODE_AGENT,
51
+ tool_categories=["coding", "code_intelligence", "filesystem", "system", "mcp"],
52
+ prompt_prefix="You are a code specialist. Focus on implementation, structure, tests, and refactoring.",
53
+ model_hint="analysis",
54
+ ),
55
+ ANALYSIS_AGENT: RoleConfig(
56
+ name=ANALYSIS_AGENT,
57
+ tool_categories=["data", "data_science", "math", "experiments", "knowledge", "mcp"],
58
+ prompt_prefix="You are an analysis specialist. Focus on data, metrics, statistics, and interpretation.",
59
+ model_hint="analysis",
60
+ ),
61
+ CRITIC_AGENT: RoleConfig(
62
+ name=CRITIC_AGENT,
63
+ tool_categories=["documents", "memory", "knowledge"],
64
+ prompt_prefix="You are a critic/reviewer. Evaluate quality, consistency, and gaps. Be concise and constructive.",
65
+ model_hint="analysis",
66
+ ),
67
+ DEFAULT_ROLE: RoleConfig(
68
+ name=DEFAULT_ROLE,
69
+ tool_categories=[], # empty = no filter; all tools (including mcp) eligible
70
+ prompt_prefix="You are an AI worker in a distributed system.",
71
+ model_hint="analysis",
72
+ ),
73
+ # Dev / autonomous builder roles
74
+ ARCHITECT_AGENT: RoleConfig(
75
+ name=ARCHITECT_AGENT,
76
+ tool_categories=["code_intelligence", "filesystem"],
77
+ prompt_prefix="You are an architect. Design system structure, APIs, and component layout. Output clear architecture plans (backend/frontend stack, modules, data flow).",
78
+ model_hint="planning",
79
+ ),
80
+ BACKEND_AGENT: RoleConfig(
81
+ name=BACKEND_AGENT,
82
+ tool_categories=["coding", "code_intelligence", "filesystem", "system"],
83
+ prompt_prefix="You are a backend specialist. Implement APIs, models, and server logic. Prefer FastAPI/Flask patterns and clear interfaces.",
84
+ model_hint="analysis",
85
+ ),
86
+ FRONTEND_AGENT: RoleConfig(
87
+ name=FRONTEND_AGENT,
88
+ tool_categories=["coding", "filesystem", "system"],
89
+ prompt_prefix="You are a frontend specialist. Implement UI components, pages, and client logic. Prefer simple HTML/JS or React patterns.",
90
+ model_hint="analysis",
91
+ ),
92
+ TEST_AGENT: RoleConfig(
93
+ name=TEST_AGENT,
94
+ tool_categories=["coding", "code_intelligence", "filesystem", "system"],
95
+ prompt_prefix="You are a test specialist. Write unit and integration tests. Use pytest for Python; ensure coverage of main flows.",
96
+ model_hint="analysis",
97
+ ),
98
+ REVIEW_AGENT: RoleConfig(
99
+ name=REVIEW_AGENT,
100
+ tool_categories=["code_intelligence", "coding", "filesystem"],
101
+ prompt_prefix="You are a code reviewer. Check correctness, style, and gaps. Suggest concrete fixes. Be concise.",
102
+ model_hint="analysis",
103
+ ),
104
+ }
105
+
106
+
107
+ def get_role_config(role: str | None) -> RoleConfig:
108
+ """Return config for the given role, or default if unknown/None."""
109
+ if role and role in ROLE_CONFIGS:
110
+ return ROLE_CONFIGS[role]
111
+ return ROLE_CONFIGS[DEFAULT_ROLE]
112
+
113
+
114
+ # Keywords to infer role from task description
115
+ RESEARCH_KEYWORDS = [
116
+ "research",
117
+ "paper",
118
+ "literature",
119
+ "cite",
120
+ "survey",
121
+ "methodology",
122
+ "findings",
123
+ ]
124
+ CODE_KEYWORDS = [
125
+ "code",
126
+ "implement",
127
+ "refactor",
128
+ "test",
129
+ "repository",
130
+ "function",
131
+ "api",
132
+ "lint",
133
+ "list",
134
+ "directory",
135
+ "files",
136
+ "contents",
137
+ "path",
138
+ "read file",
139
+ "write file",
140
+ ]
141
+ ANALYSIS_KEYWORDS = [
142
+ "analyze",
143
+ "data",
144
+ "metric",
145
+ "statistic",
146
+ "plot",
147
+ "dataset",
148
+ "experiment",
149
+ "evaluate",
150
+ ]
151
+ CRITIC_KEYWORDS = [
152
+ "review",
153
+ "critique",
154
+ "evaluate",
155
+ "quality",
156
+ "check",
157
+ "verify",
158
+ "feedback",
159
+ ]
160
+
161
+ # Dev builder: explicit roles are set by planner; no inference needed for build tasks
162
+ DEV_ROLES = [
163
+ ARCHITECT_AGENT,
164
+ BACKEND_AGENT,
165
+ FRONTEND_AGENT,
166
+ TEST_AGENT,
167
+ REVIEW_AGENT,
168
+ ]
169
+
170
+
171
+ def infer_role_from_description(description: str) -> str:
172
+ """Infer agent role from task description. Returns role name or DEFAULT_ROLE."""
173
+ text = (description or "").lower()
174
+ scores = {
175
+ RESEARCH_AGENT: sum(1 for k in RESEARCH_KEYWORDS if k in text),
176
+ CODE_AGENT: sum(1 for k in CODE_KEYWORDS if k in text),
177
+ ANALYSIS_AGENT: sum(1 for k in ANALYSIS_KEYWORDS if k in text),
178
+ CRITIC_AGENT: sum(1 for k in CRITIC_KEYWORDS if k in text),
179
+ }
180
+ best = max(scores, key=scores.get)
181
+ return best if scores[best] > 0 else DEFAULT_ROLE
@@ -0,0 +1,78 @@
1
+ """
2
+ Entry point for Rust worker subprocess executor.
3
+ Reads AgentRequest JSON from stdin, runs agent, writes AgentResponse JSON to stdout.
4
+ Exit 0 on success, 1 on error (error details in AgentResponse.error on stdout).
5
+ """
6
+
7
+ import json
8
+ import sys
9
+
10
+ from devsper.credentials import inject_into_env
11
+ from devsper.agents.agent import Agent, AgentRequest, AgentResponse
12
+
13
+
14
+ def run_agent_sync(request_json: str) -> str:
15
+ """Run agent from JSON request string; return JSON response string. Used by PyO3 executor."""
16
+ data = json.loads(request_json)
17
+ request = AgentRequest.from_dict(data)
18
+ agent = Agent()
19
+ response = agent.run(request) # sync, not async
20
+ return json.dumps(response.to_dict())
21
+
22
+
23
+ def main() -> None:
24
+ # So the subprocess can use GitHub/OpenAI etc.: inject keychain credentials into env
25
+ # (same as config resolver does for the Python worker).
26
+ inject_into_env()
27
+ try:
28
+ raw = sys.stdin.read()
29
+ if not raw.strip():
30
+ out = AgentResponse(
31
+ task_id="",
32
+ result="",
33
+ tools_called=[],
34
+ broadcasts=[],
35
+ tokens_used=None,
36
+ duration_seconds=0.0,
37
+ error="Empty stdin",
38
+ success=False,
39
+ )
40
+ print(json.dumps(out.to_dict()), flush=True)
41
+ sys.exit(1)
42
+ request = AgentRequest.from_dict(json.loads(raw))
43
+ except Exception as e:
44
+ out = AgentResponse(
45
+ task_id="",
46
+ result="",
47
+ tools_called=[],
48
+ broadcasts=[],
49
+ tokens_used=None,
50
+ duration_seconds=0.0,
51
+ error=str(e),
52
+ success=False,
53
+ )
54
+ print(json.dumps(out.to_dict()), flush=True)
55
+ sys.exit(1)
56
+
57
+ try:
58
+ agent = Agent()
59
+ response = agent.run(request) # sync, not async
60
+ print(json.dumps(response.to_dict()), flush=True)
61
+ sys.exit(0 if response.success else 1)
62
+ except Exception as e:
63
+ out = AgentResponse(
64
+ task_id=request.task.id,
65
+ result="",
66
+ tools_called=[],
67
+ broadcasts=[],
68
+ tokens_used=None,
69
+ duration_seconds=0.0,
70
+ error=str(e),
71
+ success=False,
72
+ )
73
+ print(json.dumps(out.to_dict()), flush=True)
74
+ sys.exit(1)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,5 @@
1
+ """Tool usage analytics: count, success rate, latency. Persisted in SQLite."""
2
+
3
+ from devsper.analytics.tool_analytics import ToolAnalytics, get_default_analytics
4
+
5
+ __all__ = ["ToolAnalytics", "get_default_analytics"]
@@ -0,0 +1,78 @@
1
+ """
2
+ Track tool usage: count, success rate, latency. Persist in SQLite.
3
+ """
4
+
5
+ import sqlite3
6
+ import time
7
+ from pathlib import Path
8
+
9
+
10
+ class ToolAnalytics:
11
+ """SQLite-backed analytics for tool usage: count, success rate, latency."""
12
+
13
+ def __init__(self, db_path: str | Path | None = None) -> None:
14
+ if db_path is None:
15
+ db_path = Path(".devsper") / "tool_analytics.db"
16
+ self.db_path = Path(db_path)
17
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
18
+ self._init_schema()
19
+
20
+ def _conn(self) -> sqlite3.Connection:
21
+ return sqlite3.connect(str(self.db_path))
22
+
23
+ def _init_schema(self) -> None:
24
+ with self._conn() as c:
25
+ c.execute("""
26
+ CREATE TABLE IF NOT EXISTS tool_usage (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ tool_name TEXT NOT NULL,
29
+ success INTEGER NOT NULL,
30
+ latency_ms REAL NOT NULL,
31
+ created_at REAL NOT NULL
32
+ )
33
+ """)
34
+ c.execute(
35
+ "CREATE INDEX IF NOT EXISTS idx_tool_usage_name ON tool_usage(tool_name)"
36
+ )
37
+
38
+ def record(self, tool_name: str, success: bool, latency_ms: float) -> None:
39
+ """Record one tool invocation."""
40
+ with self._conn() as c:
41
+ c.execute(
42
+ "INSERT INTO tool_usage (tool_name, success, latency_ms, created_at) VALUES (?, ?, ?, ?)",
43
+ (tool_name, 1 if success else 0, latency_ms, time.time()),
44
+ )
45
+
46
+ def get_stats(self) -> list[dict]:
47
+ """Return per-tool stats: name, count, success_rate, avg_latency_ms."""
48
+ with self._conn() as c:
49
+ rows = c.execute("""
50
+ SELECT tool_name,
51
+ COUNT(*) AS cnt,
52
+ SUM(success) AS ok,
53
+ AVG(latency_ms) AS avg_ms
54
+ FROM tool_usage
55
+ GROUP BY tool_name
56
+ ORDER BY cnt DESC
57
+ """).fetchall()
58
+ return [
59
+ {
60
+ "tool_name": r[0],
61
+ "count": r[1],
62
+ "success_count": r[2],
63
+ "success_rate": (r[2] / r[1] * 100.0) if r[1] else 0.0,
64
+ "avg_latency_ms": round(r[3], 2) if r[3] is not None else 0.0,
65
+ }
66
+ for r in rows
67
+ ]
68
+
69
+
70
+ _default: ToolAnalytics | None = None
71
+
72
+
73
+ def get_default_analytics() -> ToolAnalytics:
74
+ """Return the default analytics instance (singleton)."""
75
+ global _default
76
+ if _default is None:
77
+ _default = ToolAnalytics()
78
+ return _default
@@ -0,0 +1,5 @@
1
+ """Audit logging for compliance (v2.0)."""
2
+
3
+ from devsper.audit.logger import AuditLogger, AuditRecord
4
+
5
+ __all__ = ["AuditLogger", "AuditRecord"]
@@ -0,0 +1,214 @@
1
+ """
2
+ Append-only audit log: JSONL file per run with optional chain integrity.
3
+ """
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import uuid
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+
13
+
14
+ @dataclass
15
+ class AuditRecord:
16
+ """Single append-only audit entry."""
17
+ record_id: str
18
+ timestamp: str
19
+ run_id: str
20
+ task_id: str
21
+ agent_id: str
22
+ event_type: str
23
+ actor: str
24
+ resource: str
25
+ input_hash: str
26
+ output_hash: str
27
+ decision_rationale: str | None = None
28
+ quota_usage: dict | None = None
29
+ pii_detected: bool = False
30
+ pii_redacted: bool = False
31
+ duration_ms: int = 0
32
+ success: bool = True
33
+ prev_record_hash: str = ""
34
+
35
+ def to_json_line(self) -> str:
36
+ d = {
37
+ "record_id": self.record_id,
38
+ "timestamp": self.timestamp,
39
+ "run_id": self.run_id,
40
+ "task_id": self.task_id,
41
+ "agent_id": self.agent_id,
42
+ "event_type": self.event_type,
43
+ "actor": self.actor,
44
+ "resource": self.resource,
45
+ "input_hash": self.input_hash,
46
+ "output_hash": self.output_hash,
47
+ "decision_rationale": self.decision_rationale,
48
+ "quota_usage": self.quota_usage,
49
+ "pii_detected": self.pii_detected,
50
+ "pii_redacted": self.pii_redacted,
51
+ "duration_ms": self.duration_ms,
52
+ "success": self.success,
53
+ "prev_record_hash": self.prev_record_hash,
54
+ }
55
+ return json.dumps(d, sort_keys=True)
56
+
57
+ @classmethod
58
+ def from_json_line(cls, line: str) -> "AuditRecord":
59
+ d = json.loads(line)
60
+ return cls(
61
+ record_id=d.get("record_id", ""),
62
+ timestamp=d.get("timestamp", ""),
63
+ run_id=d.get("run_id", ""),
64
+ task_id=d.get("task_id", ""),
65
+ agent_id=d.get("agent_id", ""),
66
+ event_type=d.get("event_type", ""),
67
+ actor=d.get("actor", ""),
68
+ resource=d.get("resource", ""),
69
+ input_hash=d.get("input_hash", ""),
70
+ output_hash=d.get("output_hash", ""),
71
+ decision_rationale=d.get("decision_rationale"),
72
+ quota_usage=d.get("quota_usage"),
73
+ pii_detected=bool(d.get("pii_detected", False)),
74
+ pii_redacted=bool(d.get("pii_redacted", False)),
75
+ duration_ms=int(d.get("duration_ms", 0)),
76
+ success=bool(d.get("success", True)),
77
+ prev_record_hash=d.get("prev_record_hash", ""),
78
+ )
79
+
80
+
81
+ def _sha256(s: str) -> str:
82
+ return hashlib.sha256(s.encode("utf-8")).hexdigest()
83
+
84
+
85
+ def make_audit_record(
86
+ run_id: str,
87
+ task_id: str,
88
+ event_type: str,
89
+ actor: str = "",
90
+ resource: str = "",
91
+ input_text: str = "",
92
+ output_text: str = "",
93
+ duration_ms: int = 0,
94
+ success: bool = True,
95
+ pii_detected: bool = False,
96
+ pii_redacted: bool = False,
97
+ agent_id: str = "",
98
+ ) -> AuditRecord:
99
+ """Build an AuditRecord with hashes; agent_id = node_id + task_id or task_id."""
100
+ ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
101
+ return AuditRecord(
102
+ record_id=str(uuid.uuid4()),
103
+ timestamp=ts,
104
+ run_id=run_id,
105
+ task_id=task_id,
106
+ agent_id=agent_id or task_id,
107
+ event_type=event_type,
108
+ actor=actor,
109
+ resource=resource,
110
+ input_hash=_sha256(input_text),
111
+ output_hash=_sha256(output_text),
112
+ pii_detected=pii_detected,
113
+ pii_redacted=pii_redacted,
114
+ duration_ms=duration_ms,
115
+ success=success,
116
+ )
117
+
118
+
119
+ class AuditLogger:
120
+ """Write-once append-only audit log. Backend: JSONL file under data_dir/audit/{run_id}.audit.jsonl."""
121
+
122
+ def __init__(self, data_dir: str, run_id: str = "") -> None:
123
+ self.data_dir = data_dir
124
+ self.run_id = run_id
125
+ self._audit_dir = os.path.join(data_dir, "audit")
126
+ self._last_hash: str = ""
127
+ self._file_path: str | None = None
128
+
129
+ def _path(self, run_id: str) -> str:
130
+ return os.path.join(self._audit_dir, f"{run_id or self.run_id}.audit.jsonl")
131
+
132
+ def _ensure_chain(self, path: str) -> None:
133
+ """Load last line hash from file so chain continues."""
134
+ if self._file_path == path:
135
+ return
136
+ self._file_path = path
137
+ self._last_hash = ""
138
+ if os.path.isfile(path):
139
+ try:
140
+ with open(path, "r", encoding="utf-8") as f:
141
+ for line in f:
142
+ line = line.strip()
143
+ if line:
144
+ self._last_hash = _sha256(line)
145
+ except Exception:
146
+ pass
147
+
148
+ def log(self, record: AuditRecord) -> None:
149
+ """Append one record to the run's audit file. Permissions 0o600."""
150
+ run_id = record.run_id or self.run_id
151
+ if not run_id:
152
+ return
153
+ path = self._path(run_id)
154
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
155
+ self._ensure_chain(path)
156
+ record.prev_record_hash = self._last_hash
157
+ line = record.to_json_line()
158
+ self._last_hash = _sha256(line)
159
+ with open(path, "a", encoding="utf-8") as f:
160
+ f.write(line + "\n")
161
+ try:
162
+ os.chmod(path, 0o600)
163
+ except Exception:
164
+ pass
165
+
166
+ def export(self, run_id: str, format: str = "jsonl") -> str:
167
+ """Return formatted export for compliance (jsonl, csv, siem)."""
168
+ path = self._path(run_id)
169
+ if not os.path.isfile(path):
170
+ return ""
171
+ with open(path, "r", encoding="utf-8") as f:
172
+ lines = [ln.strip() for ln in f if ln.strip()]
173
+ records = [AuditRecord.from_json_line(ln) for ln in lines]
174
+ if format == "jsonl":
175
+ return "\n".join(r.to_json_line() for r in records)
176
+ if format == "csv":
177
+ headers = [
178
+ "record_id", "timestamp", "run_id", "task_id", "event_type",
179
+ "actor", "resource", "success", "duration_ms", "pii_detected", "pii_redacted",
180
+ ]
181
+ rows = [
182
+ ",".join(
183
+ str(getattr(r, h, "")).replace(",", ";")
184
+ for h in headers
185
+ )
186
+ for r in records
187
+ ]
188
+ return ",".join(headers) + "\n" + "\n".join(rows)
189
+ if format == "siem":
190
+ return json.dumps([r.to_json_line() for r in records])
191
+ return "\n".join(r.to_json_line() for r in records)
192
+
193
+ @staticmethod
194
+ def verify(run_id: str, data_dir: str) -> tuple[bool, str]:
195
+ """
196
+ Verify chain integrity: each record's prev_record_hash must match previous line hash.
197
+ Return (ok, message).
198
+ """
199
+ audit_dir = os.path.join(data_dir, "audit")
200
+ path = os.path.join(audit_dir, f"{run_id}.audit.jsonl")
201
+ if not os.path.isfile(path):
202
+ return False, "Audit file not found"
203
+ prev_hash = ""
204
+ with open(path, "r", encoding="utf-8") as f:
205
+ for i, line in enumerate(f, 1):
206
+ line = line.strip()
207
+ if not line:
208
+ continue
209
+ rec = AuditRecord.from_json_line(line)
210
+ expected = _sha256(line)
211
+ if rec.prev_record_hash != prev_hash:
212
+ return False, f"Chain break at record {i}: prev_record_hash mismatch"
213
+ prev_hash = expected
214
+ return True, "Chain intact"