qwen-sci 0.2.2__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 (549) hide show
  1. qwen_sci-0.2.2.dist-info/METADATA +492 -0
  2. qwen_sci-0.2.2.dist-info/RECORD +549 -0
  3. qwen_sci-0.2.2.dist-info/WHEEL +4 -0
  4. qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
  5. qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
  6. src/1.txt +1 -0
  7. src/__init__.py +1 -0
  8. src/__main__.py +5 -0
  9. src/agents/__init__.py +1 -0
  10. src/agents/experiment_design_agent/__init__.py +268 -0
  11. src/agents/experiment_design_agent/artifacts.py +747 -0
  12. src/agents/experiment_design_agent/cache.py +319 -0
  13. src/agents/experiment_design_agent/completeness.py +130 -0
  14. src/agents/experiment_design_agent/contracts.py +771 -0
  15. src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
  16. src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
  17. src/agents/experiment_design_agent/discipline_catalog.py +178 -0
  18. src/agents/experiment_design_agent/evidence_cards.py +338 -0
  19. src/agents/experiment_design_agent/evidence_planner.py +452 -0
  20. src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
  21. src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
  22. src/agents/experiment_design_agent/idea_adapter.py +100 -0
  23. src/agents/experiment_design_agent/idea_intake.py +87 -0
  24. src/agents/experiment_design_agent/llm_json.py +328 -0
  25. src/agents/experiment_design_agent/orchestrator.py +1220 -0
  26. src/agents/experiment_design_agent/reasoning_context.py +183 -0
  27. src/agents/experiment_design_agent/reasoning_validation.py +546 -0
  28. src/agents/experiment_design_agent/run.py +481 -0
  29. src/agents/experiment_design_agent/run_logging.py +292 -0
  30. src/agents/experiment_design_agent/scope_gate.py +198 -0
  31. src/agents/experiment_design_agent/study_type_composer.py +1360 -0
  32. src/agents/experiment_design_agent/survey_evidence.py +1381 -0
  33. src/agents/experiment_design_agent/template_router.py +195 -0
  34. src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
  35. src/agents/idea_agent/__init__.py +1 -0
  36. src/agents/idea_agent/agent/__init__.py +6 -0
  37. src/agents/idea_agent/agent/artifacts.py +308 -0
  38. src/agents/idea_agent/agent/base.py +122 -0
  39. src/agents/idea_agent/agent/ligagent.py +488 -0
  40. src/agents/idea_agent/agent/mcts.py +2060 -0
  41. src/agents/idea_agent/agent/prompts/__init__.py +48 -0
  42. src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
  43. src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
  44. src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
  45. src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
  46. src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
  47. src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
  48. src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
  49. src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
  50. src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
  51. src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
  52. src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
  53. src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
  54. src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
  55. src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
  56. src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
  57. src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
  58. src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
  59. src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
  60. src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
  61. src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
  62. src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
  63. src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
  64. src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
  65. src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
  66. src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
  67. src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
  68. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
  69. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
  70. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
  71. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
  72. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
  73. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
  74. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
  75. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
  76. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
  77. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
  78. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
  79. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
  80. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
  81. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
  82. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
  83. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
  84. src/agents/idea_agent/example/idea_result.json +900 -0
  85. src/agents/idea_agent/run.py +276 -0
  86. src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
  87. src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
  88. src/agents/idea_agent/tests/test_idea_debate.py +480 -0
  89. src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
  90. src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
  91. src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
  92. src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
  93. src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
  94. src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
  95. src/agents/idea_agent/tests/test_result_persistence.py +183 -0
  96. src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
  97. src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
  98. src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
  99. src/agents/idea_agent/utils/core/__init__.py +1 -0
  100. src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
  101. src/agents/idea_agent/utils/core/chat_errors.py +95 -0
  102. src/agents/idea_agent/utils/core/chat_router.py +130 -0
  103. src/agents/idea_agent/utils/core/chat_transport.py +188 -0
  104. src/agents/idea_agent/utils/core/config_loader.py +166 -0
  105. src/agents/idea_agent/utils/core/json_utils.py +28 -0
  106. src/agents/idea_agent/utils/core/logger.py +287 -0
  107. src/agents/idea_agent/utils/core/progress.py +58 -0
  108. src/agents/idea_agent/utils/core/response_parsing.py +57 -0
  109. src/agents/idea_agent/utils/core/run_inputs.py +198 -0
  110. src/agents/idea_agent/utils/mcts/__init__.py +1 -0
  111. src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
  112. src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
  113. src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
  114. src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
  115. src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
  116. src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
  117. src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
  118. src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
  119. src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
  120. src/agents/idea_agent/utils/papers/__init__.py +1 -0
  121. src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
  122. src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
  123. src/agents/idea_agent/utils/prompting/__init__.py +1 -0
  124. src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
  125. src/agents/idea_agent/utils/workflow/__init__.py +1 -0
  126. src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
  127. src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
  128. src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
  129. src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
  130. src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
  131. src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
  132. src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
  133. src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
  134. src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
  135. src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
  136. src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
  137. src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
  138. src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
  139. src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
  140. src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
  141. src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
  142. src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
  143. src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
  144. src/agents/research_plan_author/__init__.py +50 -0
  145. src/agents/research_plan_author/artifacts.py +270 -0
  146. src/agents/research_plan_author/authoring_blueprint.py +1246 -0
  147. src/agents/research_plan_author/bibtex_renderer.py +252 -0
  148. src/agents/research_plan_author/contract_repair.py +131 -0
  149. src/agents/research_plan_author/contracts.py +745 -0
  150. src/agents/research_plan_author/cross_section_editor.py +232 -0
  151. src/agents/research_plan_author/document_quality.py +987 -0
  152. src/agents/research_plan_author/idea_evolution.py +223 -0
  153. src/agents/research_plan_author/input_loader.py +76 -0
  154. src/agents/research_plan_author/latex_compiler.py +260 -0
  155. src/agents/research_plan_author/latex_safety.py +159 -0
  156. src/agents/research_plan_author/llm_json.py +74 -0
  157. src/agents/research_plan_author/markdown_renderer.py +275 -0
  158. src/agents/research_plan_author/pdf_validator.py +149 -0
  159. src/agents/research_plan_author/render.py +166 -0
  160. src/agents/research_plan_author/render_artifacts.py +187 -0
  161. src/agents/research_plan_author/run.py +765 -0
  162. src/agents/research_plan_author/run_logging.py +209 -0
  163. src/agents/research_plan_author/section_cache.py +184 -0
  164. src/agents/research_plan_author/section_composer.py +1764 -0
  165. src/agents/research_plan_author/section_router.py +209 -0
  166. src/agents/research_plan_author/semantic_validator.py +254 -0
  167. src/agents/research_plan_author/source_bundle.py +46 -0
  168. src/agents/research_plan_author/source_registry.py +208 -0
  169. src/agents/research_plan_author/survey_source_loader.py +137 -0
  170. src/agents/research_plan_author/template_adapter.py +173 -0
  171. src/agents/research_plan_author/template_profile.py +204 -0
  172. src/agents/research_plan_author/tex_renderer.py +496 -0
  173. src/agents/research_plan_author/theory_presentation.py +134 -0
  174. src/agents/research_plan_author/theory_spine.py +703 -0
  175. src/agents/survey_agent/Dockerfile +54 -0
  176. src/agents/survey_agent/README.md +350 -0
  177. src/agents/survey_agent/config/deep_survey.yaml +217 -0
  178. src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
  179. src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
  180. src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
  181. src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
  182. src/agents/survey_agent/env_report.md +348 -0
  183. src/agents/survey_agent/modules/__init__.py +0 -0
  184. src/agents/survey_agent/modules/code_collector.py +2194 -0
  185. src/agents/survey_agent/modules/code_report_generator.py +1282 -0
  186. src/agents/survey_agent/modules/data_manager.py +1713 -0
  187. src/agents/survey_agent/modules/database.py +167 -0
  188. src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
  189. src/agents/survey_agent/modules/fulltext_http.py +134 -0
  190. src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
  191. src/agents/survey_agent/modules/judge.py +434 -0
  192. src/agents/survey_agent/modules/outcome_RAG.py +236 -0
  193. src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
  194. src/agents/survey_agent/modules/pe.py +1729 -0
  195. src/agents/survey_agent/modules/pseudo_creator.py +782 -0
  196. src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
  197. src/agents/survey_agent/modules/refine_agent.py +1952 -0
  198. src/agents/survey_agent/modules/survey_generator.py +5691 -0
  199. src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
  200. src/agents/survey_agent/modules/visual_prompts.py +327 -0
  201. src/agents/survey_agent/modules/work_analyzer.py +2750 -0
  202. src/agents/survey_agent/modules/work_collector.py +5649 -0
  203. src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
  204. src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
  205. src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
  206. src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
  207. src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
  208. src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
  209. src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
  210. src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
  211. src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
  212. src/agents/survey_agent/utils/__init__.py +0 -0
  213. src/agents/survey_agent/utils/ablation_utils.py +317 -0
  214. src/agents/survey_agent/utils/api_call.py +2518 -0
  215. src/agents/survey_agent/utils/chat_utils.py +82 -0
  216. src/agents/survey_agent/utils/config_utils.py +70 -0
  217. src/agents/survey_agent/utils/convert_to_md.py +29 -0
  218. src/agents/survey_agent/utils/err_info.py +24 -0
  219. src/agents/survey_agent/utils/file_utils.py +176 -0
  220. src/agents/survey_agent/utils/gpu_utils.py +145 -0
  221. src/agents/survey_agent/utils/graph_visualizer.py +279 -0
  222. src/agents/survey_agent/utils/html_utils.py +181 -0
  223. src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
  224. src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
  225. src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
  226. src/agents/survey_agent/utils/mineru_utils.py +396 -0
  227. src/agents/survey_agent/utils/paper.py +144 -0
  228. src/agents/survey_agent/utils/read_yaml.py +15 -0
  229. src/agents/survey_agent/utils/repo_utils.py +167 -0
  230. src/agents/survey_agent/utils/rich_logger.py +77 -0
  231. src/agents/survey_agent/utils/step2v2.py +874 -0
  232. src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
  233. src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
  234. src/agents/survey_agent/utils/utils.py +118 -0
  235. src/agents/survey_agent/utils/work.py +2 -0
  236. src/cli.py +1611 -0
  237. src/config/__init__.py +213 -0
  238. src/config/default.yaml +1122 -0
  239. src/harness/LICENSE +21 -0
  240. src/harness/VENDORED.md +14 -0
  241. src/harness/src/__init__.py +0 -0
  242. src/harness/src/openharness/__init__.py +0 -0
  243. src/harness/src/openharness/__main__.py +6 -0
  244. src/harness/src/openharness/api/__init__.py +21 -0
  245. src/harness/src/openharness/api/client.py +281 -0
  246. src/harness/src/openharness/api/codex_client.py +407 -0
  247. src/harness/src/openharness/api/copilot_auth.py +240 -0
  248. src/harness/src/openharness/api/copilot_client.py +134 -0
  249. src/harness/src/openharness/api/errors.py +19 -0
  250. src/harness/src/openharness/api/openai_client.py +577 -0
  251. src/harness/src/openharness/api/provider.py +186 -0
  252. src/harness/src/openharness/api/registry.py +437 -0
  253. src/harness/src/openharness/api/usage.py +17 -0
  254. src/harness/src/openharness/auth/__init__.py +29 -0
  255. src/harness/src/openharness/auth/external.py +610 -0
  256. src/harness/src/openharness/auth/flows.py +189 -0
  257. src/harness/src/openharness/auth/manager.py +482 -0
  258. src/harness/src/openharness/auth/storage.py +269 -0
  259. src/harness/src/openharness/autopilot/__init__.py +23 -0
  260. src/harness/src/openharness/autopilot/service.py +2239 -0
  261. src/harness/src/openharness/autopilot/types.py +91 -0
  262. src/harness/src/openharness/bridge/__init__.py +20 -0
  263. src/harness/src/openharness/bridge/manager.py +106 -0
  264. src/harness/src/openharness/bridge/session_runner.py +46 -0
  265. src/harness/src/openharness/bridge/types.py +37 -0
  266. src/harness/src/openharness/bridge/work_secret.py +41 -0
  267. src/harness/src/openharness/channels/UPSTREAM +6 -0
  268. src/harness/src/openharness/channels/__init__.py +22 -0
  269. src/harness/src/openharness/channels/adapter.py +131 -0
  270. src/harness/src/openharness/channels/bus/__init__.py +6 -0
  271. src/harness/src/openharness/channels/bus/events.py +38 -0
  272. src/harness/src/openharness/channels/bus/queue.py +44 -0
  273. src/harness/src/openharness/channels/impl/__init__.py +6 -0
  274. src/harness/src/openharness/channels/impl/base.py +141 -0
  275. src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
  276. src/harness/src/openharness/channels/impl/discord.py +311 -0
  277. src/harness/src/openharness/channels/impl/email.py +410 -0
  278. src/harness/src/openharness/channels/impl/feishu.py +1342 -0
  279. src/harness/src/openharness/channels/impl/manager.py +257 -0
  280. src/harness/src/openharness/channels/impl/matrix.py +700 -0
  281. src/harness/src/openharness/channels/impl/mochat.py +897 -0
  282. src/harness/src/openharness/channels/impl/qq.py +141 -0
  283. src/harness/src/openharness/channels/impl/slack.py +284 -0
  284. src/harness/src/openharness/channels/impl/telegram.py +525 -0
  285. src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
  286. src/harness/src/openharness/cli.py +2551 -0
  287. src/harness/src/openharness/commands/__init__.py +21 -0
  288. src/harness/src/openharness/commands/registry.py +2783 -0
  289. src/harness/src/openharness/config/__init__.py +34 -0
  290. src/harness/src/openharness/config/paths.py +160 -0
  291. src/harness/src/openharness/config/schema.py +119 -0
  292. src/harness/src/openharness/config/settings.py +1104 -0
  293. src/harness/src/openharness/coordinator/__init__.py +12 -0
  294. src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
  295. src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
  296. src/harness/src/openharness/engine/__init__.py +80 -0
  297. src/harness/src/openharness/engine/cost_tracker.py +24 -0
  298. src/harness/src/openharness/engine/messages.py +222 -0
  299. src/harness/src/openharness/engine/query.py +1188 -0
  300. src/harness/src/openharness/engine/query_engine.py +313 -0
  301. src/harness/src/openharness/engine/stream_events.py +90 -0
  302. src/harness/src/openharness/hooks/__init__.py +50 -0
  303. src/harness/src/openharness/hooks/events.py +20 -0
  304. src/harness/src/openharness/hooks/executor.py +242 -0
  305. src/harness/src/openharness/hooks/hot_reload.py +31 -0
  306. src/harness/src/openharness/hooks/loader.py +68 -0
  307. src/harness/src/openharness/hooks/schemas.py +66 -0
  308. src/harness/src/openharness/hooks/types.py +38 -0
  309. src/harness/src/openharness/keybindings/__init__.py +14 -0
  310. src/harness/src/openharness/keybindings/default_bindings.py +11 -0
  311. src/harness/src/openharness/keybindings/loader.py +22 -0
  312. src/harness/src/openharness/keybindings/parser.py +18 -0
  313. src/harness/src/openharness/keybindings/resolver.py +13 -0
  314. src/harness/src/openharness/mcp/__init__.py +79 -0
  315. src/harness/src/openharness/mcp/client.py +298 -0
  316. src/harness/src/openharness/mcp/config.py +16 -0
  317. src/harness/src/openharness/mcp/types.py +76 -0
  318. src/harness/src/openharness/memory/__init__.py +25 -0
  319. src/harness/src/openharness/memory/agent.py +91 -0
  320. src/harness/src/openharness/memory/manager.py +183 -0
  321. src/harness/src/openharness/memory/memdir.py +52 -0
  322. src/harness/src/openharness/memory/migrate.py +137 -0
  323. src/harness/src/openharness/memory/paths.py +22 -0
  324. src/harness/src/openharness/memory/relevance.py +145 -0
  325. src/harness/src/openharness/memory/scan.py +116 -0
  326. src/harness/src/openharness/memory/schema.py +443 -0
  327. src/harness/src/openharness/memory/search.py +71 -0
  328. src/harness/src/openharness/memory/team.py +90 -0
  329. src/harness/src/openharness/memory/types.py +33 -0
  330. src/harness/src/openharness/memory/usage.py +153 -0
  331. src/harness/src/openharness/output_styles/__init__.py +5 -0
  332. src/harness/src/openharness/output_styles/loader.py +42 -0
  333. src/harness/src/openharness/permissions/__init__.py +26 -0
  334. src/harness/src/openharness/permissions/checker.py +200 -0
  335. src/harness/src/openharness/permissions/modes.py +13 -0
  336. src/harness/src/openharness/personalization/__init__.py +6 -0
  337. src/harness/src/openharness/personalization/extractor.py +138 -0
  338. src/harness/src/openharness/personalization/rules.py +64 -0
  339. src/harness/src/openharness/personalization/session_hook.py +65 -0
  340. src/harness/src/openharness/platforms.py +86 -0
  341. src/harness/src/openharness/plugins/__init__.py +53 -0
  342. src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
  343. src/harness/src/openharness/plugins/installer.py +39 -0
  344. src/harness/src/openharness/plugins/loader.py +730 -0
  345. src/harness/src/openharness/plugins/schemas.py +24 -0
  346. src/harness/src/openharness/plugins/types.py +59 -0
  347. src/harness/src/openharness/prompts/__init__.py +14 -0
  348. src/harness/src/openharness/prompts/claudemd.py +48 -0
  349. src/harness/src/openharness/prompts/context.py +187 -0
  350. src/harness/src/openharness/prompts/environment.py +135 -0
  351. src/harness/src/openharness/prompts/system_prompt.py +109 -0
  352. src/harness/src/openharness/sandbox/Dockerfile +6 -0
  353. src/harness/src/openharness/sandbox/__init__.py +33 -0
  354. src/harness/src/openharness/sandbox/adapter.py +148 -0
  355. src/harness/src/openharness/sandbox/docker_backend.py +232 -0
  356. src/harness/src/openharness/sandbox/docker_image.py +103 -0
  357. src/harness/src/openharness/sandbox/path_validator.py +37 -0
  358. src/harness/src/openharness/sandbox/session.py +63 -0
  359. src/harness/src/openharness/services/__init__.py +30 -0
  360. src/harness/src/openharness/services/autodream/__init__.py +34 -0
  361. src/harness/src/openharness/services/autodream/backup.py +104 -0
  362. src/harness/src/openharness/services/autodream/lock.py +138 -0
  363. src/harness/src/openharness/services/autodream/prompt.py +128 -0
  364. src/harness/src/openharness/services/autodream/service.py +313 -0
  365. src/harness/src/openharness/services/compact/__init__.py +1725 -0
  366. src/harness/src/openharness/services/cron.py +137 -0
  367. src/harness/src/openharness/services/cron_scheduler.py +595 -0
  368. src/harness/src/openharness/services/lsp/__init__.py +216 -0
  369. src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
  370. src/harness/src/openharness/services/oauth/__init__.py +0 -0
  371. src/harness/src/openharness/services/session_backend.py +97 -0
  372. src/harness/src/openharness/services/session_memory/__init__.py +139 -0
  373. src/harness/src/openharness/services/session_storage.py +230 -0
  374. src/harness/src/openharness/services/token_estimation.py +15 -0
  375. src/harness/src/openharness/services/tool_outputs.py +55 -0
  376. src/harness/src/openharness/skills/__init__.py +44 -0
  377. src/harness/src/openharness/skills/_frontmatter.py +97 -0
  378. src/harness/src/openharness/skills/bundled/__init__.py +75 -0
  379. src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
  380. src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
  381. src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
  382. src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
  383. src/harness/src/openharness/skills/bundled/content/review.md +33 -0
  384. src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
  385. src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
  386. src/harness/src/openharness/skills/bundled/content/test.md +38 -0
  387. src/harness/src/openharness/skills/loader.py +229 -0
  388. src/harness/src/openharness/skills/registry.py +29 -0
  389. src/harness/src/openharness/skills/types.py +24 -0
  390. src/harness/src/openharness/state/__init__.py +6 -0
  391. src/harness/src/openharness/state/app_state.py +30 -0
  392. src/harness/src/openharness/state/store.py +40 -0
  393. src/harness/src/openharness/swarm/__init__.py +70 -0
  394. src/harness/src/openharness/swarm/in_process.py +693 -0
  395. src/harness/src/openharness/swarm/lockfile.py +24 -0
  396. src/harness/src/openharness/swarm/mailbox.py +522 -0
  397. src/harness/src/openharness/swarm/permission_sync.py +1168 -0
  398. src/harness/src/openharness/swarm/registry.py +410 -0
  399. src/harness/src/openharness/swarm/spawn_utils.py +229 -0
  400. src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
  401. src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
  402. src/harness/src/openharness/swarm/types.py +398 -0
  403. src/harness/src/openharness/swarm/worktree.py +315 -0
  404. src/harness/src/openharness/tasks/__init__.py +18 -0
  405. src/harness/src/openharness/tasks/local_agent_task.py +28 -0
  406. src/harness/src/openharness/tasks/local_shell_task.py +17 -0
  407. src/harness/src/openharness/tasks/manager.py +475 -0
  408. src/harness/src/openharness/tasks/stop_task.py +11 -0
  409. src/harness/src/openharness/tasks/types.py +32 -0
  410. src/harness/src/openharness/themes/__init__.py +21 -0
  411. src/harness/src/openharness/themes/builtin.py +89 -0
  412. src/harness/src/openharness/themes/loader.py +55 -0
  413. src/harness/src/openharness/themes/schema.py +54 -0
  414. src/harness/src/openharness/tools/__init__.py +107 -0
  415. src/harness/src/openharness/tools/agent_tool.py +141 -0
  416. src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
  417. src/harness/src/openharness/tools/base.py +80 -0
  418. src/harness/src/openharness/tools/bash_tool.py +259 -0
  419. src/harness/src/openharness/tools/brief_tool.py +33 -0
  420. src/harness/src/openharness/tools/config_tool.py +54 -0
  421. src/harness/src/openharness/tools/cron_create_tool.py +105 -0
  422. src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
  423. src/harness/src/openharness/tools/cron_list_tool.py +69 -0
  424. src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
  425. src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
  426. src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
  427. src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
  428. src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
  429. src/harness/src/openharness/tools/file_edit_tool.py +95 -0
  430. src/harness/src/openharness/tools/file_read_tool.py +72 -0
  431. src/harness/src/openharness/tools/file_write_tool.py +87 -0
  432. src/harness/src/openharness/tools/glob_tool.py +175 -0
  433. src/harness/src/openharness/tools/grep_tool.py +374 -0
  434. src/harness/src/openharness/tools/image_generation_tool.py +447 -0
  435. src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
  436. src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
  437. src/harness/src/openharness/tools/lsp_tool.py +154 -0
  438. src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
  439. src/harness/src/openharness/tools/mcp_tool.py +72 -0
  440. src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
  441. src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
  442. src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
  443. src/harness/src/openharness/tools/send_message_tool.py +58 -0
  444. src/harness/src/openharness/tools/skill_tool.py +43 -0
  445. src/harness/src/openharness/tools/sleep_tool.py +32 -0
  446. src/harness/src/openharness/tools/task_create_tool.py +56 -0
  447. src/harness/src/openharness/tools/task_get_tool.py +33 -0
  448. src/harness/src/openharness/tools/task_list_tool.py +35 -0
  449. src/harness/src/openharness/tools/task_output_tool.py +35 -0
  450. src/harness/src/openharness/tools/task_stop_tool.py +30 -0
  451. src/harness/src/openharness/tools/task_update_tool.py +50 -0
  452. src/harness/src/openharness/tools/team_create_tool.py +31 -0
  453. src/harness/src/openharness/tools/team_delete_tool.py +30 -0
  454. src/harness/src/openharness/tools/todo_write_tool.py +46 -0
  455. src/harness/src/openharness/tools/tool_search_tool.py +38 -0
  456. src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
  457. src/harness/src/openharness/tools/web_search_tool.py +119 -0
  458. src/harness/src/openharness/ui/__init__.py +5 -0
  459. src/harness/src/openharness/ui/app.py +320 -0
  460. src/harness/src/openharness/ui/backend_host.py +941 -0
  461. src/harness/src/openharness/ui/coordinator_drain.py +198 -0
  462. src/harness/src/openharness/ui/input.py +32 -0
  463. src/harness/src/openharness/ui/output.py +265 -0
  464. src/harness/src/openharness/ui/permission_dialog.py +14 -0
  465. src/harness/src/openharness/ui/protocol.py +247 -0
  466. src/harness/src/openharness/ui/react_launcher.py +179 -0
  467. src/harness/src/openharness/ui/runtime.py +799 -0
  468. src/harness/src/openharness/ui/textual_app.py +495 -0
  469. src/harness/src/openharness/utils/__init__.py +0 -0
  470. src/harness/src/openharness/utils/file_lock.py +86 -0
  471. src/harness/src/openharness/utils/fs.py +98 -0
  472. src/harness/src/openharness/utils/helpers.py +77 -0
  473. src/harness/src/openharness/utils/network_guard.py +340 -0
  474. src/harness/src/openharness/utils/shell.py +147 -0
  475. src/harness/src/openharness/vim/__init__.py +5 -0
  476. src/harness/src/openharness/vim/transitions.py +8 -0
  477. src/harness/src/openharness/voice/__init__.py +7 -0
  478. src/harness/src/openharness/voice/keyterms.py +10 -0
  479. src/harness/src/openharness/voice/stream_stt.py +8 -0
  480. src/harness/src/openharness/voice/voice_mode.py +44 -0
  481. src/llm/__init__.py +29 -0
  482. src/llm/image_generation.py +402 -0
  483. src/llm/provider_registry.py +419 -0
  484. src/llm/runtime_env.py +64 -0
  485. src/llm/structured_output.py +131 -0
  486. src/llm/vision.py +198 -0
  487. src/memory/__init__.py +2 -0
  488. src/memory/api/__init__.py +1 -0
  489. src/memory/api/base_memory_system_api.py +98 -0
  490. src/memory/api/base_symbolic_memory_system_api.py +190 -0
  491. src/memory/api/base_vector_memory_system_api.py +116 -0
  492. src/memory/api/faiss_memory_system_api.py +228 -0
  493. src/memory/api/slot_process_api.py +827 -0
  494. src/memory/api/symbolic_memory_system_api.py +572 -0
  495. src/memory/memory_system/__init__.py +48 -0
  496. src/memory/memory_system/component_taxonomy.py +430 -0
  497. src/memory/memory_system/llm.py +437 -0
  498. src/memory/memory_system/models.py +168 -0
  499. src/memory/memory_system/user_prompt.py +418 -0
  500. src/memory/memory_system/utils.py +282 -0
  501. src/memory/memory_system/vectorstore.py +311 -0
  502. src/memory/memory_system/working_slot.py +99 -0
  503. src/pipeline/__init__.py +129 -0
  504. src/pipeline/contracts.py +531 -0
  505. src/pipeline/discipline_taxonomy.py +916 -0
  506. src/pipeline/evidence_coverage_ledger.py +1150 -0
  507. src/pipeline/evidence_refinement.py +620 -0
  508. src/pipeline/experiment_to_symbolic.py +137 -0
  509. src/pipeline/multimodal_evidence/__init__.py +28 -0
  510. src/pipeline/multimodal_evidence/capabilities.py +107 -0
  511. src/pipeline/multimodal_evidence/claims.py +135 -0
  512. src/pipeline/multimodal_evidence/contract.py +291 -0
  513. src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
  514. src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
  515. src/pipeline/multimodal_evidence/manifest.py +254 -0
  516. src/pipeline/multimodal_evidence/native_analysis.py +479 -0
  517. src/pipeline/multimodal_evidence/perception.py +277 -0
  518. src/pipeline/multimodal_evidence/query_binding.py +65 -0
  519. src/pipeline/multimodal_evidence/rendering.py +120 -0
  520. src/pipeline/multimodal_evidence/safety.py +42 -0
  521. src/pipeline/multimodal_evidence/sampling.py +69 -0
  522. src/pipeline/multimodal_evidence/service.py +148 -0
  523. src/pipeline/multimodal_evidence/survey_integration.py +460 -0
  524. src/pipeline/paper_identity.py +52 -0
  525. src/pipeline/research_design_inventory.py +338 -0
  526. src/pipeline/research_domain_resolution.py +669 -0
  527. src/pipeline/research_identity.py +548 -0
  528. src/pipeline/research_question_contract.py +646 -0
  529. src/pipeline/retrieval_lanes.py +889 -0
  530. src/pipeline/run_loop.py +862 -0
  531. src/pipeline/science_events.py +30 -0
  532. src/pipeline/science_manifests.py +602 -0
  533. src/pipeline/science_run.py +918 -0
  534. src/pipeline/science_stages.py +709 -0
  535. src/pipeline/science_workflow.py +807 -0
  536. src/pipeline/sh_cluster_projection.py +306 -0
  537. src/pipeline/sh_graph_provenance.py +648 -0
  538. src/pipeline/subhypothesis_decomposition.py +392 -0
  539. src/pipeline/subhypothesis_evidence.py +856 -0
  540. src/pipeline/survey_evidence_plan.py +858 -0
  541. src/pipeline/survey_gap_adjudication.py +647 -0
  542. src/pipeline/survey_gap_candidates.py +840 -0
  543. src/pipeline/survey_gap_ledger.py +683 -0
  544. src/pipeline/survey_gap_triage.py +401 -0
  545. src/pipeline/survey_handoff_persistence.py +481 -0
  546. src/pipeline/survey_handoff_projection.py +457 -0
  547. src/pipeline/survey_idea_handoff.py +1112 -0
  548. src/pipeline/survey_idea_loader.py +289 -0
  549. src/research_run_ids.py +11 -0
