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,1220 @@
1
+ """Core design-state orchestration without retrieval or experiment execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping, Sequence
6
+ from copy import deepcopy
7
+ from pathlib import Path
8
+ import inspect
9
+ from typing import Any
10
+
11
+ from .cache import ExperimentDesignCache
12
+ from .completeness import CompletenessValidator
13
+ from .contracts import validate_experiment_design, validate_research_brief
14
+ from .evidence_planner import EvidenceRetrievalPlanner
15
+ from .idea_adapter import IdeaResultAdapter
16
+ from .counterexample_analyzer import (
17
+ CounterexampleAnalyzer,
18
+ not_applicable_counterexample_analysis,
19
+ unavailable_counterexample_analysis,
20
+ )
21
+ from .formal_reasoning_planner import (
22
+ FormalReasoningPlanner,
23
+ not_applicable_formal_reasoning_plan,
24
+ unavailable_formal_reasoning_plan,
25
+ )
26
+ from .llm_json import build_default_json_llm_call, validation_summary
27
+ from .reasoning_context import build_reasoning_context_from_brief
28
+ from .reasoning_validation import validate_reasoning_artifacts
29
+ from .scope_gate import ScopeAndSafetyGate
30
+ from .study_type_composer import StudyTypeTemplateComposer
31
+ from .survey_evidence import SurveyEvidenceAdapter
32
+ from .template_router import TemplateRouter
33
+ from .variable_claim_extractor import VariableClaimExtractor
34
+ from .run_logging import ExperimentDesignRunLogger
35
+
36
+
37
+ DESIGN_PREPARATION_SCHEMA_VERSION = "experiment_design_preparation_v1"
38
+ DESIGN_RUN_SCHEMA_VERSION = "experiment_design_run_v1"
39
+
40
+
41
+ def _mapping(value: object) -> dict[str, Any]:
42
+ return dict(value) if isinstance(value, Mapping) else {}
43
+
44
+
45
+ def _setting(value: object, key: str, default: object = None) -> object:
46
+ if isinstance(value, Mapping):
47
+ return value.get(key, default)
48
+ return getattr(value, key, default)
49
+
50
+
51
+ def _experiment_design_cache_config(config: object | None) -> object:
52
+ if config is None:
53
+ return {"enabled": False}
54
+ experiment_design = _setting(config, "experiment_design", config)
55
+ retrieval = _setting(experiment_design, "retrieval", {})
56
+ return _setting(retrieval, "cache", {"enabled": False})
57
+
58
+
59
+ def _llm_cache_context(config: object | None, model: str | None) -> dict[str, str]:
60
+ experiment_design = _setting(config, "experiment_design", config)
61
+ return {
62
+ "provider": str(_setting(experiment_design, "provider", "") or ""),
63
+ "model": str(model or _setting(experiment_design, "model", "") or ""),
64
+ }
65
+
66
+
67
+ def _sequence_count(value: object) -> int:
68
+ return len(value) if isinstance(value, Sequence) and not isinstance(value, (str, bytes)) else 0
69
+
70
+
71
+ def _design_validation_profile(design: Mapping[str, Any]) -> dict[str, object]:
72
+ """Provide final-validation diagnostics without serializing the design itself."""
73
+
74
+ evidence = _mapping(design.get("evidence_bundle"))
75
+ coverage = _mapping(evidence.get("coverage"))
76
+ template = _mapping(design.get("template_composition"))
77
+ policy = _mapping(design.get("execution_policy"))
78
+ review = _mapping(design.get("risk_and_human_review"))
79
+ statuses = _mapping(design.get("field_statuses"))
80
+ reasoning_context = _mapping(_mapping(design.get("research_brief")).get("reasoning_context"))
81
+ variable_claim_model = _mapping(design.get("variable_claim_model"))
82
+ formal_reasoning_plan = _mapping(design.get("formal_reasoning_plan"))
83
+ counterexample_analysis = _mapping(design.get("counterexample_analysis"))
84
+ return {
85
+ "schema_version": str(design.get("schema_version") or ""),
86
+ "design_id": str(design.get("design_id") or ""),
87
+ "template_id": str(template.get("template_id") or ""),
88
+ "template_submode": str(template.get("submode") or ""),
89
+ "execution_mode": str(policy.get("mode") or ""),
90
+ "observed_results_count": _sequence_count(design.get("observed_results")),
91
+ "outcome_branch_count": _sequence_count(design.get("outcome_branches")),
92
+ "open_design_question_count": _sequence_count(design.get("open_design_questions")),
93
+ "field_status_count": len(statuses),
94
+ "needs_human_input_field_count": sum(value == "needs_human_input" for value in statuses.values()),
95
+ "evidence_card_count": _sequence_count(evidence.get("evidence_cards")),
96
+ "evidence_covered_slot_count": _sequence_count(coverage.get("covered_slots")),
97
+ "reasoning_context_unknown_item_count": _sequence_count(reasoning_context.get("gap_records")),
98
+ "variable_claim_unknown_item_count": _sequence_count(variable_claim_model.get("unknown_items")),
99
+ "formal_reasoning_unknown_item_count": _sequence_count(formal_reasoning_plan.get("unknown_items")),
100
+ "counterexample_unknown_item_count": _sequence_count(counterexample_analysis.get("unknown_items")),
101
+ "risk_review_required": bool(review.get("human_review_required")),
102
+ }
103
+
104
+
105
+ def _blocked_evidence_plan(brief_id: object) -> dict[str, Any]:
106
+ return {
107
+ "schema_version": "experiment_design_evidence_retrieval_plan_v2",
108
+ "planning_mode": "QUERY_PLANNING_ONLY",
109
+ "planning_status": "NOT_PLANNED_BLOCKED_SCOPE",
110
+ "research_brief_id": str(brief_id or ""),
111
+ "template_id": "",
112
+ "queries": [],
113
+ "retrieved_evidence": [],
114
+ "observed_results": [],
115
+ "llm_used": False,
116
+ "warnings": ["Scope must be in range before evidence queries are planned."],
117
+ }
118
+
119
+
120
+ def _degradation_reason(stage: str) -> str:
121
+ return (
122
+ f"The {stage} LLM batch was discarded after an unavailable or invalid response. "
123
+ "No generated claim, formalization, counterexample, or protocol detail from that batch is retained; "
124
+ "qualified human review is required."
125
+ )
126
+
127
+
128
+ def _degraded_variable_claim_model(*, reason: str) -> dict[str, Any]:
129
+ return {
130
+ "schema_version": "variable_claim_model_v1",
131
+ "status": "complete_or_requires_input",
132
+ "claims": [],
133
+ "variables": [],
134
+ "unknown_items": [
135
+ {
136
+ "field_path": "variable_claim_model",
137
+ "reason": reason,
138
+ "status": "needs_human_input",
139
+ }
140
+ ],
141
+ }
142
+
143
+
144
+ def _append_unique_text(values: object, value: str) -> list[str]:
145
+ existing = [
146
+ str(item).strip()
147
+ for item in values
148
+ if str(item).strip()
149
+ ] if isinstance(values, Sequence) and not isinstance(values, (str, bytes)) else []
150
+ if value not in existing:
151
+ existing.append(value)
152
+ return existing
153
+
154
+
155
+ def _record_degradation(
156
+ logger: ExperimentDesignRunLogger | None,
157
+ *,
158
+ stage: str,
159
+ brief_id: str,
160
+ error: BaseException | None = None,
161
+ disposition: str = "discarded_invalid_llm_batch",
162
+ ) -> dict[str, str]:
163
+ record = {
164
+ "stage": stage,
165
+ "disposition": disposition,
166
+ "error_code": type(error).__name__ if error is not None else "UPSTREAM_DEGRADED",
167
+ }
168
+ if logger is not None:
169
+ logger.event(
170
+ stage,
171
+ "degraded",
172
+ level="WARNING",
173
+ status="DEGRADED",
174
+ brief_id=brief_id,
175
+ requires_human_review=True,
176
+ disposition=disposition,
177
+ error_code=record["error_code"],
178
+ )
179
+ return record
180
+
181
+
182
+ def _mark_design_degraded(
183
+ design: Mapping[str, Any],
184
+ degradations: Sequence[Mapping[str, str]],
185
+ ) -> dict[str, Any]:
186
+ """Expose discarded batches through existing ExperimentDesign review fields."""
187
+
188
+ marked = deepcopy(dict(design))
189
+ if not degradations:
190
+ return marked
191
+ review = _mapping(marked.get("risk_and_human_review"))
192
+ review["human_review_required"] = True
193
+ review["execution_prohibited"] = True
194
+ review["review_triggers"] = _append_unique_text(
195
+ review.get("review_triggers"),
196
+ "LLM_OR_WORKFLOW_DEGRADATION_REVIEW",
197
+ )
198
+ review["approval_dependencies"] = _append_unique_text(
199
+ review.get("approval_dependencies"),
200
+ "Qualified human review must replace every discarded LLM or workflow batch before execution.",
201
+ )
202
+ marked["risk_and_human_review"] = review
203
+
204
+ field_statuses = _mapping(marked.get("field_statuses"))
205
+ questions = marked.get("open_design_questions")
206
+ open_questions = list(questions) if isinstance(questions, list) else []
207
+ warnings = _mapping(marked.get("validation_report")).get("warnings")
208
+ validation_warnings = list(warnings) if isinstance(warnings, list) else []
209
+ for degradation in degradations:
210
+ stage = str(degradation.get("stage") or "unknown_stage")
211
+ field_statuses[f"degraded_stages.{stage}"] = "needs_human_input"
212
+ message = (
213
+ f"Replace the discarded {stage} batch with qualified human review before treating this design as complete."
214
+ )
215
+ if message not in open_questions:
216
+ open_questions.append(message)
217
+ warning = (
218
+ f"{stage} was degraded after its LLM or contract-validation batch was discarded; no discarded output is retained."
219
+ )
220
+ if warning not in validation_warnings:
221
+ validation_warnings.append(warning)
222
+ marked["field_statuses"] = field_statuses
223
+ marked["open_design_questions"] = open_questions
224
+ marked["validation_report"] = {
225
+ "status": "BLOCKED_BY_RISK_REVIEW",
226
+ "errors": [],
227
+ "warnings": validation_warnings,
228
+ }
229
+ return marked
230
+
231
+
232
+ class ExperimentDesignOrchestrator:
233
+ """Prepare a scientific design package while retaining all unknowns and review gates."""
234
+
235
+ def __init__(
236
+ self,
237
+ *,
238
+ allow_digital_execution: bool | None = None,
239
+ llm_call: Callable[..., object] | None = None,
240
+ composer_llm_call: Callable[..., object] | None = None,
241
+ config: Any = None,
242
+ llm_model: str | None = None,
243
+ ) -> None:
244
+ self.config = config
245
+ if allow_digital_execution is None:
246
+ try:
247
+ from src.config import get_experiment_design_config
248
+
249
+ allow_digital_execution = bool(
250
+ get_experiment_design_config().execution.allow_digital_execution
251
+ )
252
+ except Exception:
253
+ allow_digital_execution = False
254
+ self.allow_digital_execution = bool(allow_digital_execution)
255
+ self.llm_call = llm_call
256
+ self.llm_model = llm_model
257
+ self._default_llm_call: Callable[..., object] | None = None
258
+ self.cache = ExperimentDesignCache(_experiment_design_cache_config(config))
259
+ self.idea_adapter = IdeaResultAdapter()
260
+ self.scope_gate = ScopeAndSafetyGate()
261
+ self.template_router = TemplateRouter()
262
+ self.evidence_planner = EvidenceRetrievalPlanner(cache=self.cache)
263
+ self.completeness_validator = CompletenessValidator()
264
+ self.variable_claim_extractor = VariableClaimExtractor()
265
+ self.formal_reasoning_planner = FormalReasoningPlanner()
266
+ self.counterexample_analyzer = CounterexampleAnalyzer()
267
+ self.study_type_composer = StudyTypeTemplateComposer(
268
+ template_router=self.template_router,
269
+ scope_gate=self.scope_gate,
270
+ )
271
+ self.composer_llm_call = composer_llm_call
272
+
273
+ def _required_reasoning_llm(self, override: Callable[..., object] | None = None) -> Callable[..., object]:
274
+ callback = override or self.llm_call
275
+ if callback is not None:
276
+ return callback
277
+ if self._default_llm_call is None:
278
+ self._default_llm_call = build_default_json_llm_call(
279
+ config=self.config,
280
+ model=self.llm_model,
281
+ )
282
+ return self._default_llm_call
283
+
284
+ def _required_composer_llm(self, override: Callable[..., object] | None = None) -> Callable[..., object]:
285
+ return override or self.composer_llm_call or self.llm_call or self._required_reasoning_llm()
286
+
287
+ def prepare(
288
+ self,
289
+ research_brief: Mapping[str, Any],
290
+ *,
291
+ user_constraints: Mapping[str, Any] | None = None,
292
+ candidate_design: Mapping[str, Any] | None = None,
293
+ evidence_bundle: Mapping[str, Any] | None = None,
294
+ logger: ExperimentDesignRunLogger | None = None,
295
+ ) -> dict[str, Any]:
296
+ brief = _mapping(research_brief)
297
+ brief_errors = validate_research_brief(brief)
298
+ scope_gate = self.scope_gate.evaluate(
299
+ brief,
300
+ user_constraints=user_constraints,
301
+ allow_digital_execution=self.allow_digital_execution,
302
+ )
303
+ template_routing = self.template_router.route(brief, user_constraints=user_constraints)
304
+ cache_run_id = self.cache.begin_run(brief.get("brief_id"))
305
+ planning_degraded = False
306
+ if scope_gate["status"] == "IN_SCOPE" and not brief_errors:
307
+ try:
308
+ planner_kwargs: dict[str, Any] = {"llm_call": self._required_reasoning_llm()}
309
+ planner_parameters = inspect.signature(self.evidence_planner.plan).parameters
310
+ if "logger" in planner_parameters:
311
+ planner_kwargs["logger"] = logger
312
+ if "cache_run_id" in planner_parameters:
313
+ planner_kwargs["cache_run_id"] = cache_run_id
314
+ if "cache_context" in planner_parameters:
315
+ planner_kwargs["cache_context"] = _llm_cache_context(self.config, self.llm_model)
316
+ evidence_plan = self.evidence_planner.plan(brief, template_routing, **planner_kwargs)
317
+ except Exception as exc:
318
+ planning_degraded = True
319
+ _record_degradation(
320
+ logger,
321
+ stage="evidence_retrieval_planner",
322
+ brief_id=str(brief.get("brief_id") or ""),
323
+ error=exc,
324
+ )
325
+ evidence_plan = self.evidence_planner.degraded_plan(
326
+ brief,
327
+ template_routing,
328
+ reason=_degradation_reason("evidence_retrieval_planner"),
329
+ )
330
+ else:
331
+ evidence_plan = _blocked_evidence_plan(brief.get("brief_id"))
332
+ planning_degraded = planning_degraded or (
333
+ scope_gate["status"] == "IN_SCOPE"
334
+ and not brief_errors
335
+ and not bool(evidence_plan.get("llm_used"))
336
+ )
337
+ completeness = self.completeness_validator.assess(
338
+ brief,
339
+ template_routing,
340
+ candidate_design=candidate_design,
341
+ evidence_bundle=evidence_bundle,
342
+ )
343
+ unknown_items = list(completeness["unknown_items"])
344
+ if planning_degraded:
345
+ unknown_items.append(
346
+ {
347
+ "field_path": "evidence_retrieval_plan.queries",
348
+ "status": "needs_human_input",
349
+ "reason": _degradation_reason("evidence_retrieval_planner"),
350
+ "blocks_final_design": True,
351
+ }
352
+ )
353
+ human_review = scope_gate["risk_and_human_review"]
354
+ if human_review["human_review_required"]:
355
+ unknown_items.append(
356
+ {
357
+ "field_path": "risk_and_human_review.approval_dependencies",
358
+ "status": "needs_human_input",
359
+ "reason": "Required review and approval dependencies must be confirmed by qualified humans.",
360
+ "blocks_final_design": True,
361
+ }
362
+ )
363
+ if scope_gate["status"] != "IN_SCOPE" or brief_errors:
364
+ validation_status = "BLOCKED_BY_SCOPE"
365
+ elif human_review["human_review_required"]:
366
+ validation_status = "BLOCKED_BY_RISK_REVIEW"
367
+ else:
368
+ validation_status = completeness["status"]
369
+ return {
370
+ "schema_version": DESIGN_PREPARATION_SCHEMA_VERSION,
371
+ "stage": "DESIGN_PREPARATION",
372
+ "research_brief": brief,
373
+ "scope_gate": scope_gate,
374
+ "template_routing": template_routing,
375
+ "evidence_retrieval_plan": evidence_plan,
376
+ "cache_run_id": cache_run_id,
377
+ "cache_manifest": self.cache.run_manifest(cache_run_id),
378
+ "completeness": completeness,
379
+ "unknown_items": unknown_items,
380
+ "risk_and_human_review": human_review,
381
+ "observed_results": [],
382
+ "validation_report": {
383
+ "status": validation_status,
384
+ "errors": brief_errors + list(completeness["errors"]),
385
+ "warnings": list(evidence_plan["warnings"]),
386
+ },
387
+ }
388
+
389
+ def prepare_from_idea_result(
390
+ self,
391
+ idea_result: Mapping[str, Any],
392
+ *,
393
+ discipline_ids: object,
394
+ brief_id: str,
395
+ selected_direction: str = "",
396
+ user_constraints: Mapping[str, Any] | None = None,
397
+ audit_sources: Mapping[str, Any] | None = None,
398
+ ) -> dict[str, Any]:
399
+ brief = self.idea_adapter.adapt(
400
+ idea_result,
401
+ discipline_ids=discipline_ids,
402
+ brief_id=brief_id,
403
+ selected_direction=selected_direction,
404
+ audit_sources=audit_sources,
405
+ )
406
+ return self.prepare(brief, user_constraints=user_constraints)
407
+
408
+ def prepare_from_idea_path(
409
+ self,
410
+ idea_result_path: str,
411
+ *,
412
+ discipline_ids: object,
413
+ brief_id: str | None = None,
414
+ selected_direction: str = "",
415
+ user_constraints: Mapping[str, Any] | None = None,
416
+ ) -> dict[str, Any]:
417
+ """Prepare from one run directory while retaining audit provenance."""
418
+
419
+ brief = self.idea_adapter.adapt_path(
420
+ idea_result_path,
421
+ discipline_ids=discipline_ids,
422
+ brief_id=brief_id,
423
+ selected_direction=selected_direction,
424
+ )
425
+ return self.prepare(brief, user_constraints=user_constraints)
426
+
427
+ def compose_design(
428
+ self,
429
+ research_brief: Mapping[str, Any],
430
+ *,
431
+ user_constraints: Mapping[str, Any] | None = None,
432
+ evidence_bundle: Mapping[str, Any] | None = None,
433
+ composer_llm_call: Callable[..., object] | None = None,
434
+ reasoning_llm_call: Callable[..., object] | None = None,
435
+ logger: ExperimentDesignRunLogger | None = None,
436
+ ) -> dict[str, Any]:
437
+ """Create the final design-state artifact without invoking any executor."""
438
+
439
+ brief = _mapping(research_brief)
440
+ scope_gate = self.scope_gate.evaluate(
441
+ brief,
442
+ user_constraints=user_constraints,
443
+ allow_digital_execution=self.allow_digital_execution,
444
+ )
445
+ if scope_gate["status"] != "IN_SCOPE" or validate_research_brief(brief):
446
+ raise ValueError("ExperimentDesign composition is unavailable until the ResearchBrief is valid and in scope.")
447
+ routing = self.template_router.route(brief, user_constraints=user_constraints)
448
+ reasoning_context = build_reasoning_context_from_brief(brief)
449
+ brief_id = str(brief.get("brief_id") or "")
450
+ degradations: list[dict[str, str]] = []
451
+ if logger is not None:
452
+ logger.event(
453
+ "reasoning_context",
454
+ "completed",
455
+ status="COMPLETED",
456
+ brief_id=brief_id,
457
+ assumption_count=_sequence_count(reasoning_context.get("assumptions")),
458
+ falsifier_count=_sequence_count(reasoning_context.get("falsifiers")),
459
+ boundary_condition_count=_sequence_count(reasoning_context.get("boundary_conditions")),
460
+ alternative_explanation_count=_sequence_count(reasoning_context.get("alternative_explanations")),
461
+ )
462
+
463
+ if logger is not None:
464
+ logger.event(
465
+ "variable_claim_extraction",
466
+ "started",
467
+ status="RUNNING",
468
+ brief_id=brief_id,
469
+ )
470
+ try:
471
+ variable_claim_model = self.variable_claim_extractor.extract(
472
+ brief,
473
+ reasoning_context=reasoning_context,
474
+ llm_call=self._required_reasoning_llm(reasoning_llm_call),
475
+ )
476
+ except Exception as exc:
477
+ degradations.append(
478
+ _record_degradation(
479
+ logger,
480
+ stage="variable_claim_extraction",
481
+ brief_id=brief_id,
482
+ error=exc,
483
+ )
484
+ )
485
+ variable_claim_model = _degraded_variable_claim_model(
486
+ reason=_degradation_reason("variable_claim_extraction"),
487
+ )
488
+ if logger is not None:
489
+ logger.event(
490
+ "variable_claim_extraction",
491
+ "completed",
492
+ status="DEGRADED" if degradations else "COMPLETED",
493
+ brief_id=brief_id,
494
+ variable_count=_sequence_count(variable_claim_model.get("variables")),
495
+ claim_count=_sequence_count(variable_claim_model.get("claims")),
496
+ unknown_item_count=_sequence_count(variable_claim_model.get("unknown_items")),
497
+ )
498
+
499
+ formal_applicable = (
500
+ routing.get("primary_template") == "mathematics_theory"
501
+ and routing.get("submode") != "physical_validation"
502
+ )
503
+ variable_claim_degraded = any(
504
+ record["stage"] == "variable_claim_extraction" for record in degradations
505
+ )
506
+ formal_degraded = False
507
+ if formal_applicable:
508
+ if logger is not None:
509
+ logger.event(
510
+ "formal_reasoning_planner",
511
+ "started",
512
+ status="RUNNING",
513
+ brief_id=brief_id,
514
+ )
515
+ if variable_claim_degraded:
516
+ formal_degraded = True
517
+ degradations.append(
518
+ _record_degradation(
519
+ logger,
520
+ stage="formal_reasoning_planner",
521
+ brief_id=brief_id,
522
+ disposition="skipped_after_upstream_degradation",
523
+ )
524
+ )
525
+ formal_reasoning_plan = unavailable_formal_reasoning_plan(
526
+ reason=(
527
+ "Formal reasoning was not run because the variable and claim extraction batch was discarded; "
528
+ "a qualified human must supply the formalization."
529
+ ),
530
+ )
531
+ else:
532
+ try:
533
+ formal_reasoning_plan = self.formal_reasoning_planner.plan(
534
+ brief,
535
+ reasoning_context,
536
+ variable_claim_model,
537
+ llm_call=self._required_reasoning_llm(reasoning_llm_call),
538
+ logger=logger,
539
+ brief_id=brief_id,
540
+ )
541
+ except Exception as exc:
542
+ formal_degraded = True
543
+ degradations.append(
544
+ _record_degradation(
545
+ logger,
546
+ stage="formal_reasoning_planner",
547
+ brief_id=brief_id,
548
+ error=exc,
549
+ )
550
+ )
551
+ formal_reasoning_plan = unavailable_formal_reasoning_plan(
552
+ reason=_degradation_reason("formal_reasoning_planner"),
553
+ )
554
+ if logger is not None:
555
+ logger.event(
556
+ "formal_reasoning_planner",
557
+ "completed",
558
+ status="DEGRADED" if formal_degraded else "COMPLETED",
559
+ brief_id=brief_id,
560
+ assumption_count=_sequence_count(formal_reasoning_plan.get("assumptions")),
561
+ definition_count=_sequence_count(formal_reasoning_plan.get("definitions")),
562
+ proposition_count=_sequence_count(formal_reasoning_plan.get("propositions")),
563
+ proof_obligation_count=_sequence_count(formal_reasoning_plan.get("proof_obligations")),
564
+ forward_step_count=_sequence_count(
565
+ _mapping(formal_reasoning_plan.get("forward_derivation")).get("steps")
566
+ ),
567
+ )
568
+
569
+ if logger is not None:
570
+ logger.event(
571
+ "counterexample_analyzer",
572
+ "started",
573
+ status="RUNNING",
574
+ brief_id=brief_id,
575
+ )
576
+ counterexample_degraded = False
577
+ if formal_degraded:
578
+ counterexample_degraded = True
579
+ degradations.append(
580
+ _record_degradation(
581
+ logger,
582
+ stage="counterexample_analyzer",
583
+ brief_id=brief_id,
584
+ disposition="skipped_after_upstream_degradation",
585
+ )
586
+ )
587
+ counterexample_analysis = unavailable_counterexample_analysis(
588
+ reason=(
589
+ "Counterexample analysis was not run because its formal reasoning input was discarded; "
590
+ "a qualified human must define the target claim and search domain."
591
+ ),
592
+ )
593
+ else:
594
+ try:
595
+ counterexample_analysis = self.counterexample_analyzer.analyze(
596
+ brief,
597
+ reasoning_context,
598
+ variable_claim_model,
599
+ formal_reasoning_plan,
600
+ llm_call=self._required_reasoning_llm(reasoning_llm_call),
601
+ logger=logger,
602
+ brief_id=brief_id,
603
+ )
604
+ except Exception as exc:
605
+ counterexample_degraded = True
606
+ degradations.append(
607
+ _record_degradation(
608
+ logger,
609
+ stage="counterexample_analyzer",
610
+ brief_id=brief_id,
611
+ error=exc,
612
+ )
613
+ )
614
+ counterexample_analysis = unavailable_counterexample_analysis(
615
+ reason=_degradation_reason("counterexample_analyzer"),
616
+ )
617
+ if logger is not None:
618
+ logger.event(
619
+ "counterexample_analyzer",
620
+ "completed",
621
+ status="DEGRADED" if counterexample_degraded else "COMPLETED",
622
+ brief_id=brief_id,
623
+ candidate_count=_sequence_count(counterexample_analysis.get("candidate_counterexamples")),
624
+ unknown_item_count=_sequence_count(counterexample_analysis.get("unknown_items")),
625
+ exhaustiveness_is_exhaustive=bool(
626
+ _mapping(counterexample_analysis.get("exhaustiveness")).get("is_exhaustive")
627
+ ),
628
+ )
629
+ else:
630
+ formal_reasoning_plan = not_applicable_formal_reasoning_plan()
631
+ counterexample_analysis = not_applicable_counterexample_analysis()
632
+ if logger is not None:
633
+ logger.event(
634
+ "formal_reasoning_planner",
635
+ "completed",
636
+ status="NOT_APPLICABLE",
637
+ brief_id=brief_id,
638
+ template_id=str(routing.get("primary_template") or ""),
639
+ )
640
+ logger.event(
641
+ "counterexample_analyzer",
642
+ "completed",
643
+ status="NOT_APPLICABLE",
644
+ brief_id=brief_id,
645
+ template_id=str(routing.get("primary_template") or ""),
646
+ )
647
+
648
+ if logger is not None:
649
+ logger.event(
650
+ "reasoning_validation",
651
+ "started",
652
+ status="RUNNING",
653
+ brief_id=brief_id,
654
+ )
655
+
656
+ def discarded_reasoning_artifacts(reason: str) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
657
+ degraded_variable_claim_model = _degraded_variable_claim_model(reason=reason)
658
+ if formal_applicable:
659
+ degraded_formal_reasoning_plan = unavailable_formal_reasoning_plan(reason=reason)
660
+ degraded_counterexample_analysis = unavailable_counterexample_analysis(reason=reason)
661
+ else:
662
+ degraded_formal_reasoning_plan = not_applicable_formal_reasoning_plan()
663
+ degraded_counterexample_analysis = not_applicable_counterexample_analysis()
664
+ return (
665
+ degraded_variable_claim_model,
666
+ degraded_formal_reasoning_plan,
667
+ degraded_counterexample_analysis,
668
+ )
669
+
670
+ try:
671
+ reasoning_errors = validate_reasoning_artifacts(
672
+ variable_claim_model=variable_claim_model,
673
+ formal_reasoning_plan=formal_reasoning_plan,
674
+ counterexample_analysis=counterexample_analysis,
675
+ template_composition=routing,
676
+ )
677
+ except Exception as exc:
678
+ degradations.append(
679
+ _record_degradation(
680
+ logger,
681
+ stage="reasoning_validation",
682
+ brief_id=brief_id,
683
+ error=exc,
684
+ )
685
+ )
686
+ variable_claim_model, formal_reasoning_plan, counterexample_analysis = discarded_reasoning_artifacts(
687
+ _degradation_reason("reasoning_validation"),
688
+ )
689
+ reasoning_errors = validate_reasoning_artifacts(
690
+ variable_claim_model=variable_claim_model,
691
+ formal_reasoning_plan=formal_reasoning_plan,
692
+ counterexample_analysis=counterexample_analysis,
693
+ template_composition=routing,
694
+ )
695
+ if reasoning_errors:
696
+ if logger is not None:
697
+ logger.event(
698
+ "reasoning_validation",
699
+ "discarded_invalid_batch",
700
+ level="WARNING",
701
+ status="DEGRADED",
702
+ brief_id=brief_id,
703
+ error_count=len(reasoning_errors),
704
+ errors=reasoning_errors,
705
+ )
706
+ degradations.append(
707
+ _record_degradation(
708
+ logger,
709
+ stage="reasoning_validation",
710
+ brief_id=brief_id,
711
+ )
712
+ )
713
+ variable_claim_model, formal_reasoning_plan, counterexample_analysis = discarded_reasoning_artifacts(
714
+ _degradation_reason("reasoning_validation"),
715
+ )
716
+ reasoning_errors = validate_reasoning_artifacts(
717
+ variable_claim_model=variable_claim_model,
718
+ formal_reasoning_plan=formal_reasoning_plan,
719
+ counterexample_analysis=counterexample_analysis,
720
+ template_composition=routing,
721
+ )
722
+ if reasoning_errors:
723
+ raise RuntimeError(
724
+ "experiment_design: deterministic reasoning degradation failed validation: "
725
+ + "; ".join(reasoning_errors)
726
+ )
727
+ if logger is not None:
728
+ logger.event(
729
+ "reasoning_validation",
730
+ "completed",
731
+ status="DEGRADED" if degradations else "COMPLETED",
732
+ brief_id=brief_id,
733
+ error_count=0,
734
+ )
735
+
736
+ if logger is not None:
737
+ logger.event(
738
+ "template_composer",
739
+ "started",
740
+ status="RUNNING",
741
+ brief_id=brief_id,
742
+ template_id=str(routing.get("primary_template") or ""),
743
+ )
744
+ try:
745
+ design = self.study_type_composer.compose(
746
+ brief,
747
+ template_routing=routing,
748
+ evidence_bundle=evidence_bundle,
749
+ user_constraints=user_constraints,
750
+ llm_call=self._required_composer_llm(composer_llm_call),
751
+ reasoning_context=reasoning_context,
752
+ variable_claim_model=variable_claim_model,
753
+ formal_reasoning_plan=formal_reasoning_plan,
754
+ counterexample_analysis=counterexample_analysis,
755
+ logger=logger,
756
+ brief_id=brief_id,
757
+ )
758
+ except Exception as exc:
759
+ degradations.append(
760
+ _record_degradation(
761
+ logger,
762
+ stage="template_composer",
763
+ brief_id=brief_id,
764
+ error=exc,
765
+ )
766
+ )
767
+ design = self.study_type_composer.compose_deterministically(
768
+ brief,
769
+ template_routing=routing,
770
+ evidence_bundle=evidence_bundle,
771
+ user_constraints=user_constraints,
772
+ reasoning_context=reasoning_context,
773
+ variable_claim_model=variable_claim_model,
774
+ formal_reasoning_plan=formal_reasoning_plan,
775
+ counterexample_analysis=counterexample_analysis,
776
+ )
777
+ if logger is not None:
778
+ logger.event(
779
+ "template_composer",
780
+ "completed",
781
+ status=(
782
+ "DEGRADED"
783
+ if any(record["stage"] == "template_composer" for record in degradations)
784
+ else "COMPLETED"
785
+ ),
786
+ brief_id=brief_id,
787
+ template_id=str(routing.get("primary_template") or ""),
788
+ design_id=str(design.get("design_id") or ""),
789
+ llm_used=bool(_mapping(design.get("template_composition")).get("llm_used")),
790
+ )
791
+ design = _mark_design_degraded(design, degradations)
792
+ if logger is not None:
793
+ logger.event(
794
+ "compose_final_validation",
795
+ "started",
796
+ status="RUNNING",
797
+ brief_id=brief_id,
798
+ design_id=str(design.get("design_id") or ""),
799
+ )
800
+ logger.event(
801
+ "compose_final_validation",
802
+ "input_profiled",
803
+ status="PROFILED",
804
+ brief_id=brief_id,
805
+ **_design_validation_profile(design),
806
+ )
807
+ try:
808
+ final_errors = validate_experiment_design(design)
809
+ except Exception as exc:
810
+ degradations.append(
811
+ _record_degradation(
812
+ logger,
813
+ stage="compose_final_validation",
814
+ brief_id=brief_id,
815
+ error=exc,
816
+ disposition="discarded_after_validator_failure",
817
+ )
818
+ )
819
+ final_errors = ["final_contract_validator_unavailable"]
820
+ if final_errors:
821
+ if logger is not None:
822
+ logger.event(
823
+ "compose_final_validation",
824
+ "contract_validated",
825
+ level="WARNING",
826
+ status="DEGRADED",
827
+ brief_id=brief_id,
828
+ **_design_validation_profile(design),
829
+ **validation_summary(final_errors),
830
+ )
831
+ logger.event(
832
+ "compose_final_validation",
833
+ "discarded_invalid_candidate",
834
+ level="WARNING",
835
+ status="DEGRADED",
836
+ brief_id=brief_id,
837
+ design_id=str(design.get("design_id") or ""),
838
+ **validation_summary(final_errors),
839
+ )
840
+ if not any(record["stage"] == "compose_final_validation" for record in degradations):
841
+ degradations.append(
842
+ _record_degradation(
843
+ logger,
844
+ stage="compose_final_validation",
845
+ brief_id=brief_id,
846
+ )
847
+ )
848
+ design = self.study_type_composer.compose_deterministically(
849
+ brief,
850
+ template_routing=routing,
851
+ evidence_bundle=evidence_bundle,
852
+ user_constraints=user_constraints,
853
+ reasoning_context=reasoning_context,
854
+ variable_claim_model=variable_claim_model,
855
+ formal_reasoning_plan=formal_reasoning_plan,
856
+ counterexample_analysis=counterexample_analysis,
857
+ )
858
+ design = _mark_design_degraded(design, degradations)
859
+ if logger is not None:
860
+ logger.event(
861
+ "compose_final_validation",
862
+ "contract_validated",
863
+ status="VALID",
864
+ brief_id=brief_id,
865
+ **_design_validation_profile(design),
866
+ **validation_summary([]),
867
+ )
868
+ logger.event(
869
+ "compose_final_validation",
870
+ "completed",
871
+ status="DEGRADED" if degradations else "COMPLETED",
872
+ brief_id=brief_id,
873
+ design_id=str(design.get("design_id") or ""),
874
+ error_count=0,
875
+ )
876
+ design["reasoning_validation_report"] = {
877
+ "status": "STRUCTURALLY_VALID_UNVERIFIED_REASONING",
878
+ "errors": [],
879
+ "warnings": [
880
+ "Forward derivation and counterexample candidates are proposals unless an independent verifier or qualified human confirms them.",
881
+ "No experiment, simulation, symbolic execution, or exhaustive counterexample search was run.",
882
+ *(
883
+ [
884
+ "One or more LLM or workflow batches were discarded and require qualified human replacement."
885
+ ]
886
+ if degradations
887
+ else []
888
+ ),
889
+ ],
890
+ }
891
+ return design
892
+
893
+ def compose_design_from_idea_path(
894
+ self,
895
+ idea_result_path: str,
896
+ *,
897
+ discipline_ids: object,
898
+ brief_id: str | None = None,
899
+ selected_direction: str = "",
900
+ user_constraints: Mapping[str, Any] | None = None,
901
+ evidence_bundle: Mapping[str, Any] | None = None,
902
+ composer_llm_call: Callable[..., object] | None = None,
903
+ reasoning_llm_call: Callable[..., object] | None = None,
904
+ ) -> dict[str, Any]:
905
+ """Compose a final design from the canonical Idea run artifact."""
906
+
907
+ brief = self.idea_adapter.adapt_path(
908
+ idea_result_path,
909
+ discipline_ids=discipline_ids,
910
+ brief_id=brief_id,
911
+ selected_direction=selected_direction,
912
+ )
913
+ return self.compose_design(
914
+ brief,
915
+ user_constraints=user_constraints,
916
+ evidence_bundle=evidence_bundle,
917
+ composer_llm_call=composer_llm_call,
918
+ reasoning_llm_call=reasoning_llm_call,
919
+ )
920
+
921
+ def collect_survey_evidence(
922
+ self,
923
+ research_brief: Mapping[str, Any],
924
+ *,
925
+ survey_evidence_adapter: SurveyEvidenceAdapter | None = None,
926
+ survey_artifacts: Mapping[str, Any] | Sequence[Mapping[str, Any]] | None = None,
927
+ card_llm_call: Callable[[str], object] | None = None,
928
+ max_results_per_query: int = 10,
929
+ max_fulltext_papers: int = 15,
930
+ ) -> dict[str, Any]:
931
+ """Collect and extract traceable design evidence after scope and plan validation."""
932
+
933
+ preparation = self.prepare(research_brief)
934
+ plan = preparation["evidence_retrieval_plan"]
935
+ if plan["planning_status"] != "READY_FOR_RETRIEVAL":
936
+ preparation["survey_evidence"] = {
937
+ "schema_version": "experiment_design_survey_evidence_adaptation_v1",
938
+ "collection": {},
939
+ "evidence_bundle": {},
940
+ "warnings": ["Evidence collection was not started because the design scope is blocked."],
941
+ }
942
+ return preparation
943
+ adapter = survey_evidence_adapter or SurveyEvidenceAdapter.from_config(
944
+ card_llm_call=card_llm_call or self._required_reasoning_llm(),
945
+ )
946
+ evidence = adapter.collect_and_extract(
947
+ brief_id=str(research_brief.get("brief_id") or ""),
948
+ evidence_plan=plan,
949
+ survey_artifacts=survey_artifacts,
950
+ max_results_per_query=max_results_per_query,
951
+ max_fulltext_papers=max_fulltext_papers,
952
+ cache_run_id=str(preparation.get("cache_run_id") or ""),
953
+ )
954
+ preparation["survey_evidence"] = evidence
955
+ preparation["evidence_bundle"] = evidence["evidence_bundle"]
956
+ return preparation
957
+
958
+ def run_from_preparation(
959
+ self,
960
+ preparation: Mapping[str, Any],
961
+ *,
962
+ user_constraints: Mapping[str, Any] | None = None,
963
+ survey_evidence_adapter: SurveyEvidenceAdapter | None = None,
964
+ survey_artifacts: Mapping[str, Any] | Sequence[Mapping[str, Any]] | None = None,
965
+ card_llm_call: Callable[[str], object] | None = None,
966
+ composer_llm_call: Callable[..., object] | None = None,
967
+ reasoning_llm_call: Callable[..., object] | None = None,
968
+ max_results_per_query: int = 10,
969
+ max_fulltext_papers: int = 15,
970
+ logger: ExperimentDesignRunLogger | None = None,
971
+ _emit_run_events: bool = True,
972
+ ) -> dict[str, Any]:
973
+ """Complete a prepared design workflow without re-running preparation.
974
+
975
+ ``preparation`` owns the already generated Evidence Retrieval Planner
976
+ output. This method passes that exact plan to the survey adapter and
977
+ never calls ``prepare`` or ``collect_survey_evidence``. The method is
978
+ design-only: it never invokes an experiment, simulation, benchmark, or
979
+ other execution path.
980
+ """
981
+
982
+ prepared = _mapping(preparation)
983
+ brief = _mapping(prepared.get("research_brief"))
984
+ scope_gate = _mapping(prepared.get("scope_gate"))
985
+ evidence_plan_value = prepared.get("evidence_retrieval_plan")
986
+ evidence_plan = evidence_plan_value if isinstance(evidence_plan_value, Mapping) else {}
987
+ cache_run_id = str(prepared.get("cache_run_id") or "")
988
+ brief_id = str(brief.get("brief_id") or "")
989
+ if logger is not None and _emit_run_events:
990
+ logger.event(
991
+ "run",
992
+ "started",
993
+ status="RUNNING",
994
+ brief_id=brief_id,
995
+ execution_mode="DESIGN_ONLY",
996
+ )
997
+
998
+ try:
999
+ if not brief:
1000
+ raise ValueError("ExperimentDesign run requires preparation.research_brief.")
1001
+ if scope_gate.get("status") != "IN_SCOPE":
1002
+ raise ValueError(
1003
+ "ExperimentDesign run requires an in-scope preparation: "
1004
+ f"{scope_gate.get('status', 'UNKNOWN')}"
1005
+ )
1006
+ if evidence_plan.get("planning_status") != "READY_FOR_RETRIEVAL":
1007
+ raise ValueError(
1008
+ "ExperimentDesign run requires an evidence plan with "
1009
+ "planning_status=READY_FOR_RETRIEVAL."
1010
+ )
1011
+ if survey_evidence_adapter is None:
1012
+ survey_evidence_adapter = SurveyEvidenceAdapter.from_config(
1013
+ card_llm_call=card_llm_call or self._required_reasoning_llm(),
1014
+ config=self.config,
1015
+ )
1016
+ if logger is None:
1017
+ evidence = survey_evidence_adapter.collect_and_extract(
1018
+ brief_id=brief_id,
1019
+ evidence_plan=evidence_plan,
1020
+ survey_artifacts=survey_artifacts,
1021
+ max_results_per_query=max(1, int(max_results_per_query)),
1022
+ max_fulltext_papers=max(0, int(max_fulltext_papers)),
1023
+ cache_run_id=cache_run_id,
1024
+ )
1025
+ else:
1026
+ with logger.stage(
1027
+ "evidence",
1028
+ brief_id=brief_id,
1029
+ query_count=len(evidence_plan.get("queries") or []),
1030
+ ):
1031
+ evidence = survey_evidence_adapter.collect_and_extract(
1032
+ brief_id=brief_id,
1033
+ evidence_plan=evidence_plan,
1034
+ survey_artifacts=survey_artifacts,
1035
+ max_results_per_query=max(1, int(max_results_per_query)),
1036
+ max_fulltext_papers=max(0, int(max_fulltext_papers)),
1037
+ logger=logger,
1038
+ cache_run_id=cache_run_id,
1039
+ )
1040
+ if not isinstance(evidence, Mapping):
1041
+ raise ValueError("SurveyEvidenceAdapter did not return an evidence object.")
1042
+ evidence = dict(evidence)
1043
+ evidence_bundle = evidence.get("evidence_bundle")
1044
+ if not isinstance(evidence_bundle, Mapping):
1045
+ raise ValueError("SurveyEvidenceAdapter did not return an EvidenceBundle.")
1046
+
1047
+ compose_call = lambda: self.compose_design(
1048
+ brief,
1049
+ user_constraints=user_constraints,
1050
+ evidence_bundle=dict(evidence_bundle),
1051
+ composer_llm_call=composer_llm_call,
1052
+ reasoning_llm_call=reasoning_llm_call,
1053
+ logger=logger,
1054
+ )
1055
+ if logger is None:
1056
+ design = compose_call()
1057
+ else:
1058
+ with logger.stage("compose", brief_id=brief_id):
1059
+ design = compose_call()
1060
+ if not isinstance(design, Mapping):
1061
+ raise ValueError("StudyTypeTemplateComposer did not return an experiment design object.")
1062
+ design = dict(design)
1063
+
1064
+ def validate() -> list[str]:
1065
+ nonlocal design
1066
+ try:
1067
+ validation_errors = validate_experiment_design(design)
1068
+ except Exception as exc:
1069
+ validation_errors = ["final_contract_validator_unavailable"]
1070
+ degradation = _record_degradation(
1071
+ logger,
1072
+ stage="validation",
1073
+ brief_id=brief_id,
1074
+ error=exc,
1075
+ disposition="discarded_after_validator_failure",
1076
+ )
1077
+ else:
1078
+ degradation = None
1079
+ if validation_errors:
1080
+ if degradation is None:
1081
+ degradation = _record_degradation(
1082
+ logger,
1083
+ stage="validation",
1084
+ brief_id=brief_id,
1085
+ disposition="discarded_invalid_candidate",
1086
+ )
1087
+ design = self.study_type_composer.compose_deterministically(
1088
+ brief,
1089
+ evidence_bundle=dict(evidence_bundle),
1090
+ user_constraints=user_constraints,
1091
+ )
1092
+ design = _mark_design_degraded(design, [degradation])
1093
+ return []
1094
+
1095
+ if logger is None:
1096
+ validation_errors = validate()
1097
+ else:
1098
+ with logger.stage("validation", design_id=str(design.get("design_id") or "")):
1099
+ validation_errors = validate()
1100
+ validation = {
1101
+ "status": "VALID",
1102
+ "schema_version": str(design.get("schema_version") or ""),
1103
+ "errors": [],
1104
+ "observed_results_count": len(design.get("observed_results") or []),
1105
+ }
1106
+ result = {
1107
+ "schema_version": DESIGN_RUN_SCHEMA_VERSION,
1108
+ "status": "COMPLETED",
1109
+ "preparation": prepared,
1110
+ "survey_evidence": evidence,
1111
+ "composition": {
1112
+ "status": "COMPOSED",
1113
+ "design_id": str(design.get("design_id") or ""),
1114
+ },
1115
+ "validation": validation,
1116
+ "experiment_design": design,
1117
+ }
1118
+ if logger is not None and _emit_run_events:
1119
+ logger.event(
1120
+ "run",
1121
+ "completed",
1122
+ status="COMPLETED",
1123
+ execution_mode=_mapping(design.get("execution_policy")).get("mode", "DESIGN_ONLY"),
1124
+ observed_results_count=validation["observed_results_count"],
1125
+ )
1126
+ return result
1127
+ except Exception as error:
1128
+ if logger is not None and _emit_run_events:
1129
+ logger.exception("run", error)
1130
+ raise
1131
+
1132
+ def run_from_idea_path(
1133
+ self,
1134
+ idea_result_path: str,
1135
+ *,
1136
+ discipline_ids: object,
1137
+ brief_id: str | None = None,
1138
+ selected_direction: str = "",
1139
+ user_constraints: Mapping[str, Any] | None = None,
1140
+ survey_evidence_adapter: SurveyEvidenceAdapter | None = None,
1141
+ survey_artifacts: Mapping[str, Any] | Sequence[Mapping[str, Any]] | None = None,
1142
+ card_llm_call: Callable[[str], object] | None = None,
1143
+ composer_llm_call: Callable[..., object] | None = None,
1144
+ reasoning_llm_call: Callable[..., object] | None = None,
1145
+ max_results_per_query: int = 10,
1146
+ max_fulltext_papers: int = 15,
1147
+ logger: ExperimentDesignRunLogger | None = None,
1148
+ ) -> dict[str, Any]:
1149
+ """Run intake, one preparation pass, evidence, composition, and validation."""
1150
+
1151
+ if logger is not None:
1152
+ logger.event(
1153
+ "run",
1154
+ "started",
1155
+ status="RUNNING",
1156
+ canonical_input_path=str(idea_result_path),
1157
+ execution_mode="DESIGN_ONLY",
1158
+ )
1159
+ try:
1160
+ if logger is None:
1161
+ brief = self.idea_adapter.adapt_path(
1162
+ idea_result_path,
1163
+ discipline_ids=discipline_ids,
1164
+ brief_id=brief_id,
1165
+ selected_direction=selected_direction,
1166
+ )
1167
+ else:
1168
+ with logger.stage("intake", canonical_input_path=str(idea_result_path)):
1169
+ brief = self.idea_adapter.adapt_path(
1170
+ idea_result_path,
1171
+ discipline_ids=discipline_ids,
1172
+ brief_id=brief_id,
1173
+ selected_direction=selected_direction,
1174
+ )
1175
+ if not isinstance(brief, Mapping):
1176
+ raise ValueError("IdeaResultAdapter did not return a ResearchBrief object.")
1177
+ if logger is None:
1178
+ preparation = self.prepare(brief, user_constraints=user_constraints)
1179
+ else:
1180
+ with logger.stage("prepare", brief_id=str(brief.get("brief_id") or "")):
1181
+ preparation = self.prepare(brief, user_constraints=user_constraints, logger=logger)
1182
+ if not isinstance(preparation, Mapping):
1183
+ raise ValueError("ExperimentDesignOrchestrator.prepare did not return a preparation object.")
1184
+
1185
+ result = self.run_from_preparation(
1186
+ preparation,
1187
+ user_constraints=user_constraints,
1188
+ survey_evidence_adapter=survey_evidence_adapter,
1189
+ survey_artifacts=survey_artifacts,
1190
+ card_llm_call=card_llm_call,
1191
+ composer_llm_call=composer_llm_call,
1192
+ reasoning_llm_call=reasoning_llm_call,
1193
+ max_results_per_query=max_results_per_query,
1194
+ max_fulltext_papers=max_fulltext_papers,
1195
+ logger=logger,
1196
+ _emit_run_events=False,
1197
+ )
1198
+ source = _mapping(brief.get("source"))
1199
+ result["intake"] = {
1200
+ "status": "LOADED",
1201
+ "canonical_input_path": str(idea_result_path),
1202
+ "run_directory": str(Path(idea_result_path).expanduser().resolve().parent),
1203
+ "idea_result_schema": source.get("idea_result_schema", ""),
1204
+ "selected_direction_id": source.get("direction_id", ""),
1205
+ "audit_source_paths": _mapping(source.get("upstream_source_paths")),
1206
+ "missing_audit_sources": list(source.get("missing_audit_sources") or []),
1207
+ }
1208
+ if logger is not None:
1209
+ logger.event(
1210
+ "run",
1211
+ "completed",
1212
+ status="COMPLETED",
1213
+ execution_mode=_mapping(result["experiment_design"].get("execution_policy")).get("mode", "DESIGN_ONLY"),
1214
+ observed_results_count=result["validation"]["observed_results_count"],
1215
+ )
1216
+ return result
1217
+ except Exception as error:
1218
+ if logger is not None:
1219
+ logger.exception("run", error)
1220
+ raise