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.
- qwen_sci-0.2.2.dist-info/METADATA +492 -0
- qwen_sci-0.2.2.dist-info/RECORD +549 -0
- qwen_sci-0.2.2.dist-info/WHEEL +4 -0
- qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
- qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
- src/1.txt +1 -0
- src/__init__.py +1 -0
- src/__main__.py +5 -0
- src/agents/__init__.py +1 -0
- src/agents/experiment_design_agent/__init__.py +268 -0
- src/agents/experiment_design_agent/artifacts.py +747 -0
- src/agents/experiment_design_agent/cache.py +319 -0
- src/agents/experiment_design_agent/completeness.py +130 -0
- src/agents/experiment_design_agent/contracts.py +771 -0
- src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
- src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
- src/agents/experiment_design_agent/discipline_catalog.py +178 -0
- src/agents/experiment_design_agent/evidence_cards.py +338 -0
- src/agents/experiment_design_agent/evidence_planner.py +452 -0
- src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
- src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
- src/agents/experiment_design_agent/idea_adapter.py +100 -0
- src/agents/experiment_design_agent/idea_intake.py +87 -0
- src/agents/experiment_design_agent/llm_json.py +328 -0
- src/agents/experiment_design_agent/orchestrator.py +1220 -0
- src/agents/experiment_design_agent/reasoning_context.py +183 -0
- src/agents/experiment_design_agent/reasoning_validation.py +546 -0
- src/agents/experiment_design_agent/run.py +481 -0
- src/agents/experiment_design_agent/run_logging.py +292 -0
- src/agents/experiment_design_agent/scope_gate.py +198 -0
- src/agents/experiment_design_agent/study_type_composer.py +1360 -0
- src/agents/experiment_design_agent/survey_evidence.py +1381 -0
- src/agents/experiment_design_agent/template_router.py +195 -0
- src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
- src/agents/idea_agent/__init__.py +1 -0
- src/agents/idea_agent/agent/__init__.py +6 -0
- src/agents/idea_agent/agent/artifacts.py +308 -0
- src/agents/idea_agent/agent/base.py +122 -0
- src/agents/idea_agent/agent/ligagent.py +488 -0
- src/agents/idea_agent/agent/mcts.py +2060 -0
- src/agents/idea_agent/agent/prompts/__init__.py +48 -0
- src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
- src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
- src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
- src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
- src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
- src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
- src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
- src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
- src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
- src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
- src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
- src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
- src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
- src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
- src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
- src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
- src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
- src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
- src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
- src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
- src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
- src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
- src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
- src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
- src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
- src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
- src/agents/idea_agent/example/idea_result.json +900 -0
- src/agents/idea_agent/run.py +276 -0
- src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
- src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
- src/agents/idea_agent/tests/test_idea_debate.py +480 -0
- src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
- src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
- src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
- src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
- src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
- src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
- src/agents/idea_agent/tests/test_result_persistence.py +183 -0
- src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
- src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
- src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
- src/agents/idea_agent/utils/core/__init__.py +1 -0
- src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
- src/agents/idea_agent/utils/core/chat_errors.py +95 -0
- src/agents/idea_agent/utils/core/chat_router.py +130 -0
- src/agents/idea_agent/utils/core/chat_transport.py +188 -0
- src/agents/idea_agent/utils/core/config_loader.py +166 -0
- src/agents/idea_agent/utils/core/json_utils.py +28 -0
- src/agents/idea_agent/utils/core/logger.py +287 -0
- src/agents/idea_agent/utils/core/progress.py +58 -0
- src/agents/idea_agent/utils/core/response_parsing.py +57 -0
- src/agents/idea_agent/utils/core/run_inputs.py +198 -0
- src/agents/idea_agent/utils/mcts/__init__.py +1 -0
- src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
- src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
- src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
- src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
- src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
- src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
- src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
- src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
- src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
- src/agents/idea_agent/utils/papers/__init__.py +1 -0
- src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
- src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
- src/agents/idea_agent/utils/prompting/__init__.py +1 -0
- src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
- src/agents/idea_agent/utils/workflow/__init__.py +1 -0
- src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
- src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
- src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
- src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
- src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
- src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
- src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
- src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
- src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
- src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
- src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
- src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
- src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
- src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
- src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
- src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
- src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
- src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
- src/agents/research_plan_author/__init__.py +50 -0
- src/agents/research_plan_author/artifacts.py +270 -0
- src/agents/research_plan_author/authoring_blueprint.py +1246 -0
- src/agents/research_plan_author/bibtex_renderer.py +252 -0
- src/agents/research_plan_author/contract_repair.py +131 -0
- src/agents/research_plan_author/contracts.py +745 -0
- src/agents/research_plan_author/cross_section_editor.py +232 -0
- src/agents/research_plan_author/document_quality.py +987 -0
- src/agents/research_plan_author/idea_evolution.py +223 -0
- src/agents/research_plan_author/input_loader.py +76 -0
- src/agents/research_plan_author/latex_compiler.py +260 -0
- src/agents/research_plan_author/latex_safety.py +159 -0
- src/agents/research_plan_author/llm_json.py +74 -0
- src/agents/research_plan_author/markdown_renderer.py +275 -0
- src/agents/research_plan_author/pdf_validator.py +149 -0
- src/agents/research_plan_author/render.py +166 -0
- src/agents/research_plan_author/render_artifacts.py +187 -0
- src/agents/research_plan_author/run.py +765 -0
- src/agents/research_plan_author/run_logging.py +209 -0
- src/agents/research_plan_author/section_cache.py +184 -0
- src/agents/research_plan_author/section_composer.py +1764 -0
- src/agents/research_plan_author/section_router.py +209 -0
- src/agents/research_plan_author/semantic_validator.py +254 -0
- src/agents/research_plan_author/source_bundle.py +46 -0
- src/agents/research_plan_author/source_registry.py +208 -0
- src/agents/research_plan_author/survey_source_loader.py +137 -0
- src/agents/research_plan_author/template_adapter.py +173 -0
- src/agents/research_plan_author/template_profile.py +204 -0
- src/agents/research_plan_author/tex_renderer.py +496 -0
- src/agents/research_plan_author/theory_presentation.py +134 -0
- src/agents/research_plan_author/theory_spine.py +703 -0
- src/agents/survey_agent/Dockerfile +54 -0
- src/agents/survey_agent/README.md +350 -0
- src/agents/survey_agent/config/deep_survey.yaml +217 -0
- src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
- src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
- src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
- src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
- src/agents/survey_agent/env_report.md +348 -0
- src/agents/survey_agent/modules/__init__.py +0 -0
- src/agents/survey_agent/modules/code_collector.py +2194 -0
- src/agents/survey_agent/modules/code_report_generator.py +1282 -0
- src/agents/survey_agent/modules/data_manager.py +1713 -0
- src/agents/survey_agent/modules/database.py +167 -0
- src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
- src/agents/survey_agent/modules/fulltext_http.py +134 -0
- src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
- src/agents/survey_agent/modules/judge.py +434 -0
- src/agents/survey_agent/modules/outcome_RAG.py +236 -0
- src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
- src/agents/survey_agent/modules/pe.py +1729 -0
- src/agents/survey_agent/modules/pseudo_creator.py +782 -0
- src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
- src/agents/survey_agent/modules/refine_agent.py +1952 -0
- src/agents/survey_agent/modules/survey_generator.py +5691 -0
- src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
- src/agents/survey_agent/modules/visual_prompts.py +327 -0
- src/agents/survey_agent/modules/work_analyzer.py +2750 -0
- src/agents/survey_agent/modules/work_collector.py +5649 -0
- src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
- src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
- src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
- src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
- src/agents/survey_agent/utils/__init__.py +0 -0
- src/agents/survey_agent/utils/ablation_utils.py +317 -0
- src/agents/survey_agent/utils/api_call.py +2518 -0
- src/agents/survey_agent/utils/chat_utils.py +82 -0
- src/agents/survey_agent/utils/config_utils.py +70 -0
- src/agents/survey_agent/utils/convert_to_md.py +29 -0
- src/agents/survey_agent/utils/err_info.py +24 -0
- src/agents/survey_agent/utils/file_utils.py +176 -0
- src/agents/survey_agent/utils/gpu_utils.py +145 -0
- src/agents/survey_agent/utils/graph_visualizer.py +279 -0
- src/agents/survey_agent/utils/html_utils.py +181 -0
- src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
- src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
- src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
- src/agents/survey_agent/utils/mineru_utils.py +396 -0
- src/agents/survey_agent/utils/paper.py +144 -0
- src/agents/survey_agent/utils/read_yaml.py +15 -0
- src/agents/survey_agent/utils/repo_utils.py +167 -0
- src/agents/survey_agent/utils/rich_logger.py +77 -0
- src/agents/survey_agent/utils/step2v2.py +874 -0
- src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
- src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
- src/agents/survey_agent/utils/utils.py +118 -0
- src/agents/survey_agent/utils/work.py +2 -0
- src/cli.py +1611 -0
- src/config/__init__.py +213 -0
- src/config/default.yaml +1122 -0
- src/harness/LICENSE +21 -0
- src/harness/VENDORED.md +14 -0
- src/harness/src/__init__.py +0 -0
- src/harness/src/openharness/__init__.py +0 -0
- src/harness/src/openharness/__main__.py +6 -0
- src/harness/src/openharness/api/__init__.py +21 -0
- src/harness/src/openharness/api/client.py +281 -0
- src/harness/src/openharness/api/codex_client.py +407 -0
- src/harness/src/openharness/api/copilot_auth.py +240 -0
- src/harness/src/openharness/api/copilot_client.py +134 -0
- src/harness/src/openharness/api/errors.py +19 -0
- src/harness/src/openharness/api/openai_client.py +577 -0
- src/harness/src/openharness/api/provider.py +186 -0
- src/harness/src/openharness/api/registry.py +437 -0
- src/harness/src/openharness/api/usage.py +17 -0
- src/harness/src/openharness/auth/__init__.py +29 -0
- src/harness/src/openharness/auth/external.py +610 -0
- src/harness/src/openharness/auth/flows.py +189 -0
- src/harness/src/openharness/auth/manager.py +482 -0
- src/harness/src/openharness/auth/storage.py +269 -0
- src/harness/src/openharness/autopilot/__init__.py +23 -0
- src/harness/src/openharness/autopilot/service.py +2239 -0
- src/harness/src/openharness/autopilot/types.py +91 -0
- src/harness/src/openharness/bridge/__init__.py +20 -0
- src/harness/src/openharness/bridge/manager.py +106 -0
- src/harness/src/openharness/bridge/session_runner.py +46 -0
- src/harness/src/openharness/bridge/types.py +37 -0
- src/harness/src/openharness/bridge/work_secret.py +41 -0
- src/harness/src/openharness/channels/UPSTREAM +6 -0
- src/harness/src/openharness/channels/__init__.py +22 -0
- src/harness/src/openharness/channels/adapter.py +131 -0
- src/harness/src/openharness/channels/bus/__init__.py +6 -0
- src/harness/src/openharness/channels/bus/events.py +38 -0
- src/harness/src/openharness/channels/bus/queue.py +44 -0
- src/harness/src/openharness/channels/impl/__init__.py +6 -0
- src/harness/src/openharness/channels/impl/base.py +141 -0
- src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
- src/harness/src/openharness/channels/impl/discord.py +311 -0
- src/harness/src/openharness/channels/impl/email.py +410 -0
- src/harness/src/openharness/channels/impl/feishu.py +1342 -0
- src/harness/src/openharness/channels/impl/manager.py +257 -0
- src/harness/src/openharness/channels/impl/matrix.py +700 -0
- src/harness/src/openharness/channels/impl/mochat.py +897 -0
- src/harness/src/openharness/channels/impl/qq.py +141 -0
- src/harness/src/openharness/channels/impl/slack.py +284 -0
- src/harness/src/openharness/channels/impl/telegram.py +525 -0
- src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
- src/harness/src/openharness/cli.py +2551 -0
- src/harness/src/openharness/commands/__init__.py +21 -0
- src/harness/src/openharness/commands/registry.py +2783 -0
- src/harness/src/openharness/config/__init__.py +34 -0
- src/harness/src/openharness/config/paths.py +160 -0
- src/harness/src/openharness/config/schema.py +119 -0
- src/harness/src/openharness/config/settings.py +1104 -0
- src/harness/src/openharness/coordinator/__init__.py +12 -0
- src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
- src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
- src/harness/src/openharness/engine/__init__.py +80 -0
- src/harness/src/openharness/engine/cost_tracker.py +24 -0
- src/harness/src/openharness/engine/messages.py +222 -0
- src/harness/src/openharness/engine/query.py +1188 -0
- src/harness/src/openharness/engine/query_engine.py +313 -0
- src/harness/src/openharness/engine/stream_events.py +90 -0
- src/harness/src/openharness/hooks/__init__.py +50 -0
- src/harness/src/openharness/hooks/events.py +20 -0
- src/harness/src/openharness/hooks/executor.py +242 -0
- src/harness/src/openharness/hooks/hot_reload.py +31 -0
- src/harness/src/openharness/hooks/loader.py +68 -0
- src/harness/src/openharness/hooks/schemas.py +66 -0
- src/harness/src/openharness/hooks/types.py +38 -0
- src/harness/src/openharness/keybindings/__init__.py +14 -0
- src/harness/src/openharness/keybindings/default_bindings.py +11 -0
- src/harness/src/openharness/keybindings/loader.py +22 -0
- src/harness/src/openharness/keybindings/parser.py +18 -0
- src/harness/src/openharness/keybindings/resolver.py +13 -0
- src/harness/src/openharness/mcp/__init__.py +79 -0
- src/harness/src/openharness/mcp/client.py +298 -0
- src/harness/src/openharness/mcp/config.py +16 -0
- src/harness/src/openharness/mcp/types.py +76 -0
- src/harness/src/openharness/memory/__init__.py +25 -0
- src/harness/src/openharness/memory/agent.py +91 -0
- src/harness/src/openharness/memory/manager.py +183 -0
- src/harness/src/openharness/memory/memdir.py +52 -0
- src/harness/src/openharness/memory/migrate.py +137 -0
- src/harness/src/openharness/memory/paths.py +22 -0
- src/harness/src/openharness/memory/relevance.py +145 -0
- src/harness/src/openharness/memory/scan.py +116 -0
- src/harness/src/openharness/memory/schema.py +443 -0
- src/harness/src/openharness/memory/search.py +71 -0
- src/harness/src/openharness/memory/team.py +90 -0
- src/harness/src/openharness/memory/types.py +33 -0
- src/harness/src/openharness/memory/usage.py +153 -0
- src/harness/src/openharness/output_styles/__init__.py +5 -0
- src/harness/src/openharness/output_styles/loader.py +42 -0
- src/harness/src/openharness/permissions/__init__.py +26 -0
- src/harness/src/openharness/permissions/checker.py +200 -0
- src/harness/src/openharness/permissions/modes.py +13 -0
- src/harness/src/openharness/personalization/__init__.py +6 -0
- src/harness/src/openharness/personalization/extractor.py +138 -0
- src/harness/src/openharness/personalization/rules.py +64 -0
- src/harness/src/openharness/personalization/session_hook.py +65 -0
- src/harness/src/openharness/platforms.py +86 -0
- src/harness/src/openharness/plugins/__init__.py +53 -0
- src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
- src/harness/src/openharness/plugins/installer.py +39 -0
- src/harness/src/openharness/plugins/loader.py +730 -0
- src/harness/src/openharness/plugins/schemas.py +24 -0
- src/harness/src/openharness/plugins/types.py +59 -0
- src/harness/src/openharness/prompts/__init__.py +14 -0
- src/harness/src/openharness/prompts/claudemd.py +48 -0
- src/harness/src/openharness/prompts/context.py +187 -0
- src/harness/src/openharness/prompts/environment.py +135 -0
- src/harness/src/openharness/prompts/system_prompt.py +109 -0
- src/harness/src/openharness/sandbox/Dockerfile +6 -0
- src/harness/src/openharness/sandbox/__init__.py +33 -0
- src/harness/src/openharness/sandbox/adapter.py +148 -0
- src/harness/src/openharness/sandbox/docker_backend.py +232 -0
- src/harness/src/openharness/sandbox/docker_image.py +103 -0
- src/harness/src/openharness/sandbox/path_validator.py +37 -0
- src/harness/src/openharness/sandbox/session.py +63 -0
- src/harness/src/openharness/services/__init__.py +30 -0
- src/harness/src/openharness/services/autodream/__init__.py +34 -0
- src/harness/src/openharness/services/autodream/backup.py +104 -0
- src/harness/src/openharness/services/autodream/lock.py +138 -0
- src/harness/src/openharness/services/autodream/prompt.py +128 -0
- src/harness/src/openharness/services/autodream/service.py +313 -0
- src/harness/src/openharness/services/compact/__init__.py +1725 -0
- src/harness/src/openharness/services/cron.py +137 -0
- src/harness/src/openharness/services/cron_scheduler.py +595 -0
- src/harness/src/openharness/services/lsp/__init__.py +216 -0
- src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
- src/harness/src/openharness/services/oauth/__init__.py +0 -0
- src/harness/src/openharness/services/session_backend.py +97 -0
- src/harness/src/openharness/services/session_memory/__init__.py +139 -0
- src/harness/src/openharness/services/session_storage.py +230 -0
- src/harness/src/openharness/services/token_estimation.py +15 -0
- src/harness/src/openharness/services/tool_outputs.py +55 -0
- src/harness/src/openharness/skills/__init__.py +44 -0
- src/harness/src/openharness/skills/_frontmatter.py +97 -0
- src/harness/src/openharness/skills/bundled/__init__.py +75 -0
- src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
- src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
- src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
- src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
- src/harness/src/openharness/skills/bundled/content/review.md +33 -0
- src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
- src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
- src/harness/src/openharness/skills/bundled/content/test.md +38 -0
- src/harness/src/openharness/skills/loader.py +229 -0
- src/harness/src/openharness/skills/registry.py +29 -0
- src/harness/src/openharness/skills/types.py +24 -0
- src/harness/src/openharness/state/__init__.py +6 -0
- src/harness/src/openharness/state/app_state.py +30 -0
- src/harness/src/openharness/state/store.py +40 -0
- src/harness/src/openharness/swarm/__init__.py +70 -0
- src/harness/src/openharness/swarm/in_process.py +693 -0
- src/harness/src/openharness/swarm/lockfile.py +24 -0
- src/harness/src/openharness/swarm/mailbox.py +522 -0
- src/harness/src/openharness/swarm/permission_sync.py +1168 -0
- src/harness/src/openharness/swarm/registry.py +410 -0
- src/harness/src/openharness/swarm/spawn_utils.py +229 -0
- src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
- src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
- src/harness/src/openharness/swarm/types.py +398 -0
- src/harness/src/openharness/swarm/worktree.py +315 -0
- src/harness/src/openharness/tasks/__init__.py +18 -0
- src/harness/src/openharness/tasks/local_agent_task.py +28 -0
- src/harness/src/openharness/tasks/local_shell_task.py +17 -0
- src/harness/src/openharness/tasks/manager.py +475 -0
- src/harness/src/openharness/tasks/stop_task.py +11 -0
- src/harness/src/openharness/tasks/types.py +32 -0
- src/harness/src/openharness/themes/__init__.py +21 -0
- src/harness/src/openharness/themes/builtin.py +89 -0
- src/harness/src/openharness/themes/loader.py +55 -0
- src/harness/src/openharness/themes/schema.py +54 -0
- src/harness/src/openharness/tools/__init__.py +107 -0
- src/harness/src/openharness/tools/agent_tool.py +141 -0
- src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
- src/harness/src/openharness/tools/base.py +80 -0
- src/harness/src/openharness/tools/bash_tool.py +259 -0
- src/harness/src/openharness/tools/brief_tool.py +33 -0
- src/harness/src/openharness/tools/config_tool.py +54 -0
- src/harness/src/openharness/tools/cron_create_tool.py +105 -0
- src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
- src/harness/src/openharness/tools/cron_list_tool.py +69 -0
- src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
- src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
- src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
- src/harness/src/openharness/tools/file_edit_tool.py +95 -0
- src/harness/src/openharness/tools/file_read_tool.py +72 -0
- src/harness/src/openharness/tools/file_write_tool.py +87 -0
- src/harness/src/openharness/tools/glob_tool.py +175 -0
- src/harness/src/openharness/tools/grep_tool.py +374 -0
- src/harness/src/openharness/tools/image_generation_tool.py +447 -0
- src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
- src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
- src/harness/src/openharness/tools/lsp_tool.py +154 -0
- src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
- src/harness/src/openharness/tools/mcp_tool.py +72 -0
- src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
- src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
- src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
- src/harness/src/openharness/tools/send_message_tool.py +58 -0
- src/harness/src/openharness/tools/skill_tool.py +43 -0
- src/harness/src/openharness/tools/sleep_tool.py +32 -0
- src/harness/src/openharness/tools/task_create_tool.py +56 -0
- src/harness/src/openharness/tools/task_get_tool.py +33 -0
- src/harness/src/openharness/tools/task_list_tool.py +35 -0
- src/harness/src/openharness/tools/task_output_tool.py +35 -0
- src/harness/src/openharness/tools/task_stop_tool.py +30 -0
- src/harness/src/openharness/tools/task_update_tool.py +50 -0
- src/harness/src/openharness/tools/team_create_tool.py +31 -0
- src/harness/src/openharness/tools/team_delete_tool.py +30 -0
- src/harness/src/openharness/tools/todo_write_tool.py +46 -0
- src/harness/src/openharness/tools/tool_search_tool.py +38 -0
- src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
- src/harness/src/openharness/tools/web_search_tool.py +119 -0
- src/harness/src/openharness/ui/__init__.py +5 -0
- src/harness/src/openharness/ui/app.py +320 -0
- src/harness/src/openharness/ui/backend_host.py +941 -0
- src/harness/src/openharness/ui/coordinator_drain.py +198 -0
- src/harness/src/openharness/ui/input.py +32 -0
- src/harness/src/openharness/ui/output.py +265 -0
- src/harness/src/openharness/ui/permission_dialog.py +14 -0
- src/harness/src/openharness/ui/protocol.py +247 -0
- src/harness/src/openharness/ui/react_launcher.py +179 -0
- src/harness/src/openharness/ui/runtime.py +799 -0
- src/harness/src/openharness/ui/textual_app.py +495 -0
- src/harness/src/openharness/utils/__init__.py +0 -0
- src/harness/src/openharness/utils/file_lock.py +86 -0
- src/harness/src/openharness/utils/fs.py +98 -0
- src/harness/src/openharness/utils/helpers.py +77 -0
- src/harness/src/openharness/utils/network_guard.py +340 -0
- src/harness/src/openharness/utils/shell.py +147 -0
- src/harness/src/openharness/vim/__init__.py +5 -0
- src/harness/src/openharness/vim/transitions.py +8 -0
- src/harness/src/openharness/voice/__init__.py +7 -0
- src/harness/src/openharness/voice/keyterms.py +10 -0
- src/harness/src/openharness/voice/stream_stt.py +8 -0
- src/harness/src/openharness/voice/voice_mode.py +44 -0
- src/llm/__init__.py +29 -0
- src/llm/image_generation.py +402 -0
- src/llm/provider_registry.py +419 -0
- src/llm/runtime_env.py +64 -0
- src/llm/structured_output.py +131 -0
- src/llm/vision.py +198 -0
- src/memory/__init__.py +2 -0
- src/memory/api/__init__.py +1 -0
- src/memory/api/base_memory_system_api.py +98 -0
- src/memory/api/base_symbolic_memory_system_api.py +190 -0
- src/memory/api/base_vector_memory_system_api.py +116 -0
- src/memory/api/faiss_memory_system_api.py +228 -0
- src/memory/api/slot_process_api.py +827 -0
- src/memory/api/symbolic_memory_system_api.py +572 -0
- src/memory/memory_system/__init__.py +48 -0
- src/memory/memory_system/component_taxonomy.py +430 -0
- src/memory/memory_system/llm.py +437 -0
- src/memory/memory_system/models.py +168 -0
- src/memory/memory_system/user_prompt.py +418 -0
- src/memory/memory_system/utils.py +282 -0
- src/memory/memory_system/vectorstore.py +311 -0
- src/memory/memory_system/working_slot.py +99 -0
- src/pipeline/__init__.py +129 -0
- src/pipeline/contracts.py +531 -0
- src/pipeline/discipline_taxonomy.py +916 -0
- src/pipeline/evidence_coverage_ledger.py +1150 -0
- src/pipeline/evidence_refinement.py +620 -0
- src/pipeline/experiment_to_symbolic.py +137 -0
- src/pipeline/multimodal_evidence/__init__.py +28 -0
- src/pipeline/multimodal_evidence/capabilities.py +107 -0
- src/pipeline/multimodal_evidence/claims.py +135 -0
- src/pipeline/multimodal_evidence/contract.py +291 -0
- src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
- src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
- src/pipeline/multimodal_evidence/manifest.py +254 -0
- src/pipeline/multimodal_evidence/native_analysis.py +479 -0
- src/pipeline/multimodal_evidence/perception.py +277 -0
- src/pipeline/multimodal_evidence/query_binding.py +65 -0
- src/pipeline/multimodal_evidence/rendering.py +120 -0
- src/pipeline/multimodal_evidence/safety.py +42 -0
- src/pipeline/multimodal_evidence/sampling.py +69 -0
- src/pipeline/multimodal_evidence/service.py +148 -0
- src/pipeline/multimodal_evidence/survey_integration.py +460 -0
- src/pipeline/paper_identity.py +52 -0
- src/pipeline/research_design_inventory.py +338 -0
- src/pipeline/research_domain_resolution.py +669 -0
- src/pipeline/research_identity.py +548 -0
- src/pipeline/research_question_contract.py +646 -0
- src/pipeline/retrieval_lanes.py +889 -0
- src/pipeline/run_loop.py +862 -0
- src/pipeline/science_events.py +30 -0
- src/pipeline/science_manifests.py +602 -0
- src/pipeline/science_run.py +918 -0
- src/pipeline/science_stages.py +709 -0
- src/pipeline/science_workflow.py +807 -0
- src/pipeline/sh_cluster_projection.py +306 -0
- src/pipeline/sh_graph_provenance.py +648 -0
- src/pipeline/subhypothesis_decomposition.py +392 -0
- src/pipeline/subhypothesis_evidence.py +856 -0
- src/pipeline/survey_evidence_plan.py +858 -0
- src/pipeline/survey_gap_adjudication.py +647 -0
- src/pipeline/survey_gap_candidates.py +840 -0
- src/pipeline/survey_gap_ledger.py +683 -0
- src/pipeline/survey_gap_triage.py +401 -0
- src/pipeline/survey_handoff_persistence.py +481 -0
- src/pipeline/survey_handoff_projection.py +457 -0
- src/pipeline/survey_idea_handoff.py +1112 -0
- src/pipeline/survey_idea_loader.py +289 -0
- src/research_run_ids.py +11 -0
|
@@ -0,0 +1,2060 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import itertools
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from omegaconf import OmegaConf
|
|
12
|
+
except Exception: # pragma: no cover - optional runtime fallback
|
|
13
|
+
OmegaConf = None
|
|
14
|
+
|
|
15
|
+
from src.memory.api.faiss_memory_system_api import FAISSMemorySystem
|
|
16
|
+
from src.memory.api.slot_process_api import SlotProcess
|
|
17
|
+
from src.memory.api.symbolic_memory_system_api import SymbolicMemorySystem
|
|
18
|
+
from src.memory.memory_system import FaissVectorStore
|
|
19
|
+
from src.memory.memory_system.models import EpisodicRecord, ProceduralRecord, SemanticRecord
|
|
20
|
+
from src.memory.memory_system.utils import _multi_thread_run, _safe_dump_str
|
|
21
|
+
from src.agents.idea_agent.utils.core.json_utils import pretty_json
|
|
22
|
+
from src.agents.idea_agent.utils.core.progress import iter_with_progress
|
|
23
|
+
from src.agents.idea_agent.utils.core.response_parsing import parse_json_response
|
|
24
|
+
from src.agents.idea_agent.utils.core.logger import LoguruCompatLogger, get_logger
|
|
25
|
+
from src.agents.idea_agent.utils.mcts.component_novelty import ComponentNoveltyScorer
|
|
26
|
+
from src.agents.idea_agent.utils.mcts.mcts_helpers import (
|
|
27
|
+
_dedupe_keep_order_strings,
|
|
28
|
+
_format_root_domains_for_prompt,
|
|
29
|
+
_normalize_root_domains,
|
|
30
|
+
_safe_float_default,
|
|
31
|
+
apply_normalized_score_weights,
|
|
32
|
+
clip_metric_score,
|
|
33
|
+
clip_text,
|
|
34
|
+
coerce_integer_metric_score,
|
|
35
|
+
parent_centers_control_surface,
|
|
36
|
+
format_mechanism_commit_references,
|
|
37
|
+
format_theory_transfer_references,
|
|
38
|
+
component_inventory_payload,
|
|
39
|
+
format_analysis_blob,
|
|
40
|
+
normalize_mechanism_commit_query_payload,
|
|
41
|
+
normalize_theory_transfer_query_payload,
|
|
42
|
+
normalize_component_explanations,
|
|
43
|
+
text_uses_forbidden_query_terms,
|
|
44
|
+
)
|
|
45
|
+
from src.pipeline.discipline_taxonomy import (
|
|
46
|
+
canonicalize_discipline_key,
|
|
47
|
+
format_taxonomy_catalog_for_prompt,
|
|
48
|
+
resolve_discipline_taxonomy,
|
|
49
|
+
)
|
|
50
|
+
from src.agents.idea_agent.utils.prompting.prompt_views import (
|
|
51
|
+
format_idea_prompt_view,
|
|
52
|
+
)
|
|
53
|
+
from src.agents.idea_agent.utils.papers.paper_graph_vector_store import (
|
|
54
|
+
PaperGraphComponentVectorStore,
|
|
55
|
+
)
|
|
56
|
+
from src.agents.idea_agent.agent.prompts.root_domain_classification import (
|
|
57
|
+
ROOT_DOMAIN_CLASSIFICATION_PROMPT,
|
|
58
|
+
)
|
|
59
|
+
from src.agents.idea_agent.agent.prompts.skill_instantiation import (
|
|
60
|
+
get_skill_instantiation_prompt,
|
|
61
|
+
)
|
|
62
|
+
from src.agents.idea_agent.agent.prompts.mechanism_commit_query import (
|
|
63
|
+
get_mechanism_commit_query_prompt,
|
|
64
|
+
)
|
|
65
|
+
from src.agents.idea_agent.agent.prompts.theory_transfer_query import (
|
|
66
|
+
get_theory_transfer_query_prompt,
|
|
67
|
+
)
|
|
68
|
+
from src.agents.idea_agent.agent.prompts.component_extraction import (
|
|
69
|
+
render_component_extraction_prompt,
|
|
70
|
+
)
|
|
71
|
+
from src.agents.idea_agent.utils.core.config_loader import (
|
|
72
|
+
load_idea_agent_config,
|
|
73
|
+
resolve_workspace_path,
|
|
74
|
+
)
|
|
75
|
+
from src.agents.idea_agent.utils.mcts.idea_taste_presets import (
|
|
76
|
+
IdeaTastePreset,
|
|
77
|
+
SCORE_WEIGHT_FIELDS,
|
|
78
|
+
get_idea_taste_preset,
|
|
79
|
+
)
|
|
80
|
+
from src.agents.idea_agent.utils.mcts.scientific_intervention_ontology import (
|
|
81
|
+
build_scientific_intervention_payload,
|
|
82
|
+
normalize_project_context_discipline_resolution,
|
|
83
|
+
resolve_scientific_intervention_profile,
|
|
84
|
+
)
|
|
85
|
+
from src.agents.idea_agent.utils.mcts.scientific_rubric import (
|
|
86
|
+
PROFILE_NOVELTY_AXES,
|
|
87
|
+
profile_score_weights,
|
|
88
|
+
)
|
|
89
|
+
from src.agents.idea_agent.utils.workflow.gap_hypothesis_bridge import (
|
|
90
|
+
build_gap_hypothesis_seeds,
|
|
91
|
+
build_gap_seed_context,
|
|
92
|
+
build_gap_seed_status,
|
|
93
|
+
)
|
|
94
|
+
from src.agents.idea_agent.utils.workflow.idea_contract import normalize_mature_idea
|
|
95
|
+
from src.agents.idea_agent.utils.mcts.mcts_runtime import (
|
|
96
|
+
EditPlan,
|
|
97
|
+
MemoryBundle,
|
|
98
|
+
MemorySnippet,
|
|
99
|
+
SkillCatalog,
|
|
100
|
+
SkillUsagePrior,
|
|
101
|
+
best_candidate,
|
|
102
|
+
build_symbolic_eval_hints,
|
|
103
|
+
build_root_state,
|
|
104
|
+
compute_protocol_score_from_plan,
|
|
105
|
+
extract_mature_idea_components_via_llm,
|
|
106
|
+
instantiate_skill_plan_for_node,
|
|
107
|
+
log_message,
|
|
108
|
+
materialize_child_state,
|
|
109
|
+
memory_bundle_log_payload,
|
|
110
|
+
new_node,
|
|
111
|
+
pareto_candidates,
|
|
112
|
+
path_summary,
|
|
113
|
+
backpropagate_rollout,
|
|
114
|
+
expand_node_with_skills,
|
|
115
|
+
reset_search_state,
|
|
116
|
+
select_leaf_for_rollout,
|
|
117
|
+
simulate_log_payload,
|
|
118
|
+
simulate_node_value,
|
|
119
|
+
update_skill_prior_from_evaluation,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
UNIFORM_CLIP_TEXT_LIMIT = 10000
|
|
124
|
+
MAX_LIST_ENTRIES = 16
|
|
125
|
+
MIN_COMPONENTS = 1
|
|
126
|
+
MAX_COMPONENTS = 5
|
|
127
|
+
HYPOTHESIS_PAYLOAD_FIELDS = (
|
|
128
|
+
"direction_mode",
|
|
129
|
+
"direction_summary",
|
|
130
|
+
"central_hypothesis",
|
|
131
|
+
"scientific_object",
|
|
132
|
+
"mechanism_or_relation",
|
|
133
|
+
"intervention_or_transformation",
|
|
134
|
+
"expected_mechanism",
|
|
135
|
+
"discriminating_observation",
|
|
136
|
+
"boundary_or_failure_condition",
|
|
137
|
+
"claim_scope",
|
|
138
|
+
"assumptions",
|
|
139
|
+
"target_gap_ids",
|
|
140
|
+
"gap_alignment",
|
|
141
|
+
"evidence_requirement",
|
|
142
|
+
"evidence_basis",
|
|
143
|
+
)
|
|
144
|
+
INTEGER_EVALUATION_FIELDS = (
|
|
145
|
+
"novelty",
|
|
146
|
+
"surprise",
|
|
147
|
+
"feasibility",
|
|
148
|
+
"clarity",
|
|
149
|
+
"impact",
|
|
150
|
+
"risk",
|
|
151
|
+
"conciseness",
|
|
152
|
+
"alignment_score",
|
|
153
|
+
"complexity_penalty",
|
|
154
|
+
"protocol_score",
|
|
155
|
+
"explanatory_power",
|
|
156
|
+
"identifiability",
|
|
157
|
+
"boundary_calibration",
|
|
158
|
+
"claim_overreach_penalty",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
module_logger = get_logger()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _load_mcts_defaults() -> Dict[str, Any]:
|
|
165
|
+
if OmegaConf is None:
|
|
166
|
+
return {}
|
|
167
|
+
config = load_idea_agent_config()
|
|
168
|
+
mcts_config = config.get("mcts") if hasattr(config, "get") else None
|
|
169
|
+
if mcts_config is None:
|
|
170
|
+
return {}
|
|
171
|
+
try:
|
|
172
|
+
return OmegaConf.to_container(mcts_config, resolve=True) or {}
|
|
173
|
+
except Exception:
|
|
174
|
+
return dict(mcts_config) if isinstance(mcts_config, dict) else {}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
_MCTS_DEFAULTS = _load_mcts_defaults()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _mcts_default(key: str, fallback: Any) -> Any:
|
|
181
|
+
value = _MCTS_DEFAULTS.get(key, fallback)
|
|
182
|
+
if key in {"generation_model", "evaluation_model", "component_novelty_eval_model"}:
|
|
183
|
+
if not str(value or "").strip():
|
|
184
|
+
role = {
|
|
185
|
+
"generation_model": "idea_generation",
|
|
186
|
+
"evaluation_model": "idea_evaluation",
|
|
187
|
+
"component_novelty_eval_model": "idea_evaluation",
|
|
188
|
+
}[key]
|
|
189
|
+
try:
|
|
190
|
+
from src.llm.provider_registry import resolve_role_model
|
|
191
|
+
|
|
192
|
+
return resolve_role_model(None, role).name
|
|
193
|
+
except Exception:
|
|
194
|
+
return fallback
|
|
195
|
+
return fallback if value is None else value
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@dataclass
|
|
199
|
+
class IdeaState:
|
|
200
|
+
title: str
|
|
201
|
+
abstract: str
|
|
202
|
+
core_contribution: str
|
|
203
|
+
method: str
|
|
204
|
+
risks: str
|
|
205
|
+
tags: List[str]
|
|
206
|
+
operator: str
|
|
207
|
+
target_defects: List[str]
|
|
208
|
+
rationale: str
|
|
209
|
+
memory_refs: List[str] = field(default_factory=list)
|
|
210
|
+
components: List[str] = field(default_factory=list)
|
|
211
|
+
component_explanations: Dict[str, str] = field(default_factory=dict)
|
|
212
|
+
root_domains: List[str] = field(default_factory=list)
|
|
213
|
+
discipline_resolution: Dict[str, Any] = field(default_factory=dict)
|
|
214
|
+
scientific_intervention: Dict[str, Any] = field(default_factory=dict)
|
|
215
|
+
paper_graph_context: str = ""
|
|
216
|
+
edit_plan: Optional[Dict[str, Any]] = None
|
|
217
|
+
skill_metrics: Dict[str, Any] = field(default_factory=dict)
|
|
218
|
+
signature: str = field(init=False)
|
|
219
|
+
|
|
220
|
+
def __post_init__(self) -> None:
|
|
221
|
+
self.title = clip_text(self.title, UNIFORM_CLIP_TEXT_LIMIT)
|
|
222
|
+
self.abstract = clip_text(self.abstract, UNIFORM_CLIP_TEXT_LIMIT)
|
|
223
|
+
self.core_contribution = clip_text(self.core_contribution, UNIFORM_CLIP_TEXT_LIMIT)
|
|
224
|
+
self.method = clip_text(self.method, UNIFORM_CLIP_TEXT_LIMIT)
|
|
225
|
+
self.risks = clip_text(self.risks, UNIFORM_CLIP_TEXT_LIMIT)
|
|
226
|
+
self.rationale = clip_text(self.rationale, UNIFORM_CLIP_TEXT_LIMIT)
|
|
227
|
+
self.paper_graph_context = clip_text(self.paper_graph_context, UNIFORM_CLIP_TEXT_LIMIT)
|
|
228
|
+
|
|
229
|
+
self.tags = _dedupe_keep_order_strings(
|
|
230
|
+
[clip_text(tag, UNIFORM_CLIP_TEXT_LIMIT) for tag in self.tags[:MAX_LIST_ENTRIES]]
|
|
231
|
+
)
|
|
232
|
+
self.target_defects = _dedupe_keep_order_strings(
|
|
233
|
+
[clip_text(tag, UNIFORM_CLIP_TEXT_LIMIT) for tag in self.target_defects[:MAX_LIST_ENTRIES]]
|
|
234
|
+
)
|
|
235
|
+
self.memory_refs = _dedupe_keep_order_strings(
|
|
236
|
+
[clip_text(ref, UNIFORM_CLIP_TEXT_LIMIT) for ref in self.memory_refs[:MAX_LIST_ENTRIES]]
|
|
237
|
+
)
|
|
238
|
+
self.components = _dedupe_keep_order_strings(
|
|
239
|
+
[clip_text(comp, UNIFORM_CLIP_TEXT_LIMIT) for comp in self.components[: 2 * MAX_LIST_ENTRIES]]
|
|
240
|
+
)
|
|
241
|
+
self.component_explanations = normalize_component_explanations(
|
|
242
|
+
self.components,
|
|
243
|
+
self.component_explanations,
|
|
244
|
+
)
|
|
245
|
+
self.root_domains = _normalize_root_domains(self.root_domains)
|
|
246
|
+
self.discipline_resolution = (
|
|
247
|
+
dict(self.discipline_resolution)
|
|
248
|
+
if isinstance(self.discipline_resolution, dict)
|
|
249
|
+
else {}
|
|
250
|
+
)
|
|
251
|
+
self.scientific_intervention = (
|
|
252
|
+
dict(self.scientific_intervention)
|
|
253
|
+
if isinstance(self.scientific_intervention, dict)
|
|
254
|
+
else {}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
canonical = "|".join(
|
|
258
|
+
[
|
|
259
|
+
self.title.lower(),
|
|
260
|
+
self.core_contribution.lower(),
|
|
261
|
+
self.method.lower(),
|
|
262
|
+
",".join(sorted(self.tags)),
|
|
263
|
+
",".join(self.root_domains),
|
|
264
|
+
str(self.scientific_intervention.get("profile_id") or ""),
|
|
265
|
+
str(self.scientific_intervention.get("contribution_mode") or ""),
|
|
266
|
+
",".join(sorted(self.components)),
|
|
267
|
+
"|".join(
|
|
268
|
+
f"{component}:{self.component_explanations.get(component, '').lower()}"
|
|
269
|
+
for component in self.components
|
|
270
|
+
),
|
|
271
|
+
]
|
|
272
|
+
)
|
|
273
|
+
self.signature = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
274
|
+
|
|
275
|
+
def describe(self) -> str:
|
|
276
|
+
return self.to_prompt_view(heading="Parent Idea")
|
|
277
|
+
|
|
278
|
+
def to_prompt_view(self, heading: str = "Idea Snapshot") -> str:
|
|
279
|
+
return format_idea_prompt_view(self, heading=heading)
|
|
280
|
+
|
|
281
|
+
def to_payload(self) -> Dict[str, Any]:
|
|
282
|
+
payload = {
|
|
283
|
+
"title": self.title,
|
|
284
|
+
"abstract": self.abstract,
|
|
285
|
+
"core_contribution": self.core_contribution,
|
|
286
|
+
"method": self.method,
|
|
287
|
+
"risks": self.risks,
|
|
288
|
+
"tags": self.tags,
|
|
289
|
+
"operator": self.operator,
|
|
290
|
+
"target_defects": self.target_defects,
|
|
291
|
+
"memory_refs": self.memory_refs,
|
|
292
|
+
"rationale": self.rationale,
|
|
293
|
+
"components": self.components,
|
|
294
|
+
"component_explanations": self.component_explanations,
|
|
295
|
+
"root_domains": self.root_domains,
|
|
296
|
+
"discipline_resolution": self.discipline_resolution,
|
|
297
|
+
"scientific_intervention": self.scientific_intervention,
|
|
298
|
+
"components_with_explanations": self.component_inventory(),
|
|
299
|
+
"paper_graph_context": self.paper_graph_context,
|
|
300
|
+
}
|
|
301
|
+
hypothesis_contract = self.scientific_intervention.get("hypothesis_contract")
|
|
302
|
+
if isinstance(hypothesis_contract, dict):
|
|
303
|
+
for field_name in HYPOTHESIS_PAYLOAD_FIELDS:
|
|
304
|
+
value = hypothesis_contract.get(field_name)
|
|
305
|
+
if value not in (None, "", [], {}):
|
|
306
|
+
payload[field_name] = value
|
|
307
|
+
direction_mode = self.scientific_intervention.get("direction_mode")
|
|
308
|
+
if direction_mode and not payload.get("direction_mode"):
|
|
309
|
+
payload["direction_mode"] = direction_mode
|
|
310
|
+
direction_summary = self.scientific_intervention.get("direction_summary")
|
|
311
|
+
if direction_summary and not payload.get("direction_summary"):
|
|
312
|
+
payload["direction_summary"] = direction_summary
|
|
313
|
+
if self.edit_plan:
|
|
314
|
+
payload["edit_plan"] = self.edit_plan
|
|
315
|
+
if self.skill_metrics:
|
|
316
|
+
payload["skill_metrics"] = self.skill_metrics
|
|
317
|
+
return payload
|
|
318
|
+
|
|
319
|
+
def component_inventory(self) -> List[Dict[str, str]]:
|
|
320
|
+
return component_inventory_payload(self.components, self.component_explanations)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@dataclass
|
|
324
|
+
class IdeaEvaluation:
|
|
325
|
+
novelty: float
|
|
326
|
+
surprise: float
|
|
327
|
+
feasibility: float
|
|
328
|
+
clarity: float
|
|
329
|
+
impact: float
|
|
330
|
+
risk: float
|
|
331
|
+
conciseness: float
|
|
332
|
+
alignment_score: float
|
|
333
|
+
complexity_penalty: float
|
|
334
|
+
protocol_score: float
|
|
335
|
+
confidence: float
|
|
336
|
+
failure_modes: List[str]
|
|
337
|
+
fairness_protocol: str
|
|
338
|
+
feedback: str
|
|
339
|
+
defect_fix_summary: str
|
|
340
|
+
detected_defects: List[str]
|
|
341
|
+
explanatory_power: float = 0.0
|
|
342
|
+
identifiability: float = 0.0
|
|
343
|
+
boundary_calibration: float = 0.0
|
|
344
|
+
claim_overreach_penalty: float = 0.0
|
|
345
|
+
novelty_axes: Dict[str, float] = field(default_factory=dict)
|
|
346
|
+
profile_id: str = ""
|
|
347
|
+
novelty_weight: float = 0.20
|
|
348
|
+
surprise_weight: float = 0.10
|
|
349
|
+
impact_weight: float = 0.18
|
|
350
|
+
feasibility_weight: float = 0.11
|
|
351
|
+
clarity_weight: float = 0.06
|
|
352
|
+
conciseness_weight: float = 0.03
|
|
353
|
+
risk_weight: float = 0.09
|
|
354
|
+
alignment_weight: float = 0.15
|
|
355
|
+
complexity_weight: float = 0.05
|
|
356
|
+
protocol_weight: float = 0.03
|
|
357
|
+
explanatory_power_weight: float = 0.06
|
|
358
|
+
identifiability_weight: float = 0.06
|
|
359
|
+
boundary_calibration_weight: float = 0.04
|
|
360
|
+
claim_overreach_weight: float = 0.10
|
|
361
|
+
|
|
362
|
+
def __post_init__(self) -> None:
|
|
363
|
+
self.failure_modes = [
|
|
364
|
+
clip_text(mode, UNIFORM_CLIP_TEXT_LIMIT)
|
|
365
|
+
for mode in (self.failure_modes or [])[:MAX_LIST_ENTRIES]
|
|
366
|
+
]
|
|
367
|
+
self.fairness_protocol = clip_text(self.fairness_protocol, UNIFORM_CLIP_TEXT_LIMIT)
|
|
368
|
+
self.feedback = clip_text(self.feedback, UNIFORM_CLIP_TEXT_LIMIT)
|
|
369
|
+
self.defect_fix_summary = clip_text(self.defect_fix_summary, UNIFORM_CLIP_TEXT_LIMIT)
|
|
370
|
+
self.detected_defects = [
|
|
371
|
+
clip_text(tag, UNIFORM_CLIP_TEXT_LIMIT)
|
|
372
|
+
for tag in (self.detected_defects or [])[:MAX_LIST_ENTRIES]
|
|
373
|
+
]
|
|
374
|
+
self.profile_id = str(self.profile_id or "").strip().lower()
|
|
375
|
+
allowed_axes = None
|
|
376
|
+
if self.profile_id:
|
|
377
|
+
allowed_axes = set(
|
|
378
|
+
PROFILE_NOVELTY_AXES.get(
|
|
379
|
+
self.profile_id,
|
|
380
|
+
PROFILE_NOVELTY_AXES["generic_scientific"],
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
allowed_axes.add("scientific_novelty")
|
|
384
|
+
normalized_axes: Dict[str, float] = {}
|
|
385
|
+
if isinstance(self.novelty_axes, dict):
|
|
386
|
+
for key, value in self.novelty_axes.items():
|
|
387
|
+
normalized_key = str(key).strip()
|
|
388
|
+
if not normalized_key:
|
|
389
|
+
continue
|
|
390
|
+
if allowed_axes is not None and normalized_key not in allowed_axes:
|
|
391
|
+
continue
|
|
392
|
+
try:
|
|
393
|
+
normalized_axes[normalized_key] = max(0.0, min(5.0, float(value)))
|
|
394
|
+
except (TypeError, ValueError):
|
|
395
|
+
continue
|
|
396
|
+
if not normalized_axes:
|
|
397
|
+
normalized_axes = {"scientific_novelty": float(self.novelty)}
|
|
398
|
+
normalized_axes.setdefault("scientific_novelty", float(self.novelty))
|
|
399
|
+
self.novelty_axes = normalized_axes
|
|
400
|
+
for field_name in INTEGER_EVALUATION_FIELDS:
|
|
401
|
+
setattr(
|
|
402
|
+
self,
|
|
403
|
+
field_name,
|
|
404
|
+
coerce_integer_metric_score(getattr(self, field_name, 0)),
|
|
405
|
+
)
|
|
406
|
+
self.confidence = max(0.0, min(1.0, _safe_float_default(self.confidence, 0.0)))
|
|
407
|
+
apply_normalized_score_weights(self, SCORE_WEIGHT_FIELDS)
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def from_payload(
|
|
411
|
+
cls,
|
|
412
|
+
payload: Dict[str, Any],
|
|
413
|
+
weights: Optional[Dict[str, float]] = None,
|
|
414
|
+
profile_id: str = "",
|
|
415
|
+
) -> "IdeaEvaluation":
|
|
416
|
+
def _num(key: str, default: float = 0.0) -> float:
|
|
417
|
+
return _safe_float_default(payload.get(key, default), default)
|
|
418
|
+
|
|
419
|
+
def _list(key: str) -> List[str]:
|
|
420
|
+
raw = payload.get(key, [])
|
|
421
|
+
if isinstance(raw, list):
|
|
422
|
+
return [str(x) for x in raw]
|
|
423
|
+
if isinstance(raw, str):
|
|
424
|
+
return [raw]
|
|
425
|
+
return []
|
|
426
|
+
|
|
427
|
+
if weights is not None:
|
|
428
|
+
w = dict(weights)
|
|
429
|
+
elif profile_id:
|
|
430
|
+
w = profile_score_weights(
|
|
431
|
+
profile_id,
|
|
432
|
+
{
|
|
433
|
+
"novelty_weight": 0.20,
|
|
434
|
+
"surprise_weight": 0.10,
|
|
435
|
+
"impact_weight": 0.18,
|
|
436
|
+
"feasibility_weight": 0.11,
|
|
437
|
+
"clarity_weight": 0.06,
|
|
438
|
+
"conciseness_weight": 0.03,
|
|
439
|
+
"risk_weight": 0.09,
|
|
440
|
+
"alignment_weight": 0.15,
|
|
441
|
+
"complexity_weight": 0.05,
|
|
442
|
+
"protocol_weight": 0.03,
|
|
443
|
+
"explanatory_power_weight": 0.06,
|
|
444
|
+
"identifiability_weight": 0.06,
|
|
445
|
+
"boundary_calibration_weight": 0.04,
|
|
446
|
+
"claim_overreach_weight": 0.10,
|
|
447
|
+
},
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
w = {}
|
|
451
|
+
return cls(
|
|
452
|
+
novelty=_num("novelty"),
|
|
453
|
+
surprise=_num("surprise"),
|
|
454
|
+
feasibility=_num("feasibility"),
|
|
455
|
+
clarity=_num("clarity"),
|
|
456
|
+
impact=_num("impact"),
|
|
457
|
+
risk=_num("risk"),
|
|
458
|
+
conciseness=_num("conciseness"),
|
|
459
|
+
alignment_score=_num("alignment_score"),
|
|
460
|
+
complexity_penalty=_num("complexity_penalty"),
|
|
461
|
+
protocol_score=_num("protocol_score"),
|
|
462
|
+
explanatory_power=_num("explanatory_power"),
|
|
463
|
+
identifiability=_num("identifiability"),
|
|
464
|
+
boundary_calibration=_num("boundary_calibration"),
|
|
465
|
+
claim_overreach_penalty=_num("claim_overreach_penalty"),
|
|
466
|
+
novelty_axes=(
|
|
467
|
+
payload.get("novelty_axes")
|
|
468
|
+
if isinstance(payload.get("novelty_axes"), dict)
|
|
469
|
+
else {}
|
|
470
|
+
),
|
|
471
|
+
profile_id=profile_id or str(payload.get("profile_id") or ""),
|
|
472
|
+
confidence=_num("confidence", 0.0),
|
|
473
|
+
failure_modes=_list("failure_modes"),
|
|
474
|
+
fairness_protocol=str(payload.get("fairness_protocol", "")),
|
|
475
|
+
feedback=str(payload.get("feedback", "")),
|
|
476
|
+
defect_fix_summary=str(payload.get("defect_fix_summary", "")),
|
|
477
|
+
detected_defects=_list("detected_defects"),
|
|
478
|
+
novelty_weight=w.get("novelty_weight", 0.20),
|
|
479
|
+
surprise_weight=w.get("surprise_weight", 0.10),
|
|
480
|
+
impact_weight=w.get("impact_weight", 0.18),
|
|
481
|
+
feasibility_weight=w.get("feasibility_weight", 0.11),
|
|
482
|
+
clarity_weight=w.get("clarity_weight", 0.06),
|
|
483
|
+
conciseness_weight=w.get("conciseness_weight", 0.03),
|
|
484
|
+
risk_weight=w.get("risk_weight", 0.09),
|
|
485
|
+
alignment_weight=w.get("alignment_weight", 0.15),
|
|
486
|
+
complexity_weight=w.get("complexity_weight", 0.05),
|
|
487
|
+
protocol_weight=w.get("protocol_weight", 0.03),
|
|
488
|
+
explanatory_power_weight=w.get("explanatory_power_weight", 0.06),
|
|
489
|
+
identifiability_weight=w.get("identifiability_weight", 0.06),
|
|
490
|
+
boundary_calibration_weight=w.get("boundary_calibration_weight", 0.04),
|
|
491
|
+
claim_overreach_weight=w.get("claim_overreach_weight", 0.10),
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
495
|
+
return {
|
|
496
|
+
"novelty": self.novelty,
|
|
497
|
+
"surprise": self.surprise,
|
|
498
|
+
"feasibility": self.feasibility,
|
|
499
|
+
"clarity": self.clarity,
|
|
500
|
+
"impact": self.impact,
|
|
501
|
+
"risk": self.risk,
|
|
502
|
+
"conciseness": self.conciseness,
|
|
503
|
+
"alignment_score": self.alignment_score,
|
|
504
|
+
"complexity_penalty": self.complexity_penalty,
|
|
505
|
+
"protocol_score": self.protocol_score,
|
|
506
|
+
"explanatory_power": self.explanatory_power,
|
|
507
|
+
"identifiability": self.identifiability,
|
|
508
|
+
"boundary_calibration": self.boundary_calibration,
|
|
509
|
+
"claim_overreach_penalty": self.claim_overreach_penalty,
|
|
510
|
+
"novelty_axes": self.novelty_axes,
|
|
511
|
+
"profile_id": self.profile_id,
|
|
512
|
+
"confidence": self.confidence,
|
|
513
|
+
"failure_modes": self.failure_modes,
|
|
514
|
+
"fairness_protocol": self.fairness_protocol,
|
|
515
|
+
"feedback": self.feedback,
|
|
516
|
+
"defect_fix_summary": self.defect_fix_summary,
|
|
517
|
+
"detected_defects": self.detected_defects,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def composite(self) -> float:
|
|
522
|
+
allowed_axes = None
|
|
523
|
+
if self.profile_id:
|
|
524
|
+
allowed_axes = set(
|
|
525
|
+
PROFILE_NOVELTY_AXES.get(
|
|
526
|
+
self.profile_id,
|
|
527
|
+
PROFILE_NOVELTY_AXES["generic_scientific"],
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
allowed_axes.add("scientific_novelty")
|
|
531
|
+
novelty_values = [
|
|
532
|
+
score for key, score in self.novelty_axes.items()
|
|
533
|
+
if key != "retrieval_similarity"
|
|
534
|
+
and (allowed_axes is None or key in allowed_axes)
|
|
535
|
+
]
|
|
536
|
+
scientific_novelty = (
|
|
537
|
+
sum(novelty_values) / len(novelty_values)
|
|
538
|
+
if novelty_values
|
|
539
|
+
else self.novelty
|
|
540
|
+
)
|
|
541
|
+
weighted_scores = {
|
|
542
|
+
"novelty_weight": clip_metric_score(scientific_novelty),
|
|
543
|
+
"surprise_weight": clip_metric_score(self.surprise),
|
|
544
|
+
"impact_weight": clip_metric_score(self.impact),
|
|
545
|
+
"feasibility_weight": clip_metric_score(self.feasibility),
|
|
546
|
+
"clarity_weight": clip_metric_score(self.clarity),
|
|
547
|
+
"conciseness_weight": clip_metric_score(self.conciseness),
|
|
548
|
+
"alignment_weight": clip_metric_score(self.alignment_score),
|
|
549
|
+
"protocol_weight": clip_metric_score(self.protocol_score),
|
|
550
|
+
"risk_weight": 5.0 - clip_metric_score(self.risk),
|
|
551
|
+
"complexity_weight": 5.0 - clip_metric_score(self.complexity_penalty),
|
|
552
|
+
"explanatory_power_weight": clip_metric_score(self.explanatory_power),
|
|
553
|
+
"identifiability_weight": clip_metric_score(self.identifiability),
|
|
554
|
+
"boundary_calibration_weight": clip_metric_score(self.boundary_calibration),
|
|
555
|
+
"claim_overreach_weight": 5.0 - clip_metric_score(self.claim_overreach_penalty),
|
|
556
|
+
}
|
|
557
|
+
return sum(
|
|
558
|
+
getattr(self, field_name) * score
|
|
559
|
+
for field_name, score in weighted_scores.items()
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@dataclass
|
|
564
|
+
class OperatorApplication:
|
|
565
|
+
operator: str
|
|
566
|
+
defects: List[str]
|
|
567
|
+
rationale: str
|
|
568
|
+
memory_refs: List[str]
|
|
569
|
+
|
|
570
|
+
def __post_init__(self) -> None:
|
|
571
|
+
self.operator = clip_text(self.operator, UNIFORM_CLIP_TEXT_LIMIT)
|
|
572
|
+
self.defects = [clip_text(defect, UNIFORM_CLIP_TEXT_LIMIT) for defect in self.defects[:MAX_LIST_ENTRIES]]
|
|
573
|
+
self.rationale = clip_text(self.rationale, UNIFORM_CLIP_TEXT_LIMIT)
|
|
574
|
+
self.memory_refs = [clip_text(ref, UNIFORM_CLIP_TEXT_LIMIT) for ref in self.memory_refs[:MAX_LIST_ENTRIES]]
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
@dataclass
|
|
578
|
+
class IdeaNode:
|
|
579
|
+
node_id: int
|
|
580
|
+
state: IdeaState
|
|
581
|
+
depth: int
|
|
582
|
+
parent: Optional["IdeaNode"]
|
|
583
|
+
transformation: OperatorApplication
|
|
584
|
+
children: List["IdeaNode"] = field(default_factory=list)
|
|
585
|
+
visits: int = 0
|
|
586
|
+
value_sum: float = 0.0
|
|
587
|
+
expanded: bool = False
|
|
588
|
+
evaluation: Optional[IdeaEvaluation] = None
|
|
589
|
+
latest_path_summary: str = ""
|
|
590
|
+
|
|
591
|
+
def uct_value(self, parent_visits: int, exploration_constant: float) -> float:
|
|
592
|
+
if self.visits == 0:
|
|
593
|
+
return float("inf")
|
|
594
|
+
exploit = self.value_sum / self.visits
|
|
595
|
+
explore = exploration_constant * math.sqrt(math.log(max(1, parent_visits)) / self.visits)
|
|
596
|
+
return exploit + explore
|
|
597
|
+
|
|
598
|
+
def path(self) -> List["IdeaNode"]:
|
|
599
|
+
chain: List[IdeaNode] = []
|
|
600
|
+
node: Optional[IdeaNode] = self
|
|
601
|
+
while node:
|
|
602
|
+
chain.append(node)
|
|
603
|
+
node = node.parent
|
|
604
|
+
return list(reversed(chain))
|
|
605
|
+
|
|
606
|
+
def path_summary(self) -> str:
|
|
607
|
+
if self.latest_path_summary:
|
|
608
|
+
return self.latest_path_summary
|
|
609
|
+
steps: List[str] = []
|
|
610
|
+
for hop in self.path():
|
|
611
|
+
steps.append(
|
|
612
|
+
f"{hop.state.title} [{hop.transformation.operator}] -> defects {hop.transformation.defects}"
|
|
613
|
+
)
|
|
614
|
+
return " | ".join(steps)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
@dataclass
|
|
618
|
+
class MCTSConfig:
|
|
619
|
+
max_iterations: int = _mcts_default("exploration_budget", _mcts_default("max_iterations", 128))
|
|
620
|
+
max_depth: int = _mcts_default("max_depth", 4)
|
|
621
|
+
branching_factor: int = _mcts_default("branching_factor", 3)
|
|
622
|
+
exploration_constant: float = _mcts_default("exploration_constant", 1.15)
|
|
623
|
+
idea_taste_mode: Optional[str] = _mcts_default("idea_taste_mode", None)
|
|
624
|
+
seed_id: Optional[str] = None
|
|
625
|
+
route_id: Optional[str] = None
|
|
626
|
+
prompt_mode: str = _mcts_default("prompt_mode", "default")
|
|
627
|
+
generation_model: str = _mcts_default("generation_model", "gpt-5-mini")
|
|
628
|
+
evaluation_model: str = _mcts_default("evaluation_model", "gpt-5.2")
|
|
629
|
+
generation_temperature: float = _mcts_default("generation_temperature", 0.2)
|
|
630
|
+
evaluation_temperature: float = _mcts_default("evaluation_temperature", 0.01)
|
|
631
|
+
generation_max_tokens: int = _mcts_default("generation_max_tokens", 8192)
|
|
632
|
+
evaluation_max_tokens: int = _mcts_default("evaluation_max_tokens", 8192)
|
|
633
|
+
component_novelty_model_path: str = _mcts_default(
|
|
634
|
+
"component_novelty_model_path", "models/bge-m3"
|
|
635
|
+
)
|
|
636
|
+
component_novelty_index_dir: Optional[str] = _mcts_default(
|
|
637
|
+
"component_novelty_index_dir", None
|
|
638
|
+
)
|
|
639
|
+
component_novelty_retrieval_top_k: int = _mcts_default(
|
|
640
|
+
"component_novelty_retrieval_top_k", 50
|
|
641
|
+
)
|
|
642
|
+
component_novelty_evidence_top_k: int = _mcts_default(
|
|
643
|
+
"component_novelty_evidence_top_k", 5
|
|
644
|
+
)
|
|
645
|
+
component_novelty_eval_model: Optional[str] = _mcts_default(
|
|
646
|
+
"component_novelty_eval_model", None
|
|
647
|
+
)
|
|
648
|
+
component_novelty_eval_temperature: float = _mcts_default(
|
|
649
|
+
"component_novelty_eval_temperature",
|
|
650
|
+
_mcts_default("evaluation_temperature", 0.01),
|
|
651
|
+
)
|
|
652
|
+
component_novelty_eval_max_tokens: int = _mcts_default(
|
|
653
|
+
"component_novelty_eval_max_tokens", 4096
|
|
654
|
+
)
|
|
655
|
+
mechanism_commit_retrieval_top_k: int = _mcts_default(
|
|
656
|
+
"mechanism_commit_retrieval_top_k", 3
|
|
657
|
+
)
|
|
658
|
+
mechanism_commit_similarity_threshold: float = _mcts_default(
|
|
659
|
+
"mechanism_commit_similarity_threshold", 0.6
|
|
660
|
+
)
|
|
661
|
+
theory_transfer_retrieval_top_k: int = _mcts_default(
|
|
662
|
+
"theory_transfer_retrieval_top_k", 3
|
|
663
|
+
)
|
|
664
|
+
theory_transfer_similarity_threshold: float = _mcts_default(
|
|
665
|
+
"theory_transfer_similarity_threshold", 0.4
|
|
666
|
+
)
|
|
667
|
+
enable_vector_memory: bool = _mcts_default("enable_vector_memory", True)
|
|
668
|
+
enable_symbolic_memory: bool = _mcts_default("enable_symbolic_memory", True)
|
|
669
|
+
min_confidence_for_memory: float = _mcts_default("min_confidence_for_memory", 0.6)
|
|
670
|
+
pareto_top_k: int = _mcts_default("pareto_top_k", 5)
|
|
671
|
+
alignment_weight: float = _mcts_default("alignment_weight", 0.15)
|
|
672
|
+
complexity_weight: float = _mcts_default("complexity_weight", 0.05)
|
|
673
|
+
novelty_weight: float = _mcts_default("novelty_weight", 0.20)
|
|
674
|
+
surprise_weight: float = _mcts_default("surprise_weight", 0.10)
|
|
675
|
+
impact_weight: float = _mcts_default("impact_weight", 0.18)
|
|
676
|
+
feasibility_weight: float = _mcts_default("feasibility_weight", 0.11)
|
|
677
|
+
clarity_weight: float = _mcts_default("clarity_weight", 0.06)
|
|
678
|
+
conciseness_weight: float = _mcts_default("conciseness_weight", 0.03)
|
|
679
|
+
risk_weight: float = _mcts_default("risk_weight", 0.09)
|
|
680
|
+
protocol_weight: float = _mcts_default("protocol_weight", 0.03)
|
|
681
|
+
explanatory_power_weight: float = _mcts_default("explanatory_power_weight", 0.06)
|
|
682
|
+
identifiability_weight: float = _mcts_default("identifiability_weight", 0.06)
|
|
683
|
+
boundary_calibration_weight: float = _mcts_default("boundary_calibration_weight", 0.04)
|
|
684
|
+
claim_overreach_weight: float = _mcts_default("claim_overreach_weight", 0.10)
|
|
685
|
+
|
|
686
|
+
symbolic_memory_path: str = _mcts_default(
|
|
687
|
+
"symbolic_memory_path",
|
|
688
|
+
_mcts_default("skill_prior_memory_path", "output/idea_skill_priors"),
|
|
689
|
+
)
|
|
690
|
+
skill_prior_success_threshold: float = _mcts_default(
|
|
691
|
+
"skill_prior_success_threshold", 0.6
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
def apply_idea_taste_preset(config: MCTSConfig) -> Optional[IdeaTastePreset]:
|
|
695
|
+
raw_mode = getattr(config, "idea_taste_mode", None)
|
|
696
|
+
preset = get_idea_taste_preset(raw_mode)
|
|
697
|
+
if preset is None:
|
|
698
|
+
apply_normalized_score_weights(config, SCORE_WEIGHT_FIELDS)
|
|
699
|
+
config.idea_taste_mode = None
|
|
700
|
+
return None
|
|
701
|
+
|
|
702
|
+
for field_name in SCORE_WEIGHT_FIELDS:
|
|
703
|
+
setattr(config, field_name, float(preset.weights[field_name]))
|
|
704
|
+
apply_normalized_score_weights(config, SCORE_WEIGHT_FIELDS)
|
|
705
|
+
config.idea_taste_mode = preset.mode
|
|
706
|
+
return preset
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@dataclass
|
|
710
|
+
class SearchCandidate:
|
|
711
|
+
node: IdeaNode
|
|
712
|
+
evaluation: IdeaEvaluation
|
|
713
|
+
|
|
714
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
715
|
+
return {
|
|
716
|
+
"idea": self.node.state.to_payload(),
|
|
717
|
+
"evaluation": self.evaluation.to_dict(),
|
|
718
|
+
"score": self.evaluation.composite,
|
|
719
|
+
"path": self.node.path_summary(),
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
@dataclass
|
|
724
|
+
class SearchResult:
|
|
725
|
+
best: Optional[SearchCandidate]
|
|
726
|
+
pareto: Dict[str, Optional[SearchCandidate]]
|
|
727
|
+
trace: List[Dict[str, Any]]
|
|
728
|
+
cache_size: int
|
|
729
|
+
experiences: List[Dict[str, Any]]
|
|
730
|
+
retrieved_core_titles: List[str]
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
class VectorMemoryAccessor:
|
|
734
|
+
def __init__(
|
|
735
|
+
self,
|
|
736
|
+
semantic_cfg: Optional[Dict[str, Any]] = None,
|
|
737
|
+
episodic_cfg: Optional[Dict[str, Any]] = None,
|
|
738
|
+
procedural_cfg: Optional[Dict[str, Any]] = None,
|
|
739
|
+
llm_name: str = "gpt-5-mini",
|
|
740
|
+
llm_backend: str = "openai",
|
|
741
|
+
llm_provider: Optional[str] = None,
|
|
742
|
+
logger: Optional[LoguruCompatLogger] = None,
|
|
743
|
+
) -> None:
|
|
744
|
+
self.semantic_cfg = semantic_cfg or {}
|
|
745
|
+
self.episodic_cfg = episodic_cfg or {}
|
|
746
|
+
self.procedural_cfg = procedural_cfg or {}
|
|
747
|
+
self.llm_name = str(llm_name or "gpt-5-mini")
|
|
748
|
+
self.llm_backend = str(llm_backend or "openai")
|
|
749
|
+
self.llm_provider = str(llm_provider or "").strip() or None
|
|
750
|
+
self.logger = logger or module_logger
|
|
751
|
+
self._stores: Dict[str, Optional[FAISSMemorySystem]] = {
|
|
752
|
+
"semantic": None,
|
|
753
|
+
"episodic": None,
|
|
754
|
+
"procedural": None,
|
|
755
|
+
}
|
|
756
|
+
self._embedding_models: Dict[str, Any] = {}
|
|
757
|
+
|
|
758
|
+
def _get_embedding_model(self, cfg: Dict[str, Any]) -> Any:
|
|
759
|
+
model_path = str((cfg or {}).get("model_path") or ".cache/all-MiniLM-L6-v2").strip()
|
|
760
|
+
resolved_model_path = resolve_workspace_path(model_path)
|
|
761
|
+
cached_model = self._embedding_models.get(resolved_model_path)
|
|
762
|
+
if cached_model is not None:
|
|
763
|
+
return cached_model
|
|
764
|
+
|
|
765
|
+
from sentence_transformers import SentenceTransformer
|
|
766
|
+
|
|
767
|
+
embedding_model = SentenceTransformer(resolved_model_path)
|
|
768
|
+
self._embedding_models[resolved_model_path] = embedding_model
|
|
769
|
+
return embedding_model
|
|
770
|
+
|
|
771
|
+
def _get_store(self, memory_type: str) -> Optional[FAISSMemorySystem]:
|
|
772
|
+
if memory_type not in self._stores:
|
|
773
|
+
return None
|
|
774
|
+
if self._stores[memory_type] is None:
|
|
775
|
+
cfg = {
|
|
776
|
+
"semantic": self.semantic_cfg,
|
|
777
|
+
"episodic": self.episodic_cfg,
|
|
778
|
+
"procedural": self.procedural_cfg,
|
|
779
|
+
}.get(memory_type, {})
|
|
780
|
+
try:
|
|
781
|
+
embedding_model = self._get_embedding_model(cfg)
|
|
782
|
+
store_cfg = dict(cfg)
|
|
783
|
+
if self.llm_provider and "llm_provider" not in store_cfg:
|
|
784
|
+
store_cfg["llm_provider"] = self.llm_provider
|
|
785
|
+
self._stores[memory_type] = FAISSMemorySystem(
|
|
786
|
+
embedding_model=embedding_model,
|
|
787
|
+
memory_type=memory_type,
|
|
788
|
+
llm_name=self.llm_name,
|
|
789
|
+
llm_backend=self.llm_backend,
|
|
790
|
+
**store_cfg,
|
|
791
|
+
)
|
|
792
|
+
except Exception as exc:
|
|
793
|
+
log_message(
|
|
794
|
+
self.logger,
|
|
795
|
+
None,
|
|
796
|
+
"warning",
|
|
797
|
+
"⚠️ Unable to initialize %s memory store: %s",
|
|
798
|
+
memory_type,
|
|
799
|
+
exc,
|
|
800
|
+
)
|
|
801
|
+
self._stores[memory_type] = None
|
|
802
|
+
return self._stores[memory_type]
|
|
803
|
+
|
|
804
|
+
def _query_store(
|
|
805
|
+
self,
|
|
806
|
+
store: FAISSMemorySystem,
|
|
807
|
+
query: str,
|
|
808
|
+
limit: int,
|
|
809
|
+
prefix: str,
|
|
810
|
+
) -> List[MemorySnippet]:
|
|
811
|
+
snippets: List[MemorySnippet] = []
|
|
812
|
+
try:
|
|
813
|
+
records_with_scores = store.query(
|
|
814
|
+
query_text=query,
|
|
815
|
+
method="embedding",
|
|
816
|
+
limit=limit,
|
|
817
|
+
agent_id="idea_agent",
|
|
818
|
+
threshold=0.4,
|
|
819
|
+
)
|
|
820
|
+
except Exception as exc:
|
|
821
|
+
log_message(
|
|
822
|
+
self.logger,
|
|
823
|
+
None,
|
|
824
|
+
"warning",
|
|
825
|
+
"⚠️ Memory query failed (%s): %s",
|
|
826
|
+
prefix,
|
|
827
|
+
exc,
|
|
828
|
+
)
|
|
829
|
+
return snippets
|
|
830
|
+
|
|
831
|
+
for idx, (_, record) in enumerate(records_with_scores, start=1):
|
|
832
|
+
if not record:
|
|
833
|
+
continue
|
|
834
|
+
identifier = f"{prefix}#{idx}"
|
|
835
|
+
|
|
836
|
+
if isinstance(record, SemanticRecord):
|
|
837
|
+
title = record.summary
|
|
838
|
+
detail = record.detail
|
|
839
|
+
elif isinstance(record, EpisodicRecord):
|
|
840
|
+
title = record.summary
|
|
841
|
+
detail = _safe_dump_str(record.detail)
|
|
842
|
+
else:
|
|
843
|
+
title = record.name
|
|
844
|
+
detail = record.description
|
|
845
|
+
|
|
846
|
+
snippets.append(
|
|
847
|
+
MemorySnippet(
|
|
848
|
+
identifier=identifier,
|
|
849
|
+
title=(title or ""),
|
|
850
|
+
detail=str(detail),
|
|
851
|
+
tags=list(getattr(record, "tags", []) or []),
|
|
852
|
+
)
|
|
853
|
+
)
|
|
854
|
+
return snippets
|
|
855
|
+
|
|
856
|
+
def retrieve_bundle(self, query: str, limit: int = 3) -> MemoryBundle:
|
|
857
|
+
bundle = MemoryBundle()
|
|
858
|
+
semantic = self._get_store("semantic")
|
|
859
|
+
episodic = self._get_store("episodic")
|
|
860
|
+
procedural = self._get_store("procedural")
|
|
861
|
+
|
|
862
|
+
if semantic:
|
|
863
|
+
bundle.field_knowledge = self._query_store(semantic, query, limit, prefix="Field")
|
|
864
|
+
if episodic:
|
|
865
|
+
bundle.anti_patterns = self._query_store(episodic, query, limit, prefix="Pattern")
|
|
866
|
+
if procedural:
|
|
867
|
+
bundle.fix_recipes = self._query_store(procedural, query, limit, prefix="Recipe")
|
|
868
|
+
|
|
869
|
+
return bundle
|
|
870
|
+
|
|
871
|
+
def persist_experience(self, experience: Dict[str, Any], max_workers: int = 20) -> None:
|
|
872
|
+
if not experience:
|
|
873
|
+
return
|
|
874
|
+
|
|
875
|
+
semantic = self._get_store("semantic")
|
|
876
|
+
episodic = self._get_store("episodic")
|
|
877
|
+
procedural = self._get_store("procedural")
|
|
878
|
+
if not semantic and not episodic and not procedural:
|
|
879
|
+
log_message(
|
|
880
|
+
self.logger,
|
|
881
|
+
None,
|
|
882
|
+
"info",
|
|
883
|
+
"ℹ️ Skipping persistence because semantic store is unavailable.",
|
|
884
|
+
)
|
|
885
|
+
return
|
|
886
|
+
|
|
887
|
+
slot_process = SlotProcess(
|
|
888
|
+
llm_name=self.llm_name,
|
|
889
|
+
llm_backend=self.llm_backend,
|
|
890
|
+
llm_provider=self.llm_provider,
|
|
891
|
+
)
|
|
892
|
+
try:
|
|
893
|
+
working_slots = slot_process.transfer_idea_agent_context_to_working_slots(experience)
|
|
894
|
+
log_message(
|
|
895
|
+
self.logger,
|
|
896
|
+
None,
|
|
897
|
+
"info",
|
|
898
|
+
"[MCTS] Transferred experience to working slots (count=%d)",
|
|
899
|
+
len(working_slots),
|
|
900
|
+
)
|
|
901
|
+
_multi_thread_run(
|
|
902
|
+
slot_process._multi_thread_filter_and_route_slot,
|
|
903
|
+
working_slots,
|
|
904
|
+
max_workers,
|
|
905
|
+
show_progress=False,
|
|
906
|
+
)
|
|
907
|
+
_multi_thread_run(
|
|
908
|
+
slot_process._multi_thread_transfer_slot_to_memory,
|
|
909
|
+
slot_process.routed_slot_container,
|
|
910
|
+
max_workers,
|
|
911
|
+
show_progress=False,
|
|
912
|
+
)
|
|
913
|
+
except Exception as exc:
|
|
914
|
+
log_message(
|
|
915
|
+
self.logger,
|
|
916
|
+
None,
|
|
917
|
+
"warning",
|
|
918
|
+
"⚠️ Failed to persist experience: %s",
|
|
919
|
+
exc,
|
|
920
|
+
)
|
|
921
|
+
return
|
|
922
|
+
|
|
923
|
+
semantic_records: List[SemanticRecord] = []
|
|
924
|
+
episodic_records: List[EpisodicRecord] = []
|
|
925
|
+
procedural_records: List[ProceduralRecord] = []
|
|
926
|
+
for memory in slot_process.memory_dict:
|
|
927
|
+
if memory["memory_type"] == "semantic" and semantic:
|
|
928
|
+
semantic_records.append(semantic.instantiate_sem_record(**memory["input"]))
|
|
929
|
+
elif memory["memory_type"] == "episodic" and episodic:
|
|
930
|
+
episodic_records.append(episodic.instantiate_epi_record(**memory["input"]))
|
|
931
|
+
elif memory["memory_type"] == "procedural" and procedural:
|
|
932
|
+
procedural_records.append(procedural.instantiate_proc_record(**memory["input"]))
|
|
933
|
+
|
|
934
|
+
if semantic and semantic_records:
|
|
935
|
+
try:
|
|
936
|
+
semantic.add(semantic_records, agent_id="idea_agent")
|
|
937
|
+
except Exception as exc:
|
|
938
|
+
log_message(self.logger, None, "warning", "⚠️ Failed to persist semantic records: %s", exc)
|
|
939
|
+
if episodic and episodic_records:
|
|
940
|
+
try:
|
|
941
|
+
episodic.add(episodic_records, agent_id="idea_agent")
|
|
942
|
+
except Exception as exc:
|
|
943
|
+
log_message(self.logger, None, "warning", "⚠️ Failed to persist episodic records: %s", exc)
|
|
944
|
+
if procedural and procedural_records:
|
|
945
|
+
try:
|
|
946
|
+
procedural.add(procedural_records, agent_id="idea_agent")
|
|
947
|
+
except Exception as exc:
|
|
948
|
+
log_message(self.logger, None, "warning", "⚠️ Failed to persist procedural records: %s", exc)
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
class MemoryGuidedMCTS:
|
|
952
|
+
def __init__(
|
|
953
|
+
self,
|
|
954
|
+
chat_fn: Callable[..., str],
|
|
955
|
+
evaluation_prompt: str,
|
|
956
|
+
config: Optional[MCTSConfig] = None,
|
|
957
|
+
memory_accessor: Optional[VectorMemoryAccessor] = None,
|
|
958
|
+
paper_graph_vector_store: Optional[PaperGraphComponentVectorStore] = None,
|
|
959
|
+
logger: Optional[LoguruCompatLogger] = None,
|
|
960
|
+
log_sink: Optional[Callable[[str, str], None]] = None,
|
|
961
|
+
) -> None:
|
|
962
|
+
self.chat_fn = chat_fn
|
|
963
|
+
self.evaluation_prompt = evaluation_prompt
|
|
964
|
+
|
|
965
|
+
self.config = config or MCTSConfig()
|
|
966
|
+
self.logger = logger or module_logger
|
|
967
|
+
self.log_sink = log_sink
|
|
968
|
+
self.enable_vector_memory = bool(self.config.enable_vector_memory)
|
|
969
|
+
self.enable_symbolic_memory = bool(self.config.enable_symbolic_memory)
|
|
970
|
+
self.memory_accessor = memory_accessor or VectorMemoryAccessor(logger=self.logger)
|
|
971
|
+
component_model_path = resolve_workspace_path(
|
|
972
|
+
self.config.component_novelty_model_path
|
|
973
|
+
)
|
|
974
|
+
self.paper_graph_vector_store = paper_graph_vector_store or PaperGraphComponentVectorStore(
|
|
975
|
+
model_name_or_path=component_model_path,
|
|
976
|
+
index_dir=self.config.component_novelty_index_dir or None,
|
|
977
|
+
)
|
|
978
|
+
self.component_novelty_scorer = ComponentNoveltyScorer(
|
|
979
|
+
model_name_or_path=component_model_path,
|
|
980
|
+
index_dir=self.config.component_novelty_index_dir or None,
|
|
981
|
+
retrieval_top_k=self.config.component_novelty_retrieval_top_k,
|
|
982
|
+
evidence_node_top_k=self.config.component_novelty_evidence_top_k,
|
|
983
|
+
evaluation_model=(
|
|
984
|
+
self.config.component_novelty_eval_model or self.config.evaluation_model
|
|
985
|
+
),
|
|
986
|
+
evaluation_temperature=self.config.component_novelty_eval_temperature,
|
|
987
|
+
evaluation_max_tokens=self.config.component_novelty_eval_max_tokens,
|
|
988
|
+
chat_fn=self.chat_fn,
|
|
989
|
+
logger=self.logger,
|
|
990
|
+
log_sink=self.log_sink,
|
|
991
|
+
vector_store=self.paper_graph_vector_store,
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
self.skill_catalog = SkillCatalog()
|
|
995
|
+
self.symbolic_memory = SymbolicMemorySystem()
|
|
996
|
+
self.symbolic_memory_path = Path(self.config.symbolic_memory_path)
|
|
997
|
+
|
|
998
|
+
self._id_iter = itertools.count()
|
|
999
|
+
self.signature_nodes: Dict[str, IdeaNode] = {}
|
|
1000
|
+
self.evaluation_cache: Dict[str, Dict[str, IdeaEvaluation]] = {}
|
|
1001
|
+
self.experience_cache: Set[str] = set()
|
|
1002
|
+
self.trace: List[Dict[str, Any]] = []
|
|
1003
|
+
self.retrieved_core_titles: List[str] = []
|
|
1004
|
+
|
|
1005
|
+
self.topic: str = ""
|
|
1006
|
+
self.seed_id: str = str(getattr(self.config, "seed_id", "") or "").strip()
|
|
1007
|
+
self.route_id: str = str(getattr(self.config, "route_id", "") or "").strip()
|
|
1008
|
+
self.route_policy: Dict[str, Any] = {}
|
|
1009
|
+
self.mature_idea_record: Dict[str, Any] = {}
|
|
1010
|
+
self.analysis_blob: str = ""
|
|
1011
|
+
self.paper_context: str = ""
|
|
1012
|
+
self.mature_idea: str = ""
|
|
1013
|
+
self.refinement_scope: str = ""
|
|
1014
|
+
self.scientific_intervention_profile: Dict[str, Any] = {}
|
|
1015
|
+
self.gap_hypothesis_seeds: List[Dict[str, Any]] = []
|
|
1016
|
+
self.gap_seed_status: Dict[str, Any] = {}
|
|
1017
|
+
self.gap_seed_context: str = ""
|
|
1018
|
+
self.multimodal_evidence_projection: Dict[str, Any] = {}
|
|
1019
|
+
self.idea_taste_preset: Optional[IdeaTastePreset] = get_idea_taste_preset(
|
|
1020
|
+
getattr(self.config, "idea_taste_mode", None)
|
|
1021
|
+
)
|
|
1022
|
+
self._mature_idea_components: List[str] = []
|
|
1023
|
+
self._mature_idea_component_explanations: Dict[str, str] = {}
|
|
1024
|
+
self._component_novelty_fallback_logged = False
|
|
1025
|
+
self._mechanism_commit_retrieval_unavailable = False
|
|
1026
|
+
self.persist_symbolic_memory = True
|
|
1027
|
+
|
|
1028
|
+
if self.enable_symbolic_memory:
|
|
1029
|
+
self.reload_symbolic_memory()
|
|
1030
|
+
|
|
1031
|
+
def reload_symbolic_memory(self) -> None:
|
|
1032
|
+
"""Reload the symbolic memory store from disk.
|
|
1033
|
+
|
|
1034
|
+
The store is populated externally (e.g. from experiment ablation
|
|
1035
|
+
results or paper-graph conclusions) — not from within MCTS. Reloading
|
|
1036
|
+
clears the current in-memory store first so the active process always
|
|
1037
|
+
reflects the latest on-disk state.
|
|
1038
|
+
"""
|
|
1039
|
+
if not self.enable_symbolic_memory:
|
|
1040
|
+
return
|
|
1041
|
+
self.symbolic_memory = SymbolicMemorySystem()
|
|
1042
|
+
try:
|
|
1043
|
+
if not self.symbolic_memory_path.exists():
|
|
1044
|
+
return
|
|
1045
|
+
self.symbolic_memory.load(str(self.symbolic_memory_path))
|
|
1046
|
+
except Exception as exc:
|
|
1047
|
+
log_message(
|
|
1048
|
+
self.logger,
|
|
1049
|
+
self.log_sink,
|
|
1050
|
+
"warning",
|
|
1051
|
+
"⚠️ Unable to load symbolic memory store: %s",
|
|
1052
|
+
exc,
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
def _skill_prior_for_prompt(self, skill_name: str) -> Dict[str, Any]:
|
|
1056
|
+
prior = self.skill_catalog.priors.get(skill_name, SkillUsagePrior())
|
|
1057
|
+
return prior.to_dict()
|
|
1058
|
+
|
|
1059
|
+
def _compute_protocol_score(self, plan: Optional[Dict[str, Any]]) -> float:
|
|
1060
|
+
return compute_protocol_score_from_plan(plan)
|
|
1061
|
+
|
|
1062
|
+
def _memory_bundle_log_payload(self, bundle: MemoryBundle) -> Dict[str, Any]:
|
|
1063
|
+
return memory_bundle_log_payload(bundle)
|
|
1064
|
+
|
|
1065
|
+
def _simulate_log_payload(self, evaluation: IdeaEvaluation) -> Dict[str, Any]:
|
|
1066
|
+
return simulate_log_payload(evaluation)
|
|
1067
|
+
|
|
1068
|
+
def _score_component_novelty(self, state: IdeaState) -> Optional[float]:
|
|
1069
|
+
try:
|
|
1070
|
+
return self.component_novelty_scorer.score(state=state, topic=self.topic)
|
|
1071
|
+
except Exception as exc:
|
|
1072
|
+
if not self._component_novelty_fallback_logged:
|
|
1073
|
+
log_message(
|
|
1074
|
+
self.logger,
|
|
1075
|
+
self.log_sink,
|
|
1076
|
+
"warning",
|
|
1077
|
+
"⚠️ Component novelty scorer unavailable (%s); falling back to LLM novelty.",
|
|
1078
|
+
exc,
|
|
1079
|
+
)
|
|
1080
|
+
self._component_novelty_fallback_logged = True
|
|
1081
|
+
return None
|
|
1082
|
+
|
|
1083
|
+
def _classify_root_discipline_resolution(
|
|
1084
|
+
self,
|
|
1085
|
+
topic: str,
|
|
1086
|
+
root_state: IdeaState,
|
|
1087
|
+
) -> Dict[str, Any]:
|
|
1088
|
+
root_text = "\n".join(
|
|
1089
|
+
[
|
|
1090
|
+
root_state.title,
|
|
1091
|
+
root_state.abstract,
|
|
1092
|
+
root_state.core_contribution,
|
|
1093
|
+
root_state.method,
|
|
1094
|
+
]
|
|
1095
|
+
)
|
|
1096
|
+
fallback = resolve_discipline_taxonomy(topic, query=root_text)
|
|
1097
|
+
if fallback.get("status") == "out_of_scope":
|
|
1098
|
+
return fallback
|
|
1099
|
+
prompt = ROOT_DOMAIN_CLASSIFICATION_PROMPT.format(
|
|
1100
|
+
topic=topic or "Unknown topic",
|
|
1101
|
+
root_idea=root_state.to_prompt_view(heading="Root Idea"),
|
|
1102
|
+
discipline_catalog=format_taxonomy_catalog_for_prompt(),
|
|
1103
|
+
)
|
|
1104
|
+
try:
|
|
1105
|
+
response = self.chat_fn(
|
|
1106
|
+
prompt,
|
|
1107
|
+
model=self.config.generation_model,
|
|
1108
|
+
temperature=0.01,
|
|
1109
|
+
max_output_tokens=65536,
|
|
1110
|
+
)
|
|
1111
|
+
payload = parse_json_response(response)
|
|
1112
|
+
if isinstance(payload, list):
|
|
1113
|
+
payload = payload[0] if payload else {}
|
|
1114
|
+
if not isinstance(payload, dict):
|
|
1115
|
+
return fallback
|
|
1116
|
+
raw_domains = [payload.get("primary_discipline")]
|
|
1117
|
+
adjacent = payload.get("adjacent_disciplines")
|
|
1118
|
+
if isinstance(adjacent, list):
|
|
1119
|
+
raw_domains.extend(adjacent)
|
|
1120
|
+
legacy_domains = payload.get("domains")
|
|
1121
|
+
if isinstance(legacy_domains, list):
|
|
1122
|
+
raw_domains.extend(legacy_domains)
|
|
1123
|
+
normalized = _normalize_root_domains(raw_domains)
|
|
1124
|
+
allowed = set(fallback.get("discipline_ids") or [])
|
|
1125
|
+
allowed.update(fallback.get("adjacent_disciplines") or [])
|
|
1126
|
+
selected = [domain for domain in normalized if domain in allowed]
|
|
1127
|
+
if not selected:
|
|
1128
|
+
return fallback
|
|
1129
|
+
resolution = resolve_discipline_taxonomy(selected[0], query=root_text)
|
|
1130
|
+
if len(selected) > 1 and str(payload.get("status") or "").strip() == "ambiguous":
|
|
1131
|
+
resolution["status"] = "ambiguous"
|
|
1132
|
+
resolution["discipline_ids"] = selected[:2]
|
|
1133
|
+
resolution["adjacent_disciplines"] = [
|
|
1134
|
+
domain for domain in selected[1:] if domain != selected[0]
|
|
1135
|
+
]
|
|
1136
|
+
resolution["llm_selection"] = {
|
|
1137
|
+
"status": str(payload.get("status") or "").strip(),
|
|
1138
|
+
"confidence": payload.get("confidence"),
|
|
1139
|
+
"matched_terms": payload.get("matched_terms")
|
|
1140
|
+
if isinstance(payload.get("matched_terms"), list)
|
|
1141
|
+
else [],
|
|
1142
|
+
}
|
|
1143
|
+
return resolution
|
|
1144
|
+
except Exception as exc:
|
|
1145
|
+
log_message(
|
|
1146
|
+
self.logger,
|
|
1147
|
+
self.log_sink,
|
|
1148
|
+
"warning",
|
|
1149
|
+
"⚠️ Root-domain classification failed; using heuristic fallback: %s",
|
|
1150
|
+
exc,
|
|
1151
|
+
)
|
|
1152
|
+
return fallback
|
|
1153
|
+
|
|
1154
|
+
def _classify_root_domains(self, topic: str, root_state: IdeaState) -> List[str]:
|
|
1155
|
+
resolution = self._classify_root_discipline_resolution(topic, root_state)
|
|
1156
|
+
return _normalize_root_domains(resolution.get("discipline_ids") or [])
|
|
1157
|
+
|
|
1158
|
+
def _build_theory_transfer_query(
|
|
1159
|
+
self,
|
|
1160
|
+
plan: EditPlan,
|
|
1161
|
+
parent_state: IdeaState,
|
|
1162
|
+
) -> Optional[Dict[str, str]]:
|
|
1163
|
+
prompt = get_theory_transfer_query_prompt(self.config.prompt_mode).format(
|
|
1164
|
+
topic=self.topic or "Unknown topic",
|
|
1165
|
+
root_domains=_format_root_domains_for_prompt(parent_state.root_domains),
|
|
1166
|
+
refinement_scope=self.refinement_scope or "None",
|
|
1167
|
+
idea=parent_state.to_prompt_view(heading="Current Idea"),
|
|
1168
|
+
edit_plan=pretty_json(plan.to_dict()),
|
|
1169
|
+
)
|
|
1170
|
+
empty_payload = {"query": "", "needed_content": "", "expected_role": ""}
|
|
1171
|
+
try:
|
|
1172
|
+
response = self.chat_fn(
|
|
1173
|
+
prompt,
|
|
1174
|
+
model=self.config.generation_model,
|
|
1175
|
+
temperature=0.2,
|
|
1176
|
+
max_output_tokens=65536,
|
|
1177
|
+
)
|
|
1178
|
+
payload = parse_json_response(response)
|
|
1179
|
+
if isinstance(payload, list):
|
|
1180
|
+
payload = payload[0] if payload else {}
|
|
1181
|
+
normalized = normalize_theory_transfer_query_payload(
|
|
1182
|
+
payload,
|
|
1183
|
+
fallback=empty_payload,
|
|
1184
|
+
clip_limit=UNIFORM_CLIP_TEXT_LIMIT,
|
|
1185
|
+
)
|
|
1186
|
+
if not str(normalized.get("query") or "").strip():
|
|
1187
|
+
return None
|
|
1188
|
+
if self._query_payload_uses_forbidden_terms(normalized):
|
|
1189
|
+
return None
|
|
1190
|
+
return normalized
|
|
1191
|
+
except Exception as exc:
|
|
1192
|
+
log_message(
|
|
1193
|
+
self.logger,
|
|
1194
|
+
self.log_sink,
|
|
1195
|
+
"warning",
|
|
1196
|
+
"⚠️ Theory-transfer query generation failed; skipping paper-graph retrieval: %s",
|
|
1197
|
+
exc,
|
|
1198
|
+
)
|
|
1199
|
+
return None
|
|
1200
|
+
|
|
1201
|
+
def _build_mechanism_commit_query(
|
|
1202
|
+
self,
|
|
1203
|
+
plan: EditPlan,
|
|
1204
|
+
parent_state: IdeaState,
|
|
1205
|
+
) -> Optional[Dict[str, str]]:
|
|
1206
|
+
prompt = get_mechanism_commit_query_prompt(self.config.prompt_mode).format(
|
|
1207
|
+
topic=self.topic or "Unknown topic",
|
|
1208
|
+
root_domains=_format_root_domains_for_prompt(parent_state.root_domains),
|
|
1209
|
+
refinement_scope=self.refinement_scope or "None",
|
|
1210
|
+
idea=parent_state.to_prompt_view(heading="Current Idea"),
|
|
1211
|
+
edit_plan=pretty_json(plan.to_dict()),
|
|
1212
|
+
)
|
|
1213
|
+
empty_payload = {"query": "", "mechanism_gap": "", "expected_role": ""}
|
|
1214
|
+
try:
|
|
1215
|
+
response = self.chat_fn(
|
|
1216
|
+
prompt,
|
|
1217
|
+
model=self.config.generation_model,
|
|
1218
|
+
temperature=0.2,
|
|
1219
|
+
max_output_tokens=65536,
|
|
1220
|
+
)
|
|
1221
|
+
payload = parse_json_response(response)
|
|
1222
|
+
if isinstance(payload, list):
|
|
1223
|
+
payload = payload[0] if payload else {}
|
|
1224
|
+
normalized = normalize_mechanism_commit_query_payload(
|
|
1225
|
+
payload,
|
|
1226
|
+
fallback=empty_payload,
|
|
1227
|
+
clip_limit=UNIFORM_CLIP_TEXT_LIMIT,
|
|
1228
|
+
)
|
|
1229
|
+
if not str(normalized.get("query") or "").strip():
|
|
1230
|
+
return None
|
|
1231
|
+
if self._query_payload_uses_forbidden_terms(normalized):
|
|
1232
|
+
return None
|
|
1233
|
+
return normalized
|
|
1234
|
+
except Exception as exc:
|
|
1235
|
+
log_message(
|
|
1236
|
+
self.logger,
|
|
1237
|
+
self.log_sink,
|
|
1238
|
+
"warning",
|
|
1239
|
+
"⚠️ Mechanism-commit query generation failed; skipping paper-graph retrieval: %s",
|
|
1240
|
+
exc,
|
|
1241
|
+
)
|
|
1242
|
+
return None
|
|
1243
|
+
|
|
1244
|
+
def _query_payload_uses_forbidden_terms(self, payload: Dict[str, str]) -> bool:
|
|
1245
|
+
return any(
|
|
1246
|
+
text_uses_forbidden_query_terms(str(payload.get(field_name) or ""))
|
|
1247
|
+
for field_name in ("query", "mechanism_gap", "needed_content", "expected_role")
|
|
1248
|
+
)
|
|
1249
|
+
|
|
1250
|
+
def _record_retrieved_core_titles(self, hits: List[Dict[str, Any]]) -> None:
|
|
1251
|
+
seen = {str(title).strip().lower() for title in self.retrieved_core_titles if str(title).strip()}
|
|
1252
|
+
for hit in hits:
|
|
1253
|
+
core_node = hit.get("core_node") if isinstance(hit.get("core_node"), dict) else {}
|
|
1254
|
+
title = str(
|
|
1255
|
+
core_node.get("paper_title")
|
|
1256
|
+
or core_node.get("title")
|
|
1257
|
+
or core_node.get("full_name")
|
|
1258
|
+
or core_node.get("label")
|
|
1259
|
+
or hit.get("node_id")
|
|
1260
|
+
or ""
|
|
1261
|
+
).strip()
|
|
1262
|
+
if not title:
|
|
1263
|
+
continue
|
|
1264
|
+
key = title.lower()
|
|
1265
|
+
if key in seen:
|
|
1266
|
+
continue
|
|
1267
|
+
seen.add(key)
|
|
1268
|
+
self.retrieved_core_titles.append(title)
|
|
1269
|
+
|
|
1270
|
+
def _retrieve_theory_transfer_references(
|
|
1271
|
+
self,
|
|
1272
|
+
query_payload: Dict[str, str],
|
|
1273
|
+
root_domains: List[str],
|
|
1274
|
+
) -> List[Dict[str, Any]]:
|
|
1275
|
+
query = str(query_payload.get("query") or "").strip()
|
|
1276
|
+
if not query:
|
|
1277
|
+
return []
|
|
1278
|
+
try:
|
|
1279
|
+
search_pool = max(int(self.config.theory_transfer_retrieval_top_k) * 6, 12)
|
|
1280
|
+
hits = self.paper_graph_vector_store.search(
|
|
1281
|
+
query=query,
|
|
1282
|
+
top_k=search_pool,
|
|
1283
|
+
component_hits_per_core=2,
|
|
1284
|
+
)
|
|
1285
|
+
except Exception as exc:
|
|
1286
|
+
log_message(
|
|
1287
|
+
self.logger,
|
|
1288
|
+
self.log_sink,
|
|
1289
|
+
"warning",
|
|
1290
|
+
"⚠️ Theory-transfer paper-graph retrieval unavailable: %s",
|
|
1291
|
+
exc,
|
|
1292
|
+
)
|
|
1293
|
+
return []
|
|
1294
|
+
|
|
1295
|
+
threshold = float(self.config.theory_transfer_similarity_threshold)
|
|
1296
|
+
blocked_domains = set(_normalize_root_domains(root_domains))
|
|
1297
|
+
filtered: List[Dict[str, Any]] = []
|
|
1298
|
+
for hit in hits:
|
|
1299
|
+
core_node = hit.get("core_node") if isinstance(hit.get("core_node"), dict) else {}
|
|
1300
|
+
paper_domain = str(core_node.get("paper_domain") or "").strip()
|
|
1301
|
+
paper_domain_key = canonicalize_discipline_key(paper_domain)
|
|
1302
|
+
if not paper_domain or (
|
|
1303
|
+
paper_domain_key and paper_domain_key in blocked_domains
|
|
1304
|
+
):
|
|
1305
|
+
continue
|
|
1306
|
+
if float(hit.get("score") or 0.0) < threshold:
|
|
1307
|
+
continue
|
|
1308
|
+
filtered.append(hit)
|
|
1309
|
+
if len(filtered) >= int(self.config.theory_transfer_retrieval_top_k):
|
|
1310
|
+
break
|
|
1311
|
+
if filtered:
|
|
1312
|
+
rendered_hits: List[str] = []
|
|
1313
|
+
for idx, hit in enumerate(filtered, start=1):
|
|
1314
|
+
core_node = hit.get("core_node") if isinstance(hit.get("core_node"), dict) else {}
|
|
1315
|
+
paper_domain = str(core_node.get("paper_domain") or "").strip() or "unknown"
|
|
1316
|
+
label = (
|
|
1317
|
+
str(core_node.get("full_name") or "").strip()
|
|
1318
|
+
or str(core_node.get("label") or "").strip()
|
|
1319
|
+
or str(core_node.get("node_id") or "").strip()
|
|
1320
|
+
or f"core_{idx}"
|
|
1321
|
+
)
|
|
1322
|
+
paper_title = clip_text(str(core_node.get("paper_title") or "").strip(), 400)
|
|
1323
|
+
summary = clip_text(str(core_node.get("summary") or "").strip(), 1000)
|
|
1324
|
+
insight = clip_text(str(core_node.get("insight") or "").strip(), 1000)
|
|
1325
|
+
rendered_hits.append(
|
|
1326
|
+
f"{idx}. {label} | domain={paper_domain} | score={float(hit.get('score') or 0.0):.3f}"
|
|
1327
|
+
)
|
|
1328
|
+
if paper_title:
|
|
1329
|
+
rendered_hits.append(f" title: {paper_title}")
|
|
1330
|
+
if summary:
|
|
1331
|
+
rendered_hits.append(f" summary: {summary}")
|
|
1332
|
+
if insight:
|
|
1333
|
+
rendered_hits.append(f" insight: {insight}")
|
|
1334
|
+
log_message(
|
|
1335
|
+
self.logger,
|
|
1336
|
+
self.log_sink,
|
|
1337
|
+
"info",
|
|
1338
|
+
"[MCTS] Theory-transfer eligible core nodes for query=%s\n%s",
|
|
1339
|
+
clip_text(query, 1000),
|
|
1340
|
+
"\n".join(rendered_hits),
|
|
1341
|
+
)
|
|
1342
|
+
self._record_retrieved_core_titles(filtered)
|
|
1343
|
+
return filtered
|
|
1344
|
+
|
|
1345
|
+
def _retrieve_mechanism_commit_references(
|
|
1346
|
+
self,
|
|
1347
|
+
query_payload: Dict[str, str],
|
|
1348
|
+
) -> List[Dict[str, Any]]:
|
|
1349
|
+
query = str(query_payload.get("query") or "").strip()
|
|
1350
|
+
if not query:
|
|
1351
|
+
return []
|
|
1352
|
+
if getattr(self, "_mechanism_commit_retrieval_unavailable", False):
|
|
1353
|
+
return []
|
|
1354
|
+
try:
|
|
1355
|
+
search_pool = max(int(self.config.mechanism_commit_retrieval_top_k) * 6, 12)
|
|
1356
|
+
hits = self.paper_graph_vector_store.search(
|
|
1357
|
+
query=query,
|
|
1358
|
+
top_k=search_pool,
|
|
1359
|
+
component_hits_per_core=2,
|
|
1360
|
+
)
|
|
1361
|
+
except Exception as exc:
|
|
1362
|
+
log_message(
|
|
1363
|
+
self.logger,
|
|
1364
|
+
self.log_sink,
|
|
1365
|
+
"warning",
|
|
1366
|
+
"⚠️ Mechanism-commit paper-graph retrieval unavailable (%s); "
|
|
1367
|
+
"continuing without paper-graph grounding.",
|
|
1368
|
+
exc,
|
|
1369
|
+
)
|
|
1370
|
+
self._mechanism_commit_retrieval_unavailable = True
|
|
1371
|
+
return []
|
|
1372
|
+
threshold = float(self.config.mechanism_commit_similarity_threshold)
|
|
1373
|
+
filtered: List[Dict[str, Any]] = []
|
|
1374
|
+
for hit in hits:
|
|
1375
|
+
if float(hit.get("score") or 0.0) < threshold:
|
|
1376
|
+
continue
|
|
1377
|
+
filtered.append(hit)
|
|
1378
|
+
if len(filtered) >= int(self.config.mechanism_commit_retrieval_top_k):
|
|
1379
|
+
break
|
|
1380
|
+
if filtered:
|
|
1381
|
+
rendered_hits: List[str] = []
|
|
1382
|
+
for idx, hit in enumerate(filtered, start=1):
|
|
1383
|
+
core_node = hit.get("core_node") if isinstance(hit.get("core_node"), dict) else {}
|
|
1384
|
+
paper_domain = str(core_node.get("paper_domain") or "").strip() or "unknown"
|
|
1385
|
+
label = (
|
|
1386
|
+
str(core_node.get("full_name") or "").strip()
|
|
1387
|
+
or str(core_node.get("label") or "").strip()
|
|
1388
|
+
or str(core_node.get("node_id") or "").strip()
|
|
1389
|
+
or f"core_{idx}"
|
|
1390
|
+
)
|
|
1391
|
+
paper_title = clip_text(str(core_node.get("paper_title") or "").strip(), 400)
|
|
1392
|
+
summary = clip_text(str(core_node.get("summary") or "").strip(), 1000)
|
|
1393
|
+
insight = clip_text(str(core_node.get("insight") or "").strip(), 1000)
|
|
1394
|
+
rendered_hits.append(
|
|
1395
|
+
f"{idx}. {label} | domain={paper_domain} | score={float(hit.get('score') or 0.0):.3f}"
|
|
1396
|
+
)
|
|
1397
|
+
if paper_title:
|
|
1398
|
+
rendered_hits.append(f" title: {paper_title}")
|
|
1399
|
+
if summary:
|
|
1400
|
+
rendered_hits.append(f" summary: {summary}")
|
|
1401
|
+
if insight:
|
|
1402
|
+
rendered_hits.append(f" insight: {insight}")
|
|
1403
|
+
log_message(
|
|
1404
|
+
self.logger,
|
|
1405
|
+
self.log_sink,
|
|
1406
|
+
"info",
|
|
1407
|
+
"[MCTS] Mechanism-commit grounding core nodes for query=%s\n%s",
|
|
1408
|
+
clip_text(query, 1000),
|
|
1409
|
+
"\n".join(rendered_hits),
|
|
1410
|
+
)
|
|
1411
|
+
self._record_retrieved_core_titles(filtered)
|
|
1412
|
+
return filtered
|
|
1413
|
+
|
|
1414
|
+
def _materialize_child_state(
|
|
1415
|
+
self,
|
|
1416
|
+
parent_state: IdeaState,
|
|
1417
|
+
plan: EditPlan,
|
|
1418
|
+
instantiated: Optional[Dict[str, Any]] = None,
|
|
1419
|
+
selection_metadata: Optional[Dict[str, Any]] = None,
|
|
1420
|
+
) -> IdeaState:
|
|
1421
|
+
return materialize_child_state(
|
|
1422
|
+
self,
|
|
1423
|
+
parent_state,
|
|
1424
|
+
plan,
|
|
1425
|
+
instantiated,
|
|
1426
|
+
selection_metadata=selection_metadata,
|
|
1427
|
+
idea_state_cls=IdeaState,
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
def _select(self, node: IdeaNode) -> Tuple[IdeaNode, List[IdeaNode]]:
|
|
1431
|
+
return select_leaf_for_rollout(self, node)
|
|
1432
|
+
|
|
1433
|
+
def _instantiate_skill_plan(
|
|
1434
|
+
self,
|
|
1435
|
+
plan: EditPlan,
|
|
1436
|
+
parent_state: IdeaState,
|
|
1437
|
+
bundle: MemoryBundle,
|
|
1438
|
+
) -> Optional[Dict[str, Any]]:
|
|
1439
|
+
root_domains_text = _format_root_domains_for_prompt(parent_state.root_domains)
|
|
1440
|
+
additional_retrieval_context = ""
|
|
1441
|
+
skip_payload: Optional[Dict[str, Any]] = None
|
|
1442
|
+
|
|
1443
|
+
if plan.skill_name == "mechanism-commit-innovation":
|
|
1444
|
+
query_payload = self._build_mechanism_commit_query(plan, parent_state)
|
|
1445
|
+
if query_payload is not None:
|
|
1446
|
+
hits = self._retrieve_mechanism_commit_references(query_payload)
|
|
1447
|
+
if hits:
|
|
1448
|
+
additional_retrieval_context = "\n".join(
|
|
1449
|
+
[
|
|
1450
|
+
"Mechanism-grounding retrieval query:",
|
|
1451
|
+
query_payload.get("query") or "None",
|
|
1452
|
+
format_mechanism_commit_references(
|
|
1453
|
+
query_payload,
|
|
1454
|
+
hits,
|
|
1455
|
+
clip_limit=UNIFORM_CLIP_TEXT_LIMIT,
|
|
1456
|
+
),
|
|
1457
|
+
]
|
|
1458
|
+
)
|
|
1459
|
+
elif plan.skill_name == "theory-transfer-injection":
|
|
1460
|
+
query_payload = self._build_theory_transfer_query(plan, parent_state)
|
|
1461
|
+
if query_payload is not None:
|
|
1462
|
+
hits = self._retrieve_theory_transfer_references(query_payload, parent_state.root_domains)
|
|
1463
|
+
if not hits:
|
|
1464
|
+
skip_payload = {
|
|
1465
|
+
"_skip_child_creation": True,
|
|
1466
|
+
"_skip_reason": (
|
|
1467
|
+
"theory-transfer-injection retrieved no eligible cross-domain core nodes "
|
|
1468
|
+
f"(threshold={self.config.theory_transfer_similarity_threshold}, "
|
|
1469
|
+
f"excluded_domains={parent_state.root_domains})"
|
|
1470
|
+
),
|
|
1471
|
+
}
|
|
1472
|
+
else:
|
|
1473
|
+
additional_retrieval_context = "\n".join(
|
|
1474
|
+
[
|
|
1475
|
+
"Cross-domain transfer query:",
|
|
1476
|
+
query_payload.get("query") or "None",
|
|
1477
|
+
format_theory_transfer_references(
|
|
1478
|
+
query_payload,
|
|
1479
|
+
hits,
|
|
1480
|
+
clip_limit=UNIFORM_CLIP_TEXT_LIMIT,
|
|
1481
|
+
),
|
|
1482
|
+
]
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
if skip_payload is not None:
|
|
1486
|
+
return skip_payload
|
|
1487
|
+
|
|
1488
|
+
payload = instantiate_skill_plan_for_node(
|
|
1489
|
+
self,
|
|
1490
|
+
plan,
|
|
1491
|
+
parent_state,
|
|
1492
|
+
bundle,
|
|
1493
|
+
prompt_template=get_skill_instantiation_prompt(
|
|
1494
|
+
self.config.prompt_mode,
|
|
1495
|
+
profile_id=str(
|
|
1496
|
+
(getattr(parent_state, "scientific_intervention", {}) or {}).get("profile_id")
|
|
1497
|
+
or "generic_scientific"
|
|
1498
|
+
),
|
|
1499
|
+
),
|
|
1500
|
+
root_domains_text=root_domains_text,
|
|
1501
|
+
additional_retrieval_context=additional_retrieval_context,
|
|
1502
|
+
)
|
|
1503
|
+
if plan.skill_name == "theory-transfer-injection":
|
|
1504
|
+
if not isinstance(payload, dict):
|
|
1505
|
+
return {
|
|
1506
|
+
"_skip_child_creation": True,
|
|
1507
|
+
"_skip_reason": "theory-transfer-injection instantiation returned no structured payload",
|
|
1508
|
+
}
|
|
1509
|
+
payload.pop("_paper_graph_context", None)
|
|
1510
|
+
payload["_paper_graph_context"] = additional_retrieval_context
|
|
1511
|
+
if (
|
|
1512
|
+
plan.skill_name == "mechanism-commit-innovation"
|
|
1513
|
+
and isinstance(payload, dict)
|
|
1514
|
+
and not parent_centers_control_surface(parent_state)
|
|
1515
|
+
):
|
|
1516
|
+
rendered = "\n".join(
|
|
1517
|
+
str(payload.get(field_name) or "")
|
|
1518
|
+
for field_name in ("title", "abstract", "core_contribution", "method", "rationale")
|
|
1519
|
+
)
|
|
1520
|
+
if text_uses_forbidden_query_terms(rendered):
|
|
1521
|
+
return {
|
|
1522
|
+
"_skip_child_creation": True,
|
|
1523
|
+
"_skip_reason": (
|
|
1524
|
+
"mechanism-commit-innovation instantiated a threshold/gating/suppression/quota-style "
|
|
1525
|
+
"patch for a parent idea that is not control-centered"
|
|
1526
|
+
),
|
|
1527
|
+
}
|
|
1528
|
+
if plan.skill_name == "mechanism-commit-innovation" and isinstance(payload, dict):
|
|
1529
|
+
payload.pop("_paper_graph_context", None)
|
|
1530
|
+
payload["_paper_graph_context"] = additional_retrieval_context
|
|
1531
|
+
return payload
|
|
1532
|
+
|
|
1533
|
+
def _expand(self, node: IdeaNode, path: List[IdeaNode]) -> Tuple[Optional[IdeaNode], List[IdeaNode]]:
|
|
1534
|
+
return expand_node_with_skills(
|
|
1535
|
+
self,
|
|
1536
|
+
node,
|
|
1537
|
+
path,
|
|
1538
|
+
min_components=MIN_COMPONENTS,
|
|
1539
|
+
max_components=MAX_COMPONENTS,
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
def _build_symbolic_eval_hints(self, node: IdeaNode) -> str:
|
|
1544
|
+
return build_symbolic_eval_hints(self, node)
|
|
1545
|
+
|
|
1546
|
+
def _simulate(
|
|
1547
|
+
self,
|
|
1548
|
+
node: IdeaNode,
|
|
1549
|
+
path: List[IdeaNode],
|
|
1550
|
+
experiences: List[Dict[str, Any]],
|
|
1551
|
+
) -> Optional[IdeaEvaluation]:
|
|
1552
|
+
return simulate_node_value(
|
|
1553
|
+
self,
|
|
1554
|
+
node,
|
|
1555
|
+
path,
|
|
1556
|
+
experiences,
|
|
1557
|
+
idea_evaluation_cls=IdeaEvaluation,
|
|
1558
|
+
)
|
|
1559
|
+
|
|
1560
|
+
def _update_skill_prior(self, node: IdeaNode, evaluation: IdeaEvaluation) -> None:
|
|
1561
|
+
update_skill_prior_from_evaluation(self, node, evaluation)
|
|
1562
|
+
|
|
1563
|
+
def _backpropagate(self, path: List[IdeaNode], evaluation: IdeaEvaluation) -> None:
|
|
1564
|
+
backpropagate_rollout(path, evaluation)
|
|
1565
|
+
|
|
1566
|
+
def _extract_mature_idea_components(
|
|
1567
|
+
self,
|
|
1568
|
+
mature_idea: str,
|
|
1569
|
+
topic: str,
|
|
1570
|
+
*,
|
|
1571
|
+
prior_components: Optional[List[str]] = None,
|
|
1572
|
+
prior_component_explanations: Optional[Dict[str, str]] = None,
|
|
1573
|
+
component_decisions: Optional[List[Dict[str, Any]]] = None,
|
|
1574
|
+
) -> Tuple[List[str], Dict[str, str]]:
|
|
1575
|
+
return extract_mature_idea_components_via_llm(
|
|
1576
|
+
self,
|
|
1577
|
+
mature_idea,
|
|
1578
|
+
topic,
|
|
1579
|
+
prompt_template=render_component_extraction_prompt(
|
|
1580
|
+
str(self.scientific_intervention_profile.get("profile_id") or "")
|
|
1581
|
+
),
|
|
1582
|
+
max_components=MAX_COMPONENTS,
|
|
1583
|
+
prior_components=prior_components,
|
|
1584
|
+
prior_component_explanations=prior_component_explanations,
|
|
1585
|
+
component_decisions=component_decisions,
|
|
1586
|
+
)
|
|
1587
|
+
|
|
1588
|
+
def _resolve_prior_component_context(
|
|
1589
|
+
self,
|
|
1590
|
+
prepared: Dict[str, Any],
|
|
1591
|
+
) -> Tuple[List[str], Dict[str, str], List[Dict[str, Any]]]:
|
|
1592
|
+
prior_components = (
|
|
1593
|
+
[str(component).strip() for component in prepared.get("prior_components", []) if str(component).strip()]
|
|
1594
|
+
if isinstance(prepared.get("prior_components"), list)
|
|
1595
|
+
else []
|
|
1596
|
+
)
|
|
1597
|
+
prior_component_explanations = (
|
|
1598
|
+
prepared.get("prior_component_explanations")
|
|
1599
|
+
if isinstance(prepared.get("prior_component_explanations"), (dict, list))
|
|
1600
|
+
else {}
|
|
1601
|
+
)
|
|
1602
|
+
component_decisions = (
|
|
1603
|
+
[decision for decision in prepared.get("component_decisions", []) if isinstance(decision, dict)]
|
|
1604
|
+
if isinstance(prepared.get("component_decisions"), list)
|
|
1605
|
+
else []
|
|
1606
|
+
)
|
|
1607
|
+
if not prior_components:
|
|
1608
|
+
for entry_key in ("latest_candidate", "root_idea"):
|
|
1609
|
+
entry = prepared.get(entry_key)
|
|
1610
|
+
if not isinstance(entry, dict):
|
|
1611
|
+
continue
|
|
1612
|
+
raw_components = entry.get("components")
|
|
1613
|
+
if not isinstance(raw_components, list):
|
|
1614
|
+
continue
|
|
1615
|
+
prior_components = [
|
|
1616
|
+
str(component).strip()
|
|
1617
|
+
for component in raw_components
|
|
1618
|
+
if str(component).strip()
|
|
1619
|
+
]
|
|
1620
|
+
if prior_components:
|
|
1621
|
+
if not prior_component_explanations:
|
|
1622
|
+
raw_explanations = entry.get("component_explanations")
|
|
1623
|
+
if isinstance(raw_explanations, (dict, list)):
|
|
1624
|
+
prior_component_explanations = raw_explanations
|
|
1625
|
+
break
|
|
1626
|
+
normalized_prior_explanations = normalize_component_explanations(
|
|
1627
|
+
prior_components,
|
|
1628
|
+
prior_component_explanations,
|
|
1629
|
+
)
|
|
1630
|
+
return prior_components, normalized_prior_explanations, component_decisions
|
|
1631
|
+
|
|
1632
|
+
def prepare_root_context(self, topic: str, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
1633
|
+
prepared = dict(context or {})
|
|
1634
|
+
self.multimodal_evidence_projection = (
|
|
1635
|
+
dict(prepared.get("multimodal_evidence_projection"))
|
|
1636
|
+
if isinstance(prepared.get("multimodal_evidence_projection"), dict)
|
|
1637
|
+
else {}
|
|
1638
|
+
)
|
|
1639
|
+
survey_handoff = prepared.get("survey_idea_handoff")
|
|
1640
|
+
self.survey_idea_handoff = (
|
|
1641
|
+
dict(survey_handoff) if isinstance(survey_handoff, dict) else {}
|
|
1642
|
+
)
|
|
1643
|
+
gap_triage = (
|
|
1644
|
+
dict(self.survey_idea_handoff.get("gap_triage") or {})
|
|
1645
|
+
if isinstance(self.survey_idea_handoff.get("gap_triage"), dict)
|
|
1646
|
+
else {}
|
|
1647
|
+
)
|
|
1648
|
+
triage_rows = [
|
|
1649
|
+
row for row in (gap_triage.get("gaps", []) if gap_triage else [])
|
|
1650
|
+
if isinstance(row, dict) and str(row.get("gap_id") or "").strip()
|
|
1651
|
+
]
|
|
1652
|
+
triage_by_id = {str(row["gap_id"]).strip(): row for row in triage_rows}
|
|
1653
|
+
active_gap_ids = {
|
|
1654
|
+
gap_id for gap_id, row in triage_by_id.items()
|
|
1655
|
+
if str(row.get("eligibility_route") or "") in {
|
|
1656
|
+
"core_hypothesis", "provisional_hypothesis", "exploratory_frontier"
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
prepared["gap_triage"] = gap_triage
|
|
1660
|
+
prepared["active_gaps"] = [
|
|
1661
|
+
row for row in triage_rows if str(row.get("gap_id")) in active_gap_ids
|
|
1662
|
+
]
|
|
1663
|
+
prepared["provisional_gaps"] = [
|
|
1664
|
+
row for row in triage_rows
|
|
1665
|
+
if str(row.get("eligibility_route") or "") in {"provisional_hypothesis", "exploratory_frontier"}
|
|
1666
|
+
]
|
|
1667
|
+
prepared["supporting_constraints"] = [
|
|
1668
|
+
row for row in triage_rows if str(row.get("eligibility_route") or "") == "supporting_constraint"
|
|
1669
|
+
]
|
|
1670
|
+
prepared["verification_only_gaps"] = [
|
|
1671
|
+
row for row in triage_rows if str(row.get("eligibility_route") or "") == "verification_only"
|
|
1672
|
+
]
|
|
1673
|
+
prepared["future_work_seeds"] = [
|
|
1674
|
+
row for row in triage_rows if str(row.get("eligibility_route") or "") == "future_work_seed"
|
|
1675
|
+
]
|
|
1676
|
+
prepared["excluded_gaps"] = [
|
|
1677
|
+
row for row in triage_rows if str(row.get("eligibility_route") or "") == "exclude"
|
|
1678
|
+
]
|
|
1679
|
+
handoff_gap_by_id = {
|
|
1680
|
+
str(gap.get("gap_id") or "").strip(): gap
|
|
1681
|
+
for gap in (self.survey_idea_handoff.get("gaps", []) if self.survey_idea_handoff else [])
|
|
1682
|
+
if isinstance(gap, dict) and str(gap.get("gap_id") or "").strip()
|
|
1683
|
+
}
|
|
1684
|
+
routing_labels = (
|
|
1685
|
+
("active_gaps", "Core or active hypothesis gaps"),
|
|
1686
|
+
("provisional_gaps", "Provisional exploratory gaps"),
|
|
1687
|
+
("supporting_constraints", "Supporting scientific constraints"),
|
|
1688
|
+
("verification_only_gaps", "Verification-only gaps"),
|
|
1689
|
+
("future_work_seeds", "Future-work hypothesis seeds"),
|
|
1690
|
+
)
|
|
1691
|
+
routing_lines = ["== Survey Gap Routing (authoritative) =="]
|
|
1692
|
+
for key, label in routing_labels:
|
|
1693
|
+
rows = prepared.get(key, [])
|
|
1694
|
+
statements = [
|
|
1695
|
+
str(handoff_gap_by_id.get(str(row.get("gap_id") or ""), {}).get("statement") or "").strip()
|
|
1696
|
+
for row in rows[:5]
|
|
1697
|
+
if isinstance(row, dict)
|
|
1698
|
+
]
|
|
1699
|
+
if statements:
|
|
1700
|
+
routing_lines.append(f"{label}: " + " | ".join(statement for statement in statements if statement))
|
|
1701
|
+
routing_lines.append("Do not turn verification-only or supporting constraints into primary novelty. Future-work items are exploratory seeds, not established evidence.")
|
|
1702
|
+
prepared["gap_routing_context"] = "\n".join(routing_lines)
|
|
1703
|
+
handoff_profile_resolution = (
|
|
1704
|
+
dict(self.survey_idea_handoff.get("profile_resolution") or {})
|
|
1705
|
+
if self.survey_idea_handoff
|
|
1706
|
+
and isinstance(self.survey_idea_handoff.get("profile_resolution"), dict)
|
|
1707
|
+
else {}
|
|
1708
|
+
)
|
|
1709
|
+
profile_hint = str(handoff_profile_resolution.get("profile_id_hint") or "").strip()
|
|
1710
|
+
if profile_hint:
|
|
1711
|
+
handoff_profile_resolution["profile_id"] = profile_hint
|
|
1712
|
+
handoff_defect_tags = []
|
|
1713
|
+
raw_handoff_gaps = (
|
|
1714
|
+
self.survey_idea_handoff.get("gaps", [])
|
|
1715
|
+
if self.survey_idea_handoff
|
|
1716
|
+
else []
|
|
1717
|
+
)
|
|
1718
|
+
for raw_gap in raw_handoff_gaps:
|
|
1719
|
+
if not isinstance(raw_gap, dict):
|
|
1720
|
+
continue
|
|
1721
|
+
gap_id = str(raw_gap.get("gap_id") or "").strip()
|
|
1722
|
+
route = str(triage_by_id.get(gap_id, {}).get("eligibility_route") or "")
|
|
1723
|
+
if route in {"supporting_constraint", "verification_only", "exclude"}:
|
|
1724
|
+
continue
|
|
1725
|
+
raw_tags = raw_gap.get("candidate_defect_tags", [])
|
|
1726
|
+
values = raw_tags if isinstance(raw_tags, list) else [raw_tags]
|
|
1727
|
+
handoff_defect_tags.extend(str(tag).strip() for tag in values if str(tag).strip())
|
|
1728
|
+
if handoff_defect_tags:
|
|
1729
|
+
existing_tags = prepared.get("defect_tags", [])
|
|
1730
|
+
existing_values = existing_tags if isinstance(existing_tags, list) else [existing_tags]
|
|
1731
|
+
prepared["defect_tags"] = _dedupe_keep_order_strings(
|
|
1732
|
+
[*existing_values, *handoff_defect_tags]
|
|
1733
|
+
)
|
|
1734
|
+
elif raw_handoff_gaps and not gap_triage and not prepared.get("active_gaps"):
|
|
1735
|
+
# Fail-open floor: a deterministic open gap must remain available
|
|
1736
|
+
# even when triage cannot establish a stronger route.
|
|
1737
|
+
fallback_gap = next(
|
|
1738
|
+
(gap for gap in raw_handoff_gaps if isinstance(gap, dict)), None
|
|
1739
|
+
)
|
|
1740
|
+
if fallback_gap:
|
|
1741
|
+
prepared["defect_tags"] = _dedupe_keep_order_strings(
|
|
1742
|
+
[
|
|
1743
|
+
*([str(value) for value in (prepared.get("defect_tags") or [])] if isinstance(prepared.get("defect_tags"), list) else []),
|
|
1744
|
+
*([str(value) for value in (fallback_gap.get("candidate_defect_tags") or [])] if isinstance(fallback_gap.get("candidate_defect_tags"), list) else []),
|
|
1745
|
+
str(fallback_gap.get("gap_kind") or "unexplored_gap"),
|
|
1746
|
+
]
|
|
1747
|
+
)
|
|
1748
|
+
base_paper_context = prepared.get("paper_context") or "No curated papers available yet."
|
|
1749
|
+
self.paper_context = str(base_paper_context) + "\n\n" + prepared["gap_routing_context"]
|
|
1750
|
+
prepared["paper_context"] = self.paper_context
|
|
1751
|
+
prepared["refinement_scope"] = str(prepared.get("refinement_scope") or "").strip()
|
|
1752
|
+
project_context = (
|
|
1753
|
+
prepared.get("project_context")
|
|
1754
|
+
if isinstance(prepared.get("project_context"), dict)
|
|
1755
|
+
else None
|
|
1756
|
+
)
|
|
1757
|
+
project_resolution = normalize_project_context_discipline_resolution(project_context)
|
|
1758
|
+
if project_resolution:
|
|
1759
|
+
prepared["project_context_discipline_resolution"] = project_resolution
|
|
1760
|
+
early_root_domains = _normalize_root_domains(prepared.get("root_domains") or [])
|
|
1761
|
+
explicit_handoff_fields = handoff_profile_resolution.get("openalex_field_ids") or handoff_profile_resolution.get(
|
|
1762
|
+
"paperseek_field_ids"
|
|
1763
|
+
)
|
|
1764
|
+
early_resolution = (
|
|
1765
|
+
handoff_profile_resolution
|
|
1766
|
+
if handoff_profile_resolution and explicit_handoff_fields
|
|
1767
|
+
else prepared.get("discipline_resolution")
|
|
1768
|
+
)
|
|
1769
|
+
early_profile = resolve_scientific_intervention_profile(
|
|
1770
|
+
early_resolution if isinstance(early_resolution, dict) else {},
|
|
1771
|
+
root_domains=early_root_domains,
|
|
1772
|
+
project_context=project_context,
|
|
1773
|
+
)
|
|
1774
|
+
if early_profile is not None:
|
|
1775
|
+
self.scientific_intervention_profile = early_profile.to_payload()
|
|
1776
|
+
mature_record = prepared.get("mature_idea_record")
|
|
1777
|
+
if isinstance(mature_record, dict):
|
|
1778
|
+
self.mature_idea_record = normalize_mature_idea(mature_record)
|
|
1779
|
+
prepared["mature_idea_record"] = dict(self.mature_idea_record)
|
|
1780
|
+
else:
|
|
1781
|
+
self.mature_idea_record = {}
|
|
1782
|
+
mature_idea_value = prepared.get("mature_idea")
|
|
1783
|
+
if isinstance(mature_idea_value, dict):
|
|
1784
|
+
self.mature_idea_record = normalize_mature_idea(mature_idea_value)
|
|
1785
|
+
prepared["mature_idea_record"] = dict(self.mature_idea_record)
|
|
1786
|
+
mature_idea = str(
|
|
1787
|
+
self.mature_idea_record.get("hypothesis")
|
|
1788
|
+
or self.mature_idea_record.get("abstract")
|
|
1789
|
+
or self.mature_idea_record.get("title")
|
|
1790
|
+
or ""
|
|
1791
|
+
).strip()
|
|
1792
|
+
else:
|
|
1793
|
+
mature_idea = str(mature_idea_value or "").strip()
|
|
1794
|
+
if mature_idea:
|
|
1795
|
+
prepared["mature_idea"] = mature_idea
|
|
1796
|
+
components = prepared.get("components")
|
|
1797
|
+
prior_components, prior_component_explanations, component_decisions = (
|
|
1798
|
+
self._resolve_prior_component_context(prepared)
|
|
1799
|
+
)
|
|
1800
|
+
if mature_idea and not isinstance(components, list):
|
|
1801
|
+
components, explanations = self._extract_mature_idea_components(
|
|
1802
|
+
mature_idea,
|
|
1803
|
+
topic,
|
|
1804
|
+
prior_components=prior_components,
|
|
1805
|
+
prior_component_explanations=prior_component_explanations,
|
|
1806
|
+
component_decisions=component_decisions,
|
|
1807
|
+
)
|
|
1808
|
+
if components:
|
|
1809
|
+
prepared["components"] = components
|
|
1810
|
+
prepared["component_explanations"] = explanations
|
|
1811
|
+
|
|
1812
|
+
handoff_root_domains = _normalize_root_domains(
|
|
1813
|
+
handoff_profile_resolution.get("discipline_ids") or []
|
|
1814
|
+
)
|
|
1815
|
+
project_root_domains = _normalize_root_domains(
|
|
1816
|
+
project_resolution.get("discipline_ids") or []
|
|
1817
|
+
)
|
|
1818
|
+
handoff_has_explicit_scope = bool(explicit_handoff_fields or handoff_root_domains)
|
|
1819
|
+
project_has_explicit_scope = bool(
|
|
1820
|
+
project_resolution.get("paperseek_openalex_field_ids")
|
|
1821
|
+
or project_root_domains
|
|
1822
|
+
)
|
|
1823
|
+
root_domains = handoff_root_domains or project_root_domains or _normalize_root_domains(
|
|
1824
|
+
prepared.get("root_domains") or []
|
|
1825
|
+
)
|
|
1826
|
+
if handoff_has_explicit_scope:
|
|
1827
|
+
resolution = dict(handoff_profile_resolution)
|
|
1828
|
+
resolution["discipline_ids"] = root_domains
|
|
1829
|
+
if root_domains:
|
|
1830
|
+
resolution["primary_discipline"] = root_domains[0]
|
|
1831
|
+
elif project_has_explicit_scope:
|
|
1832
|
+
resolution = dict(project_resolution)
|
|
1833
|
+
resolution["discipline_ids"] = project_root_domains
|
|
1834
|
+
if project_root_domains:
|
|
1835
|
+
resolution["primary_discipline"] = project_root_domains[0]
|
|
1836
|
+
elif not root_domains:
|
|
1837
|
+
draft_root_state = build_root_state(topic, prepared, IdeaState)
|
|
1838
|
+
resolution = self._classify_root_discipline_resolution(topic, draft_root_state)
|
|
1839
|
+
root_domains = _normalize_root_domains(resolution.get("discipline_ids") or [])
|
|
1840
|
+
else:
|
|
1841
|
+
resolution = resolve_discipline_taxonomy(
|
|
1842
|
+
root_domains[0],
|
|
1843
|
+
query="\n".join(
|
|
1844
|
+
[
|
|
1845
|
+
topic,
|
|
1846
|
+
str(prepared.get("mature_idea") or ""),
|
|
1847
|
+
str(prepared.get("paper_context") or ""),
|
|
1848
|
+
]
|
|
1849
|
+
),
|
|
1850
|
+
)
|
|
1851
|
+
resolution["discipline_ids"] = root_domains
|
|
1852
|
+
prepared["root_domains"] = root_domains
|
|
1853
|
+
prepared["discipline_resolution"] = resolution
|
|
1854
|
+
profile_resolution = dict(resolution)
|
|
1855
|
+
existing_intervention = prepared.get("scientific_intervention")
|
|
1856
|
+
if isinstance(existing_intervention, dict) and existing_intervention.get("profile_id"):
|
|
1857
|
+
profile_resolution.setdefault("profile_id", existing_intervention.get("profile_id"))
|
|
1858
|
+
profile = resolve_scientific_intervention_profile(
|
|
1859
|
+
profile_resolution,
|
|
1860
|
+
root_domains=root_domains,
|
|
1861
|
+
project_context=project_context,
|
|
1862
|
+
)
|
|
1863
|
+
prepared["scientific_intervention_profile"] = (
|
|
1864
|
+
profile.to_payload()
|
|
1865
|
+
if profile is not None
|
|
1866
|
+
else {"schema_version": "scientific_intervention_profile_v1", "status": "out_of_scope"}
|
|
1867
|
+
)
|
|
1868
|
+
self.scientific_intervention_profile = dict(prepared["scientific_intervention_profile"])
|
|
1869
|
+
gap_hypothesis_seeds = build_gap_hypothesis_seeds(
|
|
1870
|
+
self.survey_idea_handoff,
|
|
1871
|
+
gap_triage,
|
|
1872
|
+
profile_resolution=prepared["scientific_intervention_profile"],
|
|
1873
|
+
scope=prepared.get("scope") if isinstance(prepared.get("scope"), dict) else None,
|
|
1874
|
+
topic=topic,
|
|
1875
|
+
multimodal_evidence_projection=self.multimodal_evidence_projection,
|
|
1876
|
+
)
|
|
1877
|
+
gap_seed_status = build_gap_seed_status(gap_hypothesis_seeds)
|
|
1878
|
+
gap_seed_context = build_gap_seed_context(gap_hypothesis_seeds)
|
|
1879
|
+
prepared["gap_hypothesis_seeds"] = gap_hypothesis_seeds
|
|
1880
|
+
prepared["gap_seed_status"] = gap_seed_status
|
|
1881
|
+
prepared["gap_seed_context"] = gap_seed_context
|
|
1882
|
+
self.gap_hypothesis_seeds = gap_hypothesis_seeds
|
|
1883
|
+
self.gap_seed_status = gap_seed_status
|
|
1884
|
+
self.gap_seed_context = gap_seed_context
|
|
1885
|
+
return prepared
|
|
1886
|
+
|
|
1887
|
+
def search(self, topic: str, context: Dict[str, Any]) -> SearchResult:
|
|
1888
|
+
reset_search_state(self)
|
|
1889
|
+
context = self.prepare_root_context(topic, context)
|
|
1890
|
+
self.topic = topic
|
|
1891
|
+
self.analysis_blob = format_analysis_blob(context.get("analysis", []))
|
|
1892
|
+
self.paper_context = context.get("paper_context") or "No curated papers available yet."
|
|
1893
|
+
mature_value = context.get("mature_idea")
|
|
1894
|
+
if isinstance(mature_value, dict):
|
|
1895
|
+
self.mature_idea_record = normalize_mature_idea(mature_value)
|
|
1896
|
+
self.mature_idea = str(
|
|
1897
|
+
self.mature_idea_record.get("hypothesis")
|
|
1898
|
+
or self.mature_idea_record.get("abstract")
|
|
1899
|
+
or self.mature_idea_record.get("title")
|
|
1900
|
+
or ""
|
|
1901
|
+
).strip()
|
|
1902
|
+
else:
|
|
1903
|
+
self.mature_idea = str(mature_value or "").strip()
|
|
1904
|
+
self.seed_id = str(context.get("seed_id") or context.get("idea_id") or self.seed_id or "").strip()
|
|
1905
|
+
self.route_id = str(context.get("route_id") or self.route_id or "").strip()
|
|
1906
|
+
self.route_policy = (
|
|
1907
|
+
dict(context.get("route_policy"))
|
|
1908
|
+
if isinstance(context.get("route_policy"), dict)
|
|
1909
|
+
else {}
|
|
1910
|
+
)
|
|
1911
|
+
self.refinement_scope = (context.get("refinement_scope") or "").strip()
|
|
1912
|
+
self._mature_idea_components = list(context.get("components") or [])
|
|
1913
|
+
self._mature_idea_component_explanations = dict(context.get("component_explanations") or {})
|
|
1914
|
+
raw_iteration_budget = context.get("mcts_iteration_budget")
|
|
1915
|
+
try:
|
|
1916
|
+
effective_iterations = int(raw_iteration_budget)
|
|
1917
|
+
except (TypeError, ValueError):
|
|
1918
|
+
effective_iterations = self.config.max_iterations
|
|
1919
|
+
effective_iterations = max(0, min(self.config.max_iterations, effective_iterations))
|
|
1920
|
+
try:
|
|
1921
|
+
depth_multiplier = float(context.get("data_anchored_mcts_depth_multiplier", 1.0))
|
|
1922
|
+
except (TypeError, ValueError):
|
|
1923
|
+
depth_multiplier = 1.0
|
|
1924
|
+
effective_max_depth = max(
|
|
1925
|
+
1,
|
|
1926
|
+
int(math.ceil(self.config.max_depth * max(1.0, depth_multiplier))),
|
|
1927
|
+
)
|
|
1928
|
+
root_domains = context.get("root_domains") or []
|
|
1929
|
+
log_message(
|
|
1930
|
+
self.logger,
|
|
1931
|
+
self.log_sink,
|
|
1932
|
+
"info",
|
|
1933
|
+
"[MCTS] Root-domain classification: %s",
|
|
1934
|
+
_format_root_domains_for_prompt(root_domains),
|
|
1935
|
+
)
|
|
1936
|
+
|
|
1937
|
+
root_state = build_root_state(topic, context, IdeaState)
|
|
1938
|
+
root = new_node(
|
|
1939
|
+
root_state,
|
|
1940
|
+
depth=0,
|
|
1941
|
+
parent=None,
|
|
1942
|
+
signature_nodes=self.signature_nodes,
|
|
1943
|
+
id_iter=self._id_iter,
|
|
1944
|
+
idea_node_cls=IdeaNode,
|
|
1945
|
+
operator_application_cls=OperatorApplication,
|
|
1946
|
+
)
|
|
1947
|
+
experiences: List[Dict[str, Any]] = []
|
|
1948
|
+
|
|
1949
|
+
# Prime root defects via one evaluator pass before the first expand so
|
|
1950
|
+
# initial skill selection is not forced to rely on the placeholder
|
|
1951
|
+
# "unexplored_gap" defect tag.
|
|
1952
|
+
root_eval = self._simulate(root, [root], experiences)
|
|
1953
|
+
if root_eval and root_eval.detected_defects:
|
|
1954
|
+
inferred_defects = _dedupe_keep_order_strings(
|
|
1955
|
+
[str(tag) for tag in root_eval.detected_defects if str(tag).strip()]
|
|
1956
|
+
)
|
|
1957
|
+
if inferred_defects:
|
|
1958
|
+
previous_defects = list(root.state.target_defects)
|
|
1959
|
+
root.state.target_defects = _dedupe_keep_order_strings(
|
|
1960
|
+
[*previous_defects, *inferred_defects]
|
|
1961
|
+
)
|
|
1962
|
+
root.transformation.defects = list(root.state.target_defects)
|
|
1963
|
+
root.latest_path_summary = ""
|
|
1964
|
+
|
|
1965
|
+
def _record_rollout(iteration: int, target: IdeaNode, rollout_path: List[IdeaNode], evaluation: IdeaEvaluation) -> None:
|
|
1966
|
+
self._backpropagate(rollout_path, evaluation)
|
|
1967
|
+
self._update_skill_prior(target, evaluation)
|
|
1968
|
+
|
|
1969
|
+
rollout_summary = path_summary(rollout_path)
|
|
1970
|
+
transformation = target.transformation
|
|
1971
|
+
defects = list(transformation.defects) if transformation and transformation.defects else []
|
|
1972
|
+
operator = transformation.operator if transformation else "unknown"
|
|
1973
|
+
action_summary = f"{operator} -> {', '.join(defects) if defects else 'unspecified_defect'}"
|
|
1974
|
+
|
|
1975
|
+
self.trace.append(
|
|
1976
|
+
{
|
|
1977
|
+
"iteration": iteration,
|
|
1978
|
+
"node_id": target.node_id,
|
|
1979
|
+
"depth": len(rollout_path) - 1,
|
|
1980
|
+
"title": target.state.title,
|
|
1981
|
+
"operator": operator,
|
|
1982
|
+
"defects": defects,
|
|
1983
|
+
"memory_refs": list(transformation.memory_refs) if transformation else [],
|
|
1984
|
+
"rationale": transformation.rationale if transformation else "",
|
|
1985
|
+
"score": evaluation.composite,
|
|
1986
|
+
"visits": target.visits,
|
|
1987
|
+
"path": rollout_summary,
|
|
1988
|
+
"action_summary": action_summary,
|
|
1989
|
+
"evaluation": {**evaluation.to_dict(), "composite": evaluation.composite},
|
|
1990
|
+
"signature": target.state.signature,
|
|
1991
|
+
"edit_plan": target.state.edit_plan,
|
|
1992
|
+
"skill_metrics": target.state.skill_metrics,
|
|
1993
|
+
"data_anchored_coverage_branch": (
|
|
1994
|
+
dict(
|
|
1995
|
+
target.state.scientific_intervention.get(
|
|
1996
|
+
"data_anchored_coverage_branch"
|
|
1997
|
+
)
|
|
1998
|
+
or {}
|
|
1999
|
+
)
|
|
2000
|
+
if isinstance(target.state.scientific_intervention, dict)
|
|
2001
|
+
else {}
|
|
2002
|
+
),
|
|
2003
|
+
}
|
|
2004
|
+
)
|
|
2005
|
+
|
|
2006
|
+
for iteration in iter_with_progress(
|
|
2007
|
+
range(effective_iterations),
|
|
2008
|
+
description="MCTS search",
|
|
2009
|
+
total=effective_iterations,
|
|
2010
|
+
):
|
|
2011
|
+
leaf, path = self._select(root)
|
|
2012
|
+
depth = len(path) - 1
|
|
2013
|
+
rollout_targets: List[Tuple[IdeaNode, List[IdeaNode]]] = []
|
|
2014
|
+
|
|
2015
|
+
if depth >= effective_max_depth:
|
|
2016
|
+
rollout_targets = [(leaf, path)]
|
|
2017
|
+
else:
|
|
2018
|
+
target, rollout_path = self._expand(leaf, path)
|
|
2019
|
+
if depth <= 1:
|
|
2020
|
+
rollout_targets = [
|
|
2021
|
+
(child, path + [child])
|
|
2022
|
+
for child in leaf.children
|
|
2023
|
+
if child.visits == 0
|
|
2024
|
+
]
|
|
2025
|
+
elif target is not None:
|
|
2026
|
+
rollout_targets = [(target, rollout_path)]
|
|
2027
|
+
|
|
2028
|
+
for target, rollout_path in rollout_targets:
|
|
2029
|
+
evaluation = self._simulate(target, rollout_path, experiences)
|
|
2030
|
+
if evaluation is None:
|
|
2031
|
+
continue
|
|
2032
|
+
_record_rollout(iteration, target, rollout_path, evaluation)
|
|
2033
|
+
|
|
2034
|
+
best = best_candidate(root, SearchCandidate)
|
|
2035
|
+
pareto = pareto_candidates(root, SearchCandidate)
|
|
2036
|
+
cache_entries = sum(len(entries) for entries in self.evaluation_cache.values())
|
|
2037
|
+
|
|
2038
|
+
# Persist injected symbolic priors to disk so they survive across runs
|
|
2039
|
+
if self.enable_symbolic_memory and self.persist_symbolic_memory:
|
|
2040
|
+
try:
|
|
2041
|
+
self.symbolic_memory_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2042
|
+
self.symbolic_memory.save(str(self.symbolic_memory_path))
|
|
2043
|
+
|
|
2044
|
+
except Exception as exc:
|
|
2045
|
+
log_message(
|
|
2046
|
+
self.logger,
|
|
2047
|
+
self.log_sink,
|
|
2048
|
+
"warning",
|
|
2049
|
+
"⚠️ Failed to save symbolic memory: %s",
|
|
2050
|
+
exc,
|
|
2051
|
+
)
|
|
2052
|
+
|
|
2053
|
+
return SearchResult(
|
|
2054
|
+
best=best,
|
|
2055
|
+
pareto=pareto,
|
|
2056
|
+
trace=self.trace,
|
|
2057
|
+
cache_size=cache_entries,
|
|
2058
|
+
experiences=experiences,
|
|
2059
|
+
retrieved_core_titles=list(self.retrieved_core_titles),
|
|
2060
|
+
)
|