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,765 @@
1
+ """Batch-A preparation entrypoint for the Research Plan Author."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
6
+ from datetime import datetime, timezone
7
+ import os
8
+ from pathlib import Path
9
+ import re
10
+ from copy import deepcopy
11
+ from collections.abc import Callable, Mapping
12
+ from typing import Any
13
+
14
+ from .authoring_blueprint import AuthoringBlueprintError, AuthoringBlueprintPlanner
15
+ from .bibtex_renderer import BibtexRenderError, bibliography_preflight_errors
16
+ from .contract_repair import AuthorContractRepairError
17
+ from .document_quality import optimize_research_plan_document
18
+ from .contracts import (
19
+ AUTHORING_LANGUAGE,
20
+ AUTHOR_PREPARATION_SCHEMA_VERSION,
21
+ build_research_plan_document_skeleton,
22
+ )
23
+ from .idea_evolution import (
24
+ IdeaEvolutionError,
25
+ disabled_idea_evolution,
26
+ project_idea_evolution,
27
+ unavailable_idea_evolution,
28
+ )
29
+ from .input_loader import AuthorInputLoadError, load_author_input_with_identity
30
+ from .run_logging import AuthorRunLogger
31
+ from .section_cache import SectionCompositionCache, section_cache_identity
32
+ from .section_composer import SectionComposer, SectionCompositionError, validate_section_output
33
+ from .section_router import route_author_sections
34
+ from .semantic_validator import validate_composed_research_plan
35
+ from .source_bundle import build_author_source_bundle
36
+ from .source_registry import (
37
+ build_authoring_knowledge_base,
38
+ build_frozen_source_registry,
39
+ source_registry_for_blueprint_section,
40
+ )
41
+ from .survey_source_loader import SurveyAuthorSourceError, load_verified_survey_sources
42
+ from .theory_spine import TheorySpineError, build_theory_spine
43
+
44
+
45
+ class AuthorRunError(RuntimeError):
46
+ """Typed Author preparation failure with a stage for CLI reporting."""
47
+
48
+ def __init__(self, stage: str, message: str) -> None:
49
+ super().__init__(f"{stage}: {message}")
50
+ self.stage = stage
51
+
52
+
53
+ class AuthorCompositionError(AuthorRunError):
54
+ """Composition failure that preserves a private contract-repair audit."""
55
+
56
+ def __init__(self, message: str, *, audit: Mapping[str, Any] | None = None) -> None:
57
+ super().__init__("composition", message)
58
+ self.audit = deepcopy(dict(audit)) if isinstance(audit, Mapping) else None
59
+
60
+
61
+ _WSL_MOUNTED_PATH = re.compile(r"^/mnt/([A-Za-z])(?:/(.*))?$")
62
+ _WINDOWS_DRIVE_PATH = re.compile(r"^([A-Za-z]):[\\/](.*)$")
63
+ _SURVEY_BINDING_FIELDS = (
64
+ "survey_run_id",
65
+ "project_id",
66
+ "project_context_fingerprint",
67
+ )
68
+
69
+
70
+ def _resolve_optional_path(value: str | Path | None) -> Path | None:
71
+ if value is None or not str(value).strip():
72
+ return None
73
+ raw_value = os.fspath(value)
74
+ normalized = raw_value.replace("\\", "/")
75
+ if os.name == "nt":
76
+ wsl_match = _WSL_MOUNTED_PATH.fullmatch(normalized)
77
+ path = Path(f"{wsl_match.group(1).upper()}:/{wsl_match.group(2) or ''}") if wsl_match else Path(raw_value)
78
+ else:
79
+ windows_match = _WINDOWS_DRIVE_PATH.fullmatch(normalized)
80
+ path = Path(f"/mnt/{windows_match.group(1).lower()}/{windows_match.group(2)}") if windows_match else Path(raw_value)
81
+ return path.expanduser().resolve()
82
+
83
+
84
+ def _mapping(value: object) -> dict[str, Any]:
85
+ return dict(value) if isinstance(value, dict) else {}
86
+
87
+
88
+ def _text(value: object) -> str:
89
+ return str(value or "").strip()
90
+
91
+
92
+ def _verify_survey_binding(
93
+ author_input: dict[str, Any],
94
+ survey_sources: dict[str, Any],
95
+ *,
96
+ strict: bool,
97
+ ) -> dict[str, Any]:
98
+ """Bind Survey identity to the design or expose the missing binding clearly."""
99
+
100
+ provenance = _mapping(author_input.get("provenance"))
101
+ expected = {field: _text(_mapping(provenance.get("survey_binding")).get(field)) for field in _SURVEY_BINDING_FIELDS}
102
+ resolved = {field: _text(survey_sources.get(field)) for field in _SURVEY_BINDING_FIELDS}
103
+ has_binding = any(expected.values())
104
+ if not has_binding:
105
+ if strict:
106
+ raise AuthorRunError(
107
+ "survey",
108
+ "Author handoff has no provenance.survey_binding; strict Survey binding requires all identity fields",
109
+ )
110
+ return {
111
+ "status": "UNBOUND_REQUIRES_HUMAN_CONFIRMATION",
112
+ "expected": expected,
113
+ "resolved": resolved,
114
+ "human_confirmation_required": True,
115
+ }
116
+ missing = [field for field, value in expected.items() if not value]
117
+ if missing:
118
+ raise AuthorRunError(
119
+ "survey",
120
+ "Author handoff provenance.survey_binding is incomplete; missing " + ", ".join(missing),
121
+ )
122
+ mismatched = [field for field in _SURVEY_BINDING_FIELDS if expected[field] != resolved[field]]
123
+ if mismatched:
124
+ raise AuthorRunError(
125
+ "survey",
126
+ "Survey manifest does not match Author handoff provenance.survey_binding for " + ", ".join(mismatched),
127
+ )
128
+ return {
129
+ "status": "BOUND_VERIFIED",
130
+ "expected": expected,
131
+ "resolved": resolved,
132
+ "human_confirmation_required": False,
133
+ }
134
+
135
+
136
+ def run_author_preparation(
137
+ author_input_path: str | Path,
138
+ *,
139
+ survey_manifest_path: str | Path,
140
+ idea_result_path: str | Path | None = None,
141
+ include_idea_evolution: str = "auto",
142
+ max_idea_iterations: int = 3,
143
+ strict_survey_binding: bool = False,
144
+ logger: AuthorRunLogger | None = None,
145
+ ) -> dict[str, Any]:
146
+ """Verify all Batch-A sources and produce no prose, TeX, or PDF yet."""
147
+
148
+ mode = str(include_idea_evolution or "auto").strip().casefold()
149
+ if mode not in {"auto", "on", "off"}:
150
+ raise AuthorRunError("input", "include_idea_evolution must be one of auto, on, or off")
151
+ if max_idea_iterations not in {2, 3}:
152
+ raise AuthorRunError("input", "max_idea_iterations must be either 2 or 3")
153
+ active_logger = logger
154
+ try:
155
+ if active_logger is None:
156
+ resolved_author_input, author_input, author_input_identity = load_author_input_with_identity(author_input_path)
157
+ else:
158
+ with active_logger.stage("input", input_path=str(author_input_path)):
159
+ resolved_author_input, author_input, author_input_identity = load_author_input_with_identity(author_input_path)
160
+ except AuthorInputLoadError as error:
161
+ raise AuthorRunError("input", str(error)) from error
162
+ source_registry = _mapping(author_input.get("source_registry"))
163
+ try:
164
+ if active_logger is None:
165
+ bibliography_errors = bibliography_preflight_errors(source_registry.get("citation_registry"))
166
+ else:
167
+ with active_logger.stage(
168
+ "bibliography_preflight",
169
+ citation_count=len(source_registry.get("citation_registry") or []),
170
+ ):
171
+ bibliography_errors = bibliography_preflight_errors(source_registry.get("citation_registry"))
172
+ if bibliography_errors:
173
+ raise AuthorRunError(
174
+ "input",
175
+ "citation registry contains records that cannot render before composition: "
176
+ + "; ".join(bibliography_errors),
177
+ )
178
+ except BibtexRenderError as error:
179
+ raise AuthorRunError("input", f"invalid citation registry before composition: {error}") from error
180
+ if bibliography_errors:
181
+ raise AuthorRunError(
182
+ "input",
183
+ "citation registry contains records that cannot render before composition: "
184
+ + "; ".join(bibliography_errors),
185
+ )
186
+ try:
187
+ if active_logger is None:
188
+ survey_sources = load_verified_survey_sources(survey_manifest_path)
189
+ else:
190
+ with active_logger.stage("survey", manifest_path=str(survey_manifest_path)):
191
+ survey_sources = load_verified_survey_sources(survey_manifest_path)
192
+ except SurveyAuthorSourceError as error:
193
+ raise AuthorRunError("survey", str(error)) from error
194
+ try:
195
+ survey_binding = _verify_survey_binding(
196
+ author_input,
197
+ survey_sources,
198
+ strict=bool(strict_survey_binding),
199
+ )
200
+ except AuthorRunError:
201
+ raise
202
+ if active_logger is not None:
203
+ active_logger.emit(
204
+ "survey",
205
+ "binding_verified" if survey_binding["status"] == "BOUND_VERIFIED" else "binding_unbound",
206
+ level="INFO" if survey_binding["status"] == "BOUND_VERIFIED" else "WARNING",
207
+ status=survey_binding["status"],
208
+ human_confirmation_required=survey_binding["human_confirmation_required"],
209
+ )
210
+ provenance = author_input.get("provenance") or {}
211
+ selected_direction_id = str(provenance.get("selected_direction_id") or "").strip()
212
+ requested_idea_path = _resolve_optional_path(idea_result_path)
213
+ inherited_idea_path = _resolve_optional_path(provenance.get("idea_result_path"))
214
+ resolved_idea_path = requested_idea_path or inherited_idea_path
215
+ if mode == "off":
216
+ idea_evolution = disabled_idea_evolution()
217
+ elif resolved_idea_path is None:
218
+ if mode == "on":
219
+ raise AuthorRunError("idea_evolution", "--idea-result is required when --include-idea-evolution=on")
220
+ idea_evolution = unavailable_idea_evolution("No explicit or provenance idea_result.json path is available")
221
+ else:
222
+ try:
223
+ if active_logger is None:
224
+ idea_evolution = project_idea_evolution(
225
+ resolved_idea_path,
226
+ selected_direction_id=selected_direction_id,
227
+ max_iterations=max_idea_iterations,
228
+ )
229
+ else:
230
+ with active_logger.stage(
231
+ "idea_evolution",
232
+ idea_result_path=str(resolved_idea_path),
233
+ selected_direction_id=selected_direction_id,
234
+ ):
235
+ idea_evolution = project_idea_evolution(
236
+ resolved_idea_path,
237
+ selected_direction_id=selected_direction_id,
238
+ max_iterations=max_idea_iterations,
239
+ )
240
+ except IdeaEvolutionError as error:
241
+ if "selected direction mismatch" in str(error):
242
+ raise AuthorRunError("idea_evolution", str(error)) from error
243
+ if mode == "auto" and requested_idea_path is None:
244
+ idea_evolution = unavailable_idea_evolution(str(error))
245
+ if active_logger is not None:
246
+ active_logger.emit(
247
+ "idea_evolution",
248
+ "unavailable",
249
+ level="WARNING",
250
+ status="UNAVAILABLE",
251
+ reason=str(error),
252
+ )
253
+ else:
254
+ raise AuthorRunError("idea_evolution", str(error)) from error
255
+ source_bundle = build_author_source_bundle(
256
+ author_input,
257
+ author_input_path=str(resolved_author_input),
258
+ author_input_identity=author_input_identity,
259
+ survey_sources=survey_sources,
260
+ survey_binding=survey_binding,
261
+ idea_evolution=idea_evolution,
262
+ )
263
+ document = build_research_plan_document_skeleton(author_input, source_bundle)
264
+ theory_preparation = {
265
+ "source_design_id": author_input["source_design_id"],
266
+ "source_bundle": source_bundle,
267
+ "document": document,
268
+ }
269
+ routing = route_author_sections(author_input)
270
+ try:
271
+ if active_logger is None:
272
+ theory_spine = build_theory_spine(
273
+ theory_preparation,
274
+ routing=routing,
275
+ source_registry=build_frozen_source_registry(theory_preparation),
276
+ )
277
+ else:
278
+ with active_logger.stage("theory_spine", template_family=routing["template_family"]):
279
+ theory_spine = build_theory_spine(
280
+ theory_preparation,
281
+ routing=routing,
282
+ source_registry=build_frozen_source_registry(theory_preparation),
283
+ )
284
+ except TheorySpineError as error:
285
+ raise AuthorRunError("theory_spine", str(error)) from error
286
+ result = {
287
+ "schema_version": AUTHOR_PREPARATION_SCHEMA_VERSION,
288
+ "status": "PREPARED_FOR_COMPOSITION",
289
+ "generated_at": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
290
+ "language": AUTHORING_LANGUAGE,
291
+ "source_design_id": author_input["source_design_id"],
292
+ "selected_direction_id": selected_direction_id,
293
+ "source_bundle": source_bundle,
294
+ "document": document,
295
+ "theory_spine": theory_spine,
296
+ }
297
+ if active_logger is not None:
298
+ active_logger.emit(
299
+ "preparation",
300
+ "completed",
301
+ status="COMPLETED",
302
+ source_design_id=result["source_design_id"],
303
+ selected_direction_id=selected_direction_id,
304
+ idea_evolution_status=idea_evolution["status"],
305
+ survey_binding_status=survey_binding["status"],
306
+ theory_spine_enabled=theory_spine["enabled"],
307
+ language=AUTHORING_LANGUAGE,
308
+ )
309
+ return result
310
+
311
+
312
+ def _mapping_value(value: object) -> dict[str, Any]:
313
+ return dict(value) if isinstance(value, Mapping) else {}
314
+
315
+
316
+ def _append_unique(items: list[Any], additions: object) -> None:
317
+ if not isinstance(additions, list):
318
+ return
319
+ for candidate in additions:
320
+ if candidate not in items:
321
+ items.append(deepcopy(candidate))
322
+
323
+
324
+ def _namespace_section_claim_ids(section: Mapping[str, Any]) -> dict[str, Any]:
325
+ """Make locally valid claim identifiers unique in the assembled document."""
326
+
327
+ normalized = deepcopy(dict(section))
328
+ section_id = str(normalized.get("section_id") or "").strip()
329
+ claims = [dict(claim) for claim in normalized.get("claim_provenance") or [] if isinstance(claim, Mapping)]
330
+ normalized["claim_provenance"] = claims
331
+ claim_id_map = {
332
+ str(claim.get("claim_id") or "").strip(): f"{section_id}:{str(claim.get('claim_id') or '').strip()}"
333
+ for claim in claims
334
+ }
335
+ for claim in claims:
336
+ claim_id = str(claim.get("claim_id") or "").strip()
337
+ claim["claim_id"] = claim_id_map[claim_id]
338
+ blocks: list[Any] = []
339
+ for block in normalized.get("blocks") or []:
340
+ if not isinstance(block, Mapping):
341
+ blocks.append(deepcopy(block))
342
+ continue
343
+ normalized_block = dict(block)
344
+ block_claim_ids = [str(claim_id).strip() for claim_id in normalized_block.get("claim_ids") or []]
345
+ normalized_block["claim_ids"] = [claim_id_map.get(claim_id, claim_id) for claim_id in block_claim_ids]
346
+ blocks.append(normalized_block)
347
+ normalized["blocks"] = blocks
348
+ return normalized
349
+
350
+
351
+ def _render_composed_document(
352
+ preparation: Mapping[str, Any],
353
+ *,
354
+ blueprint: Mapping[str, Any],
355
+ routing: Mapping[str, Any],
356
+ source_registry: Mapping[str, Any],
357
+ composed_sections: list[tuple[Mapping[str, Any], Mapping[str, Any]]],
358
+ repair_audits: list[Mapping[str, Any]],
359
+ ) -> dict[str, Any]:
360
+ """Merge validated routed sections without allowing them to replace provenance."""
361
+
362
+ document = deepcopy(_mapping_value(preparation.get("document")))
363
+ document["document_status"] = "PROPOSAL_NO_OBSERVED_RESULTS"
364
+ metadata = _mapping_value(document.get("document_metadata"))
365
+ metadata["title"] = str(blueprint.get("document_title") or "").strip()
366
+ metadata["title_status"] = "english_llm_composed"
367
+ document["document_metadata"] = metadata
368
+ document["keywords"] = list(blueprint.get("keywords") or [])
369
+ document["authoring_blueprint"] = deepcopy(dict(blueprint))
370
+ document["theory_spine"] = deepcopy(_mapping_value(preparation.get("theory_spine")))
371
+ document["citation_registry"] = deepcopy(list(source_registry.get("citation_registry") or []))
372
+ document["claim_provenance"] = []
373
+ document["sections"] = []
374
+ document["appendices"] = []
375
+ document["contract_repair_audit"] = [deepcopy(dict(audit)) for audit in repair_audits]
376
+ all_open_items: list[Any] = []
377
+ all_review_items: list[Any] = []
378
+ for route, raw_section in composed_sections:
379
+ section = _namespace_section_claim_ids(raw_section)
380
+ section_id = str(section.get("section_id") or "")
381
+ _append_unique(all_open_items, section.get("open_items"))
382
+ _append_unique(all_review_items, section.get("review_items"))
383
+ document["claim_provenance"].extend(deepcopy(list(section.get("claim_provenance") or [])))
384
+ if route.get("target") == "abstract":
385
+ document["abstract"] = {
386
+ "text": "\n\n".join(
387
+ str(block.get("text") or "").strip()
388
+ for block in section.get("blocks") or []
389
+ if isinstance(block, Mapping) and str(block.get("text") or "").strip()
390
+ ),
391
+ "claim_ids": list(
392
+ dict.fromkeys(
393
+ str(claim_id).strip()
394
+ for block in section.get("blocks") or []
395
+ if isinstance(block, Mapping)
396
+ for claim_id in block.get("claim_ids") or []
397
+ if str(claim_id).strip()
398
+ )
399
+ ),
400
+ }
401
+ continue
402
+ rendered = {
403
+ "section_id": section_id,
404
+ "title": str(section.get("title") or ""),
405
+ "applicability": str(section.get("applicability") or ""),
406
+ "blocks": deepcopy(list(section.get("blocks") or [])),
407
+ }
408
+ target = document["appendices"] if route.get("target") == "appendices" else document["sections"]
409
+ target.append(rendered)
410
+ document["open_items"] = all_open_items
411
+ document["review_items"] = all_review_items
412
+ source_manifest = _mapping_value(document.get("source_manifest"))
413
+ bundle = _mapping_value(preparation.get("source_bundle"))
414
+ source_manifest.update(
415
+ {
416
+ "author_context_sha256": _mapping_value(bundle.get("author_input_identity")).get("sha256", ""),
417
+ "survey_binding": deepcopy(_mapping_value(bundle.get("survey_binding"))),
418
+ "template_family": routing.get("template_family", ""),
419
+ "theory_sampling_power_status": routing.get("theory_sampling_power_status", "route_specific"),
420
+ }
421
+ )
422
+ document["source_manifest"] = source_manifest
423
+ return document
424
+
425
+
426
+ def run_research_plan_author(
427
+ author_input_path: str | Path,
428
+ *,
429
+ survey_manifest_path: str | Path,
430
+ idea_result_path: str | Path | None = None,
431
+ include_idea_evolution: str = "auto",
432
+ max_idea_iterations: int = 3,
433
+ strict_survey_binding: bool = False,
434
+ llm_call: Callable[..., object] | None = None,
435
+ max_contract_repairs: int = 1,
436
+ composer_concurrency: int = 5,
437
+ section_cache_config: Mapping[str, Any] | None = None,
438
+ document_quality_config: Mapping[str, Any] | None = None,
439
+ quality_judge_llm_call: Callable[..., object] | None = None,
440
+ quality_revision_llm_call: Callable[..., object] | None = None,
441
+ collect_section_contract_errors: bool = True,
442
+ logger: AuthorRunLogger | None = None,
443
+ ) -> dict[str, Any]:
444
+ """Run preparation, section composition, and final proposal-only validation.
445
+
446
+ Every routed section is attempted even when earlier sections fail. Failed
447
+ sections emit warnings and the run stops only after the full section batch
448
+ has completed, with one aggregate failure audit. The legacy
449
+ ``collect_section_contract_errors`` argument is retained for callers but
450
+ section failures are always aggregated.
451
+ """
452
+
453
+ if max_contract_repairs not in (0, 1):
454
+ raise AuthorRunError(
455
+ "input",
456
+ "max_contract_repairs must be 0 or 1; each section supports at most one generic repair",
457
+ )
458
+ resolved_input_path = _resolve_optional_path(author_input_path)
459
+ preparation = run_author_preparation(
460
+ author_input_path,
461
+ survey_manifest_path=survey_manifest_path,
462
+ idea_result_path=idea_result_path,
463
+ include_idea_evolution=include_idea_evolution,
464
+ max_idea_iterations=max_idea_iterations,
465
+ strict_survey_binding=strict_survey_binding,
466
+ logger=logger,
467
+ )
468
+ if composer_concurrency < 1:
469
+ raise AuthorRunError("input", "composer_concurrency must be at least 1")
470
+ source_registry = build_frozen_source_registry(preparation)
471
+ # The catalog remains source-bounded, but all sections receive the same
472
+ # upstream knowledge base. Route recommendations are a writing aid, not
473
+ # a permission system.
474
+ source_registry["authoring_knowledge_base"] = build_authoring_knowledge_base(
475
+ preparation,
476
+ source_registry,
477
+ )
478
+ author_context = _mapping_value(_mapping_value(preparation.get("source_bundle")).get("author_context"))
479
+ routing = route_author_sections(author_context)
480
+ planner = AuthoringBlueprintPlanner()
481
+ section_repairs_enabled = bool(max_contract_repairs)
482
+ resolved_cache_config = dict(section_cache_config or {})
483
+ if "root" not in resolved_cache_config and resolved_input_path is not None:
484
+ resolved_cache_config["root"] = str(
485
+ resolved_input_path.parent / ".science" / "cache" / "research_plan_author" / "v1"
486
+ )
487
+ section_cache = SectionCompositionCache(resolved_cache_config)
488
+ repair_audits: list[Mapping[str, Any]] = []
489
+ try:
490
+ if logger is None:
491
+ blueprint, blueprint_audit = planner.plan_with_audit(
492
+ preparation,
493
+ routing=routing,
494
+ source_registry=source_registry,
495
+ llm_call=llm_call,
496
+ allow_contract_repair=section_repairs_enabled,
497
+ logger=logger,
498
+ )
499
+ else:
500
+ with logger.stage("blueprint", template_family=routing["template_family"]):
501
+ blueprint, blueprint_audit = planner.plan_with_audit(
502
+ preparation,
503
+ routing=routing,
504
+ source_registry=source_registry,
505
+ llm_call=llm_call,
506
+ allow_contract_repair=section_repairs_enabled,
507
+ logger=logger,
508
+ )
509
+ if blueprint_audit is not None:
510
+ repair_audits.append(blueprint_audit)
511
+ except AuthorContractRepairError as error:
512
+ raise AuthorCompositionError(str(error), audit=error.audit) from error
513
+ except AuthoringBlueprintError as error:
514
+ raise AuthorCompositionError(str(error), audit=error.audit) from error
515
+ except Exception as error:
516
+ raise AuthorCompositionError(str(error)) from error
517
+
518
+ blueprint_sections = {
519
+ str(section.get("section_id") or ""): section
520
+ for section in blueprint.get("sections") or []
521
+ if isinstance(section, Mapping)
522
+ }
523
+ composed_sections: list[tuple[Mapping[str, Any], Mapping[str, Any]]] = []
524
+ section_failures: list[dict[str, Any]] = []
525
+ section_jobs = [
526
+ (
527
+ route_index,
528
+ route,
529
+ _mapping_value(blueprint_sections.get(str(route["section_id"]))),
530
+ )
531
+ for route_index, route in enumerate(routing["routes"])
532
+ ]
533
+
534
+ def compose_section(
535
+ route_index: int,
536
+ route: Mapping[str, Any],
537
+ blueprint_section: Mapping[str, Any],
538
+ ) -> tuple[int, Mapping[str, Any], Mapping[str, Any] | None, Mapping[str, Any] | None, dict[str, Any] | None]:
539
+ section_id = str(route["section_id"])
540
+ section_source_registry = source_registry_for_blueprint_section(source_registry, route, blueprint_section)
541
+ cache_identity = section_cache_identity(
542
+ preparation=preparation,
543
+ blueprint=blueprint,
544
+ route=route,
545
+ blueprint_section=blueprint_section,
546
+ source_registry=section_source_registry,
547
+ )
548
+ cached_section = section_cache.read(cache_identity)
549
+ if cached_section is not None:
550
+ cached_errors = validate_section_output(
551
+ cached_section,
552
+ route=route,
553
+ blueprint_section=blueprint_section,
554
+ preparation=preparation,
555
+ source_registry=section_source_registry,
556
+ allow_cross_reference_quality_warnings=True,
557
+ )
558
+ if not cached_errors:
559
+ if logger is not None:
560
+ logger.emit(
561
+ "section_composition",
562
+ "cache_hit",
563
+ status="CACHED",
564
+ section_id=section_id,
565
+ )
566
+ return route_index, route, cached_section, None, None
567
+ if logger is not None:
568
+ logger.emit(
569
+ "section_composition",
570
+ "cache_rejected",
571
+ level="WARNING",
572
+ status="CACHE_REJECTED",
573
+ section_id=section_id,
574
+ validation_error_count=len(cached_errors),
575
+ )
576
+ try:
577
+ if logger is None:
578
+ section, audit = SectionComposer().compose(
579
+ preparation,
580
+ blueprint=blueprint,
581
+ route=route,
582
+ blueprint_section=blueprint_section,
583
+ source_registry=section_source_registry,
584
+ llm_call=llm_call,
585
+ allow_contract_repair=section_repairs_enabled,
586
+ )
587
+ else:
588
+ with logger.stage(
589
+ "section_composition",
590
+ failure_level="WARNING",
591
+ failure_status="WARNING",
592
+ section_id=section_id,
593
+ ):
594
+ section, audit = SectionComposer().compose(
595
+ preparation,
596
+ blueprint=blueprint,
597
+ route=route,
598
+ blueprint_section=blueprint_section,
599
+ source_registry=section_source_registry,
600
+ llm_call=llm_call,
601
+ allow_contract_repair=section_repairs_enabled,
602
+ )
603
+ except (AuthorContractRepairError, SectionCompositionError) as error:
604
+ failure = {
605
+ "section_id": section_id,
606
+ "error_code": type(error).__name__,
607
+ "error": str(error),
608
+ }
609
+ if isinstance(error.audit, Mapping):
610
+ failure["audit"] = deepcopy(dict(error.audit))
611
+ validation_errors = error.audit.get("initial_validation_errors") or error.audit.get("repair_validation_errors") or []
612
+ if isinstance(validation_errors, list):
613
+ failure["validation_error_count"] = len(validation_errors)
614
+ return route_index, route, None, None, failure
615
+ except Exception as error:
616
+ return (
617
+ route_index,
618
+ route,
619
+ None,
620
+ None,
621
+ {
622
+ "section_id": section_id,
623
+ "error_code": type(error).__name__,
624
+ "error": str(error),
625
+ "validation_error_count": 0,
626
+ },
627
+ )
628
+ section_cache.write(cache_identity, section)
629
+ return route_index, route, section, audit, None
630
+
631
+ with ThreadPoolExecutor(
632
+ max_workers=min(int(composer_concurrency), len(section_jobs)),
633
+ thread_name_prefix="author-section",
634
+ ) as executor:
635
+ futures = [
636
+ executor.submit(compose_section, route_index, route, blueprint_section)
637
+ for route_index, route, blueprint_section in section_jobs
638
+ ]
639
+ section_results = [future.result() for future in as_completed(futures)]
640
+ section_quality_warning_count = 0
641
+ for _route_index, route, section, audit, failure in sorted(section_results, key=lambda result: result[0]):
642
+ if failure is not None:
643
+ section_failures.append(failure)
644
+ continue
645
+ if audit is not None:
646
+ repair_audits.append(audit)
647
+ quality_warnings = audit.get("quality_warnings") if isinstance(audit, Mapping) else []
648
+ if quality_warnings:
649
+ section_quality_warning_count += 1
650
+ if logger is not None:
651
+ logger.emit(
652
+ "section_composition",
653
+ "quality_warning",
654
+ level="WARNING",
655
+ status="WARNING",
656
+ section_id=str(route.get("section_id") or ""),
657
+ warning_count=len(quality_warnings),
658
+ )
659
+ if section is not None:
660
+ composed_sections.append((route, section))
661
+ if section_failures:
662
+ diagnostic_audit = {
663
+ "schema_version": "research_plan_author_section_contract_diagnostics_v1",
664
+ "artifact_kind": "section_contract_preflight",
665
+ "mode": "complete_all_sections_before_abort",
666
+ "section_count": len(routing["routes"]),
667
+ "attempted_section_count": len(routing["routes"]),
668
+ "passed_section_count": len(composed_sections),
669
+ "failed_section_count": len(section_failures),
670
+ "section_cache": section_cache.summary(),
671
+ "failures": section_failures,
672
+ }
673
+ if logger is not None:
674
+ logger.emit(
675
+ "section_diagnostics",
676
+ "completed",
677
+ level="WARNING",
678
+ status="REJECTED",
679
+ section_count=len(routing["routes"]),
680
+ failed_section_count=len(section_failures),
681
+ passed_section_count=len(composed_sections),
682
+ )
683
+ failed_section_ids = ", ".join(failure["section_id"] for failure in section_failures)
684
+ raise AuthorCompositionError(
685
+ f"section contract preflight found {len(section_failures)} failing section(s): {failed_section_ids}",
686
+ audit=diagnostic_audit,
687
+ )
688
+ document = _render_composed_document(
689
+ preparation,
690
+ blueprint=blueprint,
691
+ routing=routing,
692
+ source_registry=source_registry,
693
+ composed_sections=composed_sections,
694
+ repair_audits=repair_audits,
695
+ )
696
+ # Whole-document quality scoring and revision now owns cross-section
697
+ # coherence, deduplication, and scholarly depth. Do not pre-edit the
698
+ # canonical 15-section manuscript with the legacy constrained editor:
699
+ # it makes an unscored extra LLM call and prevents the quality loop from
700
+ # considering the actual composed draft as its baseline candidate.
701
+ if logger is not None:
702
+ logger.emit("document_quality", "started", status="RUNNING")
703
+ try:
704
+ document, document_quality = optimize_research_plan_document(
705
+ document,
706
+ preparation=preparation,
707
+ routing=routing,
708
+ source_registry=source_registry,
709
+ quality_config=document_quality_config,
710
+ judge_llm_call=quality_judge_llm_call,
711
+ revision_llm_call=quality_revision_llm_call,
712
+ logger=logger,
713
+ )
714
+ except Exception as error:
715
+ document_quality = {
716
+ "schema_version": "research_plan_author_document_quality_v1",
717
+ "enabled": bool(_mapping_value(document_quality_config).get("enabled")),
718
+ "candidates": [],
719
+ "selected_candidate_index": 0,
720
+ "warnings": [f"document quality loop failed without blocking composition: {type(error).__name__}: {error}"],
721
+ }
722
+ if logger is not None:
723
+ logger.emit("document_quality", "failed", level="WARNING", status="WARNING", error=str(error))
724
+ else:
725
+ if logger is not None:
726
+ logger.emit(
727
+ "document_quality",
728
+ "completed",
729
+ status="COMPLETED",
730
+ selected_candidate_index=document_quality.get("selected_candidate_index", 0),
731
+ candidate_count=len(document_quality.get("candidates") or []),
732
+ warning_count=len(document_quality.get("warnings") or []),
733
+ )
734
+ errors = validate_composed_research_plan(
735
+ document,
736
+ preparation=preparation,
737
+ routing=routing,
738
+ source_registry=source_registry,
739
+ )
740
+ if errors:
741
+ raise AuthorCompositionError("final semantic validation failed: " + "; ".join(errors))
742
+ result = deepcopy(preparation)
743
+ result["status"] = "COMPOSED_FOR_RENDERING"
744
+ result["document"] = document
745
+ result["section_cache"] = section_cache.summary()
746
+ result["document_quality"] = document_quality
747
+ if logger is not None:
748
+ logger.emit(
749
+ "composition",
750
+ "completed",
751
+ status="COMPLETED",
752
+ source_design_id=result["source_design_id"],
753
+ template_family=routing["template_family"],
754
+ contract_repair_count=sum(
755
+ 1 for audit in repair_audits if isinstance(audit, Mapping) and audit.get("repair_attempted")
756
+ ),
757
+ section_quality_warning_count=section_quality_warning_count,
758
+ section_cache_hits=result["section_cache"]["hits"],
759
+ section_cache_writes=result["section_cache"]["writes"],
760
+ language=AUTHORING_LANGUAGE,
761
+ )
762
+ return result
763
+
764
+
765
+ __all__ = ["AuthorCompositionError", "AuthorRunError", "run_author_preparation", "run_research_plan_author"]