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,60 @@
1
+ """
2
+ Lightweight embeddings for memory index.
3
+
4
+ Uses optional OpenAI embeddings when OPENAI_API_KEY is set;
5
+ otherwise a deterministic stub for tests and offline use.
6
+ """
7
+
8
+ import hashlib
9
+ import os
10
+ import re
11
+
12
+ DEFAULT_DIM = 64
13
+
14
+
15
+ def _stub_embed(text: str, dim: int = DEFAULT_DIM) -> list[float]:
16
+ """Deterministic pseudo-embedding from text (no API). Normalized to unit-ish scale."""
17
+ words = re.findall(r"\w+", text.lower())
18
+ if not words:
19
+ return [0.0] * dim
20
+ vec = [0.0] * dim
21
+ for w in words:
22
+ h = hashlib.sha256(w.encode()).hexdigest()
23
+ for i in range(0, min(dim * 2, len(h)), 2):
24
+ vec[i % dim] += int(h[i : i + 2], 16) / 255.0
25
+ total = sum(x * x for x in vec) ** 0.5
26
+ if total > 0:
27
+ vec = [x / total for x in vec]
28
+ return vec
29
+
30
+
31
+ def _openai_embed(text: str, dim: int = DEFAULT_DIM) -> list[float] | None:
32
+ """Use OpenAI embeddings if available. Returns None if not configured."""
33
+ api_key = os.environ.get("OPENAI_API_KEY")
34
+ if not api_key or not text.strip():
35
+ return None
36
+ try:
37
+ from openai import OpenAI
38
+
39
+ client = OpenAI(api_key=api_key)
40
+ r = client.embeddings.create(
41
+ input=text[:8192],
42
+ model="text-embedding-3-small",
43
+ )
44
+ emb = r.data[0].embedding
45
+ if dim and len(emb) > dim:
46
+ return emb[:dim]
47
+ return emb
48
+ except Exception:
49
+ return None
50
+
51
+
52
+ def embed_text(text: str, dim: int = DEFAULT_DIM) -> list[float]:
53
+ """
54
+ Return an embedding vector for text.
55
+ Uses OpenAI when OPENAI_API_KEY is set; otherwise a deterministic stub.
56
+ """
57
+ if not text or not text.strip():
58
+ return _stub_embed(" ", dim)
59
+ out = _openai_embed(text, dim)
60
+ return out if out is not None else _stub_embed(text, dim)
@@ -0,0 +1,97 @@
1
+ """
2
+ Semantic search across stored memory via embeddings and top_k retrieval.
3
+ """
4
+
5
+ from devsper.memory.embeddings import embed_text
6
+ from devsper.memory.memory_store import MemoryStore
7
+ from devsper.memory.memory_types import MemoryRecord
8
+
9
+
10
+ def _cosine_sim(a: list[float], b: list[float]) -> float:
11
+ if not a or not b or len(a) != len(b):
12
+ return 0.0
13
+ dot = sum(x * y for x, y in zip(a, b))
14
+ na = sum(x * x for x in a) ** 0.5
15
+ nb = sum(x * x for x in b) ** 0.5
16
+ if na == 0 or nb == 0:
17
+ return 0.0
18
+ return dot / (na * nb)
19
+
20
+
21
+ class MemoryIndex:
22
+ """
23
+ Vector search over memory. Uses store for persistence and optional
24
+ embeddings on records for query_memory(text, top_k).
25
+ """
26
+
27
+ def __init__(self, store: MemoryStore | None = None) -> None:
28
+ self.store = store or MemoryStore()
29
+
30
+ def query_memory(
31
+ self,
32
+ text: str,
33
+ top_k: int = 5,
34
+ min_similarity: float = 0.0,
35
+ include_archived: bool = False,
36
+ ) -> list[MemoryRecord]:
37
+ """
38
+ Semantic search: embed query, score against stored records with embeddings,
39
+ return top_k by similarity above min_similarity. Records without embeddings
40
+ are skipped for ranking; if none have embeddings, return latest by timestamp.
41
+ Use min_similarity > 0 (e.g. 0.45) to avoid injecting barely-related memory.
42
+ By default excludes archived records (consolidation).
43
+ """
44
+ records = self.store.list_memory(limit=500, include_archived=include_archived)
45
+ if not records:
46
+ return []
47
+ query_emb = embed_text(text)
48
+ with_emb = [r for r in records if r.embedding is not None]
49
+ if not with_emb:
50
+ return records[:top_k]
51
+ scored = [
52
+ (_cosine_sim(query_emb, r.embedding), r)
53
+ for r in with_emb
54
+ ]
55
+ scored.sort(key=lambda x: -x[0])
56
+ if min_similarity > 0:
57
+ scored = [(s, r) for s, r in scored if s >= min_similarity]
58
+ return [r for _, r in scored[:top_k]]
59
+
60
+ def query_across_runs(
61
+ self,
62
+ text: str,
63
+ top_k: int = 20,
64
+ min_similarity: float = 0.0,
65
+ run_id_filter: str | None = None,
66
+ include_archived: bool = False,
67
+ ) -> list[MemoryRecord]:
68
+ """
69
+ v1.8: Same as query_memory but over more records (all runs), optional run_id filter.
70
+ Used by CrossRunSynthesizer. Excludes archived by default.
71
+ """
72
+ records = self.store.list_memory(
73
+ limit=2000,
74
+ include_archived=include_archived,
75
+ run_id_filter=run_id_filter,
76
+ )
77
+ if not records:
78
+ return []
79
+ query_emb = embed_text(text)
80
+ with_emb = [r for r in records if r.embedding is not None]
81
+ if not with_emb:
82
+ return records[:top_k]
83
+ scored = [
84
+ (_cosine_sim(query_emb, r.embedding), r)
85
+ for r in with_emb
86
+ ]
87
+ scored.sort(key=lambda x: -x[0])
88
+ if min_similarity > 0:
89
+ scored = [(s, r) for s, r in scored if s >= min_similarity]
90
+ return [r for _, r in scored[:top_k]]
91
+
92
+ def ensure_embedding(self, record: MemoryRecord) -> MemoryRecord:
93
+ """Compute and attach embedding if missing; return record (unchanged if already set)."""
94
+ if record.embedding is not None:
95
+ return record
96
+ record.embedding = embed_text(record.content)
97
+ return record
@@ -0,0 +1,62 @@
1
+ """
2
+ Memory router: determine which memories are relevant to a task and return context for the agent.
3
+ """
4
+
5
+ from devsper.memory.memory_index import MemoryIndex
6
+ from devsper.memory.memory_store import MemoryStore
7
+ from devsper.memory.memory_types import MemoryRecord
8
+
9
+
10
+ class MemoryRouter:
11
+ """
12
+ Routes task descriptions to relevant memories (e.g. research, papers, codebase)
13
+ and formats them as context for the agent. Only memories above min_similarity
14
+ are included to avoid injecting off-topic context.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ store: MemoryStore | None = None,
20
+ index: MemoryIndex | None = None,
21
+ top_k: int = 10,
22
+ min_similarity: float = 0.55,
23
+ ) -> None:
24
+ self.store = store or MemoryStore()
25
+ self.index = index or MemoryIndex(self.store)
26
+ self.top_k = top_k
27
+ self.min_similarity = min_similarity
28
+
29
+ def get_relevant_memory(self, task: str) -> list[MemoryRecord]:
30
+ """
31
+ Return memories relevant to the task (semantic search).
32
+ Only returns records with similarity >= min_similarity to avoid off-topic injection.
33
+ """
34
+ return self.index.query_memory(
35
+ task,
36
+ top_k=self.top_k,
37
+ min_similarity=self.min_similarity,
38
+ )
39
+
40
+ def get_memory_context(self, task: str) -> str:
41
+ """
42
+ Format relevant memories as a string block for injection into the agent prompt.
43
+ User injections (tag user_injection) are always included first. Then semantic results.
44
+ Empty if no memories meet the relevance threshold.
45
+ """
46
+ lines = []
47
+ inject_records = self.store.list_memory(tag_contains="user_injection", limit=10)
48
+ if inject_records:
49
+ lines.append("USER INJECTIONS (high priority):")
50
+ for r in inject_records:
51
+ lines.append(f"- {r.content[:1000]}{'...' if len(r.content) > 1000 else ''}")
52
+ records = self.get_relevant_memory(task)
53
+ if records:
54
+ if lines:
55
+ lines.append("")
56
+ lines.append("RELEVANT MEMORY (previous research notes, findings, artifacts):")
57
+ for r in records:
58
+ lines.append(
59
+ f"- [{r.memory_type.value}] {r.source_task or 'general'}: "
60
+ f"{r.content[:500]}{'...' if len(r.content) > 500 else ''}"
61
+ )
62
+ return "\n".join(lines) if lines else ""
@@ -0,0 +1,221 @@
1
+ """
2
+ Persistent memory store: SQLite-backed store, retrieve, delete, list.
3
+ """
4
+
5
+ import json
6
+ import os
7
+ import sqlite3
8
+ import uuid
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+
12
+ from devsper.memory.memory_types import MemoryRecord, MemoryType
13
+
14
+
15
+ def _default_db_path() -> str:
16
+ from devsper.config import get_config
17
+
18
+ base = os.environ.get("DEVSPER_DATA_DIR") or get_config().data_dir
19
+ os.makedirs(base, exist_ok=True)
20
+ return os.path.join(base, "memory.db")
21
+
22
+
23
+ class MemoryStore:
24
+ """Local persistent store for memory records. Uses SQLite."""
25
+
26
+ def __init__(self, db_path: str | None = None) -> None:
27
+ self.db_path = db_path or _default_db_path()
28
+ Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
29
+ self._init_schema()
30
+
31
+ def _conn(self) -> sqlite3.Connection:
32
+ return sqlite3.connect(self.db_path)
33
+
34
+ def _init_schema(self) -> None:
35
+ with self._conn() as conn:
36
+ conn.execute(
37
+ """
38
+ CREATE TABLE IF NOT EXISTS memory (
39
+ memory_id TEXT PRIMARY KEY,
40
+ memory_type TEXT NOT NULL,
41
+ content TEXT NOT NULL,
42
+ tags TEXT,
43
+ timestamp TEXT NOT NULL,
44
+ source_task TEXT,
45
+ embedding TEXT
46
+ )
47
+ """
48
+ )
49
+ conn.execute(
50
+ "CREATE INDEX IF NOT EXISTS ix_memory_type ON memory(memory_type)"
51
+ )
52
+ conn.execute(
53
+ "CREATE INDEX IF NOT EXISTS ix_memory_timestamp ON memory(timestamp)"
54
+ )
55
+ try:
56
+ conn.execute("ALTER TABLE memory ADD COLUMN run_id TEXT DEFAULT ''")
57
+ except sqlite3.OperationalError:
58
+ pass
59
+ try:
60
+ conn.execute("ALTER TABLE memory ADD COLUMN archived INTEGER DEFAULT 0")
61
+ except sqlite3.OperationalError:
62
+ pass
63
+
64
+ def store(self, record: MemoryRecord) -> str:
65
+ """Store a memory record. Returns record id. Redacts PII if compliance.pii_redaction enabled."""
66
+ content = record.content
67
+ try:
68
+ from devsper.config import get_config
69
+ cfg = get_config()
70
+ if getattr(getattr(cfg, "compliance", None), "pii_redaction", False):
71
+ from devsper.compliance.pii import PIIRedactor
72
+ redactor = PIIRedactor(
73
+ pii_types=getattr(cfg.compliance, "pii_types", None),
74
+ gdpr_mode=getattr(cfg.compliance, "gdpr_mode", False),
75
+ )
76
+ res = redactor.redact(content or "")
77
+ content = res.redacted_text
78
+ except Exception:
79
+ pass
80
+ if content != record.content:
81
+ record = record.model_copy(update={"content": content})
82
+ row = record.to_store_row()
83
+ emb = row.get("embedding")
84
+ embedding_json = json.dumps(emb) if emb is not None else None
85
+ archived = row.get("archived", 0)
86
+ run_id = row.get("run_id", "") or ""
87
+ with self._conn() as conn:
88
+ conn.execute(
89
+ """
90
+ INSERT OR REPLACE INTO memory
91
+ (memory_id, memory_type, content, tags, timestamp, source_task, embedding, run_id, archived)
92
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
93
+ """,
94
+ (
95
+ row["memory_id"],
96
+ row["memory_type"],
97
+ row["content"],
98
+ row["tags"],
99
+ row["timestamp"],
100
+ row["source_task"],
101
+ embedding_json,
102
+ run_id,
103
+ archived,
104
+ ),
105
+ )
106
+ return row["memory_id"]
107
+
108
+ def retrieve(self, memory_id: str) -> MemoryRecord | None:
109
+ """Retrieve a single record by id."""
110
+ with self._conn() as conn:
111
+ conn.row_factory = sqlite3.Row
112
+ cur = conn.execute(
113
+ "SELECT memory_id, memory_type, content, tags, timestamp, source_task, embedding, run_id, archived FROM memory WHERE memory_id = ?",
114
+ (memory_id,),
115
+ )
116
+ row = cur.fetchone()
117
+ if row is None:
118
+ return None
119
+ return _row_to_record(dict(row))
120
+
121
+ def delete(self, memory_id: str) -> bool:
122
+ """Delete a record. Returns True if something was deleted."""
123
+ with self._conn() as conn:
124
+ cur = conn.execute("DELETE FROM memory WHERE memory_id = ?", (memory_id,))
125
+ return cur.rowcount > 0
126
+
127
+ def list_memory(
128
+ self,
129
+ memory_type: MemoryType | None = None,
130
+ limit: int = 100,
131
+ offset: int = 0,
132
+ tag_contains: str | None = None,
133
+ include_archived: bool = False,
134
+ run_id_filter: str | None = None,
135
+ ) -> list[MemoryRecord]:
136
+ """List records, optionally filtered by type, tag, archived, run_id, with limit/offset."""
137
+ with self._conn() as conn:
138
+ conn.row_factory = sqlite3.Row
139
+ conditions = []
140
+ params = []
141
+ if memory_type is not None:
142
+ conditions.append("memory_type = ?")
143
+ params.append(memory_type.value)
144
+ if tag_contains:
145
+ conditions.append("tags LIKE ?")
146
+ params.append(f"%{tag_contains}%")
147
+ if not include_archived:
148
+ conditions.append("COALESCE(archived, 0) = 0")
149
+ if run_id_filter is not None:
150
+ conditions.append("run_id = ?")
151
+ params.append(run_id_filter)
152
+ where = (" WHERE " + " AND ".join(conditions)) if conditions else ""
153
+ params.extend([limit, offset])
154
+ cur = conn.execute(
155
+ f"""
156
+ SELECT memory_id, memory_type, content, tags, timestamp, source_task, embedding,
157
+ COALESCE(run_id, '') as run_id, COALESCE(archived, 0) as archived
158
+ FROM memory{where} ORDER BY timestamp DESC LIMIT ? OFFSET ?
159
+ """,
160
+ params,
161
+ )
162
+ rows = cur.fetchall()
163
+ return [_row_to_record(dict(r)) for r in rows]
164
+
165
+ def list_all_ids(self, memory_type: MemoryType | None = None) -> list[str]:
166
+ """List all memory ids (for index sync)."""
167
+ with self._conn() as conn:
168
+ if memory_type is not None:
169
+ cur = conn.execute(
170
+ "SELECT memory_id FROM memory WHERE memory_type = ?",
171
+ (memory_type.value,),
172
+ )
173
+ else:
174
+ cur = conn.execute("SELECT memory_id FROM memory")
175
+ return [r[0] for r in cur.fetchall()]
176
+
177
+ def set_archived(self, memory_id: str, archived: bool = True) -> bool:
178
+ """v1.8: Mark a record as archived (e.g. after consolidation)."""
179
+ with self._conn() as conn:
180
+ cur = conn.execute(
181
+ "UPDATE memory SET archived = ? WHERE memory_id = ?",
182
+ (1 if archived else 0, memory_id),
183
+ )
184
+ return cur.rowcount > 0
185
+
186
+
187
+ def _row_to_record(row: dict) -> MemoryRecord:
188
+ tags_str = row.get("tags") or ""
189
+ tags = [t.strip() for t in tags_str.split(",") if t.strip()]
190
+ emb_raw = row.get("embedding")
191
+ embedding = json.loads(emb_raw) if isinstance(emb_raw, str) and emb_raw else None
192
+ archived = row.get("archived")
193
+ if archived is None and "archived" not in row:
194
+ archived = 0
195
+ return MemoryRecord(
196
+ id=row["memory_id"],
197
+ memory_type=MemoryType(row["memory_type"]),
198
+ content=row["content"],
199
+ tags=tags,
200
+ timestamp=datetime.fromisoformat(row["timestamp"]),
201
+ source_task=row.get("source_task") or "",
202
+ embedding=embedding,
203
+ run_id=row.get("run_id") or "",
204
+ archived=bool(archived) if isinstance(archived, (int, bool)) else False,
205
+ )
206
+
207
+
208
+ def generate_memory_id() -> str:
209
+ """Generate a unique memory id."""
210
+ return str(uuid.uuid4())
211
+
212
+
213
+ _default_store: MemoryStore | None = None
214
+
215
+
216
+ def get_default_store() -> MemoryStore:
217
+ """Return the default process-wide memory store (for tools)."""
218
+ global _default_store
219
+ if _default_store is None:
220
+ _default_store = MemoryStore()
221
+ return _default_store
@@ -0,0 +1,54 @@
1
+ """
2
+ Structured memory types for the swarm.
3
+
4
+ Each memory record has: id, timestamp, source_task, content, tags, embedding (optional).
5
+ """
6
+
7
+ from datetime import datetime, timezone
8
+ from enum import Enum
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+
14
+ class MemoryType(str, Enum):
15
+ """Kind of memory for routing and indexing."""
16
+
17
+ EPISODIC = "episodic"
18
+ SEMANTIC = "semantic"
19
+ ARTIFACT = "artifact"
20
+ RESEARCH = "research"
21
+
22
+
23
+ class MemoryRecord(BaseModel):
24
+ """Single memory entry with optional embedding."""
25
+
26
+ id: str
27
+ memory_type: MemoryType
28
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
29
+ source_task: str = ""
30
+ content: str
31
+ tags: list[str] = Field(default_factory=list)
32
+ embedding: list[float] | None = None
33
+ run_id: str = "" # v1.8: which swarm run produced this (for cross-run synthesis)
34
+ archived: bool = False # v1.8: consolidated into a summary record
35
+
36
+ def to_store_row(self) -> dict[str, Any]:
37
+ """Serialize for storage (embedding as JSON list or null)."""
38
+ return {
39
+ "memory_id": self.id,
40
+ "memory_type": self.memory_type.value,
41
+ "content": self.content,
42
+ "tags": ",".join(self.tags) if self.tags else "",
43
+ "timestamp": self.timestamp.isoformat(),
44
+ "source_task": self.source_task,
45
+ "embedding": self.embedding,
46
+ "run_id": getattr(self, "run_id", "") or "",
47
+ "archived": 1 if getattr(self, "archived", False) else 0,
48
+ }
49
+
50
+
51
+ EpisodicMemory = MemoryRecord
52
+ SemanticMemory = MemoryRecord
53
+ ArtifactMemory = MemoryRecord
54
+ ResearchMemory = MemoryRecord
@@ -0,0 +1,45 @@
1
+ """
2
+ Memory namespaces: filter and tag memories by namespace (e.g. research_memory, coding_memory).
3
+ Implemented via tags with prefix "ns:" for backward compatibility.
4
+ """
5
+
6
+ from devsper.memory.memory_types import MemoryRecord
7
+
8
+ NAMESPACE_TAG_PREFIX = "ns:"
9
+
10
+ # Standard namespaces
11
+ RESEARCH_MEMORY = "research_memory"
12
+ CODING_MEMORY = "coding_memory"
13
+ DATASET_MEMORY = "dataset_memory"
14
+
15
+ DEFAULT_NAMESPACES = [RESEARCH_MEMORY, CODING_MEMORY, DATASET_MEMORY]
16
+
17
+
18
+ def namespace_tag(namespace: str) -> str:
19
+ """Return the tag string for a namespace."""
20
+ return f"{NAMESPACE_TAG_PREFIX}{namespace}"
21
+
22
+
23
+ def add_namespace(record: MemoryRecord, namespace: str) -> MemoryRecord:
24
+ """Add namespace tag to a record (returns new record with tag)."""
25
+ tag = namespace_tag(namespace)
26
+ tags = list(record.tags) if record.tags else []
27
+ if tag not in tags:
28
+ tags.append(tag)
29
+ return record.model_copy(update={"tags": tags})
30
+
31
+
32
+ def record_namespace(record: MemoryRecord) -> str | None:
33
+ """Extract namespace from record tags, or None if none."""
34
+ if not record.tags:
35
+ return None
36
+ for t in record.tags:
37
+ if t.startswith(NAMESPACE_TAG_PREFIX):
38
+ return t[len(NAMESPACE_TAG_PREFIX) :]
39
+ return None
40
+
41
+
42
+ def filter_by_namespace(records: list[MemoryRecord], namespace: str) -> list[MemoryRecord]:
43
+ """Return records that have the given namespace tag."""
44
+ tag = namespace_tag(namespace)
45
+ return [r for r in records if r.tags and tag in r.tags]
@@ -0,0 +1,77 @@
1
+ """
2
+ Memory scoring: relevance, recency, importance for ranking results.
3
+ """
4
+
5
+ from datetime import datetime, timezone
6
+ from math import exp
7
+
8
+ from devsper.memory.memory_types import MemoryRecord
9
+
10
+
11
+ def recency_score(record: MemoryRecord, now: datetime | None = None) -> float:
12
+ """Score by recency: newer = higher. Exponential decay."""
13
+ now = now or datetime.now(timezone.utc)
14
+ ts = record.timestamp
15
+ delta = (now - ts).total_seconds()
16
+ # Half-life ~ 1 day: score = 0.5 at 86400 seconds
17
+ half_life = 86400.0
18
+ return exp(-delta * (0.693 / half_life))
19
+
20
+
21
+ def importance_score(record: MemoryRecord) -> float:
22
+ """Heuristic importance: longer content and more tags = slightly higher."""
23
+ base = 1.0
24
+ content_len = len(record.content or "")
25
+ if content_len > 500:
26
+ base += 0.2
27
+ if content_len > 2000:
28
+ base += 0.2
29
+ tag_count = len(record.tags or [])
30
+ if tag_count > 3:
31
+ base += 0.1
32
+ return min(base, 2.0)
33
+
34
+
35
+ def combine_scores(
36
+ similarity: float,
37
+ recency: float,
38
+ importance: float,
39
+ similarity_weight: float = 0.7,
40
+ recency_weight: float = 0.2,
41
+ importance_weight: float = 0.1,
42
+ ) -> float:
43
+ """Combine normalized scores. Default: similarity dominates."""
44
+ return (
45
+ similarity_weight * similarity
46
+ + recency_weight * recency
47
+ + importance_weight * importance
48
+ )
49
+
50
+
51
+ def score_and_sort(
52
+ records: list[MemoryRecord],
53
+ similarity_scores: list[float] | None = None,
54
+ now: datetime | None = None,
55
+ ) -> list[tuple[MemoryRecord, float]]:
56
+ """
57
+ Return (record, combined_score) sorted by score descending.
58
+ If similarity_scores is None, use 1.0 for all (recency + importance only).
59
+ """
60
+ if similarity_scores is None:
61
+ similarity_scores = [1.0] * len(records)
62
+ if len(similarity_scores) != len(records):
63
+ similarity_scores = [1.0] * len(records)
64
+ now = now or datetime.now(timezone.utc)
65
+ scored = [
66
+ (
67
+ r,
68
+ combine_scores(
69
+ similarity_scores[i],
70
+ recency_score(r, now),
71
+ importance_score(r),
72
+ ),
73
+ )
74
+ for i, r in enumerate(records)
75
+ ]
76
+ scored.sort(key=lambda x: -x[1])
77
+ return scored
@@ -0,0 +1,52 @@
1
+ """
2
+ Memory summarization: summarize a set of memory records into shorter text.
3
+ Optional LLM or extractive (first N chars / key sentences).
4
+ """
5
+
6
+ from devsper.memory.memory_types import MemoryRecord
7
+
8
+ MAX_EXTRACTIVE_CHARS = 4000
9
+
10
+
11
+ def summarize_extractive(records: list[MemoryRecord], max_chars: int = MAX_EXTRACTIVE_CHARS) -> str:
12
+ """Concatenate record contents up to max_chars (no LLM)."""
13
+ parts: list[str] = []
14
+ total = 0
15
+ for r in records:
16
+ if total >= max_chars:
17
+ break
18
+ chunk = (r.content or "")[: max_chars - total]
19
+ if chunk:
20
+ parts.append(chunk)
21
+ total += len(chunk)
22
+ return "\n\n".join(parts) if parts else ""
23
+
24
+
25
+ def summarize_with_llm(records: list[MemoryRecord], model_name: str = "gpt-4o-mini") -> str:
26
+ """Use LLM to summarize records. Returns summary or fallback to extractive on failure."""
27
+ text = summarize_extractive(records, max_chars=8000)
28
+ if not text.strip():
29
+ return ""
30
+ try:
31
+ from devsper.utils.models import generate
32
+ prompt = f"""Summarize the following memory entries into a concise summary (2-4 paragraphs).
33
+
34
+ {text}
35
+
36
+ Summary:"""
37
+ return generate(model_name, prompt).strip()
38
+ except Exception:
39
+ return summarize_extractive(records, max_chars=MAX_EXTRACTIVE_CHARS)
40
+
41
+
42
+ def summarize(
43
+ records: list[MemoryRecord],
44
+ use_llm: bool = False,
45
+ model_name: str = "gpt-4o-mini",
46
+ ) -> str:
47
+ """Summarize records. use_llm=False uses extractive only."""
48
+ if not records:
49
+ return ""
50
+ if use_llm:
51
+ return summarize_with_llm(records, model_name=model_name)
52
+ return summarize_extractive(records)
@@ -0,0 +1,5 @@
1
+ """Distributed nodes: controller, worker, single-node."""
2
+
3
+ from devsper.nodes.single import SingleNode
4
+
5
+ __all__ = ["SingleNode"]