@@ -0,0 +1,277 @@
1
+ """Constrained qwen3-vl-plus perception for explicitly supplied data only."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections import OrderedDict
7
+ from collections.abc import Callable, Mapping, Sequence
8
+ from typing import Any
9
+
10
+ from src.llm.vision import QwenVisionClient, resolve_vision_settings
11
+
12
+ from .contract import MultimodalInputError, MultimodalInputSpec
13
+ from .rendering import PreviewRenderError, render_png_preview, supports_remote_preview
14
+ from .safety import violates_noncausal_policy
15
+
16
+
17
+ QWEN3_VL_PLUS = "qwen3-vl-plus"
18
+ _FOCUSES = ("mechanism", "measurement", "boundary", "contradiction", "theory")
19
+ OBSERVATION_SCHEMA: dict[str, Any] = {
20
+ "type": "object",
21
+ "properties": {
22
+ "finding": {"type": "string", "minLength": 4, "maxLength": 700},
23
+ "candidate_explanation": {"type": "string", "minLength": 4, "maxLength": 700},
24
+ "alternative_explanations": {
25
+ "type": "array",
26
+ "minItems": 1,
27
+ "maxItems": 3,
28
+ "items": {"type": "string", "minLength": 3, "maxLength": 400},
29
+ },
30
+ "discriminating_prediction": {"type": "string", "minLength": 4, "maxLength": 500},
31
+ "falsifier": {"type": "string", "minLength": 4, "maxLength": 500},
32
+ "claim_limits": {"type": "string", "minLength": 8, "maxLength": 500},
33
+ "confidence": {"type": "string", "enum": ["low", "medium", "high"]},
34
+ "focus": {"type": "string", "enum": list(_FOCUSES)},
35
+ },
36
+ "required": [
37
+ "finding",
38
+ "candidate_explanation",
39
+ "alternative_explanations",
40
+ "discriminating_prediction",
41
+ "falsifier",
42
+ "claim_limits",
43
+ "confidence",
44
+ "focus",
45
+ ],
46
+ "additionalProperties": False,
47
+ }
48
+
49
+
50
+ def run_remote_perception(
51
+ *,
52
+ input_spec: MultimodalInputSpec,
53
+ local_context: Mapping[str, Any],
54
+ config: Any,
55
+ max_calls: int,
56
+ max_preview_pixels: int,
57
+ max_preview_bytes: int,
58
+ client_factory: Callable[..., Any] | None = None,
59
+ ) -> tuple[list[dict[str, Any]], list[str], dict[str, str]]:
60
+ """Return validated observation cards without retaining previews or raw replies."""
61
+
62
+ if not bool(local_context.get("remote_perception_authorized")):
63
+ return [], [], {}
64
+ if max_calls < 1:
65
+ raise MultimodalInputError("max_vl_calls must be at least 1 when remote perception is enabled.")
66
+ settings = _resolve_pinned_vision_settings(config)
67
+ factory = client_factory or QwenVisionClient
68
+ client = factory(
69
+ model=settings["model"],
70
+ provider=settings["provider"],
71
+ api_key=settings["api_key"],
72
+ base_url=settings["base_url"],
73
+ timeout=settings["timeout"],
74
+ config=config,
75
+ )
76
+ records_by_id = {record.record_id: record for record in input_spec.records}
77
+ findings_by_id = {
78
+ str(finding.get("record_id")): finding
79
+ for finding in local_context.get("native_findings", [])
80
+ if isinstance(finding, Mapping) and finding.get("status") == "success"
81
+ }
82
+ selected_ids = {
83
+ str(record_id)
84
+ for record_id in local_context.get("selected_record_ids", [])
85
+ if str(record_id) in records_by_id and str(record_id) in findings_by_id
86
+ }
87
+ selected_records = [
88
+ record
89
+ for record in sorted(input_spec.records, key=lambda item: item.input_index)
90
+ if record.record_id in selected_ids
91
+ ]
92
+ previewable_records = [
93
+ record for record in selected_records if supports_remote_preview(record)
94
+ ]
95
+ candidates = _representative_preview_records(previewable_records)
96
+ observations: list[dict[str, Any]] = []
97
+ limitations: list[str] = []
98
+ sent_record_ids: list[str] = []
99
+ remote_call_count = 0
100
+ for record in candidates:
101
+ if remote_call_count >= max_calls:
102
+ break
103
+ record_id = record.record_id
104
+ finding = findings_by_id[record_id]
105
+ try:
106
+ preview = render_png_preview(
107
+ record,
108
+ finding,
109
+ max_pixels=max_preview_pixels,
110
+ max_bytes=max_preview_bytes,
111
+ )
112
+ except PreviewRenderError as exc:
113
+ limitations.append(f"Record {record_id} was not sent for remote perception: {exc}")
114
+ continue
115
+ try:
116
+ response = client.describe_json(
117
+ preview,
118
+ prompt=_observation_prompt(record.modality),
119
+ schema=OBSERVATION_SCHEMA,
120
+ media_type="image/png",
121
+ max_tokens=min(int(settings["max_tokens"]), 1200),
122
+ )
123
+ except Exception as exc:
124
+ raise MultimodalInputError(
125
+ "Remote perception failed for an explicitly authorized preview; no fallback model was used."
126
+ ) from exc
127
+ remote_call_count += 1
128
+ sent_record_ids.append(record_id)
129
+ observation = _sanitize_observation(response, record_id=record_id, modality=record.modality)
130
+ if observation is None:
131
+ limitations.append(
132
+ f"Record {record_id} returned an observation outside the non-causal claim policy."
133
+ )
134
+ continue
135
+ observations.append(observation)
136
+ unsupported_modalities = sorted(
137
+ {record.modality for record in selected_records if not supports_remote_preview(record)}
138
+ )
139
+ if unsupported_modalities:
140
+ limitations.append(
141
+ "Selected records for these modalities have no Batch B remote preview and remained local-only: "
142
+ + ", ".join(unsupported_modalities)
143
+ )
144
+ if len(candidates) > max_calls:
145
+ excluded_record_ids = [record.record_id for record in candidates[max_calls:]]
146
+ limitations.append(
147
+ f"Remote perception used the configured budget of {max_calls} stratified cross-modality previews; "
148
+ f"eligible record IDs deferred by budget: {', '.join(excluded_record_ids)}."
149
+ )
150
+ return observations, limitations, {
151
+ "provider": settings["provider"],
152
+ "model": settings["model"],
153
+ "record_ids": sent_record_ids,
154
+ }
155
+
156
+
157
+ def _representative_preview_records(
158
+ records: Sequence[Any],
159
+ ) -> list[Any]:
160
+ """Round-robin selected records across modalities and metadata strata."""
161
+
162
+ by_modality: "OrderedDict[str, list[Any]]" = OrderedDict()
163
+ for record in records:
164
+ by_modality.setdefault(record.modality, []).append(record)
165
+ modality_queues: "OrderedDict[str, list[Any]]" = OrderedDict()
166
+ for modality, modality_records in by_modality.items():
167
+ by_stratum: "OrderedDict[tuple[str, str, str, str], list[Any]]" = OrderedDict()
168
+ for record in modality_records:
169
+ metadata = record.metadata
170
+ stratum = tuple(
171
+ str(metadata.get(key, ""))
172
+ for key in ("label", "group", "condition", "timepoint")
173
+ )
174
+ by_stratum.setdefault(stratum, []).append(record)
175
+ queue: list[Any] = []
176
+ round_index = 0
177
+ while True:
178
+ added = False
179
+ for stratum_records in by_stratum.values():
180
+ if round_index < len(stratum_records):
181
+ queue.append(stratum_records[round_index])
182
+ added = True
183
+ if not added:
184
+ break
185
+ round_index += 1
186
+ modality_queues[modality] = queue
187
+ ordered: list[Any] = []
188
+ round_index = 0
189
+ while True:
190
+ added = False
191
+ for queue in modality_queues.values():
192
+ if round_index < len(queue):
193
+ ordered.append(queue[round_index])
194
+ added = True
195
+ if not added:
196
+ return ordered
197
+ round_index += 1
198
+
199
+
200
+ def _resolve_pinned_vision_settings(config: Any) -> dict[str, Any]:
201
+ multimodal = _multimodal_settings(config)
202
+ configured_model = str(multimodal.get("quality_model") or "").strip()
203
+ if configured_model != QWEN3_VL_PLUS:
204
+ raise MultimodalInputError(
205
+ "Remote multimodal perception requires survey.multimodal_evidence.quality_model=qwen3-vl-plus."
206
+ )
207
+ try:
208
+ settings = resolve_vision_settings(config, batch=False)
209
+ except Exception as exc:
210
+ raise MultimodalInputError("Remote vision settings could not be resolved.") from exc
211
+ if str(settings.get("model") or "").strip() != QWEN3_VL_PLUS:
212
+ raise MultimodalInputError(
213
+ "Remote multimodal perception is pinned to qwen3-vl-plus; batch or flash models are not permitted."
214
+ )
215
+ if str(settings.get("provider") or "").strip().casefold() != "qwen":
216
+ raise MultimodalInputError(
217
+ "Remote multimodal perception requires the Qwen provider for qwen3-vl-plus."
218
+ )
219
+ return dict(settings)
220
+
221
+
222
+ def _multimodal_settings(config: Any) -> Mapping[str, Any]:
223
+ survey = config.get("survey") if hasattr(config, "get") and config.get("survey") is not None else config
224
+ settings = survey.get("multimodal_evidence", {}) if hasattr(survey, "get") else {}
225
+ return settings if isinstance(settings, Mapping) or hasattr(settings, "get") else {}
226
+
227
+
228
+ def _observation_prompt(modality: str) -> str:
229
+ return f"""You are reviewing one sanitized PNG preview derived from an explicitly supplied {modality} record.
230
+ Return exactly one JSON object matching the provided schema. Treat only visible preview content as evidence.
231
+
232
+ Rules:
233
+ - Describe a local pattern, not a settled scientific result. Never claim causality, universality, priority, or proof.
234
+ - Provide a tentative candidate explanation, at least one competing explanation, one discriminating prediction, and one falsifier.
235
+ - State clear limits: this is one bounded representative preview and cannot identify people, patients, identities, raw records, or metadata.
236
+ - Do not transcribe raw data, infer hidden values, or mention file names or paths.
237
+ - Use concise scientific English; use confidence=low unless the visible preview itself is unambiguous.
238
+ """
239
+
240
+
241
+ def _sanitize_observation(
242
+ value: Mapping[str, Any],
243
+ *,
244
+ record_id: str,
245
+ modality: str,
246
+ ) -> dict[str, Any] | None:
247
+ raw = dict(value)
248
+ text_fields = (
249
+ "finding",
250
+ "candidate_explanation",
251
+ "discriminating_prediction",
252
+ "falsifier",
253
+ "claim_limits",
254
+ )
255
+ normalized = {key: _clean_text(raw.get(key), limit=700) for key in text_fields}
256
+ alternatives = [
257
+ _clean_text(item, limit=400)
258
+ for item in raw.get("alternative_explanations", [])
259
+ if _clean_text(item, limit=400)
260
+ ][:3]
261
+ if not all(normalized.values()) or not alternatives:
262
+ return None
263
+ if any(violates_noncausal_policy(item) for item in [*normalized.values(), *alternatives]):
264
+ return None
265
+ return {
266
+ "observation_id": "",
267
+ "record_ids": [record_id],
268
+ "modality": modality,
269
+ **normalized,
270
+ "alternative_explanations": alternatives,
271
+ "confidence": str(raw.get("confidence") or "low"),
272
+ "focus": str(raw.get("focus") or "mechanism"),
273
+ }
274
+
275
+
276
+ def _clean_text(value: Any, *, limit: int) -> str:
277
+ return re.sub(r"\s+", " ", str(value or "").strip())[:limit]
@@ -0,0 +1,65 @@
1
+ """External epistemic bindings for data-anchored SH query variants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any
8
+
9
+
10
+ SUPPORTED_EPISTEMIC_ROLES = frozenset(
11
+ {
12
+ "support",
13
+ "counter",
14
+ "alternative_explanation",
15
+ "boundary",
16
+ "measurement_confound",
17
+ "discriminating_test",
18
+ }
19
+ )
20
+
21
+
22
+ def normalize_query_variant_bindings(value: Any) -> list[dict[str, str]]:
23
+ """Validate outer-only metadata without expanding the SH schema."""
24
+
25
+ if value is None:
26
+ return []
27
+ if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
28
+ raise ValueError("query_variant_bindings must be a list.")
29
+ bindings: list[dict[str, str]] = []
30
+ seen: set[tuple[str, str, str]] = set()
31
+ for index, item in enumerate(value, start=1):
32
+ if not isinstance(item, Mapping):
33
+ raise ValueError(f"query_variant_binding {index} must be an object.")
34
+ binding = {
35
+ "sub_hypothesis_id": _text(item.get("sub_hypothesis_id"), limit=120),
36
+ "slot_name": _text(item.get("slot_name"), limit=160),
37
+ "query_variant_id": _text(item.get("query_variant_id"), limit=80),
38
+ "epistemic_role": _text(item.get("epistemic_role"), limit=80).casefold(),
39
+ "evidence_mode": _text(item.get("evidence_mode"), limit=80).casefold(),
40
+ "required_result": _text(item.get("required_result"), limit=700),
41
+ "claim_id": _text(item.get("claim_id"), limit=120),
42
+ }
43
+ if not all(binding[key] for key in ("sub_hypothesis_id", "slot_name", "query_variant_id", "epistemic_role", "required_result", "claim_id")):
44
+ raise ValueError(f"query_variant_binding {index} has a missing required field.")
45
+ if binding["epistemic_role"] not in SUPPORTED_EPISTEMIC_ROLES:
46
+ raise ValueError(
47
+ f"query_variant_binding {index} has unsupported epistemic_role '{binding['epistemic_role']}'."
48
+ )
49
+ key = (binding["sub_hypothesis_id"], binding["slot_name"], binding["query_variant_id"])
50
+ if key in seen:
51
+ raise ValueError(f"Duplicate query_variant_binding for {'.'.join(key)}.")
52
+ seen.add(key)
53
+ bindings.append(binding)
54
+ return bindings
55
+
56
+
57
+ def query_binding_lookup(value: Any) -> dict[tuple[str, str, str], dict[str, str]]:
58
+ return {
59
+ (binding["sub_hypothesis_id"], binding["slot_name"], binding["query_variant_id"]): binding
60
+ for binding in normalize_query_variant_bindings(value)
61
+ }
62
+
63
+
64
+ def _text(value: Any, *, limit: int) -> str:
65
+ return re.sub(r"\s+", " ", str(value or "").strip())[:limit]
@@ -0,0 +1,120 @@
1
+ """Build bounded, metadata-free in-memory previews for remote perception."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from io import BytesIO
6
+ from typing import Any, Mapping
7
+
8
+ from .contract import ValidatedMultimodalRecord
9
+
10
+
11
+ _PREVIEWABLE_MODALITIES = frozenset({"image", "signal", "audio", "threeD", "trajectory"})
12
+
13
+
14
+ class PreviewRenderError(ValueError):
15
+ """Raised when a selected record cannot become a bounded PNG preview."""
16
+
17
+
18
+ def supports_remote_preview(record: ValidatedMultimodalRecord) -> bool:
19
+ return record.modality in _PREVIEWABLE_MODALITIES
20
+
21
+
22
+ def render_png_preview(
23
+ record: ValidatedMultimodalRecord,
24
+ native_finding: Mapping[str, Any],
25
+ *,
26
+ max_pixels: int,
27
+ max_bytes: int,
28
+ ) -> bytes:
29
+ """Return a re-encoded PNG held only in memory.
30
+
31
+ Image records retain their pixels but lose source metadata. Other allowed
32
+ modalities become an aggregate-only card, so no source samples or table
33
+ contents are sent to a remote model.
34
+ """
35
+
36
+ if max_pixels < 1 or max_bytes < 1:
37
+ raise PreviewRenderError("Preview limits must be positive.")
38
+ if not supports_remote_preview(record):
39
+ raise PreviewRenderError("The selected modality has no remote preview in Batch B.")
40
+ if record.modality == "image":
41
+ return _render_image(record, max_pixels=max_pixels, max_bytes=max_bytes)
42
+ return _render_aggregate_card(
43
+ record,
44
+ native_finding,
45
+ max_pixels=max_pixels,
46
+ max_bytes=max_bytes,
47
+ )
48
+
49
+
50
+ def _render_image(
51
+ record: ValidatedMultimodalRecord,
52
+ *,
53
+ max_pixels: int,
54
+ max_bytes: int,
55
+ ) -> bytes:
56
+ try:
57
+ from PIL import Image, ImageOps
58
+ except ImportError as exc:
59
+ raise PreviewRenderError("Pillow is required to prepare image previews.") from exc
60
+
61
+ try:
62
+ with Image.open(record.source_path) as source:
63
+ source.load()
64
+ transformed = ImageOps.exif_transpose(source).convert("RGB")
65
+ clean = Image.new("RGB", transformed.size)
66
+ clean.paste(transformed)
67
+ except Exception as exc:
68
+ raise PreviewRenderError("The selected image could not be rendered safely.") from exc
69
+ return _encode_bounded_png(clean, max_pixels=max_pixels, max_bytes=max_bytes)
70
+
71
+
72
+ def _render_aggregate_card(
73
+ record: ValidatedMultimodalRecord,
74
+ native_finding: Mapping[str, Any],
75
+ *,
76
+ max_pixels: int,
77
+ max_bytes: int,
78
+ ) -> bytes:
79
+ try:
80
+ from PIL import Image, ImageDraw
81
+ except ImportError as exc:
82
+ raise PreviewRenderError("Pillow is required to prepare aggregate previews.") from exc
83
+
84
+ metrics = native_finding.get("metrics")
85
+ metric_count = len(metrics) if isinstance(metrics, Mapping) else 0
86
+ image = Image.new("RGB", (960, 540), "white")
87
+ draw = ImageDraw.Draw(image)
88
+ draw.rectangle((0, 0, 960, 86), fill=(20, 55, 93))
89
+ draw.text((34, 28), "Aggregate multimodal preview", fill="white")
90
+ draw.text((34, 130), f"Modality: {record.modality}", fill=(24, 24, 24))
91
+ draw.text((34, 184), "This visual contains bounded local summary metadata only.", fill=(24, 24, 24))
92
+ draw.text((34, 222), "Individual records, samples, and source paths are not included.", fill=(24, 24, 24))
93
+ draw.text((34, 286), f"Local aggregate metric groups available: {metric_count}", fill=(24, 24, 24))
94
+ draw.text((34, 390), "Interpret only visible aggregate structure; do not infer causality.", fill=(112, 38, 38))
95
+ return _encode_bounded_png(image, max_pixels=max_pixels, max_bytes=max_bytes)
96
+
97
+
98
+ def _encode_bounded_png(image: Any, *, max_pixels: int, max_bytes: int) -> bytes:
99
+ width, height = image.size
100
+ if width < 1 or height < 1:
101
+ raise PreviewRenderError("Preview image dimensions must be positive.")
102
+ if width * height > max_pixels:
103
+ scale = (max_pixels / float(width * height)) ** 0.5
104
+ resampling = getattr(getattr(image, "Resampling", None), "LANCZOS", 1)
105
+ image = image.resize(
106
+ (max(1, int(width * scale)), max(1, int(height * scale))),
107
+ resampling,
108
+ )
109
+ resampling = getattr(getattr(image, "Resampling", None), "LANCZOS", 1)
110
+ for _ in range(6):
111
+ buffer = BytesIO()
112
+ image.save(buffer, format="PNG", optimize=True)
113
+ payload = buffer.getvalue()
114
+ if len(payload) <= max_bytes:
115
+ return payload
116
+ width, height = image.size
117
+ if width == 1 and height == 1:
118
+ break
119
+ image = image.resize((max(1, int(width * 0.7)), max(1, int(height * 0.7))), resampling)
120
+ raise PreviewRenderError("The sanitized PNG preview exceeds the configured byte limit.")
@@ -0,0 +1,42 @@
1
+ """Shared lexical guard for local observations that must remain non-causal."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+
9
+ _PROHIBITED_ASSERTIONS = (
10
+ re.compile(
11
+ r"\b(?:proves?|proven|establishes?|confirms?|demonstrates?|"
12
+ r"universally?|first[- ]ever)\b",
13
+ flags=re.IGNORECASE,
14
+ ),
15
+ re.compile(
16
+ r"\b(?:causes?|causal(?:ly)?|drives?|leads?\s+to|induces?|"
17
+ r"results?\s+in|produces?|creates?|determines?|mediates?|triggers?|"
18
+ r"gives?\s+rise\s+to|is responsible for)\b",
19
+ flags=re.IGNORECASE,
20
+ ),
21
+ re.compile(r"\b(?:shows?|indicates?|suggests?)\s+that\b", flags=re.IGNORECASE),
22
+ )
23
+
24
+
25
+ def violates_noncausal_policy(value: Any) -> bool:
26
+ text = str(value or "")
27
+ for pattern in _PROHIBITED_ASSERTIONS:
28
+ for match in pattern.finditer(text):
29
+ if _is_explicitly_tentative(text, match.start()):
30
+ continue
31
+ return True
32
+ return False
33
+
34
+
35
+ def _is_explicitly_tentative(text: str, start: int) -> bool:
36
+ prefix = text[max(0, start - 36) : start].casefold()
37
+ return bool(
38
+ re.search(
39
+ r"\b(?:may|might|could|can|possibly|potentially)\s+(?:\w+\s+){0,2}$",
40
+ prefix,
41
+ )
42
+ )
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import OrderedDict
4
+ from dataclasses import dataclass
5
+ from typing import Iterable
6
+
7
+ from .contract import ValidatedMultimodalRecord
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class SamplingResult:
12
+ records: tuple[ValidatedMultimodalRecord, ...]
13
+ policy: str
14
+ truncated_strata_by_modality: dict[str, int]
15
+
16
+ @property
17
+ def selected_record_ids(self) -> list[str]:
18
+ return [record.record_id for record in self.records]
19
+
20
+
21
+ def _stratum_key(record: ValidatedMultimodalRecord) -> tuple[str, str, str, str]:
22
+ metadata = record.metadata
23
+ return tuple(str(metadata.get(key, "")) for key in ("label", "group", "condition", "timepoint"))
24
+
25
+
26
+ def select_stratified_records(
27
+ records: Iterable[ValidatedMultimodalRecord],
28
+ *,
29
+ max_records_per_modality: int,
30
+ ) -> SamplingResult:
31
+ if max_records_per_modality < 1:
32
+ raise ValueError("max_records_per_modality must be at least 1.")
33
+ by_modality: "OrderedDict[str, list[ValidatedMultimodalRecord]]" = OrderedDict()
34
+ for record in sorted(records, key=lambda item: item.input_index):
35
+ by_modality.setdefault(record.modality, []).append(record)
36
+
37
+ selected: list[ValidatedMultimodalRecord] = []
38
+ truncated_strata_by_modality: dict[str, int] = {}
39
+ for modality, modality_records in by_modality.items():
40
+ strata: "OrderedDict[tuple[str, str, str, str], list[ValidatedMultimodalRecord]]" = OrderedDict()
41
+ for record in modality_records:
42
+ strata.setdefault(_stratum_key(record), []).append(record)
43
+ stratum_records = list(strata.values())
44
+ if len(stratum_records) > max_records_per_modality:
45
+ truncated_strata_by_modality[modality] = len(stratum_records) - max_records_per_modality
46
+ modality_selected: list[ValidatedMultimodalRecord] = [
47
+ group[0] for group in stratum_records[:max_records_per_modality]
48
+ ]
49
+ next_indices = [1 for _ in modality_selected]
50
+ while len(modality_selected) < max_records_per_modality:
51
+ added = False
52
+ for group_index, group in enumerate(stratum_records[:max_records_per_modality]):
53
+ next_index = next_indices[group_index]
54
+ if next_index >= len(group):
55
+ continue
56
+ modality_selected.append(group[next_index])
57
+ next_indices[group_index] += 1
58
+ added = True
59
+ if len(modality_selected) >= max_records_per_modality:
60
+ break
61
+ if not added:
62
+ break
63
+ selected.extend(modality_selected)
64
+
65
+ return SamplingResult(
66
+ records=tuple(sorted(selected, key=lambda item: item.input_index)),
67
+ policy="stratified_by_label_group_condition_timepoint_round_robin_v1",
68
+ truncated_strata_by_modality=truncated_strata_by_modality,
69
+ )