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,3000 @@
|
|
|
1
|
+
"""Runtime models and helper functions for memory-guided MCTS idea search."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import itertools
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import random
|
|
10
|
+
import re
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
from dataclasses import dataclass, field, replace
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Mapping, Optional, Sequence, Set, Tuple
|
|
16
|
+
from src.agents.idea_agent.utils.core.json_utils import (
|
|
17
|
+
pretty_json,
|
|
18
|
+
read_json_file,
|
|
19
|
+
)
|
|
20
|
+
from src.agents.idea_agent.utils.core.response_parsing import parse_json_response
|
|
21
|
+
from src.agents.idea_agent.utils.mcts.defect_registry import (
|
|
22
|
+
format_defect_registry,
|
|
23
|
+
profile_skill_defect_tags,
|
|
24
|
+
)
|
|
25
|
+
from src.agents.idea_agent.utils.mcts.idea_taste_presets import IdeaTastePreset
|
|
26
|
+
from src.agents.idea_agent.utils.mcts.skill_parsing import (
|
|
27
|
+
parse_blueprint_step,
|
|
28
|
+
parse_markdown_sections,
|
|
29
|
+
split_frontmatter,
|
|
30
|
+
)
|
|
31
|
+
from src.agents.idea_agent.utils.mcts.scientific_intervention_ontology import (
|
|
32
|
+
build_scientific_intervention_payload,
|
|
33
|
+
detect_profile_drift,
|
|
34
|
+
format_scientific_intervention_profile_for_prompt,
|
|
35
|
+
get_scientific_intervention_profile,
|
|
36
|
+
get_scientific_object_schema,
|
|
37
|
+
resolve_scientific_intervention_profile,
|
|
38
|
+
)
|
|
39
|
+
from src.agents.idea_agent.utils.mcts.scientific_rubric import (
|
|
40
|
+
SCIENTIFIC_INTERVENTION_PROFILE_VERSION,
|
|
41
|
+
SCIENTIFIC_RUBRIC_VERSION,
|
|
42
|
+
PROFILE_NOVELTY_AXES,
|
|
43
|
+
format_scientific_rubric_for_prompt,
|
|
44
|
+
profile_score_weights,
|
|
45
|
+
)
|
|
46
|
+
from src.memory.memory_system.component_taxonomy import extract_component_families
|
|
47
|
+
from src.agents.idea_agent.utils.mcts.mcts_helpers import (
|
|
48
|
+
_format_root_domains_for_prompt,
|
|
49
|
+
_clean_component_explanation,
|
|
50
|
+
_coerce_component_name,
|
|
51
|
+
_dedupe_keep_order_strings,
|
|
52
|
+
_filter_component_mapping_to_plan_keys,
|
|
53
|
+
_normalize_component_mapping,
|
|
54
|
+
build_structural_profile,
|
|
55
|
+
clip_text,
|
|
56
|
+
component_inventory_payload,
|
|
57
|
+
normalize_component_explanations,
|
|
58
|
+
parse_component_bundle_payload,
|
|
59
|
+
plan_to_experiment_text,
|
|
60
|
+
plan_to_method_text,
|
|
61
|
+
)
|
|
62
|
+
from src.agents.idea_agent.utils.prompting.prompt_views import (
|
|
63
|
+
format_evaluator_edit_plan_prompt_view,
|
|
64
|
+
format_evaluator_idea_prompt_view,
|
|
65
|
+
)
|
|
66
|
+
from src.agents.idea_agent.utils.workflow.idea_contract import (
|
|
67
|
+
normalize_idea_contract,
|
|
68
|
+
normalize_mature_idea,
|
|
69
|
+
)
|
|
70
|
+
from src.agents.idea_agent.utils.workflow.multimodal_data_anchoring import (
|
|
71
|
+
DATA_ANCHORED_PRIORITY,
|
|
72
|
+
is_data_anchored,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
HYPOTHESIS_CONTRACT_FIELDS = (
|
|
77
|
+
"direction_mode",
|
|
78
|
+
"direction_summary",
|
|
79
|
+
"central_hypothesis",
|
|
80
|
+
"scientific_object",
|
|
81
|
+
"mechanism_or_relation",
|
|
82
|
+
"intervention_or_transformation",
|
|
83
|
+
"expected_mechanism",
|
|
84
|
+
"discriminating_observation",
|
|
85
|
+
"boundary_or_failure_condition",
|
|
86
|
+
"claim_scope",
|
|
87
|
+
"assumptions",
|
|
88
|
+
"target_gap_ids",
|
|
89
|
+
"gap_alignment",
|
|
90
|
+
"evidence_requirement",
|
|
91
|
+
"evidence_basis",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
_ROUTE_REQUIRED_CONTRACT_FIELDS = {
|
|
95
|
+
"premise_inversion": ("central_hypothesis",),
|
|
96
|
+
"object_substitution": ("scientific_object",),
|
|
97
|
+
"mechanism_replacement": ("mechanism_or_relation", "expected_mechanism"),
|
|
98
|
+
"representation_shift": ("intervention_or_transformation",),
|
|
99
|
+
"verification_reversal": ("discriminating_observation",),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AtomicEditOp(str, Enum):
|
|
104
|
+
ADD_COMPONENT = "ADD_COMPONENT"
|
|
105
|
+
REMOVE_COMPONENT = "REMOVE_COMPONENT"
|
|
106
|
+
REPLACE_COMPONENT = "REPLACE_COMPONENT"
|
|
107
|
+
REWIRE = "REWIRE"
|
|
108
|
+
ADD_PROTOCOL = "ADD_PROTOCOL"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Descriptions for each atomic edit operation, used in prompt formatting and skill documentation.
|
|
112
|
+
ATOMIC_OP_DESCRIPTIONS: Dict[str, str] = {
|
|
113
|
+
AtomicEditOp.ADD_COMPONENT: "Introduce a new module or sub-module into the architecture. ",
|
|
114
|
+
AtomicEditOp.REMOVE_COMPONENT: "Delete an existing module from the architecture. ",
|
|
115
|
+
AtomicEditOp.REPLACE_COMPONENT: "Swap an existing module with a new implementation. ",
|
|
116
|
+
AtomicEditOp.REWIRE: "Change how two components are connected (data flow, gradient path, or API coupling). ",
|
|
117
|
+
AtomicEditOp.ADD_PROTOCOL: "Attach a validation protocol (regression, ablation, or stress test) to the plan. "
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def format_op_descriptions(profile_id: Optional[str] = None) -> str:
|
|
122
|
+
"""Return a human-readable reference block describing every atomic edit operation."""
|
|
123
|
+
if profile_id and str(profile_id).strip().lower() != "computational_algorithmic":
|
|
124
|
+
return "\n".join(
|
|
125
|
+
[
|
|
126
|
+
"Scientific transformation reference (use the selected profile's native objects directly):",
|
|
127
|
+
" - ADD_COMPONENT: introduce one object, condition, mechanism, relation, or observation named by the profile.",
|
|
128
|
+
" - REMOVE_COMPONENT: remove one unsupported or redundant object, assumption, process, or condition.",
|
|
129
|
+
" - REPLACE_COMPONENT: replace one weak object, assumption, process, or mechanism with a refined one.",
|
|
130
|
+
" - REWIRE: change a causal relation, derivation, observation mapping, or process relation.",
|
|
131
|
+
" - ADD_PROTOCOL: attach the evidence, measurement, proof, comparator, or boundary check required by the profile.",
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
lines = ["Atomic edit operation reference:"]
|
|
135
|
+
for op in AtomicEditOp:
|
|
136
|
+
desc = ATOMIC_OP_DESCRIPTIONS.get(op, "No description.")
|
|
137
|
+
lines.append(f" - {op.value}: {desc}")
|
|
138
|
+
return "\n".join(lines)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def render_profile_evaluation_prompt(prompt: str, profile_id: str) -> str:
|
|
142
|
+
"""Remove computational evaluation priors from non-computational prompts."""
|
|
143
|
+
|
|
144
|
+
if str(profile_id or "").strip().lower() == "computational_algorithmic":
|
|
145
|
+
return prompt
|
|
146
|
+
replacements = {
|
|
147
|
+
"- Evaluate the candidate using the scientific intervention profile and rubric above. The native contribution may be a material manipulation, biological or clinical causal intervention, environmental condition, engineering process, formal assumption/derivation/counterexample, measurement construct, or algorithmic mechanism.": "- Evaluate the candidate using the scientific intervention profile and rubric above. The native contribution must use the selected profile's object, process, mechanism, relation, observation, intervention, or formal claim.",
|
|
148
|
+
"- Do not treat training signal, loss, backbone, benchmark, or learned model components as universal requirements. For non-computational profiles, judge innovation through the profile's native mechanism, object, observation, comparator, proof, or boundary conditions; missing training machinery is not an innovation defect.": "- Judge innovation through the selected profile's native object, mechanism, relation, observation, comparator, proof, intervention, or boundary conditions.",
|
|
149
|
+
"- If the mature idea above is training-free or inference-time only, penalize candidates that add new training stages, learned controllers, auxiliary losses, or fine-tuning loops without a strong mechanism-level justification. Such drift should usually reduce alignment_score and increase complexity_penalty.": "- Penalize candidates that add unrelated optimization, control, or wrapper machinery without a strong profile-native mechanism justification.",
|
|
150
|
+
"generator, objective, representation, planner, or data path": "scientific mechanism, intervention, relation, observation, or process",
|
|
151
|
+
"- Do not speculate about compute/resource budgeting unless the candidate explicitly makes resource management part of its core mechanism.": "- Do not speculate about implementation resources unless the candidate explicitly makes resource constraints part of its core scientific claim.",
|
|
152
|
+
}
|
|
153
|
+
for source, target in replacements.items():
|
|
154
|
+
prompt = prompt.replace(source, target)
|
|
155
|
+
return prompt
|
|
156
|
+
|
|
157
|
+
def _synthesize_component_explanation_from_edit(component: str, edit: Optional["ComponentEdit"]) -> str:
|
|
158
|
+
if edit is None:
|
|
159
|
+
return _clean_component_explanation("", fallback_component=component)
|
|
160
|
+
|
|
161
|
+
if edit.reason:
|
|
162
|
+
return _clean_component_explanation(edit.reason, fallback_component=component)
|
|
163
|
+
if edit.op == AtomicEditOp.REPLACE_COMPONENT and edit.target:
|
|
164
|
+
return _clean_component_explanation(
|
|
165
|
+
f"Replaces {edit.target} with a stronger implementation for the updated idea.",
|
|
166
|
+
fallback_component=component,
|
|
167
|
+
)
|
|
168
|
+
if edit.op == AtomicEditOp.ADD_COMPONENT:
|
|
169
|
+
return _clean_component_explanation(
|
|
170
|
+
"Adds a targeted capability that was missing in the parent idea.",
|
|
171
|
+
fallback_component=component,
|
|
172
|
+
)
|
|
173
|
+
if edit.details:
|
|
174
|
+
return _clean_component_explanation(edit.details, fallback_component=component)
|
|
175
|
+
return _clean_component_explanation("", fallback_component=component)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def build_child_component_explanations(
|
|
179
|
+
parent_state: Any,
|
|
180
|
+
new_components: Sequence[str],
|
|
181
|
+
plan: "EditPlan",
|
|
182
|
+
instantiated: Optional[Dict[str, Any]] = None,
|
|
183
|
+
) -> Dict[str, str]:
|
|
184
|
+
parent_lookup = normalize_component_explanations(
|
|
185
|
+
getattr(parent_state, "components", []),
|
|
186
|
+
getattr(parent_state, "component_explanations", {}),
|
|
187
|
+
)
|
|
188
|
+
payload_lookup = normalize_component_explanations(
|
|
189
|
+
list((instantiated or {}).get("component_role_explanations", {}).keys())
|
|
190
|
+
if isinstance((instantiated or {}).get("component_role_explanations"), dict)
|
|
191
|
+
else [],
|
|
192
|
+
(instantiated or {}).get("component_role_explanations", {}),
|
|
193
|
+
)
|
|
194
|
+
edit_lookup: Dict[str, ComponentEdit] = {}
|
|
195
|
+
for edit in getattr(plan, "component_edits", []) or []:
|
|
196
|
+
name = _coerce_component_name(getattr(edit, "component", ""))
|
|
197
|
+
if name:
|
|
198
|
+
edit_lookup[name] = edit
|
|
199
|
+
|
|
200
|
+
explanations: Dict[str, str] = {}
|
|
201
|
+
for component in new_components:
|
|
202
|
+
name = str(component).strip()
|
|
203
|
+
if not name:
|
|
204
|
+
continue
|
|
205
|
+
if name in parent_lookup:
|
|
206
|
+
explanations[name] = parent_lookup[name]
|
|
207
|
+
continue
|
|
208
|
+
if name in payload_lookup:
|
|
209
|
+
explanations[name] = payload_lookup[name]
|
|
210
|
+
continue
|
|
211
|
+
explanations[name] = _synthesize_component_explanation_from_edit(
|
|
212
|
+
name,
|
|
213
|
+
edit_lookup.get(name),
|
|
214
|
+
)
|
|
215
|
+
return explanations
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass
|
|
219
|
+
class ComponentEdit:
|
|
220
|
+
op: AtomicEditOp
|
|
221
|
+
component: str
|
|
222
|
+
target: str = ""
|
|
223
|
+
condition: str = ""
|
|
224
|
+
details: str = ""
|
|
225
|
+
reason: str = ""
|
|
226
|
+
|
|
227
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
228
|
+
return {
|
|
229
|
+
"op": self.op.value,
|
|
230
|
+
"component": _coerce_component_name(self.component),
|
|
231
|
+
"target": _coerce_component_name(self.target),
|
|
232
|
+
"condition": self.condition,
|
|
233
|
+
"details": self.details,
|
|
234
|
+
"reason": self.reason,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class ValidationProtocol:
|
|
240
|
+
regression_tests: List[str] = field(default_factory=list)
|
|
241
|
+
ablation_tests: List[str] = field(default_factory=list)
|
|
242
|
+
stress_tests: List[str] = field(default_factory=list)
|
|
243
|
+
|
|
244
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
245
|
+
return {
|
|
246
|
+
"regression_tests": self.regression_tests,
|
|
247
|
+
"ablation_tests": self.ablation_tests,
|
|
248
|
+
"stress_tests": self.stress_tests,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@dataclass
|
|
253
|
+
class EditPlan:
|
|
254
|
+
skill_name: str
|
|
255
|
+
objective: str
|
|
256
|
+
target_defects: List[str]
|
|
257
|
+
component_edits: List[ComponentEdit]
|
|
258
|
+
validation: ValidationProtocol
|
|
259
|
+
guardrails: List[str]
|
|
260
|
+
memory_refs: List[str]
|
|
261
|
+
compile_notes: str
|
|
262
|
+
profile_id: str = ""
|
|
263
|
+
profile_rendering_id: str = ""
|
|
264
|
+
preferred_operations: List[str] = field(default_factory=list)
|
|
265
|
+
scientific_object_schema: Dict[str, Any] = field(default_factory=dict)
|
|
266
|
+
|
|
267
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
268
|
+
return {
|
|
269
|
+
"skill_name": self.skill_name,
|
|
270
|
+
"objective": self.objective,
|
|
271
|
+
"target_defects": self.target_defects,
|
|
272
|
+
"component_edits": [edit.to_dict() for edit in self.component_edits],
|
|
273
|
+
"validation": self.validation.to_dict(),
|
|
274
|
+
"guardrails": self.guardrails,
|
|
275
|
+
"memory_refs": self.memory_refs,
|
|
276
|
+
"compile_notes": self.compile_notes,
|
|
277
|
+
"profile_id": self.profile_id,
|
|
278
|
+
"profile_rendering_id": self.profile_rendering_id,
|
|
279
|
+
"preferred_operations": self.preferred_operations,
|
|
280
|
+
"scientific_object_schema": self.scientific_object_schema,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass
|
|
285
|
+
class EditOperatorSkill:
|
|
286
|
+
name: str
|
|
287
|
+
description: str
|
|
288
|
+
structural_mode: str
|
|
289
|
+
scope_preference: str
|
|
290
|
+
requires_control_centered_parent: bool
|
|
291
|
+
defects: List[str] = field(default_factory=list)
|
|
292
|
+
guardrails: List[str] = field(default_factory=list)
|
|
293
|
+
atomic_blueprint: List[str] = field(default_factory=list)
|
|
294
|
+
required_protocols: List[str] = field(default_factory=list)
|
|
295
|
+
avoid_combinations: List[str] = field(default_factory=list)
|
|
296
|
+
execution_logic: List[str] = field(default_factory=list)
|
|
297
|
+
source_path: str = ""
|
|
298
|
+
profile_renderings: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
|
299
|
+
rendered_profile_id: str = ""
|
|
300
|
+
allowed_object_types: List[str] = field(default_factory=list)
|
|
301
|
+
preferred_operations: List[str] = field(default_factory=list)
|
|
302
|
+
profile_rendering_id: str = ""
|
|
303
|
+
|
|
304
|
+
def to_prompt_line(self) -> str:
|
|
305
|
+
defects = ", ".join(self.defects) if self.defects else "unspecified"
|
|
306
|
+
blueprint = ", ".join(self.atomic_blueprint) if self.atomic_blueprint else "none"
|
|
307
|
+
guardrails = ", ".join(self.guardrails) if self.guardrails else "none"
|
|
308
|
+
exec_logic = " | ".join(self.execution_logic) if self.execution_logic else "none"
|
|
309
|
+
profile = f" | profile={self.rendered_profile_id}" if self.rendered_profile_id else ""
|
|
310
|
+
native_ops = (
|
|
311
|
+
f" | native_operations={','.join(self.preferred_operations)}"
|
|
312
|
+
if self.preferred_operations
|
|
313
|
+
else ""
|
|
314
|
+
)
|
|
315
|
+
native_objects = (
|
|
316
|
+
f" | native_objects={','.join(self.allowed_object_types)}"
|
|
317
|
+
if self.allowed_object_types
|
|
318
|
+
else ""
|
|
319
|
+
)
|
|
320
|
+
return (
|
|
321
|
+
f"- {self.name}: {self.description} | defects={defects} "
|
|
322
|
+
f"| blueprint={blueprint} | guardrails={guardrails} "
|
|
323
|
+
f"| execution_logic={exec_logic}{profile}{native_ops}{native_objects}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass
|
|
328
|
+
class SkillUsagePrior:
|
|
329
|
+
attempts: int = 0
|
|
330
|
+
successes: int = 0
|
|
331
|
+
reward_ema: float = 0.5
|
|
332
|
+
prior: float = 0.5
|
|
333
|
+
rule_constraints: List[str] = field(default_factory=list)
|
|
334
|
+
|
|
335
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
336
|
+
return {
|
|
337
|
+
"attempts": self.attempts,
|
|
338
|
+
"successes": self.successes,
|
|
339
|
+
"reward_ema": self.reward_ema,
|
|
340
|
+
"prior": self.prior,
|
|
341
|
+
"rule_constraints": self.rule_constraints,
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclass
|
|
346
|
+
class SkillSelectionCandidate:
|
|
347
|
+
skill: EditOperatorSkill
|
|
348
|
+
defect_score: float
|
|
349
|
+
prior_score: float
|
|
350
|
+
preset_bias: float
|
|
351
|
+
structure_fit: float
|
|
352
|
+
structure_reason: str
|
|
353
|
+
selection_total: float
|
|
354
|
+
attempts: int = 0
|
|
355
|
+
|
|
356
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
357
|
+
return {
|
|
358
|
+
"skill_name": self.skill.name,
|
|
359
|
+
"defect_score": self.defect_score,
|
|
360
|
+
"prior_score": self.prior_score,
|
|
361
|
+
"preset_bias": self.preset_bias,
|
|
362
|
+
"structure_fit": self.structure_fit,
|
|
363
|
+
"structure_reason": self.structure_reason,
|
|
364
|
+
"selection_total": self.selection_total,
|
|
365
|
+
"attempts": self.attempts,
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@dataclass
|
|
370
|
+
class MemorySnippet:
|
|
371
|
+
identifier: str
|
|
372
|
+
title: str
|
|
373
|
+
detail: str
|
|
374
|
+
tags: List[str] = field(default_factory=list)
|
|
375
|
+
|
|
376
|
+
def to_prompt_line(self) -> str:
|
|
377
|
+
tags_str = f" tags={','.join(self.tags)}" if self.tags else ""
|
|
378
|
+
return f"[{self.identifier}] {self.title}{tags_str}: {self.detail}"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@dataclass
|
|
382
|
+
class MemoryBundle:
|
|
383
|
+
field_knowledge: List[MemorySnippet] = field(default_factory=list)
|
|
384
|
+
anti_patterns: List[MemorySnippet] = field(default_factory=list)
|
|
385
|
+
fix_recipes: List[MemorySnippet] = field(default_factory=list)
|
|
386
|
+
|
|
387
|
+
def to_prompt_block(self) -> str:
|
|
388
|
+
sections: List[str] = []
|
|
389
|
+
if self.field_knowledge:
|
|
390
|
+
sections.append("== Field Knowledge ==")
|
|
391
|
+
sections.extend(snippet.to_prompt_line() for snippet in self.field_knowledge)
|
|
392
|
+
if self.anti_patterns:
|
|
393
|
+
sections.append("== Anti-patterns ==")
|
|
394
|
+
sections.extend(snippet.to_prompt_line() for snippet in self.anti_patterns)
|
|
395
|
+
if self.fix_recipes:
|
|
396
|
+
sections.append("== Fix Recipes ==")
|
|
397
|
+
sections.extend(snippet.to_prompt_line() for snippet in self.fix_recipes)
|
|
398
|
+
if not sections:
|
|
399
|
+
return "No validated memory snippets matched. Rely on analysis context only."
|
|
400
|
+
return "\n".join(sections)
|
|
401
|
+
|
|
402
|
+
def referenced_ids(self) -> List[str]:
|
|
403
|
+
ids: List[str] = []
|
|
404
|
+
for bank in (self.field_knowledge, self.anti_patterns, self.fix_recipes):
|
|
405
|
+
ids.extend(snippet.identifier for snippet in bank)
|
|
406
|
+
return ids
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
DEFAULT_SKILL_TEMPLATES_PATH = (
|
|
410
|
+
Path(__file__).resolve().parents[2]
|
|
411
|
+
/ "agent"
|
|
412
|
+
/ "skills"
|
|
413
|
+
/ "DEFAULT_SKILL_TEMPLATES.json"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _load_default_skill_templates(path: Path) -> Dict[str, Dict[str, Any]]:
|
|
418
|
+
if not path.exists():
|
|
419
|
+
return {}
|
|
420
|
+
payload = read_json_file(path)
|
|
421
|
+
if not isinstance(payload, dict):
|
|
422
|
+
return {}
|
|
423
|
+
normalized: Dict[str, Dict[str, Any]] = {}
|
|
424
|
+
for key, value in payload.items():
|
|
425
|
+
name = str(key).strip()
|
|
426
|
+
if not name or not isinstance(value, dict):
|
|
427
|
+
continue
|
|
428
|
+
normalized[name] = value
|
|
429
|
+
return normalized
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
DEFAULT_SKILL_TEMPLATES: Dict[str, Dict[str, Any]] = _load_default_skill_templates(
|
|
433
|
+
DEFAULT_SKILL_TEMPLATES_PATH
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
ANTI_PATTERN_CONSTRAINTS: List[str] = [
|
|
437
|
+
"No feature dumping: every component edit must map to a measured defect.",
|
|
438
|
+
"Use the lightest validation suite that can falsify the core mechanism; do not let protocol bulk replace mechanism work.",
|
|
439
|
+
"Prefer mechanism clarity over loosely coupled add-ons.",
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
_PROFILE_SKILL_TOKEN_MAPS: Dict[str, Dict[str, str]] = {
|
|
444
|
+
"physical_materials_chemical": {
|
|
445
|
+
"weak_internal_component": "weak_process_or_mechanism",
|
|
446
|
+
"refined_internal_component": "refined_process_or_mechanism",
|
|
447
|
+
"alternative_path_module": "competing_mechanism_or_phase",
|
|
448
|
+
"failure_regime_interface": "mechanism_discriminating_observation",
|
|
449
|
+
"weak_block": "weak_process_or_composition",
|
|
450
|
+
"modular_block": "localized_process_or_mechanism",
|
|
451
|
+
"downstream_interface": "property_or_performance_endpoint",
|
|
452
|
+
"scale_consistency_module": "structure_process_property_link",
|
|
453
|
+
"scale_interface": "cross_scale_characterization",
|
|
454
|
+
"flat_pipeline": "uncalibrated_process_structure_relation",
|
|
455
|
+
"hierarchical_pipeline": "structure_process_property_decomposition",
|
|
456
|
+
"execution_path": "process_observation_path",
|
|
457
|
+
"feedback_monitor": "characterization_feedback",
|
|
458
|
+
"adaptation_rule": "process_condition_update",
|
|
459
|
+
"theory_transfer_module": "transferred_material_mechanism",
|
|
460
|
+
"core_objective": "target_property_or_mechanism",
|
|
461
|
+
"speculative_executor": "exploratory_process_condition",
|
|
462
|
+
"repair_handler": "recovery_or_reprocessing_condition",
|
|
463
|
+
},
|
|
464
|
+
"life_molecular_mechanistic": {
|
|
465
|
+
"weak_internal_component": "weak_pathway_or_assay",
|
|
466
|
+
"refined_internal_component": "refined_mediator_or_perturbation",
|
|
467
|
+
"alternative_path_module": "competing_biological_pathway",
|
|
468
|
+
"failure_regime_interface": "mechanism_discriminating_assay",
|
|
469
|
+
"weak_block": "weak_intervention_or_mediator",
|
|
470
|
+
"modular_block": "localized_perturbation_pathway",
|
|
471
|
+
"downstream_interface": "phenotype_or_endpoint",
|
|
472
|
+
"scale_consistency_module": "mediator_phenotype_link",
|
|
473
|
+
"scale_interface": "multi_level_assay",
|
|
474
|
+
"flat_pipeline": "unresolved_perturbation_mediator_relation",
|
|
475
|
+
"hierarchical_pipeline": "system_pathway_phenotype_decomposition",
|
|
476
|
+
"execution_path": "perturbation_observation_path",
|
|
477
|
+
"feedback_monitor": "assay_response_observation",
|
|
478
|
+
"adaptation_rule": "dose_or_condition_update",
|
|
479
|
+
"theory_transfer_module": "transferred_biological_mechanism",
|
|
480
|
+
"core_objective": "target_phenotype_or_pathway",
|
|
481
|
+
"speculative_executor": "exploratory_perturbation",
|
|
482
|
+
"repair_handler": "recovery_or_control_condition",
|
|
483
|
+
},
|
|
484
|
+
"clinical_health": {
|
|
485
|
+
"weak_internal_component": "weak_intervention_or_mediator",
|
|
486
|
+
"refined_internal_component": "refined_intervention_or_mediator",
|
|
487
|
+
"alternative_path_module": "competing_causal_explanation",
|
|
488
|
+
"failure_regime_interface": "comparator_or_counterfactual",
|
|
489
|
+
"weak_block": "weak_intervention_or_measurement",
|
|
490
|
+
"modular_block": "targeted_intervention_or_mediator",
|
|
491
|
+
"downstream_interface": "clinical_endpoint",
|
|
492
|
+
"scale_consistency_module": "population_endpoint_mediation_link",
|
|
493
|
+
"scale_interface": "cohort_subgroup_boundary",
|
|
494
|
+
"flat_pipeline": "unresolved_intervention_outcome_relation",
|
|
495
|
+
"hierarchical_pipeline": "population_intervention_mediator_endpoint_decomposition",
|
|
496
|
+
"execution_path": "intervention_measurement_path",
|
|
497
|
+
"feedback_monitor": "outcome_measurement",
|
|
498
|
+
"adaptation_rule": "care_or_intervention_update",
|
|
499
|
+
"theory_transfer_module": "transferred_causal_mechanism",
|
|
500
|
+
"core_objective": "clinical_endpoint_or_utility",
|
|
501
|
+
"speculative_executor": "pilot_intervention_condition",
|
|
502
|
+
"repair_handler": "safety_or_escalation_condition",
|
|
503
|
+
},
|
|
504
|
+
"earth_environment_agro": {
|
|
505
|
+
"weak_internal_component": "weak_forcing_or_process",
|
|
506
|
+
"refined_internal_component": "refined_forcing_or_process",
|
|
507
|
+
"alternative_path_module": "competing_regime_or_process",
|
|
508
|
+
"failure_regime_interface": "spatiotemporal_regime_boundary",
|
|
509
|
+
"weak_block": "weak_observation_or_attribution",
|
|
510
|
+
"modular_block": "localized_process_or_observation",
|
|
511
|
+
"downstream_interface": "response_or_endpoint",
|
|
512
|
+
"scale_consistency_module": "spatiotemporal_scale_link",
|
|
513
|
+
"scale_interface": "observation_scale_alignment",
|
|
514
|
+
"flat_pipeline": "unresolved_forcing_process_response_relation",
|
|
515
|
+
"hierarchical_pipeline": "forcing_process_response_decomposition",
|
|
516
|
+
"execution_path": "forcing_observation_path",
|
|
517
|
+
"feedback_monitor": "observation_response_update",
|
|
518
|
+
"adaptation_rule": "scenario_or_management_update",
|
|
519
|
+
"theory_transfer_module": "transferred_earth_system_mechanism",
|
|
520
|
+
"core_objective": "response_or_attribution_claim",
|
|
521
|
+
"speculative_executor": "exploratory_scenario",
|
|
522
|
+
"repair_handler": "observation_or_scenario_recovery",
|
|
523
|
+
},
|
|
524
|
+
"energy_engineering_systems": {
|
|
525
|
+
"weak_internal_component": "weak_design_or_mechanism",
|
|
526
|
+
"refined_internal_component": "refined_design_or_mechanism",
|
|
527
|
+
"alternative_path_module": "alternate_operating_or_failure_regime",
|
|
528
|
+
"failure_regime_interface": "operating_state_boundary",
|
|
529
|
+
"weak_block": "weak_design_or_control_condition",
|
|
530
|
+
"modular_block": "localized_design_or_process",
|
|
531
|
+
"downstream_interface": "performance_or_safety_readout",
|
|
532
|
+
"scale_consistency_module": "component_system_link",
|
|
533
|
+
"scale_interface": "system_constraint_boundary",
|
|
534
|
+
"flat_pipeline": "uncalibrated_design_mechanism_relation",
|
|
535
|
+
"hierarchical_pipeline": "component_process_system_decomposition",
|
|
536
|
+
"execution_path": "operating_state_path",
|
|
537
|
+
"feedback_monitor": "sensor_or_performance_readout",
|
|
538
|
+
"adaptation_rule": "operating_condition_update",
|
|
539
|
+
"theory_transfer_module": "transferred_engineering_mechanism",
|
|
540
|
+
"core_objective": "design_or_safety_claim",
|
|
541
|
+
"speculative_executor": "recoverable_operating_condition",
|
|
542
|
+
"repair_handler": "fault_recovery_condition",
|
|
543
|
+
},
|
|
544
|
+
"formal_theoretical": {
|
|
545
|
+
"weak_internal_component": "weak_assumption_or_derivation",
|
|
546
|
+
"refined_internal_component": "refined_assumption_or_derivation",
|
|
547
|
+
"alternative_path_module": "counterexample_or_alternative_derivation",
|
|
548
|
+
"failure_regime_interface": "validity_domain_boundary",
|
|
549
|
+
"weak_block": "weak_proof_obligation",
|
|
550
|
+
"modular_block": "localized_proof_obligation",
|
|
551
|
+
"downstream_interface": "target_relation",
|
|
552
|
+
"scale_consistency_module": "multi_level_formal_relation",
|
|
553
|
+
"scale_interface": "validity_domain",
|
|
554
|
+
"flat_pipeline": "unresolved_assumption_derivation_relation",
|
|
555
|
+
"hierarchical_pipeline": "object_assumption_relation_decomposition",
|
|
556
|
+
"execution_path": "derivation_or_construction_path",
|
|
557
|
+
"feedback_monitor": "counterexample_check",
|
|
558
|
+
"adaptation_rule": "conjecture_refinement",
|
|
559
|
+
"theory_transfer_module": "transferred_formal_invariant",
|
|
560
|
+
"core_objective": "target_proposition_or_relation",
|
|
561
|
+
"speculative_executor": "candidate_construction",
|
|
562
|
+
"repair_handler": "counterexample_repair_obligation",
|
|
563
|
+
},
|
|
564
|
+
"generic_scientific": {
|
|
565
|
+
"weak_internal_component": "weak_candidate_mechanism",
|
|
566
|
+
"refined_internal_component": "refined_candidate_mechanism",
|
|
567
|
+
"alternative_path_module": "competing_explanation",
|
|
568
|
+
"failure_regime_interface": "boundary_condition",
|
|
569
|
+
"weak_block": "weak_manipulable_condition",
|
|
570
|
+
"modular_block": "localized_scientific_intervention",
|
|
571
|
+
"downstream_interface": "observable_or_endpoint",
|
|
572
|
+
"scale_consistency_module": "cross_scale_relation",
|
|
573
|
+
"scale_interface": "validity_domain",
|
|
574
|
+
"flat_pipeline": "unresolved_mechanism_relation",
|
|
575
|
+
"hierarchical_pipeline": "object_mechanism_observation_decomposition",
|
|
576
|
+
"execution_path": "intervention_observation_path",
|
|
577
|
+
"feedback_monitor": "measurement_observation",
|
|
578
|
+
"adaptation_rule": "condition_update",
|
|
579
|
+
"theory_transfer_module": "transferred_scientific_mechanism",
|
|
580
|
+
"core_objective": "target_scientific_relation",
|
|
581
|
+
"speculative_executor": "exploratory_intervention",
|
|
582
|
+
"repair_handler": "recovery_condition",
|
|
583
|
+
},
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
_PROFILE_SKILL_DESCRIPTION_PREFIXES: Dict[str, str] = {
|
|
588
|
+
"physical_materials_chemical": "materials/process-native",
|
|
589
|
+
"life_molecular_mechanistic": "biological/mechanistic",
|
|
590
|
+
"clinical_health": "clinical/causal",
|
|
591
|
+
"earth_environment_agro": "earth-system/observational",
|
|
592
|
+
"energy_engineering_systems": "engineering/system",
|
|
593
|
+
"formal_theoretical": "formal/theoretical",
|
|
594
|
+
"generic_scientific": "domain-neutral scientific",
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
_PROFILE_DISABLED_SKILLS: Dict[str, Set[str]] = {
|
|
599
|
+
"formal_theoretical": {"speculative-execution-with-repair"},
|
|
600
|
+
"clinical_health": {"speculative-execution-with-repair"},
|
|
601
|
+
"earth_environment_agro": {"speculative-execution-with-repair"},
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
_PROFILE_ALLOWED_SKILLS: Dict[str, Set[str]] = {
|
|
606
|
+
"physical_materials_chemical": {
|
|
607
|
+
"mechanism-commit-innovation",
|
|
608
|
+
"alternative-path-contrast",
|
|
609
|
+
"feedback-closed-loop",
|
|
610
|
+
"multi-scale-coordinator",
|
|
611
|
+
"theory-transfer-injection",
|
|
612
|
+
},
|
|
613
|
+
"life_molecular_mechanistic": {
|
|
614
|
+
"mechanism-commit-innovation",
|
|
615
|
+
"alternative-path-contrast",
|
|
616
|
+
"feedback-closed-loop",
|
|
617
|
+
"multi-scale-coordinator",
|
|
618
|
+
"theory-transfer-injection",
|
|
619
|
+
},
|
|
620
|
+
"clinical_health": {
|
|
621
|
+
"mechanism-commit-innovation",
|
|
622
|
+
"alternative-path-contrast",
|
|
623
|
+
"feedback-closed-loop",
|
|
624
|
+
"theory-transfer-injection",
|
|
625
|
+
},
|
|
626
|
+
"earth_environment_agro": {
|
|
627
|
+
"mechanism-commit-innovation",
|
|
628
|
+
"alternative-path-contrast",
|
|
629
|
+
"feedback-closed-loop",
|
|
630
|
+
"multi-scale-coordinator",
|
|
631
|
+
"theory-transfer-injection",
|
|
632
|
+
},
|
|
633
|
+
"energy_engineering_systems": {
|
|
634
|
+
"mechanism-commit-innovation",
|
|
635
|
+
"alternative-path-contrast",
|
|
636
|
+
"feedback-closed-loop",
|
|
637
|
+
"multi-scale-coordinator",
|
|
638
|
+
"theory-transfer-injection",
|
|
639
|
+
},
|
|
640
|
+
"formal_theoretical": {
|
|
641
|
+
"mechanism-commit-innovation",
|
|
642
|
+
"alternative-path-contrast",
|
|
643
|
+
"hierarchical-decomposition",
|
|
644
|
+
"theory-transfer-injection",
|
|
645
|
+
},
|
|
646
|
+
"generic_scientific": {
|
|
647
|
+
"mechanism-commit-innovation",
|
|
648
|
+
"alternative-path-contrast",
|
|
649
|
+
"feedback-closed-loop",
|
|
650
|
+
"multi-scale-coordinator",
|
|
651
|
+
"theory-transfer-injection",
|
|
652
|
+
},
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _profile_skill_description(skill_name: str, profile_id: str) -> str:
|
|
657
|
+
prefix = _PROFILE_SKILL_DESCRIPTION_PREFIXES.get(profile_id, "profile-native scientific")
|
|
658
|
+
descriptions = {
|
|
659
|
+
"mechanism-commit-innovation": "Commit to one concrete mechanism-level intervention in the selected scientific object.",
|
|
660
|
+
"alternative-path-contrast": "Contrast one competing mechanism, explanation, or validity regime.",
|
|
661
|
+
"surgical-modularity": "Perform one localized intervention on a process, relation, object, or mechanism.",
|
|
662
|
+
"multi-scale-coordinator": "Link the relevant scales, regimes, or levels of the scientific relation.",
|
|
663
|
+
"hierarchical-decomposition": "Decompose the scientific object, assumptions, process, or evidence obligation across explicit levels.",
|
|
664
|
+
"feedback-closed-loop": "Use observations or counterexamples to refine the intervention or scientific relation.",
|
|
665
|
+
"theory-transfer-injection": "Transfer one principle or invariant into the selected scientific mechanism or relation.",
|
|
666
|
+
"speculative-execution-with-repair": "Explore a recoverable alternative condition and state its repair or recovery boundary.",
|
|
667
|
+
}
|
|
668
|
+
return f"{prefix} {descriptions.get(skill_name, 'scientific intervention refinement')}"
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
class SkillCatalog:
|
|
672
|
+
def __init__(self, skill_root: Optional[Path] = None) -> None:
|
|
673
|
+
if skill_root is None:
|
|
674
|
+
skill_root = (
|
|
675
|
+
Path(__file__).resolve().parents[2]
|
|
676
|
+
/ "agent"
|
|
677
|
+
/ "skills"
|
|
678
|
+
/ "edit_operator_skills"
|
|
679
|
+
)
|
|
680
|
+
self.skill_root = skill_root
|
|
681
|
+
self.skills: Dict[str, EditOperatorSkill] = {}
|
|
682
|
+
self.priors: Dict[str, SkillUsagePrior] = {}
|
|
683
|
+
self._load()
|
|
684
|
+
|
|
685
|
+
def _load(self) -> None:
|
|
686
|
+
loaded: Dict[str, EditOperatorSkill] = {}
|
|
687
|
+
if self.skill_root.exists():
|
|
688
|
+
for skill_file in sorted(self.skill_root.glob("*/SKILL.md")):
|
|
689
|
+
parsed = self._parse_skill_file(skill_file)
|
|
690
|
+
if parsed:
|
|
691
|
+
loaded[parsed.name] = parsed
|
|
692
|
+
for name, payload in DEFAULT_SKILL_TEMPLATES.items():
|
|
693
|
+
if name in loaded:
|
|
694
|
+
continue
|
|
695
|
+
loaded[name] = EditOperatorSkill(
|
|
696
|
+
name=name,
|
|
697
|
+
description=payload["description"],
|
|
698
|
+
structural_mode=payload["structural_mode"],
|
|
699
|
+
scope_preference=payload["scope_preference"],
|
|
700
|
+
requires_control_centered_parent=bool(payload["requires_control_centered_parent"]),
|
|
701
|
+
defects=list(payload.get("defects", [])),
|
|
702
|
+
guardrails=list(payload.get("guardrails", [])),
|
|
703
|
+
atomic_blueprint=list(payload.get("atomic_blueprint", [])),
|
|
704
|
+
required_protocols=list(payload.get("required_protocols", [])),
|
|
705
|
+
avoid_combinations=list(payload.get("avoid_combinations", [])),
|
|
706
|
+
execution_logic=list(payload.get("execution_logic", [])),
|
|
707
|
+
source_path="builtin-default",
|
|
708
|
+
profile_renderings=(
|
|
709
|
+
dict(payload.get("profile_renderings"))
|
|
710
|
+
if isinstance(payload.get("profile_renderings"), dict)
|
|
711
|
+
else {}
|
|
712
|
+
),
|
|
713
|
+
)
|
|
714
|
+
self.skills = loaded
|
|
715
|
+
for name in self.skills:
|
|
716
|
+
self.priors.setdefault(name, SkillUsagePrior())
|
|
717
|
+
|
|
718
|
+
def _parse_skill_file(self, path: Path) -> Optional[EditOperatorSkill]:
|
|
719
|
+
text = path.read_text(encoding="utf-8")
|
|
720
|
+
frontmatter, body = split_frontmatter(text)
|
|
721
|
+
name = str(frontmatter.get("name", "")).strip() or path.parent.name
|
|
722
|
+
description = str(frontmatter.get("description", "")).strip()
|
|
723
|
+
sections = parse_markdown_sections(body)
|
|
724
|
+
template = DEFAULT_SKILL_TEMPLATES.get(name, {})
|
|
725
|
+
structural_mode_entries = sections.get("structural_mode", [])
|
|
726
|
+
scope_preference_entries = sections.get("scope_preference", [])
|
|
727
|
+
requires_control_centered_entries = sections.get("requires_control_centered_parent", [])
|
|
728
|
+
structural_mode = (
|
|
729
|
+
structural_mode_entries[0].strip()
|
|
730
|
+
if structural_mode_entries and str(structural_mode_entries[0]).strip()
|
|
731
|
+
else str(template.get("structural_mode", "local_refinement")).strip()
|
|
732
|
+
)
|
|
733
|
+
scope_preference = (
|
|
734
|
+
scope_preference_entries[0].strip()
|
|
735
|
+
if scope_preference_entries and str(scope_preference_entries[0]).strip()
|
|
736
|
+
else str(template.get("scope_preference", "existing_subsystem")).strip()
|
|
737
|
+
)
|
|
738
|
+
if (
|
|
739
|
+
requires_control_centered_entries
|
|
740
|
+
and str(requires_control_centered_entries[0]).strip()
|
|
741
|
+
):
|
|
742
|
+
requires_control_centered_parent = (
|
|
743
|
+
requires_control_centered_entries[0].strip().lower() == "true"
|
|
744
|
+
)
|
|
745
|
+
else:
|
|
746
|
+
requires_control_centered_parent = bool(
|
|
747
|
+
template.get("requires_control_centered_parent", False)
|
|
748
|
+
)
|
|
749
|
+
defects = sections.get("defect_tags", []) or sections.get("defects", [])
|
|
750
|
+
guardrails = sections.get("guardrails", [])
|
|
751
|
+
atomic_blueprint = sections.get("atomic_blueprint", [])
|
|
752
|
+
required_protocols = sections.get("required_protocols", [])
|
|
753
|
+
avoid_combinations = sections.get("avoid_combinations", [])
|
|
754
|
+
execution_logic = sections.get("execution_logic", [])
|
|
755
|
+
if not description:
|
|
756
|
+
description = str(template.get("description", ""))
|
|
757
|
+
if not defects:
|
|
758
|
+
defects = list(template.get("defects", []))
|
|
759
|
+
if not guardrails:
|
|
760
|
+
guardrails = list(template.get("guardrails", []))
|
|
761
|
+
if not atomic_blueprint:
|
|
762
|
+
atomic_blueprint = list(template.get("atomic_blueprint", []))
|
|
763
|
+
if not required_protocols:
|
|
764
|
+
required_protocols = list(template.get("required_protocols", []))
|
|
765
|
+
if not execution_logic:
|
|
766
|
+
execution_logic = list(template.get("execution_logic", []))
|
|
767
|
+
if not description:
|
|
768
|
+
return None
|
|
769
|
+
|
|
770
|
+
return EditOperatorSkill(
|
|
771
|
+
name=name,
|
|
772
|
+
description=description,
|
|
773
|
+
structural_mode=structural_mode,
|
|
774
|
+
scope_preference=scope_preference,
|
|
775
|
+
requires_control_centered_parent=requires_control_centered_parent,
|
|
776
|
+
defects=defects,
|
|
777
|
+
guardrails=guardrails,
|
|
778
|
+
atomic_blueprint=atomic_blueprint,
|
|
779
|
+
required_protocols=required_protocols,
|
|
780
|
+
avoid_combinations=avoid_combinations,
|
|
781
|
+
execution_logic=execution_logic,
|
|
782
|
+
source_path=str(path),
|
|
783
|
+
profile_renderings=(
|
|
784
|
+
dict(template.get("profile_renderings"))
|
|
785
|
+
if isinstance(template.get("profile_renderings"), dict)
|
|
786
|
+
else {}
|
|
787
|
+
),
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
def list_skills(self) -> List[EditOperatorSkill]:
|
|
791
|
+
return [self.skills[key] for key in sorted(self.skills.keys())]
|
|
792
|
+
|
|
793
|
+
def render_skill_for_profile(
|
|
794
|
+
self,
|
|
795
|
+
skill: EditOperatorSkill | str,
|
|
796
|
+
profile_id: str,
|
|
797
|
+
object_schema: Optional[Dict[str, Any]] = None,
|
|
798
|
+
) -> EditOperatorSkill:
|
|
799
|
+
"""Render a skill using the selected profile's native object vocabulary.
|
|
800
|
+
|
|
801
|
+
The atomic operation names remain legacy-compatible. Only their
|
|
802
|
+
placeholders, descriptions, and guardrails are rendered, so existing
|
|
803
|
+
parsers and persisted edit plans continue to work.
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
base = self.skills[skill] if isinstance(skill, str) else skill
|
|
807
|
+
normalized_profile = str(profile_id or "generic_scientific").strip().lower()
|
|
808
|
+
explicit_rendering = base.profile_renderings.get(normalized_profile)
|
|
809
|
+
if not isinstance(explicit_rendering, dict):
|
|
810
|
+
explicit_rendering = {}
|
|
811
|
+
|
|
812
|
+
def schema_payload(value: Any) -> Dict[str, Any]:
|
|
813
|
+
if isinstance(value, dict):
|
|
814
|
+
return value
|
|
815
|
+
if hasattr(value, "to_payload"):
|
|
816
|
+
rendered = value.to_payload()
|
|
817
|
+
return rendered if isinstance(rendered, dict) else {}
|
|
818
|
+
return {}
|
|
819
|
+
|
|
820
|
+
if normalized_profile == "computational_algorithmic":
|
|
821
|
+
schema = schema_payload(object_schema)
|
|
822
|
+
if not schema:
|
|
823
|
+
schema_spec = get_scientific_object_schema(normalized_profile)
|
|
824
|
+
schema = schema_spec.to_payload() if schema_spec is not None else {}
|
|
825
|
+
rendered = replace(
|
|
826
|
+
base,
|
|
827
|
+
rendered_profile_id=normalized_profile,
|
|
828
|
+
profile_rendering_id=f"{base.name}:{normalized_profile}:v1",
|
|
829
|
+
allowed_object_types=list(schema.get("object_types") or []),
|
|
830
|
+
preferred_operations=list(schema.get("allowed_operations") or []),
|
|
831
|
+
)
|
|
832
|
+
if explicit_rendering:
|
|
833
|
+
rendered = replace(rendered, **{
|
|
834
|
+
key: value for key, value in explicit_rendering.items()
|
|
835
|
+
if key in {"description", "structural_mode", "scope_preference", "guardrails", "atomic_blueprint", "required_protocols", "avoid_combinations", "execution_logic", "allowed_object_types", "preferred_operations"}
|
|
836
|
+
})
|
|
837
|
+
return rendered
|
|
838
|
+
|
|
839
|
+
if normalized_profile not in _PROFILE_SKILL_TOKEN_MAPS:
|
|
840
|
+
normalized_profile = "generic_scientific"
|
|
841
|
+
schema = schema_payload(object_schema)
|
|
842
|
+
if not schema:
|
|
843
|
+
schema_spec = get_scientific_object_schema(normalized_profile)
|
|
844
|
+
schema = schema_spec.to_payload() if schema_spec is not None else {}
|
|
845
|
+
token_map = _PROFILE_SKILL_TOKEN_MAPS[normalized_profile]
|
|
846
|
+
|
|
847
|
+
def render_text(value: str) -> str:
|
|
848
|
+
rendered = str(value or "")
|
|
849
|
+
for source, target in token_map.items():
|
|
850
|
+
rendered = rendered.replace(source, target)
|
|
851
|
+
return rendered
|
|
852
|
+
|
|
853
|
+
native_guardrails = [
|
|
854
|
+
"Use profile-native scientific objects and operations; do not recast the intervention as software architecture by default.",
|
|
855
|
+
"Keep the claim delta, mechanism delta, evidence obligation, and boundary condition explicit.",
|
|
856
|
+
]
|
|
857
|
+
rendered = replace(
|
|
858
|
+
base,
|
|
859
|
+
description=_profile_skill_description(base.name, normalized_profile),
|
|
860
|
+
guardrails=[render_text(item) for item in base.guardrails] + native_guardrails,
|
|
861
|
+
atomic_blueprint=[render_text(item) for item in base.atomic_blueprint],
|
|
862
|
+
required_protocols=list(base.required_protocols),
|
|
863
|
+
execution_logic=[render_text(item) for item in base.execution_logic],
|
|
864
|
+
rendered_profile_id=normalized_profile,
|
|
865
|
+
profile_rendering_id=f"{base.name}:{normalized_profile}:v1",
|
|
866
|
+
allowed_object_types=list(schema.get("object_types") or []),
|
|
867
|
+
preferred_operations=list(schema.get("allowed_operations") or []),
|
|
868
|
+
)
|
|
869
|
+
if explicit_rendering:
|
|
870
|
+
rendered = replace(rendered, **{
|
|
871
|
+
key: value for key, value in explicit_rendering.items()
|
|
872
|
+
if key in {"description", "structural_mode", "scope_preference", "guardrails", "atomic_blueprint", "required_protocols", "avoid_combinations", "execution_logic", "allowed_object_types", "preferred_operations"}
|
|
873
|
+
})
|
|
874
|
+
return rendered
|
|
875
|
+
|
|
876
|
+
def format_for_prompt(
|
|
877
|
+
self,
|
|
878
|
+
skills: Optional[Sequence[EditOperatorSkill]] = None,
|
|
879
|
+
*,
|
|
880
|
+
profile_id: Optional[str] = None,
|
|
881
|
+
object_schema: Optional[Dict[str, Any]] = None,
|
|
882
|
+
) -> str:
|
|
883
|
+
chosen = list(skills) if skills is not None else self.list_skills()
|
|
884
|
+
if profile_id:
|
|
885
|
+
normalized_profile = str(profile_id).strip().lower()
|
|
886
|
+
allowed_skills = _PROFILE_ALLOWED_SKILLS.get(normalized_profile)
|
|
887
|
+
if allowed_skills is not None:
|
|
888
|
+
chosen = [skill for skill in chosen if skill.name in allowed_skills]
|
|
889
|
+
chosen = [
|
|
890
|
+
self.render_skill_for_profile(skill, profile_id, object_schema)
|
|
891
|
+
for skill in chosen
|
|
892
|
+
]
|
|
893
|
+
if not chosen:
|
|
894
|
+
return "No edit-operator skills available."
|
|
895
|
+
op_ref = format_op_descriptions(profile_id)
|
|
896
|
+
skill_lines = "\n".join(skill.to_prompt_line() for skill in chosen)
|
|
897
|
+
return f"{op_ref}\n\nAvailable edit-operator skills:\n{skill_lines}"
|
|
898
|
+
|
|
899
|
+
def render_for_profile(
|
|
900
|
+
self,
|
|
901
|
+
skill: EditOperatorSkill | str,
|
|
902
|
+
profile_id: str,
|
|
903
|
+
object_schema: Optional[Dict[str, Any]] = None,
|
|
904
|
+
) -> EditOperatorSkill:
|
|
905
|
+
"""Compatibility alias for callers using the shorter renderer name."""
|
|
906
|
+
|
|
907
|
+
return self.render_skill_for_profile(skill, profile_id, object_schema)
|
|
908
|
+
|
|
909
|
+
def render_references_for_prompt(self, skill_name: str) -> str:
|
|
910
|
+
skill = self.skills[skill_name]
|
|
911
|
+
if skill.source_path == "builtin-default":
|
|
912
|
+
return "None."
|
|
913
|
+
reference_dir = Path(skill.source_path).parent / "references"
|
|
914
|
+
if not reference_dir.exists():
|
|
915
|
+
return "None."
|
|
916
|
+
sections: List[str] = []
|
|
917
|
+
for reference_file in sorted(reference_dir.glob("*.md")):
|
|
918
|
+
content = reference_file.read_text(encoding="utf-8").strip()
|
|
919
|
+
if content:
|
|
920
|
+
sections.append(f"== {reference_file.name} ==\n{content}")
|
|
921
|
+
return "\n\n".join(sections) if sections else "None."
|
|
922
|
+
|
|
923
|
+
def select_skills(
|
|
924
|
+
self,
|
|
925
|
+
defect_tags: Sequence[str],
|
|
926
|
+
max_children: int,
|
|
927
|
+
structural_profile: Dict[str, Any],
|
|
928
|
+
preset: Optional[IdeaTastePreset] = None,
|
|
929
|
+
profile_id: Optional[str] = None,
|
|
930
|
+
) -> List[SkillSelectionCandidate]:
|
|
931
|
+
defects = {str(tag).strip().lower() for tag in defect_tags if str(tag).strip()}
|
|
932
|
+
if not defects:
|
|
933
|
+
defects = {"unexplored_gap"}
|
|
934
|
+
|
|
935
|
+
preset_bias_map = dict(getattr(preset, "skill_bias", {}) or {})
|
|
936
|
+
scored: List[SkillSelectionCandidate] = []
|
|
937
|
+
disabled_skills = _PROFILE_DISABLED_SKILLS.get(
|
|
938
|
+
str(profile_id or "").strip().lower(),
|
|
939
|
+
set(),
|
|
940
|
+
)
|
|
941
|
+
normalized_profile = str(profile_id or "").strip().lower()
|
|
942
|
+
allowed_skills = _PROFILE_ALLOWED_SKILLS.get(normalized_profile)
|
|
943
|
+
for skill in self.skills.values():
|
|
944
|
+
if skill.name in disabled_skills or (
|
|
945
|
+
allowed_skills is not None and skill.name not in allowed_skills
|
|
946
|
+
):
|
|
947
|
+
continue
|
|
948
|
+
skill_defects = {d.lower() for d in skill.defects}
|
|
949
|
+
skill_defects.update(
|
|
950
|
+
profile_skill_defect_tags(profile_id, skill.name)
|
|
951
|
+
)
|
|
952
|
+
overlap = len(defects & skill_defects)
|
|
953
|
+
defect_score = overlap / max(1, len(defects))
|
|
954
|
+
prior_state = self.priors.get(skill.name, SkillUsagePrior())
|
|
955
|
+
prior = prior_state.prior
|
|
956
|
+
attempts = max(0, int(prior_state.attempts))
|
|
957
|
+
raw_preset_bias = preset_bias_map.get(skill.name, 0.0)
|
|
958
|
+
try:
|
|
959
|
+
preset_bias = max(0.0, min(1.0, float(raw_preset_bias)))
|
|
960
|
+
except (TypeError, ValueError):
|
|
961
|
+
preset_bias = 0.0
|
|
962
|
+
structure_fit, structure_reason = self._structure_fit(skill, structural_profile)
|
|
963
|
+
if structure_fit == 0.0:
|
|
964
|
+
continue
|
|
965
|
+
total = (
|
|
966
|
+
0.60 * defect_score
|
|
967
|
+
+ 0.20 * prior
|
|
968
|
+
+ 0.20 * preset_bias
|
|
969
|
+
) * structure_fit
|
|
970
|
+
scored.append(
|
|
971
|
+
SkillSelectionCandidate(
|
|
972
|
+
skill=skill,
|
|
973
|
+
defect_score=defect_score,
|
|
974
|
+
prior_score=prior,
|
|
975
|
+
preset_bias=preset_bias,
|
|
976
|
+
structure_fit=structure_fit,
|
|
977
|
+
structure_reason=structure_reason,
|
|
978
|
+
selection_total=total,
|
|
979
|
+
attempts=attempts,
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
scored.sort(key=lambda item: (-item.selection_total, -item.defect_score, item.skill.name))
|
|
984
|
+
max_children = max(1, int(max_children))
|
|
985
|
+
if max_children == 1:
|
|
986
|
+
picked = [scored[0]] if scored else []
|
|
987
|
+
else:
|
|
988
|
+
exploit_count = min(len(scored), max(0, max_children - 1))
|
|
989
|
+
picked = list(scored[:exploit_count])
|
|
990
|
+
remaining = scored[exploit_count:]
|
|
991
|
+
if remaining and len(picked) < max_children:
|
|
992
|
+
eligible = [entry for entry in remaining if float(entry.defect_score) > 0.0]
|
|
993
|
+
if eligible:
|
|
994
|
+
weights = [
|
|
995
|
+
float(entry.defect_score)
|
|
996
|
+
* (1.0 + 1.0 / math.sqrt(float(entry.attempts) + 1.0))
|
|
997
|
+
for entry in eligible
|
|
998
|
+
]
|
|
999
|
+
picked.append(random.choices(eligible, weights=weights, k=1)[0])
|
|
1000
|
+
else:
|
|
1001
|
+
picked.append(random.choice(remaining))
|
|
1002
|
+
if not picked:
|
|
1003
|
+
return scored[: max(1, max_children)]
|
|
1004
|
+
return picked
|
|
1005
|
+
|
|
1006
|
+
def _scope_fit(self, scope_preference: str, scope_kind: str) -> float:
|
|
1007
|
+
if scope_preference == scope_kind:
|
|
1008
|
+
return 1.0
|
|
1009
|
+
if {scope_preference, scope_kind} <= {"existing_component", "existing_subsystem"}:
|
|
1010
|
+
return 0.85
|
|
1011
|
+
if {scope_preference, scope_kind} <= {"execution_path", "broad_architecture"}:
|
|
1012
|
+
return 0.75
|
|
1013
|
+
if scope_preference == "core_objective" and scope_kind in {
|
|
1014
|
+
"existing_subsystem",
|
|
1015
|
+
"execution_path",
|
|
1016
|
+
"broad_architecture",
|
|
1017
|
+
}:
|
|
1018
|
+
return 0.7
|
|
1019
|
+
return 0.0
|
|
1020
|
+
|
|
1021
|
+
def _structure_fit(
|
|
1022
|
+
self,
|
|
1023
|
+
skill: EditOperatorSkill,
|
|
1024
|
+
structural_profile: Dict[str, Any],
|
|
1025
|
+
) -> tuple[float, str]:
|
|
1026
|
+
if (
|
|
1027
|
+
skill.requires_control_centered_parent
|
|
1028
|
+
and not bool(structural_profile["control_centered"])
|
|
1029
|
+
and str(structural_profile.get("profile_id") or "").strip().lower()
|
|
1030
|
+
== "computational_algorithmic"
|
|
1031
|
+
):
|
|
1032
|
+
return 0.0, "requires_control_centered_parent"
|
|
1033
|
+
|
|
1034
|
+
scope_kind = str(structural_profile["scope_kind"])
|
|
1035
|
+
fit = self._scope_fit(skill.scope_preference, scope_kind)
|
|
1036
|
+
profile_id = str(structural_profile.get("profile_id") or "").strip().lower()
|
|
1037
|
+
if (
|
|
1038
|
+
fit == 0.0
|
|
1039
|
+
and profile_id != "computational_algorithmic"
|
|
1040
|
+
and skill.structural_mode == "feedback_loop"
|
|
1041
|
+
and scope_kind == "existing_subsystem"
|
|
1042
|
+
):
|
|
1043
|
+
fit = 0.85
|
|
1044
|
+
if fit == 0.0:
|
|
1045
|
+
return 0.0, "scope_mismatch"
|
|
1046
|
+
|
|
1047
|
+
if skill.structural_mode == "path_branching" and scope_kind == "existing_component":
|
|
1048
|
+
return 0.0, "path_branching_out_of_scope"
|
|
1049
|
+
if skill.structural_mode == "feedback_loop" and bool(structural_profile["training_free_like"]):
|
|
1050
|
+
return 0.0, "feedback_loop_out_of_scope"
|
|
1051
|
+
if skill.structural_mode == "path_branching" and not bool(structural_profile["has_multi_path_shape"]):
|
|
1052
|
+
return fit * 0.75, "scope_only"
|
|
1053
|
+
return fit, "aligned"
|
|
1054
|
+
|
|
1055
|
+
def compile_plan(
|
|
1056
|
+
self,
|
|
1057
|
+
skill: EditOperatorSkill,
|
|
1058
|
+
parent_title: str,
|
|
1059
|
+
parent_components: Sequence[str],
|
|
1060
|
+
target_defects: Sequence[str],
|
|
1061
|
+
memory_refs: Sequence[str],
|
|
1062
|
+
*,
|
|
1063
|
+
profile_id: str = "",
|
|
1064
|
+
object_schema: Optional[Dict[str, Any]] = None,
|
|
1065
|
+
) -> EditPlan:
|
|
1066
|
+
if isinstance(object_schema, dict):
|
|
1067
|
+
normalized_object_schema = dict(object_schema)
|
|
1068
|
+
elif hasattr(object_schema, "to_payload"):
|
|
1069
|
+
rendered_schema = object_schema.to_payload()
|
|
1070
|
+
normalized_object_schema = (
|
|
1071
|
+
dict(rendered_schema) if isinstance(rendered_schema, dict) else {}
|
|
1072
|
+
)
|
|
1073
|
+
else:
|
|
1074
|
+
normalized_object_schema = {}
|
|
1075
|
+
parsed_steps = [parse_blueprint_step(step) for step in skill.atomic_blueprint]
|
|
1076
|
+
parsed_steps = [step for step in parsed_steps if step is not None]
|
|
1077
|
+
|
|
1078
|
+
component_edits: List[ComponentEdit] = []
|
|
1079
|
+
validation = ValidationProtocol()
|
|
1080
|
+
|
|
1081
|
+
required_protocols = {
|
|
1082
|
+
token.lower().strip()
|
|
1083
|
+
for token in skill.required_protocols
|
|
1084
|
+
if token and token.strip()
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
for step in parsed_steps:
|
|
1088
|
+
op = step["op"]
|
|
1089
|
+
if op == AtomicEditOp.ADD_PROTOCOL.value:
|
|
1090
|
+
protocols = step.get("protocols", [])
|
|
1091
|
+
for protocol in protocols:
|
|
1092
|
+
required_protocols.add(protocol.lower().strip())
|
|
1093
|
+
continue
|
|
1094
|
+
component_edits.append(
|
|
1095
|
+
ComponentEdit(
|
|
1096
|
+
op=AtomicEditOp(op),
|
|
1097
|
+
component=step.get("component", ""),
|
|
1098
|
+
target=step.get("target", ""),
|
|
1099
|
+
condition=step.get("condition", ""),
|
|
1100
|
+
details=step.get("details", ""),
|
|
1101
|
+
)
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
if not required_protocols:
|
|
1105
|
+
required_protocols = {"ablation"}
|
|
1106
|
+
|
|
1107
|
+
for protocol in sorted(required_protocols):
|
|
1108
|
+
test_text = _default_protocol_text(protocol, skill.name, parent_title, target_defects)
|
|
1109
|
+
if protocol == "regression":
|
|
1110
|
+
validation.regression_tests.append(test_text)
|
|
1111
|
+
elif protocol == "ablation":
|
|
1112
|
+
validation.ablation_tests.append(test_text)
|
|
1113
|
+
else:
|
|
1114
|
+
validation.stress_tests.append(test_text)
|
|
1115
|
+
component_edits.append(
|
|
1116
|
+
ComponentEdit(
|
|
1117
|
+
op=AtomicEditOp.ADD_PROTOCOL,
|
|
1118
|
+
component=protocol,
|
|
1119
|
+
details=test_text,
|
|
1120
|
+
)
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
objective_defect = next(iter(target_defects), "unspecified_defect")
|
|
1124
|
+
plan = EditPlan(
|
|
1125
|
+
skill_name=skill.name,
|
|
1126
|
+
objective=f"Use {skill.name} to address {objective_defect}",
|
|
1127
|
+
target_defects=[str(tag) for tag in target_defects] or skill.defects[:1] or ["unspecified_defect"],
|
|
1128
|
+
component_edits=component_edits,
|
|
1129
|
+
validation=validation,
|
|
1130
|
+
guardrails=list(skill.guardrails),
|
|
1131
|
+
memory_refs=[str(ref) for ref in memory_refs][:6],
|
|
1132
|
+
compile_notes=(
|
|
1133
|
+
f"Compiled from skill '{skill.name}' with blueprint ops={len(skill.atomic_blueprint)}; "
|
|
1134
|
+
f"source={skill.source_path or 'builtin'}"
|
|
1135
|
+
),
|
|
1136
|
+
profile_id=str(profile_id or skill.rendered_profile_id or ""),
|
|
1137
|
+
profile_rendering_id=str(skill.profile_rendering_id or ""),
|
|
1138
|
+
preferred_operations=list(skill.preferred_operations),
|
|
1139
|
+
scientific_object_schema=normalized_object_schema,
|
|
1140
|
+
)
|
|
1141
|
+
return plan
|
|
1142
|
+
|
|
1143
|
+
def update_prior(
|
|
1144
|
+
self,
|
|
1145
|
+
skill_name: str,
|
|
1146
|
+
reward: float,
|
|
1147
|
+
feedback: str,
|
|
1148
|
+
failure_modes: Sequence[str],
|
|
1149
|
+
success_threshold: float = 0.6,
|
|
1150
|
+
) -> SkillUsagePrior:
|
|
1151
|
+
prior = self.priors.setdefault(skill_name, SkillUsagePrior())
|
|
1152
|
+
prior.attempts += 1
|
|
1153
|
+
clipped_reward = max(0.0, min(1.0, reward))
|
|
1154
|
+
if clipped_reward >= max(0.0, min(1.0, success_threshold)):
|
|
1155
|
+
prior.successes += 1
|
|
1156
|
+
prior.reward_ema = 0.8 * prior.reward_ema + 0.2 * clipped_reward
|
|
1157
|
+
beta_mean = (prior.successes + 1.0) / (prior.attempts + 2.0)
|
|
1158
|
+
prior.prior = 0.55 * beta_mean + 0.45 * prior.reward_ema
|
|
1159
|
+
|
|
1160
|
+
for failure in failure_modes:
|
|
1161
|
+
text = str(failure).strip()
|
|
1162
|
+
if not text:
|
|
1163
|
+
continue
|
|
1164
|
+
rule = f"Avoid failure mode: {text}"
|
|
1165
|
+
if rule not in prior.rule_constraints:
|
|
1166
|
+
prior.rule_constraints.append(rule)
|
|
1167
|
+
prior.rule_constraints = prior.rule_constraints[:8]
|
|
1168
|
+
return prior
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
def _default_protocol_text(
|
|
1173
|
+
protocol: str,
|
|
1174
|
+
skill_name: str,
|
|
1175
|
+
parent_title: str,
|
|
1176
|
+
target_defects: Sequence[str],
|
|
1177
|
+
) -> str:
|
|
1178
|
+
defect = next((str(tag) for tag in target_defects if str(tag).strip()), "target defect")
|
|
1179
|
+
if protocol == "regression":
|
|
1180
|
+
return (
|
|
1181
|
+
f"Run regression against parent '{parent_title}' and verify no degradation on core metrics while fixing {defect}."
|
|
1182
|
+
)
|
|
1183
|
+
if protocol == "ablation":
|
|
1184
|
+
return (
|
|
1185
|
+
f"Ablate the {skill_name} delta to isolate contribution and confirm defect-level lift on {defect}."
|
|
1186
|
+
)
|
|
1187
|
+
return (
|
|
1188
|
+
f"Stress test {skill_name} under worst-case conditions tied to {defect} and record failure boundaries."
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
def apply_edit_plan_to_components(
|
|
1193
|
+
components: Sequence[str],
|
|
1194
|
+
edit_plan: EditPlan,
|
|
1195
|
+
) -> List[str]:
|
|
1196
|
+
ordered = [str(comp) for comp in components if str(comp).strip()]
|
|
1197
|
+
existing = list(ordered)
|
|
1198
|
+
|
|
1199
|
+
def _contains(name: str) -> bool:
|
|
1200
|
+
return any(name == item for item in existing)
|
|
1201
|
+
|
|
1202
|
+
for edit in edit_plan.component_edits:
|
|
1203
|
+
component = _coerce_component_name(edit.component)
|
|
1204
|
+
target = _coerce_component_name(edit.target)
|
|
1205
|
+
if edit.op == AtomicEditOp.ADD_COMPONENT:
|
|
1206
|
+
if component and not _contains(component):
|
|
1207
|
+
existing.append(component)
|
|
1208
|
+
elif edit.op == AtomicEditOp.REMOVE_COMPONENT:
|
|
1209
|
+
if component:
|
|
1210
|
+
existing = [item for item in existing if item != component]
|
|
1211
|
+
elif edit.op == AtomicEditOp.REPLACE_COMPONENT:
|
|
1212
|
+
if target:
|
|
1213
|
+
replaced = False
|
|
1214
|
+
for idx, item in enumerate(existing):
|
|
1215
|
+
if item == target:
|
|
1216
|
+
existing[idx] = component or target
|
|
1217
|
+
replaced = True
|
|
1218
|
+
break
|
|
1219
|
+
if not replaced and component and not _contains(component):
|
|
1220
|
+
existing.append(component)
|
|
1221
|
+
elif component and not _contains(component):
|
|
1222
|
+
existing.append(component)
|
|
1223
|
+
elif edit.op == AtomicEditOp.REWIRE:
|
|
1224
|
+
# REWIRE affects topology, not component inventory.
|
|
1225
|
+
continue
|
|
1226
|
+
elif edit.op == AtomicEditOp.ADD_PROTOCOL:
|
|
1227
|
+
# Protocols are first-class edits but not structural components.
|
|
1228
|
+
continue
|
|
1229
|
+
|
|
1230
|
+
return existing
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
def log_message(
|
|
1234
|
+
logger: Any,
|
|
1235
|
+
log_sink: Optional[Any],
|
|
1236
|
+
level: str,
|
|
1237
|
+
message: str,
|
|
1238
|
+
*args: Any,
|
|
1239
|
+
) -> None:
|
|
1240
|
+
log_fn = getattr(logger, level, logger.info)
|
|
1241
|
+
try:
|
|
1242
|
+
log_fn(message, *args)
|
|
1243
|
+
except Exception:
|
|
1244
|
+
logger.exception("MCTS logging failure for message: %s", message)
|
|
1245
|
+
if log_sink:
|
|
1246
|
+
try:
|
|
1247
|
+
formatted = message % args if args else message
|
|
1248
|
+
except Exception:
|
|
1249
|
+
formatted = f"{message} | args={args}"
|
|
1250
|
+
try:
|
|
1251
|
+
log_sink(level, formatted)
|
|
1252
|
+
except Exception as exc:
|
|
1253
|
+
logger.debug("MCTS log sink failed: %s", exc)
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def compute_protocol_score_from_plan(plan: Optional[Dict[str, Any]]) -> float:
|
|
1257
|
+
if not plan:
|
|
1258
|
+
return 0.0
|
|
1259
|
+
validation = plan.get("validation") if isinstance(plan.get("validation"), dict) else {}
|
|
1260
|
+
score = 0.0
|
|
1261
|
+
if validation.get("regression_tests"):
|
|
1262
|
+
score += 0.9
|
|
1263
|
+
if validation.get("ablation_tests"):
|
|
1264
|
+
score += 1.2
|
|
1265
|
+
if validation.get("stress_tests"):
|
|
1266
|
+
score += 0.9
|
|
1267
|
+
return min(3.0, score)
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
def memory_bundle_log_payload(bundle: Any) -> Dict[str, Any]:
|
|
1271
|
+
def _snippet_payload(snippet: Any) -> Dict[str, Any]:
|
|
1272
|
+
return {
|
|
1273
|
+
"id": getattr(snippet, "identifier", ""),
|
|
1274
|
+
"title": getattr(snippet, "title", ""),
|
|
1275
|
+
"detail": getattr(snippet, "detail", ""),
|
|
1276
|
+
"tags": list(getattr(snippet, "tags", []) or []),
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
return {
|
|
1280
|
+
"field_knowledge": [_snippet_payload(s) for s in getattr(bundle, "field_knowledge", []) or []],
|
|
1281
|
+
"anti_patterns": [_snippet_payload(s) for s in getattr(bundle, "anti_patterns", []) or []],
|
|
1282
|
+
"fix_recipes": [_snippet_payload(s) for s in getattr(bundle, "fix_recipes", []) or []],
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def simulate_log_payload(evaluation: Any) -> Dict[str, Any]:
|
|
1287
|
+
payload = {**evaluation.to_dict(), "composite": evaluation.composite}
|
|
1288
|
+
return payload
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
def _direction_prompt_context(
|
|
1292
|
+
mcts: Any,
|
|
1293
|
+
state: Any,
|
|
1294
|
+
*,
|
|
1295
|
+
object_schema: Optional[Dict[str, Any]] = None,
|
|
1296
|
+
) -> Dict[str, str]:
|
|
1297
|
+
intervention = getattr(state, "scientific_intervention", {})
|
|
1298
|
+
intervention = intervention if isinstance(intervention, Mapping) else {}
|
|
1299
|
+
profile_payload = getattr(mcts, "scientific_intervention_profile", {})
|
|
1300
|
+
profile_payload = profile_payload if isinstance(profile_payload, Mapping) else {}
|
|
1301
|
+
profile_id = str(
|
|
1302
|
+
intervention.get("profile_id")
|
|
1303
|
+
or profile_payload.get("profile_id")
|
|
1304
|
+
or "generic_scientific"
|
|
1305
|
+
).strip().lower()
|
|
1306
|
+
preset = getattr(mcts, "idea_taste_preset", None)
|
|
1307
|
+
direction_mode = str(
|
|
1308
|
+
intervention.get("direction_mode")
|
|
1309
|
+
or getattr(preset, "mode", None)
|
|
1310
|
+
or "default"
|
|
1311
|
+
).strip()
|
|
1312
|
+
direction_summary = str(
|
|
1313
|
+
intervention.get("direction_summary")
|
|
1314
|
+
or getattr(preset, "summary", None)
|
|
1315
|
+
or "Use the default scientific search balance."
|
|
1316
|
+
).strip()
|
|
1317
|
+
taste_guidance = str(
|
|
1318
|
+
getattr(preset, "instantiation_guidance", None)
|
|
1319
|
+
or "No special taste guidance."
|
|
1320
|
+
).strip()
|
|
1321
|
+
seed_records = [
|
|
1322
|
+
dict(seed)
|
|
1323
|
+
for seed in getattr(mcts, "gap_hypothesis_seeds", []) or []
|
|
1324
|
+
if isinstance(seed, Mapping)
|
|
1325
|
+
]
|
|
1326
|
+
hypothesis_contract = intervention.get("hypothesis_contract")
|
|
1327
|
+
hypothesis_contract = (
|
|
1328
|
+
hypothesis_contract if isinstance(hypothesis_contract, Mapping) else {}
|
|
1329
|
+
)
|
|
1330
|
+
raw_target_gap_ids = hypothesis_contract.get("target_gap_ids")
|
|
1331
|
+
if isinstance(raw_target_gap_ids, (list, tuple, set, frozenset)):
|
|
1332
|
+
target_gap_ids = [
|
|
1333
|
+
str(gap_id).strip()
|
|
1334
|
+
for gap_id in raw_target_gap_ids
|
|
1335
|
+
if str(gap_id).strip()
|
|
1336
|
+
]
|
|
1337
|
+
elif str(raw_target_gap_ids or "").strip():
|
|
1338
|
+
target_gap_ids = [str(raw_target_gap_ids).strip()]
|
|
1339
|
+
else:
|
|
1340
|
+
target_gap_ids = [
|
|
1341
|
+
str(seed.get("gap_id") or "").strip()
|
|
1342
|
+
for seed in seed_records
|
|
1343
|
+
if str(seed.get("gap_id") or "").strip()
|
|
1344
|
+
]
|
|
1345
|
+
selected_schema = object_schema
|
|
1346
|
+
if not isinstance(selected_schema, Mapping):
|
|
1347
|
+
selected_schema = intervention.get("scientific_object_schema")
|
|
1348
|
+
if not isinstance(selected_schema, Mapping):
|
|
1349
|
+
selected_schema = profile_payload.get("scientific_object_schema")
|
|
1350
|
+
return {
|
|
1351
|
+
"direction_mode": direction_mode,
|
|
1352
|
+
"direction_summary": direction_summary,
|
|
1353
|
+
"taste_guidance": taste_guidance,
|
|
1354
|
+
"seed_id": str(getattr(mcts, "seed_id", "") or "None supplied"),
|
|
1355
|
+
"route_id": str(getattr(mcts, "route_id", "") or "None supplied"),
|
|
1356
|
+
"route_policy": pretty_json(getattr(mcts, "route_policy", {}) or {}),
|
|
1357
|
+
"target_gap_ids": ", ".join(dict.fromkeys(target_gap_ids)) or "None supplied",
|
|
1358
|
+
"profile_id": profile_id,
|
|
1359
|
+
"profile_native_object_schema": pretty_json(dict(selected_schema or {})),
|
|
1360
|
+
"gap_seed_context": str(getattr(mcts, "gap_seed_context", "") or "No gap seeds supplied."),
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def _data_anchored_contract_prompt(state: Any) -> str:
|
|
1365
|
+
intervention = getattr(state, "scientific_intervention", {})
|
|
1366
|
+
intervention = intervention if isinstance(intervention, Mapping) else {}
|
|
1367
|
+
contract = intervention.get("data_anchored_contract")
|
|
1368
|
+
if not isinstance(contract, Mapping) or not contract.get("data_anchor_refs"):
|
|
1369
|
+
return ""
|
|
1370
|
+
return (
|
|
1371
|
+
"\n\n== Bounded supplied-data requirement ==\n"
|
|
1372
|
+
+ pretty_json(dict(contract))
|
|
1373
|
+
+ "\nThe data anchors are local observations, not paper citations. Keep their claim scope "
|
|
1374
|
+
"dataset-local and do not state that they prove a mechanism or a first discovery. "
|
|
1375
|
+
"The coverage pass must explicitly distinguish candidate_mechanism, "
|
|
1376
|
+
"alternative_explanation, and measurement_artifact branches. A measurement-at-risk "
|
|
1377
|
+
"anchor may support only measurement validity or workflow reasoning."
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
def _contract_from_instantiation(instantiated: Mapping[str, Any]) -> Dict[str, Any]:
|
|
1382
|
+
explicit_contract = instantiated.get("scientific_contract")
|
|
1383
|
+
if isinstance(explicit_contract, Mapping):
|
|
1384
|
+
contract = dict(explicit_contract)
|
|
1385
|
+
else:
|
|
1386
|
+
contract = {}
|
|
1387
|
+
for field_name in HYPOTHESIS_CONTRACT_FIELDS:
|
|
1388
|
+
value = instantiated.get(field_name)
|
|
1389
|
+
if value not in (None, "", [], {}):
|
|
1390
|
+
contract.setdefault(field_name, value)
|
|
1391
|
+
return contract
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
def _nonempty_contract_value(value: Any) -> bool:
|
|
1395
|
+
return value not in (None, "", [], {})
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
def _parent_hypothesis_contract(parent_state: Any) -> Dict[str, Any]:
|
|
1399
|
+
"""Return the parent scientific contract without exposing mutable state."""
|
|
1400
|
+
|
|
1401
|
+
intervention = getattr(parent_state, "scientific_intervention", {}) or {}
|
|
1402
|
+
if not isinstance(intervention, Mapping):
|
|
1403
|
+
return {}
|
|
1404
|
+
parent_contract = intervention.get("hypothesis_contract")
|
|
1405
|
+
merged = (
|
|
1406
|
+
{
|
|
1407
|
+
field_name: deepcopy(value)
|
|
1408
|
+
for field_name, value in parent_contract.items()
|
|
1409
|
+
if field_name in HYPOTHESIS_CONTRACT_FIELDS
|
|
1410
|
+
and _nonempty_contract_value(value)
|
|
1411
|
+
}
|
|
1412
|
+
if isinstance(parent_contract, Mapping)
|
|
1413
|
+
else {}
|
|
1414
|
+
)
|
|
1415
|
+
for field_name in HYPOTHESIS_CONTRACT_FIELDS:
|
|
1416
|
+
value = intervention.get(field_name)
|
|
1417
|
+
if _nonempty_contract_value(value):
|
|
1418
|
+
merged.setdefault(field_name, deepcopy(value))
|
|
1419
|
+
return merged
|
|
1420
|
+
|
|
1421
|
+
|
|
1422
|
+
def merge_hypothesis_contract(
|
|
1423
|
+
parent_contract: Mapping[str, Any],
|
|
1424
|
+
instantiated_contract: Mapping[str, Any],
|
|
1425
|
+
) -> Dict[str, Any]:
|
|
1426
|
+
"""Merge a child instantiation onto its parent without treating blanks as clears."""
|
|
1427
|
+
|
|
1428
|
+
merged = {
|
|
1429
|
+
field_name: deepcopy(value)
|
|
1430
|
+
for field_name, value in parent_contract.items()
|
|
1431
|
+
if field_name in HYPOTHESIS_CONTRACT_FIELDS
|
|
1432
|
+
and _nonempty_contract_value(value)
|
|
1433
|
+
}
|
|
1434
|
+
for field_name, value in instantiated_contract.items():
|
|
1435
|
+
if field_name not in HYPOTHESIS_CONTRACT_FIELDS:
|
|
1436
|
+
continue
|
|
1437
|
+
if _nonempty_contract_value(value):
|
|
1438
|
+
merged[field_name] = deepcopy(value)
|
|
1439
|
+
return merged
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
def _route_contract_incomplete_fields(
|
|
1443
|
+
route_id: str,
|
|
1444
|
+
parent_contract: Mapping[str, Any],
|
|
1445
|
+
instantiated_contract: Mapping[str, Any],
|
|
1446
|
+
) -> List[str]:
|
|
1447
|
+
"""Identify route-defining fields that a child omitted and would only inherit."""
|
|
1448
|
+
|
|
1449
|
+
required_fields = _ROUTE_REQUIRED_CONTRACT_FIELDS.get(route_id, ())
|
|
1450
|
+
if route_id == "mechanism_replacement":
|
|
1451
|
+
child_has_mechanism = any(
|
|
1452
|
+
_nonempty_contract_value(instantiated_contract.get(field_name))
|
|
1453
|
+
for field_name in required_fields
|
|
1454
|
+
)
|
|
1455
|
+
parent_has_mechanism = any(
|
|
1456
|
+
_nonempty_contract_value(parent_contract.get(field_name))
|
|
1457
|
+
for field_name in required_fields
|
|
1458
|
+
)
|
|
1459
|
+
return ["mechanism_or_relation"] if parent_has_mechanism and not child_has_mechanism else []
|
|
1460
|
+
incomplete: List[str] = []
|
|
1461
|
+
for field_name in required_fields:
|
|
1462
|
+
child_value = instantiated_contract.get(field_name)
|
|
1463
|
+
if _nonempty_contract_value(child_value):
|
|
1464
|
+
continue
|
|
1465
|
+
if _nonempty_contract_value(parent_contract.get(field_name)):
|
|
1466
|
+
incomplete.append(field_name)
|
|
1467
|
+
return incomplete
|
|
1468
|
+
|
|
1469
|
+
|
|
1470
|
+
def _contract_value_signature(value: Any) -> str:
|
|
1471
|
+
if isinstance(value, (Mapping, list, tuple, set, frozenset)):
|
|
1472
|
+
try:
|
|
1473
|
+
return json.dumps(value, sort_keys=True, ensure_ascii=False, default=str)
|
|
1474
|
+
except (TypeError, ValueError):
|
|
1475
|
+
pass
|
|
1476
|
+
return re.sub(r"\s+", " ", str(value or "")).strip().casefold()
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
def _route_contract_noop_fields(
|
|
1480
|
+
route_id: str,
|
|
1481
|
+
parent_contract: Mapping[str, Any],
|
|
1482
|
+
instantiated_contract: Mapping[str, Any],
|
|
1483
|
+
) -> List[str]:
|
|
1484
|
+
"""Identify explicit route fields that only restate the parent contract."""
|
|
1485
|
+
|
|
1486
|
+
required_fields = _ROUTE_REQUIRED_CONTRACT_FIELDS.get(route_id, ())
|
|
1487
|
+
if route_id == "mechanism_replacement":
|
|
1488
|
+
child_values = [
|
|
1489
|
+
(field_name, instantiated_contract.get(field_name))
|
|
1490
|
+
for field_name in required_fields
|
|
1491
|
+
if _nonempty_contract_value(instantiated_contract.get(field_name))
|
|
1492
|
+
]
|
|
1493
|
+
if child_values and all(
|
|
1494
|
+
_nonempty_contract_value(parent_contract.get(field_name))
|
|
1495
|
+
and _contract_value_signature(child_value)
|
|
1496
|
+
== _contract_value_signature(parent_contract.get(field_name))
|
|
1497
|
+
for field_name, child_value in child_values
|
|
1498
|
+
):
|
|
1499
|
+
return ["mechanism_or_relation"]
|
|
1500
|
+
return []
|
|
1501
|
+
noops: List[str] = []
|
|
1502
|
+
for field_name in required_fields:
|
|
1503
|
+
child_value = instantiated_contract.get(field_name)
|
|
1504
|
+
parent_value = parent_contract.get(field_name)
|
|
1505
|
+
if (
|
|
1506
|
+
_nonempty_contract_value(child_value)
|
|
1507
|
+
and _nonempty_contract_value(parent_value)
|
|
1508
|
+
and _contract_value_signature(child_value)
|
|
1509
|
+
== _contract_value_signature(parent_value)
|
|
1510
|
+
):
|
|
1511
|
+
noops.append(field_name)
|
|
1512
|
+
return noops
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def _route_parent_values(
|
|
1516
|
+
route_id: str,
|
|
1517
|
+
parent_contract: Mapping[str, Any],
|
|
1518
|
+
) -> Dict[str, Any]:
|
|
1519
|
+
return {
|
|
1520
|
+
field_name: deepcopy(parent_contract[field_name])
|
|
1521
|
+
for field_name in _ROUTE_REQUIRED_CONTRACT_FIELDS.get(route_id, ())
|
|
1522
|
+
if _nonempty_contract_value(parent_contract.get(field_name))
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def materialize_child_state(
|
|
1527
|
+
mcts: Any,
|
|
1528
|
+
parent_state: Any,
|
|
1529
|
+
plan: Any,
|
|
1530
|
+
instantiated: Optional[Dict[str, Any]] = None,
|
|
1531
|
+
selection_metadata: Optional[Dict[str, Any]] = None,
|
|
1532
|
+
*,
|
|
1533
|
+
idea_state_cls: Any,
|
|
1534
|
+
) -> Any:
|
|
1535
|
+
new_components = apply_edit_plan_to_components(parent_state.components, plan)
|
|
1536
|
+
component_explanations = build_child_component_explanations(
|
|
1537
|
+
parent_state,
|
|
1538
|
+
new_components,
|
|
1539
|
+
plan,
|
|
1540
|
+
instantiated,
|
|
1541
|
+
)
|
|
1542
|
+
|
|
1543
|
+
inst = instantiated or {}
|
|
1544
|
+
title = inst.get("title") or f"{parent_state.title} | {plan.skill_name.replace('-', ' ').title()}"
|
|
1545
|
+
abstract = inst.get("abstract") or (
|
|
1546
|
+
f"Component-level macro action '{plan.skill_name}' targets defects "
|
|
1547
|
+
f"{', '.join(plan.target_defects)} via {len(plan.component_edits)} atomic edits."
|
|
1548
|
+
)
|
|
1549
|
+
core = inst.get("core_contribution") or plan.objective
|
|
1550
|
+
method = inst.get("method") or plan_to_method_text(plan)
|
|
1551
|
+
risks = inst.get("risks") or f"Guardrails: {'; '.join(plan.guardrails)}"
|
|
1552
|
+
rationale = inst.get("rationale") or plan.compile_notes
|
|
1553
|
+
tags = _dedupe_keep_order_strings(list(parent_state.tags) + [plan.skill_name] + list(plan.target_defects))
|
|
1554
|
+
selection_metadata = selection_metadata or {}
|
|
1555
|
+
skill_metrics = {
|
|
1556
|
+
"idea_taste_mode": str(selection_metadata.get("idea_taste_mode") or "none"),
|
|
1557
|
+
"skill_prior_before": mcts._skill_prior_for_prompt(plan.skill_name),
|
|
1558
|
+
"guardrails": plan.guardrails,
|
|
1559
|
+
"constraints": ANTI_PATTERN_CONSTRAINTS,
|
|
1560
|
+
"llm_instantiated": bool(inst),
|
|
1561
|
+
}
|
|
1562
|
+
skill_selection_breakdown = selection_metadata.get("skill_selection_breakdown")
|
|
1563
|
+
if isinstance(skill_selection_breakdown, dict) and skill_selection_breakdown:
|
|
1564
|
+
skill_metrics["skill_selection_breakdown"] = skill_selection_breakdown
|
|
1565
|
+
scientific_intervention = dict(
|
|
1566
|
+
getattr(parent_state, "scientific_intervention", {}) or {}
|
|
1567
|
+
)
|
|
1568
|
+
coverage_branch = selection_metadata.get("data_anchored_coverage_branch")
|
|
1569
|
+
if isinstance(coverage_branch, Mapping):
|
|
1570
|
+
coverage_branch = dict(coverage_branch)
|
|
1571
|
+
branch_id = str(coverage_branch.get("branch_id") or "").strip()
|
|
1572
|
+
if branch_id:
|
|
1573
|
+
tags = _dedupe_keep_order_strings(
|
|
1574
|
+
[*tags, f"data_coverage:{branch_id}"]
|
|
1575
|
+
)
|
|
1576
|
+
skill_metrics["data_anchored_coverage_branch"] = coverage_branch
|
|
1577
|
+
scientific_intervention["data_anchored_coverage_branch"] = coverage_branch
|
|
1578
|
+
inherited_data_contract = scientific_intervention.get("data_anchored_contract")
|
|
1579
|
+
if isinstance(inherited_data_contract, Mapping):
|
|
1580
|
+
inherited_data_contract = dict(inherited_data_contract)
|
|
1581
|
+
inherited_data_contract["active_coverage_branch"] = coverage_branch
|
|
1582
|
+
scientific_intervention["data_anchored_contract"] = inherited_data_contract
|
|
1583
|
+
parent_contract = _parent_hypothesis_contract(parent_state)
|
|
1584
|
+
instantiated_contract = _contract_from_instantiation(inst)
|
|
1585
|
+
scientific_contract = merge_hypothesis_contract(
|
|
1586
|
+
parent_contract,
|
|
1587
|
+
instantiated_contract,
|
|
1588
|
+
)
|
|
1589
|
+
if scientific_contract:
|
|
1590
|
+
scientific_intervention["hypothesis_contract"] = scientific_contract
|
|
1591
|
+
direction_mode = str(
|
|
1592
|
+
scientific_contract.get("direction_mode")
|
|
1593
|
+
or selection_metadata.get("idea_taste_mode")
|
|
1594
|
+
or getattr(getattr(mcts, "idea_taste_preset", None), "mode", None)
|
|
1595
|
+
or "default"
|
|
1596
|
+
).strip()
|
|
1597
|
+
if direction_mode:
|
|
1598
|
+
scientific_intervention["direction_mode"] = direction_mode
|
|
1599
|
+
direction_summary = str(
|
|
1600
|
+
scientific_contract.get("direction_summary")
|
|
1601
|
+
or getattr(getattr(mcts, "idea_taste_preset", None), "summary", None)
|
|
1602
|
+
or ""
|
|
1603
|
+
).strip()
|
|
1604
|
+
if direction_summary:
|
|
1605
|
+
scientific_intervention["direction_summary"] = direction_summary
|
|
1606
|
+
route_id = str(getattr(mcts, "route_id", "") or "").strip()
|
|
1607
|
+
incomplete_route_fields = _route_contract_incomplete_fields(
|
|
1608
|
+
route_id,
|
|
1609
|
+
parent_contract,
|
|
1610
|
+
instantiated_contract,
|
|
1611
|
+
)
|
|
1612
|
+
if incomplete_route_fields:
|
|
1613
|
+
scientific_intervention["route_contract_incomplete_fields"] = incomplete_route_fields
|
|
1614
|
+
else:
|
|
1615
|
+
scientific_intervention.pop("route_contract_incomplete_fields", None)
|
|
1616
|
+
noop_route_fields = _route_contract_noop_fields(
|
|
1617
|
+
route_id,
|
|
1618
|
+
parent_contract,
|
|
1619
|
+
instantiated_contract,
|
|
1620
|
+
)
|
|
1621
|
+
if noop_route_fields:
|
|
1622
|
+
scientific_intervention["route_contract_noop_fields"] = noop_route_fields
|
|
1623
|
+
else:
|
|
1624
|
+
scientific_intervention.pop("route_contract_noop_fields", None)
|
|
1625
|
+
if incomplete_route_fields or noop_route_fields:
|
|
1626
|
+
scientific_intervention["route_contract_parent_values"] = _route_parent_values(
|
|
1627
|
+
route_id,
|
|
1628
|
+
parent_contract,
|
|
1629
|
+
)
|
|
1630
|
+
else:
|
|
1631
|
+
scientific_intervention.pop("route_contract_parent_values", None)
|
|
1632
|
+
return idea_state_cls(
|
|
1633
|
+
title=title,
|
|
1634
|
+
abstract=abstract,
|
|
1635
|
+
core_contribution=core,
|
|
1636
|
+
method=method,
|
|
1637
|
+
risks=risks,
|
|
1638
|
+
tags=tags,
|
|
1639
|
+
operator=plan.skill_name,
|
|
1640
|
+
target_defects=plan.target_defects,
|
|
1641
|
+
rationale=rationale,
|
|
1642
|
+
memory_refs=plan.memory_refs,
|
|
1643
|
+
components=new_components,
|
|
1644
|
+
component_explanations=component_explanations,
|
|
1645
|
+
root_domains=list(getattr(parent_state, "root_domains", []) or []),
|
|
1646
|
+
discipline_resolution=dict(
|
|
1647
|
+
getattr(parent_state, "discipline_resolution", {}) or {}
|
|
1648
|
+
),
|
|
1649
|
+
scientific_intervention=scientific_intervention,
|
|
1650
|
+
paper_graph_context=(
|
|
1651
|
+
str(inst.get("_paper_graph_context") or "")
|
|
1652
|
+
if isinstance(inst, dict) and str(inst.get("_paper_graph_context") or "").strip()
|
|
1653
|
+
else parent_state.paper_graph_context
|
|
1654
|
+
),
|
|
1655
|
+
edit_plan=plan.to_dict(),
|
|
1656
|
+
skill_metrics=skill_metrics,
|
|
1657
|
+
)
|
|
1658
|
+
|
|
1659
|
+
def apply_instantiated_mapping_to_plan(
|
|
1660
|
+
plan: Any,
|
|
1661
|
+
instantiated: Optional[Dict[str, Any]],
|
|
1662
|
+
*,
|
|
1663
|
+
rewrite_components: bool = True,
|
|
1664
|
+
) -> None:
|
|
1665
|
+
if not instantiated:
|
|
1666
|
+
return
|
|
1667
|
+
edit_reasons = instantiated.get("edit_reasons")
|
|
1668
|
+
if isinstance(edit_reasons, list):
|
|
1669
|
+
for reason_idx, edit in enumerate(plan.component_edits):
|
|
1670
|
+
if reason_idx < len(edit_reasons) and isinstance(edit_reasons[reason_idx], str):
|
|
1671
|
+
edit.reason = edit_reasons[reason_idx]
|
|
1672
|
+
if not rewrite_components or not isinstance(instantiated.get("component_mapping"), dict):
|
|
1673
|
+
return
|
|
1674
|
+
mapping = _normalize_component_mapping(instantiated.get("component_mapping"))
|
|
1675
|
+
|
|
1676
|
+
for edit in plan.component_edits:
|
|
1677
|
+
component_name = _coerce_component_name(edit.component)
|
|
1678
|
+
target_name = _coerce_component_name(edit.target)
|
|
1679
|
+
edit.component = mapping.get(component_name, component_name)
|
|
1680
|
+
edit.target = mapping.get(target_name, target_name) if target_name else ""
|
|
1681
|
+
if edit.op == AtomicEditOp.REWIRE:
|
|
1682
|
+
edit.details = f"Rewire {edit.component} -> {edit.target}"
|
|
1683
|
+
elif edit.op == AtomicEditOp.REPLACE_COMPONENT:
|
|
1684
|
+
edit.details = f"Replace {edit.target} with {edit.component}"
|
|
1685
|
+
elif edit.op == AtomicEditOp.ADD_COMPONENT:
|
|
1686
|
+
edit.details = f"ADD_COMPONENT on {edit.component}"
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
def instantiate_compiled_plan_for_node(
|
|
1690
|
+
mcts: Any,
|
|
1691
|
+
plan: Any,
|
|
1692
|
+
parent_state: Any,
|
|
1693
|
+
bundle: Any,
|
|
1694
|
+
*,
|
|
1695
|
+
prompt_template: str,
|
|
1696
|
+
plan_name: str,
|
|
1697
|
+
plan_references: str = "None.",
|
|
1698
|
+
taste_guidance: str = "No special taste guidance.",
|
|
1699
|
+
root_domains_text: str = "Unspecified",
|
|
1700
|
+
additional_retrieval_context: str = "",
|
|
1701
|
+
stage: str = "mcts_expand",
|
|
1702
|
+
) -> Optional[Dict[str, Any]]:
|
|
1703
|
+
component_edits_text = plan_to_method_text(plan)
|
|
1704
|
+
validation_text = plan_to_experiment_text(plan)
|
|
1705
|
+
schema = getattr(plan, "scientific_object_schema", {}) or {}
|
|
1706
|
+
native_guidance = list(plan.guardrails)
|
|
1707
|
+
if schema:
|
|
1708
|
+
native_guidance.extend(
|
|
1709
|
+
[
|
|
1710
|
+
"Profile rendering: " + str(getattr(plan, "profile_rendering_id", "") or "native"),
|
|
1711
|
+
"Allowed scientific object types: " + ", ".join(str(item) for item in schema.get("object_types", [])),
|
|
1712
|
+
"Preferred native operations: " + ", ".join(str(item) for item in schema.get("allowed_operations", [])),
|
|
1713
|
+
"Keep target_object, claim_delta, mechanism_delta, evidence_obligation, boundary_condition, and measurement_or_observation explicit.",
|
|
1714
|
+
]
|
|
1715
|
+
)
|
|
1716
|
+
|
|
1717
|
+
prompt = prompt_template.format(
|
|
1718
|
+
topic=mcts.topic,
|
|
1719
|
+
root_domains=root_domains_text,
|
|
1720
|
+
scientific_intervention_profile=format_scientific_intervention_profile_for_prompt(
|
|
1721
|
+
getattr(parent_state, "scientific_intervention", None)
|
|
1722
|
+
),
|
|
1723
|
+
refinement_scope=mcts.refinement_scope or "None",
|
|
1724
|
+
taste_guidance=taste_guidance,
|
|
1725
|
+
mature_idea=mcts.mature_idea or "None",
|
|
1726
|
+
parent_summary=parent_state.describe(),
|
|
1727
|
+
parent_components=", ".join(parent_state.components) if parent_state.components else "None",
|
|
1728
|
+
paper_context=mcts.paper_context,
|
|
1729
|
+
memory_bundle=bundle.to_prompt_block(),
|
|
1730
|
+
skill_references=plan_references,
|
|
1731
|
+
additional_retrieval_context=additional_retrieval_context,
|
|
1732
|
+
skill_name=plan_name,
|
|
1733
|
+
plan_objective=plan.objective,
|
|
1734
|
+
target_defects=", ".join(plan.target_defects),
|
|
1735
|
+
component_edits=component_edits_text,
|
|
1736
|
+
validation_protocols=validation_text,
|
|
1737
|
+
guardrails="; ".join(native_guidance) if native_guidance else "None",
|
|
1738
|
+
)
|
|
1739
|
+
direction_context = _direction_prompt_context(
|
|
1740
|
+
mcts,
|
|
1741
|
+
parent_state,
|
|
1742
|
+
object_schema=schema if isinstance(schema, dict) else None,
|
|
1743
|
+
)
|
|
1744
|
+
prompt += (
|
|
1745
|
+
"\n\n== Direction and Gap-to-Hypothesis Contract ==\n"
|
|
1746
|
+
f"Direction mode: {direction_context['direction_mode']}\n"
|
|
1747
|
+
f"Direction summary: {direction_context['direction_summary']}\n"
|
|
1748
|
+
f"Taste guidance: {direction_context['taste_guidance']}\n"
|
|
1749
|
+
f"Mature idea seed ID: {direction_context['seed_id']}\n"
|
|
1750
|
+
f"Search route ID: {direction_context['route_id']}\n"
|
|
1751
|
+
f"Search route policy: {direction_context['route_policy']}\n"
|
|
1752
|
+
f"Target gap IDs: {direction_context['target_gap_ids']}\n"
|
|
1753
|
+
f"Resolved profile ID: {direction_context['profile_id']}\n"
|
|
1754
|
+
"Profile-native scientific object schema:\n"
|
|
1755
|
+
f"{direction_context['profile_native_object_schema']}\n"
|
|
1756
|
+
"Gap-to-hypothesis seeds:\n"
|
|
1757
|
+
f"{direction_context['gap_seed_context']}\n"
|
|
1758
|
+
"\n== Required Directional Scientific Fields ==\n"
|
|
1759
|
+
"Return these fields in the JSON payload, either at the top level or mirrored inside scientific_contract: "
|
|
1760
|
+
"direction_mode, direction_summary, central_hypothesis, scientific_object, mechanism_or_relation, "
|
|
1761
|
+
"intervention_or_transformation, expected_mechanism, discriminating_observation, "
|
|
1762
|
+
"boundary_or_failure_condition, claim_scope, assumptions, target_gap_ids, gap_alignment, "
|
|
1763
|
+
"evidence_requirement, and evidence_basis.\n"
|
|
1764
|
+
"evidence_requirement must name the observation, contrast, proof, measurement, or boundary needed "
|
|
1765
|
+
"to distinguish the claim; it must not contain an experiment design. Do not return predicted results, "
|
|
1766
|
+
"sample sizes, statistical tests, instrument configurations, ablation plans, or failure-repair plans."
|
|
1767
|
+
)
|
|
1768
|
+
prompt += _data_anchored_contract_prompt(parent_state)
|
|
1769
|
+
survey_handoff = getattr(mcts, "survey_idea_handoff", {})
|
|
1770
|
+
if isinstance(survey_handoff, dict) and survey_handoff:
|
|
1771
|
+
prompt += "\n\n== Verified Survey -> Idea handoff ==\n" + pretty_json(survey_handoff)
|
|
1772
|
+
try:
|
|
1773
|
+
response = mcts.chat_fn(
|
|
1774
|
+
prompt,
|
|
1775
|
+
model=mcts.config.generation_model,
|
|
1776
|
+
stage=stage,
|
|
1777
|
+
temperature=mcts.config.generation_temperature,
|
|
1778
|
+
max_output_tokens=mcts.config.generation_max_tokens,
|
|
1779
|
+
)
|
|
1780
|
+
payload = parse_json_response(response)
|
|
1781
|
+
if isinstance(payload, list):
|
|
1782
|
+
payload = payload[0]
|
|
1783
|
+
if not isinstance(payload, dict):
|
|
1784
|
+
return None
|
|
1785
|
+
contract = payload.get("scientific_contract")
|
|
1786
|
+
if not isinstance(contract, dict):
|
|
1787
|
+
contract = {
|
|
1788
|
+
key: payload.get(key)
|
|
1789
|
+
for key in (
|
|
1790
|
+
"contribution_mode",
|
|
1791
|
+
"scientific_object",
|
|
1792
|
+
"central_hypothesis",
|
|
1793
|
+
"intervention_or_transformation",
|
|
1794
|
+
"expected_mechanism",
|
|
1795
|
+
"discriminating_observation",
|
|
1796
|
+
"boundary_or_failure_condition",
|
|
1797
|
+
"evidence_requirement",
|
|
1798
|
+
)
|
|
1799
|
+
if payload.get(key) not in (None, "", [], {})
|
|
1800
|
+
}
|
|
1801
|
+
if contract:
|
|
1802
|
+
payload["scientific_contract"] = contract
|
|
1803
|
+
payload["component_mapping"] = _filter_component_mapping_to_plan_keys(
|
|
1804
|
+
payload.get("component_mapping"),
|
|
1805
|
+
plan,
|
|
1806
|
+
)
|
|
1807
|
+
payload["component_role_explanations"] = normalize_component_explanations(
|
|
1808
|
+
list(payload["component_mapping"].values()),
|
|
1809
|
+
payload.get("component_role_explanations"),
|
|
1810
|
+
)
|
|
1811
|
+
return payload
|
|
1812
|
+
except Exception as exc:
|
|
1813
|
+
log_message(
|
|
1814
|
+
mcts.logger,
|
|
1815
|
+
mcts.log_sink,
|
|
1816
|
+
"warning",
|
|
1817
|
+
"⚠️ Plan instantiation failed for %s: %s",
|
|
1818
|
+
plan_name,
|
|
1819
|
+
exc,
|
|
1820
|
+
)
|
|
1821
|
+
return None
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
def instantiate_skill_plan_for_node(
|
|
1825
|
+
mcts: Any,
|
|
1826
|
+
plan: Any,
|
|
1827
|
+
parent_state: Any,
|
|
1828
|
+
bundle: Any,
|
|
1829
|
+
*,
|
|
1830
|
+
prompt_template: str,
|
|
1831
|
+
root_domains_text: str = "Unspecified",
|
|
1832
|
+
additional_retrieval_context: str = "",
|
|
1833
|
+
) -> Optional[Dict[str, Any]]:
|
|
1834
|
+
return instantiate_compiled_plan_for_node(
|
|
1835
|
+
mcts,
|
|
1836
|
+
plan,
|
|
1837
|
+
parent_state,
|
|
1838
|
+
bundle,
|
|
1839
|
+
prompt_template=prompt_template,
|
|
1840
|
+
plan_name=plan.skill_name,
|
|
1841
|
+
plan_references=mcts.skill_catalog.render_references_for_prompt(plan.skill_name),
|
|
1842
|
+
taste_guidance=(
|
|
1843
|
+
getattr(getattr(mcts, "idea_taste_preset", None), "instantiation_guidance", None)
|
|
1844
|
+
or "No special taste guidance."
|
|
1845
|
+
),
|
|
1846
|
+
root_domains_text=root_domains_text,
|
|
1847
|
+
additional_retrieval_context=additional_retrieval_context,
|
|
1848
|
+
stage="mcts_expand",
|
|
1849
|
+
)
|
|
1850
|
+
|
|
1851
|
+
|
|
1852
|
+
def build_symbolic_eval_hints(mcts: Any, node: Any) -> str:
|
|
1853
|
+
if not getattr(mcts, "enable_symbolic_memory", True):
|
|
1854
|
+
return "No symbolic memory hints available."
|
|
1855
|
+
component_families = extract_component_families(node.state.components, node.state.method)
|
|
1856
|
+
if not component_families:
|
|
1857
|
+
return "No symbolic memory hints available."
|
|
1858
|
+
|
|
1859
|
+
retrieved_records: List[Tuple[str, str, float, Any]] = []
|
|
1860
|
+
|
|
1861
|
+
for cf in component_families:
|
|
1862
|
+
component_name = str(cf.get("component", "") or "").strip()
|
|
1863
|
+
family = cf.get("family", "")
|
|
1864
|
+
if not component_name and not family:
|
|
1865
|
+
continue
|
|
1866
|
+
|
|
1867
|
+
records = mcts.symbolic_memory.retrieve_hierarchical(
|
|
1868
|
+
target_component=component_name,
|
|
1869
|
+
target_family=family,
|
|
1870
|
+
limit=2,
|
|
1871
|
+
threshold=0.2,
|
|
1872
|
+
agent_id="idea_agent",
|
|
1873
|
+
query_context=str(getattr(node.state, "abstract", "") or "").strip(),
|
|
1874
|
+
)
|
|
1875
|
+
if not records:
|
|
1876
|
+
continue
|
|
1877
|
+
|
|
1878
|
+
for score, rec in records:
|
|
1879
|
+
retrieved_records.append((component_name, str(family or ""), float(score), rec))
|
|
1880
|
+
|
|
1881
|
+
if not retrieved_records:
|
|
1882
|
+
return "No symbolic memory hints available."
|
|
1883
|
+
|
|
1884
|
+
deduped_records: Dict[Tuple[str, ...], Tuple[str, str, float, Any]] = {}
|
|
1885
|
+
for query_component, query_family, score, rec in retrieved_records:
|
|
1886
|
+
dedupe_key = (
|
|
1887
|
+
str(getattr(rec, "component", "") or "").strip().lower(),
|
|
1888
|
+
str(getattr(rec, "component_family", "") or "").strip().lower(),
|
|
1889
|
+
str(getattr(rec, "result", "") or "").strip().lower(),
|
|
1890
|
+
str(getattr(rec, "metric", "") or "").strip().lower(),
|
|
1891
|
+
str(getattr(rec, "value", "") or "").strip().lower(),
|
|
1892
|
+
str(getattr(rec, "analysis", "") or "").strip().lower(),
|
|
1893
|
+
)
|
|
1894
|
+
existing = deduped_records.get(dedupe_key)
|
|
1895
|
+
if existing is None or score > existing[2]:
|
|
1896
|
+
deduped_records[dedupe_key] = (query_component, query_family, score, rec)
|
|
1897
|
+
|
|
1898
|
+
if not deduped_records:
|
|
1899
|
+
return "No symbolic memory hints available."
|
|
1900
|
+
|
|
1901
|
+
hints_parts: List[str] = []
|
|
1902
|
+
for query_component, query_family, score, rec in sorted(
|
|
1903
|
+
deduped_records.values(),
|
|
1904
|
+
key=lambda item: item[2],
|
|
1905
|
+
reverse=True,
|
|
1906
|
+
):
|
|
1907
|
+
component = clip_text(getattr(rec, "component", "")) or "unknown_component"
|
|
1908
|
+
component_family = clip_text(getattr(rec, "component_family", "")) or "unknown_family"
|
|
1909
|
+
result = clip_text(getattr(rec, "result", "")) or "inconclusive"
|
|
1910
|
+
metric = clip_text(getattr(rec, "metric", "")) or "unspecified_metric"
|
|
1911
|
+
value = clip_text(getattr(rec, "value", "")) or "unspecified_value"
|
|
1912
|
+
confidence = float(getattr(rec, "confidence", 0.0))
|
|
1913
|
+
analysis = clip_text(getattr(rec, "analysis", ""))
|
|
1914
|
+
line = (
|
|
1915
|
+
f" - query_component={query_component or 'unknown_component'}"
|
|
1916
|
+
f" | query_family={query_family or 'unknown_family'}"
|
|
1917
|
+
f" | matched_family={component_family}"
|
|
1918
|
+
f" | component={component}"
|
|
1919
|
+
f" | result={result} | metric={metric} | value={value}"
|
|
1920
|
+
f" (conf={confidence:.2f}, score={score:.3f})"
|
|
1921
|
+
)
|
|
1922
|
+
if analysis:
|
|
1923
|
+
line += f" analysis: {analysis}"
|
|
1924
|
+
hints_parts.append(line)
|
|
1925
|
+
|
|
1926
|
+
header = (
|
|
1927
|
+
"Historical ablation records for the component families in this idea "
|
|
1928
|
+
"(positive result means removing the component helped, "
|
|
1929
|
+
"negative result means removing the component hurt):"
|
|
1930
|
+
)
|
|
1931
|
+
return header + "\n" + "\n".join(hints_parts)
|
|
1932
|
+
|
|
1933
|
+
|
|
1934
|
+
def update_skill_prior_from_evaluation(mcts: Any, node: Any, evaluation: Any) -> None:
|
|
1935
|
+
skill_name = node.state.operator
|
|
1936
|
+
if not skill_name or skill_name == "seed":
|
|
1937
|
+
return
|
|
1938
|
+
|
|
1939
|
+
normalized_reward = max(0.0, min(1.0, evaluation.composite / 5.0))
|
|
1940
|
+
prior = mcts.skill_catalog.update_prior(
|
|
1941
|
+
skill_name=skill_name,
|
|
1942
|
+
reward=normalized_reward,
|
|
1943
|
+
feedback=evaluation.feedback,
|
|
1944
|
+
failure_modes=evaluation.failure_modes,
|
|
1945
|
+
success_threshold=mcts.config.skill_prior_success_threshold,
|
|
1946
|
+
)
|
|
1947
|
+
node.state.skill_metrics["skill_prior_after"] = prior.to_dict()
|
|
1948
|
+
|
|
1949
|
+
|
|
1950
|
+
def extract_mature_idea_components_via_llm(
|
|
1951
|
+
mcts: Any,
|
|
1952
|
+
mature_idea: str,
|
|
1953
|
+
topic: str,
|
|
1954
|
+
*,
|
|
1955
|
+
prompt_template: str,
|
|
1956
|
+
max_components: int,
|
|
1957
|
+
prior_components: Optional[Sequence[str]] = None,
|
|
1958
|
+
prior_component_explanations: Optional[Any] = None,
|
|
1959
|
+
component_decisions: Optional[Sequence[Dict[str, Any]]] = None,
|
|
1960
|
+
) -> Tuple[List[str], Dict[str, str]]:
|
|
1961
|
+
normalized_prior_components = [
|
|
1962
|
+
str(component).strip()
|
|
1963
|
+
for component in (prior_components or [])
|
|
1964
|
+
if str(component).strip()
|
|
1965
|
+
]
|
|
1966
|
+
normalized_prior_explanations = normalize_component_explanations(
|
|
1967
|
+
normalized_prior_components,
|
|
1968
|
+
prior_component_explanations,
|
|
1969
|
+
)
|
|
1970
|
+
normalized_component_decisions = [
|
|
1971
|
+
decision for decision in (component_decisions or []) if isinstance(decision, dict)
|
|
1972
|
+
]
|
|
1973
|
+
prompt = prompt_template.format(
|
|
1974
|
+
mature_idea=mature_idea,
|
|
1975
|
+
topic=topic,
|
|
1976
|
+
scientific_intervention_profile=format_scientific_intervention_profile_for_prompt(
|
|
1977
|
+
getattr(mcts, "scientific_intervention_profile", None)
|
|
1978
|
+
),
|
|
1979
|
+
prior_components=pretty_json(
|
|
1980
|
+
component_inventory_payload(
|
|
1981
|
+
normalized_prior_components,
|
|
1982
|
+
normalized_prior_explanations,
|
|
1983
|
+
),
|
|
1984
|
+
)
|
|
1985
|
+
if normalized_prior_components
|
|
1986
|
+
else "[]",
|
|
1987
|
+
component_decisions=pretty_json(normalized_component_decisions)
|
|
1988
|
+
if normalized_component_decisions
|
|
1989
|
+
else "[]",
|
|
1990
|
+
)
|
|
1991
|
+
try:
|
|
1992
|
+
response = mcts.chat_fn(
|
|
1993
|
+
prompt,
|
|
1994
|
+
model=mcts.config.generation_model,
|
|
1995
|
+
temperature=0.3,
|
|
1996
|
+
max_output_tokens=mcts.config.generation_max_tokens,
|
|
1997
|
+
)
|
|
1998
|
+
payload = parse_json_response(response)
|
|
1999
|
+
if isinstance(payload, list):
|
|
2000
|
+
payload = payload[0]
|
|
2001
|
+
components, explanations = parse_component_bundle_payload(
|
|
2002
|
+
payload,
|
|
2003
|
+
max_components=max_components,
|
|
2004
|
+
)
|
|
2005
|
+
if components:
|
|
2006
|
+
return components, explanations
|
|
2007
|
+
except Exception as exc:
|
|
2008
|
+
log_message(
|
|
2009
|
+
mcts.logger,
|
|
2010
|
+
mcts.log_sink,
|
|
2011
|
+
"warning",
|
|
2012
|
+
"⚠️ Component extraction from mature idea failed: %s",
|
|
2013
|
+
exc,
|
|
2014
|
+
)
|
|
2015
|
+
return [], {}
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
def select_leaf_for_rollout(mcts: Any, node: Any) -> Tuple[Any, List[Any]]:
|
|
2019
|
+
current = node
|
|
2020
|
+
path = [node]
|
|
2021
|
+
while current.children and current.expanded:
|
|
2022
|
+
parent = current
|
|
2023
|
+
current = max(
|
|
2024
|
+
parent.children,
|
|
2025
|
+
key=lambda child: child.uct_value(
|
|
2026
|
+
parent_visits=parent.visits or 1,
|
|
2027
|
+
exploration_constant=mcts.config.exploration_constant,
|
|
2028
|
+
),
|
|
2029
|
+
)
|
|
2030
|
+
path.append(current)
|
|
2031
|
+
return current, path
|
|
2032
|
+
|
|
2033
|
+
|
|
2034
|
+
def expand_node_with_skills(
|
|
2035
|
+
mcts: Any,
|
|
2036
|
+
node: Any,
|
|
2037
|
+
path: List[Any],
|
|
2038
|
+
*,
|
|
2039
|
+
min_components: int,
|
|
2040
|
+
max_components: int,
|
|
2041
|
+
) -> Tuple[Optional[Any], List[Any]]:
|
|
2042
|
+
bundle = MemoryBundle()
|
|
2043
|
+
if getattr(mcts, "enable_vector_memory", True):
|
|
2044
|
+
bundle = mcts.memory_accessor.retrieve_bundle(
|
|
2045
|
+
query=(
|
|
2046
|
+
f"{mcts.topic}\n{node.state.title}\n"
|
|
2047
|
+
f"{node.state.core_contribution}\n"
|
|
2048
|
+
f"defects={','.join(node.state.target_defects)}"
|
|
2049
|
+
)
|
|
2050
|
+
)
|
|
2051
|
+
log_message(
|
|
2052
|
+
mcts.logger,
|
|
2053
|
+
mcts.log_sink,
|
|
2054
|
+
"info",
|
|
2055
|
+
"[MCTS] Expand: vector_memory\n%s",
|
|
2056
|
+
pretty_json(mcts._memory_bundle_log_payload(bundle)),
|
|
2057
|
+
)
|
|
2058
|
+
structural_profile = build_structural_profile(
|
|
2059
|
+
node.state,
|
|
2060
|
+
refinement_scope=mcts.refinement_scope,
|
|
2061
|
+
mature_idea=mcts.mature_idea,
|
|
2062
|
+
defect_tags=node.state.target_defects,
|
|
2063
|
+
profile_id=str(
|
|
2064
|
+
(getattr(node.state, "scientific_intervention", {}) or {}).get("profile_id")
|
|
2065
|
+
or "generic_scientific"
|
|
2066
|
+
).strip().lower(),
|
|
2067
|
+
)
|
|
2068
|
+
log_message(
|
|
2069
|
+
mcts.logger,
|
|
2070
|
+
mcts.log_sink,
|
|
2071
|
+
"info",
|
|
2072
|
+
"[MCTS] Expand: structural_profile\n%s",
|
|
2073
|
+
pretty_json(structural_profile),
|
|
2074
|
+
)
|
|
2075
|
+
scientific_intervention = getattr(node.state, "scientific_intervention", {}) or {}
|
|
2076
|
+
profile_id = str(
|
|
2077
|
+
scientific_intervention.get("profile_id") or "generic_scientific"
|
|
2078
|
+
).strip().lower()
|
|
2079
|
+
object_schema = scientific_intervention.get("scientific_object_schema")
|
|
2080
|
+
if not isinstance(object_schema, dict):
|
|
2081
|
+
schema_spec = get_scientific_object_schema(profile_id)
|
|
2082
|
+
object_schema = schema_spec.to_payload() if schema_spec is not None else {}
|
|
2083
|
+
selected_skill_candidates = mcts.skill_catalog.select_skills(
|
|
2084
|
+
defect_tags=node.state.target_defects,
|
|
2085
|
+
max_children=mcts.config.branching_factor,
|
|
2086
|
+
preset=getattr(mcts, "idea_taste_preset", None),
|
|
2087
|
+
structural_profile=structural_profile,
|
|
2088
|
+
profile_id=profile_id,
|
|
2089
|
+
)
|
|
2090
|
+
skill_candidates = list(selected_skill_candidates)
|
|
2091
|
+
data_contract = scientific_intervention.get("data_anchored_contract")
|
|
2092
|
+
data_contract = data_contract if isinstance(data_contract, Mapping) else {}
|
|
2093
|
+
coverage_assignment = data_contract.get("coverage_assignment")
|
|
2094
|
+
coverage_assignment = (
|
|
2095
|
+
coverage_assignment if isinstance(coverage_assignment, Mapping) else {}
|
|
2096
|
+
)
|
|
2097
|
+
coverage_branches = [
|
|
2098
|
+
dict(branch)
|
|
2099
|
+
for branch in data_contract.get("coverage_branches", [])
|
|
2100
|
+
if isinstance(branch, Mapping) and str(branch.get("branch_id") or "").strip()
|
|
2101
|
+
]
|
|
2102
|
+
is_coverage_root = bool(coverage_assignment and coverage_branches) and int(
|
|
2103
|
+
getattr(node, "depth", len(path) - 1)
|
|
2104
|
+
) == 0
|
|
2105
|
+
expansion_specs: list[tuple[Any, Mapping[str, Any] | None]]
|
|
2106
|
+
if is_coverage_root and skill_candidates:
|
|
2107
|
+
expansion_specs = [
|
|
2108
|
+
(skill_candidates[index % len(skill_candidates)], branch)
|
|
2109
|
+
for index, branch in enumerate(coverage_branches)
|
|
2110
|
+
]
|
|
2111
|
+
else:
|
|
2112
|
+
expansion_specs = [(selection_candidate, None) for selection_candidate in skill_candidates]
|
|
2113
|
+
log_message(
|
|
2114
|
+
mcts.logger,
|
|
2115
|
+
mcts.log_sink,
|
|
2116
|
+
"info",
|
|
2117
|
+
"[MCTS] Expand: skill_prior\n%s",
|
|
2118
|
+
pretty_json(
|
|
2119
|
+
{candidate.skill.name: candidate.to_dict() for candidate in skill_candidates},
|
|
2120
|
+
),
|
|
2121
|
+
)
|
|
2122
|
+
payload_count = len(skill_candidates)
|
|
2123
|
+
pre_children = len(node.children)
|
|
2124
|
+
new_child: Optional[Any] = None
|
|
2125
|
+
|
|
2126
|
+
idea_node_cls = type(node)
|
|
2127
|
+
operator_application_cls = type(node.transformation)
|
|
2128
|
+
for selection_candidate, coverage_branch in expansion_specs:
|
|
2129
|
+
skill = mcts.skill_catalog.render_skill_for_profile(
|
|
2130
|
+
selection_candidate.skill,
|
|
2131
|
+
profile_id,
|
|
2132
|
+
object_schema=object_schema,
|
|
2133
|
+
)
|
|
2134
|
+
plan = mcts.skill_catalog.compile_plan(
|
|
2135
|
+
skill=skill,
|
|
2136
|
+
parent_title=node.state.title,
|
|
2137
|
+
parent_components=node.state.components,
|
|
2138
|
+
target_defects=node.state.target_defects,
|
|
2139
|
+
memory_refs=bundle.referenced_ids(),
|
|
2140
|
+
profile_id=profile_id,
|
|
2141
|
+
object_schema=object_schema,
|
|
2142
|
+
)
|
|
2143
|
+
prior_constraints = mcts.skill_catalog.priors.get(skill.name, SkillUsagePrior()).rule_constraints
|
|
2144
|
+
if prior_constraints:
|
|
2145
|
+
plan.guardrails = _dedupe_keep_order_strings(plan.guardrails + list(prior_constraints))
|
|
2146
|
+
if coverage_branch is not None:
|
|
2147
|
+
branch_id = str(coverage_branch.get("branch_id") or "").strip()
|
|
2148
|
+
branch_instruction = str(coverage_branch.get("instruction") or "").strip()
|
|
2149
|
+
plan.objective = (
|
|
2150
|
+
f"{plan.objective} Data-coverage branch `{branch_id}`: {branch_instruction}"
|
|
2151
|
+
).strip()
|
|
2152
|
+
plan.guardrails = _dedupe_keep_order_strings(
|
|
2153
|
+
[
|
|
2154
|
+
*plan.guardrails,
|
|
2155
|
+
f"Materialize the required data-coverage branch `{branch_id}`.",
|
|
2156
|
+
"Keep this branch dataset-local; it cannot establish a universal mechanism or novelty claim.",
|
|
2157
|
+
]
|
|
2158
|
+
)
|
|
2159
|
+
plan.compile_notes = (
|
|
2160
|
+
f"{plan.compile_notes}\nRequired data-coverage branch: {branch_id}. "
|
|
2161
|
+
f"{branch_instruction}"
|
|
2162
|
+
).strip()
|
|
2163
|
+
|
|
2164
|
+
current_count = len(node.state.components)
|
|
2165
|
+
filtered_edits: List[ComponentEdit] = []
|
|
2166
|
+
for edit in plan.component_edits:
|
|
2167
|
+
if current_count <= min_components and edit.op == AtomicEditOp.REMOVE_COMPONENT:
|
|
2168
|
+
continue
|
|
2169
|
+
if current_count >= max_components and edit.op == AtomicEditOp.ADD_COMPONENT:
|
|
2170
|
+
continue
|
|
2171
|
+
if edit.op == AtomicEditOp.ADD_COMPONENT:
|
|
2172
|
+
current_count += 1
|
|
2173
|
+
elif edit.op == AtomicEditOp.REMOVE_COMPONENT:
|
|
2174
|
+
current_count -= 1
|
|
2175
|
+
filtered_edits.append(edit)
|
|
2176
|
+
plan.component_edits = filtered_edits
|
|
2177
|
+
|
|
2178
|
+
instantiated = mcts._instantiate_skill_plan(plan, node.state, bundle)
|
|
2179
|
+
if isinstance(instantiated, dict) and instantiated.get("_skip_child_creation"):
|
|
2180
|
+
log_message(
|
|
2181
|
+
mcts.logger,
|
|
2182
|
+
mcts.log_sink,
|
|
2183
|
+
"info",
|
|
2184
|
+
"[MCTS] Expand: skipping skill=%s child creation (%s)",
|
|
2185
|
+
skill.name,
|
|
2186
|
+
instantiated.get("_skip_reason", "no reason provided"),
|
|
2187
|
+
)
|
|
2188
|
+
continue
|
|
2189
|
+
log_message(
|
|
2190
|
+
mcts.logger,
|
|
2191
|
+
mcts.log_sink,
|
|
2192
|
+
"info",
|
|
2193
|
+
"[MCTS] Expand: skill=%s instantiation_result\n%s",
|
|
2194
|
+
skill.name,
|
|
2195
|
+
pretty_json(
|
|
2196
|
+
instantiated
|
|
2197
|
+
if instantiated is not None
|
|
2198
|
+
else {"status": "empty", "message": "instantiation returned no output"},
|
|
2199
|
+
),
|
|
2200
|
+
)
|
|
2201
|
+
|
|
2202
|
+
apply_instantiated_mapping_to_plan(plan, instantiated)
|
|
2203
|
+
|
|
2204
|
+
protocol_names: List[str] = []
|
|
2205
|
+
for edit_idx, edit in enumerate(plan.component_edits):
|
|
2206
|
+
if edit.op == AtomicEditOp.ADD_PROTOCOL:
|
|
2207
|
+
protocol_names.append(edit.component)
|
|
2208
|
+
continue
|
|
2209
|
+
op_dict = {
|
|
2210
|
+
"op": edit.op.value if hasattr(edit.op, "value") else edit.op,
|
|
2211
|
+
"component": edit.component,
|
|
2212
|
+
"target": edit.target,
|
|
2213
|
+
"condition": edit.condition,
|
|
2214
|
+
"details": edit.details or "",
|
|
2215
|
+
"reason": edit.reason or "",
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
selection_metadata: Dict[str, Any] = {
|
|
2219
|
+
"idea_taste_mode": getattr(getattr(mcts, "idea_taste_preset", None), "mode", None) or "none",
|
|
2220
|
+
"skill_selection_breakdown": {
|
|
2221
|
+
"defect_score": selection_candidate.defect_score,
|
|
2222
|
+
"prior_score": selection_candidate.prior_score,
|
|
2223
|
+
"preset_bias": selection_candidate.preset_bias,
|
|
2224
|
+
"selection_total": selection_candidate.selection_total,
|
|
2225
|
+
},
|
|
2226
|
+
}
|
|
2227
|
+
if coverage_branch is not None:
|
|
2228
|
+
selection_metadata["data_anchored_coverage_branch"] = dict(coverage_branch)
|
|
2229
|
+
child_state = mcts._materialize_child_state(
|
|
2230
|
+
node.state,
|
|
2231
|
+
plan,
|
|
2232
|
+
instantiated,
|
|
2233
|
+
selection_metadata=selection_metadata,
|
|
2234
|
+
)
|
|
2235
|
+
child_node = attach_child(
|
|
2236
|
+
node,
|
|
2237
|
+
child_state,
|
|
2238
|
+
signature_nodes=mcts.signature_nodes,
|
|
2239
|
+
id_iter=mcts._id_iter,
|
|
2240
|
+
idea_node_cls=idea_node_cls,
|
|
2241
|
+
operator_application_cls=operator_application_cls,
|
|
2242
|
+
logger=mcts.logger,
|
|
2243
|
+
log_sink=mcts.log_sink,
|
|
2244
|
+
)
|
|
2245
|
+
if child_node is None:
|
|
2246
|
+
continue
|
|
2247
|
+
cached_eval = get_best_cached_evaluation(child_state.signature, mcts.evaluation_cache)
|
|
2248
|
+
if cached_eval:
|
|
2249
|
+
child_node.evaluation = cached_eval
|
|
2250
|
+
if new_child is None and child_node.visits == 0:
|
|
2251
|
+
new_child = child_node
|
|
2252
|
+
|
|
2253
|
+
node.expanded = True
|
|
2254
|
+
|
|
2255
|
+
if new_child is None and node.children:
|
|
2256
|
+
new_child = min(node.children, key=lambda c: c.visits)
|
|
2257
|
+
if new_child:
|
|
2258
|
+
return new_child, path + [new_child]
|
|
2259
|
+
return node, path
|
|
2260
|
+
|
|
2261
|
+
|
|
2262
|
+
def apply_profile_drift_to_evaluation(
|
|
2263
|
+
node: Any,
|
|
2264
|
+
evaluation: Any,
|
|
2265
|
+
profile_drift: Mapping[str, Any],
|
|
2266
|
+
) -> None:
|
|
2267
|
+
"""Apply a soft profile-drift penalty without rejecting the candidate."""
|
|
2268
|
+
|
|
2269
|
+
severity = str(profile_drift.get("drift_severity") or "none").strip().lower()
|
|
2270
|
+
if severity == "none":
|
|
2271
|
+
return
|
|
2272
|
+
intervention_payload = getattr(node.state, "scientific_intervention", None)
|
|
2273
|
+
if isinstance(intervention_payload, dict):
|
|
2274
|
+
intervention_payload["profile_drift"] = dict(profile_drift)
|
|
2275
|
+
detected_defects = getattr(evaluation, "detected_defects", None)
|
|
2276
|
+
if isinstance(detected_defects, list) and "profile_drift" not in detected_defects:
|
|
2277
|
+
detected_defects.append("profile_drift")
|
|
2278
|
+
target_defects = getattr(node.state, "target_defects", None)
|
|
2279
|
+
if isinstance(target_defects, list) and "profile_drift" not in target_defects:
|
|
2280
|
+
target_defects.append("profile_drift")
|
|
2281
|
+
if "profile_drift" in (detected_defects or []):
|
|
2282
|
+
penalty = 1.0 if severity == "material" else 0.5
|
|
2283
|
+
current_alignment = float(getattr(evaluation, "alignment_score", 0.0) or 0.0)
|
|
2284
|
+
if not getattr(evaluation, "_profile_drift_penalized", False):
|
|
2285
|
+
evaluation.alignment_score = max(0, round(current_alignment - penalty))
|
|
2286
|
+
setattr(evaluation, "_profile_drift_penalized", True)
|
|
2287
|
+
drift_terms = profile_drift.get("forbidden_primary_terms") or profile_drift.get("secondary_tool_mentions") or []
|
|
2288
|
+
drift_note = "Profile drift detected: " + ", ".join(str(term) for term in drift_terms)
|
|
2289
|
+
feedback = str(getattr(evaluation, "feedback", "") or "")
|
|
2290
|
+
if drift_note not in feedback:
|
|
2291
|
+
evaluation.feedback = f"{feedback}; {drift_note}" if feedback else drift_note
|
|
2292
|
+
|
|
2293
|
+
|
|
2294
|
+
def simulate_node_value(
|
|
2295
|
+
mcts: Any,
|
|
2296
|
+
node: Any,
|
|
2297
|
+
path: List[Any],
|
|
2298
|
+
experiences: List[Dict[str, Any]],
|
|
2299
|
+
*,
|
|
2300
|
+
idea_evaluation_cls: Any,
|
|
2301
|
+
) -> Optional[Any]:
|
|
2302
|
+
path_summary_text = path_summary(path)
|
|
2303
|
+
scientific_intervention = getattr(node.state, "scientific_intervention", {})
|
|
2304
|
+
profile_id = str(
|
|
2305
|
+
scientific_intervention.get("profile_id") or "generic_scientific"
|
|
2306
|
+
).strip().lower()
|
|
2307
|
+
|
|
2308
|
+
symbolic_hints = mcts._build_symbolic_eval_hints(node)
|
|
2309
|
+
log_message(
|
|
2310
|
+
mcts.logger,
|
|
2311
|
+
mcts.log_sink,
|
|
2312
|
+
"info",
|
|
2313
|
+
"[MCTS] Simulate: symbolic_memory\n%s",
|
|
2314
|
+
symbolic_hints,
|
|
2315
|
+
)
|
|
2316
|
+
|
|
2317
|
+
direction_context = _direction_prompt_context(mcts, node.state)
|
|
2318
|
+
prompt = mcts.evaluation_prompt.format(
|
|
2319
|
+
topic=mcts.topic,
|
|
2320
|
+
direction_mode=direction_context["direction_mode"],
|
|
2321
|
+
direction_summary=direction_context["direction_summary"],
|
|
2322
|
+
taste_guidance=direction_context["taste_guidance"],
|
|
2323
|
+
target_gap_ids=direction_context["target_gap_ids"],
|
|
2324
|
+
profile_id=direction_context["profile_id"],
|
|
2325
|
+
gap_seed_context=direction_context["gap_seed_context"],
|
|
2326
|
+
root_domains=_format_root_domains_for_prompt(getattr(node.state, "root_domains", [])),
|
|
2327
|
+
refinement_scope=mcts.refinement_scope or "None",
|
|
2328
|
+
mature_idea=mcts.mature_idea or "None",
|
|
2329
|
+
edit_plan=format_evaluator_edit_plan_prompt_view(node.state.edit_plan)
|
|
2330
|
+
if node.state.edit_plan
|
|
2331
|
+
else "No edit plan available.",
|
|
2332
|
+
idea=format_evaluator_idea_prompt_view(node.state, heading="Candidate Idea"),
|
|
2333
|
+
scientific_intervention_profile=format_scientific_intervention_profile_for_prompt(
|
|
2334
|
+
scientific_intervention
|
|
2335
|
+
),
|
|
2336
|
+
profile_native_object_schema=direction_context["profile_native_object_schema"],
|
|
2337
|
+
scientific_rubric=format_scientific_rubric_for_prompt(scientific_intervention),
|
|
2338
|
+
defect_registry=format_defect_registry(profile_id),
|
|
2339
|
+
symbolic_memory_hints=symbolic_hints,
|
|
2340
|
+
)
|
|
2341
|
+
prompt += _data_anchored_contract_prompt(node.state)
|
|
2342
|
+
survey_handoff = getattr(mcts, "survey_idea_handoff", {})
|
|
2343
|
+
if isinstance(survey_handoff, dict) and survey_handoff:
|
|
2344
|
+
prompt += "\n\n== Verified Survey -> Idea handoff ==\n" + pretty_json(survey_handoff)
|
|
2345
|
+
prompt = render_profile_evaluation_prompt(prompt, profile_id)
|
|
2346
|
+
allowed_novelty_axes = PROFILE_NOVELTY_AXES.get(
|
|
2347
|
+
profile_id,
|
|
2348
|
+
PROFILE_NOVELTY_AXES["generic_scientific"],
|
|
2349
|
+
)
|
|
2350
|
+
prompt += (
|
|
2351
|
+
"\nFor `novelty_axes`, output only these profile-native keys: "
|
|
2352
|
+
+ ", ".join(allowed_novelty_axes)
|
|
2353
|
+
+ ". Do not add any other axis names.\n"
|
|
2354
|
+
)
|
|
2355
|
+
cache_key = evaluation_prompt_cache_key(
|
|
2356
|
+
prompt,
|
|
2357
|
+
rubric_version=SCIENTIFIC_RUBRIC_VERSION,
|
|
2358
|
+
profile_version=SCIENTIFIC_INTERVENTION_PROFILE_VERSION,
|
|
2359
|
+
profile_id=profile_id,
|
|
2360
|
+
)
|
|
2361
|
+
|
|
2362
|
+
survey_handoff = getattr(mcts, "survey_idea_handoff", {})
|
|
2363
|
+
profile_drift = detect_profile_drift(
|
|
2364
|
+
profile_id,
|
|
2365
|
+
candidate=node.state.to_payload(),
|
|
2366
|
+
survey_handoff=survey_handoff if isinstance(survey_handoff, Mapping) else None,
|
|
2367
|
+
research_object=getattr(node.state, "scientific_intervention", {}).get("scientific_object_schema")
|
|
2368
|
+
if isinstance(getattr(node.state, "scientific_intervention", {}), Mapping)
|
|
2369
|
+
else None,
|
|
2370
|
+
)
|
|
2371
|
+
|
|
2372
|
+
cached_evaluation = get_cached_evaluation(node.state.signature, cache_key, mcts.evaluation_cache)
|
|
2373
|
+
if cached_evaluation:
|
|
2374
|
+
apply_profile_drift_to_evaluation(node, cached_evaluation, profile_drift)
|
|
2375
|
+
node.evaluation = cached_evaluation
|
|
2376
|
+
node.latest_path_summary = path_summary_text
|
|
2377
|
+
log_message(
|
|
2378
|
+
mcts.logger,
|
|
2379
|
+
mcts.log_sink,
|
|
2380
|
+
"info",
|
|
2381
|
+
"[MCTS] Simulate (cache hit): node=%s\n[MCTS] Score: %.4f\n%s",
|
|
2382
|
+
node.state.title,
|
|
2383
|
+
cached_evaluation.composite,
|
|
2384
|
+
pretty_json(mcts._simulate_log_payload(cached_evaluation)),
|
|
2385
|
+
)
|
|
2386
|
+
maybe_record_experience(
|
|
2387
|
+
cache_key,
|
|
2388
|
+
node,
|
|
2389
|
+
cached_evaluation,
|
|
2390
|
+
path_summary_text,
|
|
2391
|
+
experiences,
|
|
2392
|
+
mcts.experience_cache,
|
|
2393
|
+
getattr(mcts, "enable_vector_memory", True),
|
|
2394
|
+
mcts.memory_accessor,
|
|
2395
|
+
mcts.config.min_confidence_for_memory,
|
|
2396
|
+
)
|
|
2397
|
+
return cached_evaluation
|
|
2398
|
+
|
|
2399
|
+
try:
|
|
2400
|
+
response = mcts.chat_fn(
|
|
2401
|
+
prompt,
|
|
2402
|
+
model=mcts.config.evaluation_model,
|
|
2403
|
+
temperature=mcts.config.evaluation_temperature,
|
|
2404
|
+
max_output_tokens=mcts.config.evaluation_max_tokens,
|
|
2405
|
+
)
|
|
2406
|
+
payload = parse_json_response(response)
|
|
2407
|
+
if isinstance(payload, list):
|
|
2408
|
+
payload = payload[0]
|
|
2409
|
+
legacy_weights = {
|
|
2410
|
+
"novelty_weight": mcts.config.novelty_weight,
|
|
2411
|
+
"surprise_weight": mcts.config.surprise_weight,
|
|
2412
|
+
"impact_weight": mcts.config.impact_weight,
|
|
2413
|
+
"feasibility_weight": mcts.config.feasibility_weight,
|
|
2414
|
+
"clarity_weight": mcts.config.clarity_weight,
|
|
2415
|
+
"conciseness_weight": mcts.config.conciseness_weight,
|
|
2416
|
+
"risk_weight": mcts.config.risk_weight,
|
|
2417
|
+
"alignment_weight": mcts.config.alignment_weight,
|
|
2418
|
+
"complexity_weight": mcts.config.complexity_weight,
|
|
2419
|
+
"protocol_weight": mcts.config.protocol_weight,
|
|
2420
|
+
"explanatory_power_weight": getattr(
|
|
2421
|
+
mcts.config, "explanatory_power_weight", 0.06
|
|
2422
|
+
),
|
|
2423
|
+
"identifiability_weight": getattr(
|
|
2424
|
+
mcts.config, "identifiability_weight", 0.06
|
|
2425
|
+
),
|
|
2426
|
+
"boundary_calibration_weight": getattr(
|
|
2427
|
+
mcts.config, "boundary_calibration_weight", 0.04
|
|
2428
|
+
),
|
|
2429
|
+
"claim_overreach_weight": getattr(
|
|
2430
|
+
mcts.config, "claim_overreach_weight", 0.10
|
|
2431
|
+
),
|
|
2432
|
+
}
|
|
2433
|
+
evaluation = idea_evaluation_cls.from_payload(
|
|
2434
|
+
payload,
|
|
2435
|
+
weights=profile_score_weights(profile_id, legacy_weights),
|
|
2436
|
+
profile_id=profile_id,
|
|
2437
|
+
)
|
|
2438
|
+
except Exception as exc:
|
|
2439
|
+
log_message(mcts.logger, mcts.log_sink, "warning", "⚠️ Simulation failed: %s", exc)
|
|
2440
|
+
return None
|
|
2441
|
+
|
|
2442
|
+
novelty_override = (
|
|
2443
|
+
mcts._score_component_novelty(node.state)
|
|
2444
|
+
if profile_id == "computational_algorithmic"
|
|
2445
|
+
else None
|
|
2446
|
+
)
|
|
2447
|
+
if novelty_override is not None:
|
|
2448
|
+
evaluation.novelty = round(novelty_override)
|
|
2449
|
+
evaluation.novelty_axes["scientific_novelty"] = float(evaluation.novelty)
|
|
2450
|
+
novelty_axes = getattr(mcts.component_novelty_scorer, "last_novelty_axes", {})
|
|
2451
|
+
if isinstance(novelty_axes, dict):
|
|
2452
|
+
evaluation.novelty_axes.update(novelty_axes)
|
|
2453
|
+
|
|
2454
|
+
if evaluation.protocol_score <= 0.0:
|
|
2455
|
+
evaluation.protocol_score = mcts._compute_protocol_score(node.state.edit_plan)
|
|
2456
|
+
|
|
2457
|
+
apply_profile_drift_to_evaluation(node, evaluation, profile_drift)
|
|
2458
|
+
|
|
2459
|
+
log_message(
|
|
2460
|
+
mcts.logger,
|
|
2461
|
+
mcts.log_sink,
|
|
2462
|
+
"info",
|
|
2463
|
+
"[MCTS] Simulate: node=%s\n[MCTS] Score: %.4f\n%s",
|
|
2464
|
+
node.state.title,
|
|
2465
|
+
evaluation.composite,
|
|
2466
|
+
pretty_json(mcts._simulate_log_payload(evaluation)),
|
|
2467
|
+
)
|
|
2468
|
+
|
|
2469
|
+
cache_evaluation(node.state.signature, cache_key, evaluation, mcts.evaluation_cache)
|
|
2470
|
+
node.evaluation = evaluation
|
|
2471
|
+
node.latest_path_summary = path_summary_text
|
|
2472
|
+
|
|
2473
|
+
maybe_record_experience(
|
|
2474
|
+
cache_key,
|
|
2475
|
+
node,
|
|
2476
|
+
evaluation,
|
|
2477
|
+
path_summary_text,
|
|
2478
|
+
experiences,
|
|
2479
|
+
mcts.experience_cache,
|
|
2480
|
+
getattr(mcts, "enable_vector_memory", True),
|
|
2481
|
+
mcts.memory_accessor,
|
|
2482
|
+
mcts.config.min_confidence_for_memory,
|
|
2483
|
+
)
|
|
2484
|
+
|
|
2485
|
+
return evaluation
|
|
2486
|
+
|
|
2487
|
+
|
|
2488
|
+
def backpropagate_rollout(path: List[Any], evaluation: Any) -> None:
|
|
2489
|
+
score = evaluation.composite
|
|
2490
|
+
for hop in reversed(path):
|
|
2491
|
+
hop.visits += 1
|
|
2492
|
+
hop.value_sum += score
|
|
2493
|
+
|
|
2494
|
+
|
|
2495
|
+
def reset_search_state(mcts: Any) -> None:
|
|
2496
|
+
mcts.signature_nodes = {}
|
|
2497
|
+
mcts.evaluation_cache = {}
|
|
2498
|
+
mcts.experience_cache.clear()
|
|
2499
|
+
mcts.trace = []
|
|
2500
|
+
mcts.retrieved_core_titles = []
|
|
2501
|
+
mcts._id_iter = itertools.count()
|
|
2502
|
+
|
|
2503
|
+
|
|
2504
|
+
def new_node(
|
|
2505
|
+
state: Any,
|
|
2506
|
+
depth: int,
|
|
2507
|
+
parent: Optional[Any],
|
|
2508
|
+
signature_nodes: Dict[str, Any],
|
|
2509
|
+
id_iter: Any,
|
|
2510
|
+
idea_node_cls: Any,
|
|
2511
|
+
operator_application_cls: Any,
|
|
2512
|
+
) -> Any:
|
|
2513
|
+
existing = signature_nodes.get(state.signature)
|
|
2514
|
+
if existing:
|
|
2515
|
+
return existing
|
|
2516
|
+
node = idea_node_cls(
|
|
2517
|
+
node_id=next(id_iter),
|
|
2518
|
+
state=state,
|
|
2519
|
+
depth=depth,
|
|
2520
|
+
parent=parent,
|
|
2521
|
+
transformation=operator_application_cls(
|
|
2522
|
+
operator=state.operator,
|
|
2523
|
+
defects=state.target_defects,
|
|
2524
|
+
rationale=state.rationale,
|
|
2525
|
+
memory_refs=state.memory_refs,
|
|
2526
|
+
),
|
|
2527
|
+
)
|
|
2528
|
+
signature_nodes[state.signature] = node
|
|
2529
|
+
if parent:
|
|
2530
|
+
parent.children.append(node)
|
|
2531
|
+
return node
|
|
2532
|
+
|
|
2533
|
+
|
|
2534
|
+
def attach_child(
|
|
2535
|
+
parent: Any,
|
|
2536
|
+
state: Any,
|
|
2537
|
+
signature_nodes: Dict[str, Any],
|
|
2538
|
+
id_iter: Any,
|
|
2539
|
+
idea_node_cls: Any,
|
|
2540
|
+
operator_application_cls: Any,
|
|
2541
|
+
logger: Any,
|
|
2542
|
+
log_sink: Optional[Any] = None,
|
|
2543
|
+
) -> Optional[Any]:
|
|
2544
|
+
child = signature_nodes.get(state.signature)
|
|
2545
|
+
if child is None:
|
|
2546
|
+
return new_node(
|
|
2547
|
+
state,
|
|
2548
|
+
depth=parent.depth + 1,
|
|
2549
|
+
parent=parent,
|
|
2550
|
+
signature_nodes=signature_nodes,
|
|
2551
|
+
id_iter=id_iter,
|
|
2552
|
+
idea_node_cls=idea_node_cls,
|
|
2553
|
+
operator_application_cls=operator_application_cls,
|
|
2554
|
+
)
|
|
2555
|
+
if child is parent or is_ancestor(parent, child):
|
|
2556
|
+
return None
|
|
2557
|
+
if child not in parent.children:
|
|
2558
|
+
parent.children.append(child)
|
|
2559
|
+
return child
|
|
2560
|
+
|
|
2561
|
+
|
|
2562
|
+
def is_ancestor(node: Any, candidate: Any) -> bool:
|
|
2563
|
+
cursor: Optional[Any] = node
|
|
2564
|
+
while cursor is not None:
|
|
2565
|
+
if cursor is candidate:
|
|
2566
|
+
return True
|
|
2567
|
+
cursor = cursor.parent
|
|
2568
|
+
return False
|
|
2569
|
+
|
|
2570
|
+
|
|
2571
|
+
def path_summary(path: Sequence[Any], limit: int = 2048) -> str:
|
|
2572
|
+
steps: List[str] = []
|
|
2573
|
+
for hop in path:
|
|
2574
|
+
defects = hop.transformation.defects or ["unspecified"]
|
|
2575
|
+
steps.append(f"{hop.state.title} [{hop.transformation.operator}] -> defects {defects}")
|
|
2576
|
+
return clip_text(" | ".join(steps), limit)
|
|
2577
|
+
|
|
2578
|
+
|
|
2579
|
+
def evaluation_prompt_cache_key(
|
|
2580
|
+
prompt_text: str,
|
|
2581
|
+
*,
|
|
2582
|
+
rubric_version: str = SCIENTIFIC_RUBRIC_VERSION,
|
|
2583
|
+
profile_version: str = SCIENTIFIC_INTERVENTION_PROFILE_VERSION,
|
|
2584
|
+
profile_id: str = "generic_scientific",
|
|
2585
|
+
) -> str:
|
|
2586
|
+
cache_material = "\n".join(
|
|
2587
|
+
[
|
|
2588
|
+
str(rubric_version),
|
|
2589
|
+
str(profile_version),
|
|
2590
|
+
str(profile_id),
|
|
2591
|
+
str(prompt_text),
|
|
2592
|
+
]
|
|
2593
|
+
)
|
|
2594
|
+
return hashlib.sha256(cache_material.encode("utf-8")).hexdigest()
|
|
2595
|
+
|
|
2596
|
+
|
|
2597
|
+
def get_cached_evaluation(
|
|
2598
|
+
signature: str,
|
|
2599
|
+
path_key: str,
|
|
2600
|
+
evaluation_cache: Dict[str, Dict[str, Any]],
|
|
2601
|
+
) -> Optional[Any]:
|
|
2602
|
+
sig_cache = evaluation_cache.get(signature)
|
|
2603
|
+
if not sig_cache:
|
|
2604
|
+
return None
|
|
2605
|
+
return sig_cache.get(path_key)
|
|
2606
|
+
|
|
2607
|
+
|
|
2608
|
+
def cache_evaluation(
|
|
2609
|
+
signature: str,
|
|
2610
|
+
path_key: str,
|
|
2611
|
+
evaluation: Any,
|
|
2612
|
+
evaluation_cache: Dict[str, Dict[str, Any]],
|
|
2613
|
+
) -> None:
|
|
2614
|
+
evaluation_cache.setdefault(signature, {})[path_key] = evaluation
|
|
2615
|
+
|
|
2616
|
+
|
|
2617
|
+
def get_best_cached_evaluation(
|
|
2618
|
+
signature: str,
|
|
2619
|
+
evaluation_cache: Dict[str, Dict[str, Any]],
|
|
2620
|
+
) -> Optional[Any]:
|
|
2621
|
+
sig_cache = evaluation_cache.get(signature)
|
|
2622
|
+
if not sig_cache:
|
|
2623
|
+
return None
|
|
2624
|
+
return max(sig_cache.values(), key=lambda ev: ev.composite)
|
|
2625
|
+
|
|
2626
|
+
|
|
2627
|
+
def maybe_record_experience(
|
|
2628
|
+
cache_key: str,
|
|
2629
|
+
node: Any,
|
|
2630
|
+
evaluation: Any,
|
|
2631
|
+
path_summary_text: str,
|
|
2632
|
+
experiences: List[Dict[str, Any]],
|
|
2633
|
+
experience_cache: Set[str],
|
|
2634
|
+
enable_vector_memory: bool,
|
|
2635
|
+
memory_accessor: Any,
|
|
2636
|
+
min_confidence_for_memory: float,
|
|
2637
|
+
) -> None:
|
|
2638
|
+
if not enable_vector_memory:
|
|
2639
|
+
return
|
|
2640
|
+
if cache_key in experience_cache:
|
|
2641
|
+
return
|
|
2642
|
+
experience = harvest_experience(
|
|
2643
|
+
node,
|
|
2644
|
+
evaluation,
|
|
2645
|
+
path_summary_text,
|
|
2646
|
+
min_confidence_for_memory,
|
|
2647
|
+
)
|
|
2648
|
+
if not experience:
|
|
2649
|
+
return
|
|
2650
|
+
memory_accessor.persist_experience(experience)
|
|
2651
|
+
experiences.append(experience)
|
|
2652
|
+
experience_cache.add(cache_key)
|
|
2653
|
+
|
|
2654
|
+
|
|
2655
|
+
def build_root_state(
|
|
2656
|
+
topic: str,
|
|
2657
|
+
context: Dict[str, Any],
|
|
2658
|
+
idea_state_cls: Any,
|
|
2659
|
+
) -> Any:
|
|
2660
|
+
latest_candidate = context.get("latest_candidate")
|
|
2661
|
+
root_idea = context.get("root_idea")
|
|
2662
|
+
mature_value = context.get("mature_idea")
|
|
2663
|
+
mature_record = context.get("mature_idea_record")
|
|
2664
|
+
if isinstance(mature_value, Mapping):
|
|
2665
|
+
mature_record = mature_value
|
|
2666
|
+
if isinstance(mature_record, Mapping):
|
|
2667
|
+
mature_record = normalize_mature_idea(mature_record)
|
|
2668
|
+
mature_idea = str(
|
|
2669
|
+
mature_record.get("hypothesis")
|
|
2670
|
+
or mature_record.get("abstract")
|
|
2671
|
+
or mature_record.get("title")
|
|
2672
|
+
or ""
|
|
2673
|
+
).strip()
|
|
2674
|
+
else:
|
|
2675
|
+
mature_idea = str(mature_value or "").strip()
|
|
2676
|
+
background = context.get("background_knowledge") or []
|
|
2677
|
+
raw_defect_tags = context.get("defect_tags")
|
|
2678
|
+
if isinstance(raw_defect_tags, (list, tuple)):
|
|
2679
|
+
defect_tags = [str(tag).strip() for tag in raw_defect_tags if str(tag).strip()]
|
|
2680
|
+
elif str(raw_defect_tags or "").strip():
|
|
2681
|
+
defect_tags = [str(raw_defect_tags).strip()]
|
|
2682
|
+
else:
|
|
2683
|
+
defect_tags = []
|
|
2684
|
+
components = context.get("components") if isinstance(context.get("components"), list) else []
|
|
2685
|
+
context_component_explanations = (
|
|
2686
|
+
context.get("component_explanations")
|
|
2687
|
+
if isinstance(context.get("component_explanations"), (dict, list))
|
|
2688
|
+
else {}
|
|
2689
|
+
)
|
|
2690
|
+
root_domains = context.get("root_domains") if isinstance(context.get("root_domains"), list) else []
|
|
2691
|
+
discipline_resolution = (
|
|
2692
|
+
context.get("discipline_resolution")
|
|
2693
|
+
if isinstance(context.get("discipline_resolution"), dict)
|
|
2694
|
+
else {}
|
|
2695
|
+
)
|
|
2696
|
+
latest_payload: Dict[str, Any] = {}
|
|
2697
|
+
|
|
2698
|
+
if mature_idea:
|
|
2699
|
+
title = re.split(r"(?<=[.!?])\s+", mature_idea, maxsplit=1)[0].strip() or f"{topic} mature idea"
|
|
2700
|
+
abstract = mature_idea
|
|
2701
|
+
core = mature_idea
|
|
2702
|
+
method = mature_idea
|
|
2703
|
+
risks = "Primary risk is mechanism drift away from the mature idea during refinement."
|
|
2704
|
+
tags = ["seed", "mature_idea", "contract_root"]
|
|
2705
|
+
rationale = "Starting point anchored directly in the selected mature idea seed."
|
|
2706
|
+
if isinstance(mature_record, Mapping):
|
|
2707
|
+
title = str(mature_record.get("title") or title).strip()
|
|
2708
|
+
abstract = str(mature_record.get("abstract") or abstract).strip()
|
|
2709
|
+
core = str(
|
|
2710
|
+
mature_record.get("central_hypothesis")
|
|
2711
|
+
or mature_record.get("hypothesis")
|
|
2712
|
+
or core
|
|
2713
|
+
).strip()
|
|
2714
|
+
method = str(
|
|
2715
|
+
mature_record.get("mechanism_or_relation")
|
|
2716
|
+
or mature_record.get("mechanism")
|
|
2717
|
+
or method
|
|
2718
|
+
).strip()
|
|
2719
|
+
risks = "Primary risk is falsification of the mature idea mechanism or its stated assumptions."
|
|
2720
|
+
tags = ["seed", "mature_idea", "contract_root", str(mature_record.get("idea_id") or "")]
|
|
2721
|
+
tags = [tag for tag in tags if tag]
|
|
2722
|
+
elif isinstance(latest_candidate, dict) and latest_candidate:
|
|
2723
|
+
latest_payload = normalize_idea_contract(latest_candidate, keep_extra=True)
|
|
2724
|
+
title = latest_payload.get("title", f"{topic} seed idea")
|
|
2725
|
+
abstract = latest_payload.get("abstract", "")
|
|
2726
|
+
core = latest_payload.get("core_contribution", "")
|
|
2727
|
+
method = latest_payload.get("method", "")
|
|
2728
|
+
risks = latest_payload.get("risks", latest_payload.get("evaluation", ""))
|
|
2729
|
+
tags = latest_payload.get("tags")
|
|
2730
|
+
rationale = "Starting point from the latest candidate in the current run."
|
|
2731
|
+
if not components and isinstance(latest_payload.get("components"), list):
|
|
2732
|
+
components = [str(comp).strip() for comp in latest_payload.get("components", []) if str(comp).strip()]
|
|
2733
|
+
if not context_component_explanations:
|
|
2734
|
+
context_component_explanations = latest_payload.get("component_explanations", {})
|
|
2735
|
+
elif isinstance(root_idea, dict) and root_idea:
|
|
2736
|
+
latest_payload = normalize_idea_contract(root_idea, keep_extra=True)
|
|
2737
|
+
title = latest_payload.get("title", f"{topic} root idea")
|
|
2738
|
+
abstract = latest_payload.get("abstract", "")
|
|
2739
|
+
core = latest_payload.get("core_contribution", "")
|
|
2740
|
+
method = latest_payload.get("method", "")
|
|
2741
|
+
risks = latest_payload.get("risks", latest_payload.get("evaluation", ""))
|
|
2742
|
+
tags = latest_payload.get("tags")
|
|
2743
|
+
rationale = "Starting point from the explicit root idea produced by advanced analysis."
|
|
2744
|
+
if not components and isinstance(latest_payload.get("components"), list):
|
|
2745
|
+
components = [str(comp).strip() for comp in latest_payload.get("components", []) if str(comp).strip()]
|
|
2746
|
+
if not context_component_explanations:
|
|
2747
|
+
context_component_explanations = latest_payload.get("component_explanations", {})
|
|
2748
|
+
else:
|
|
2749
|
+
title = f"{topic} baseline"
|
|
2750
|
+
abstract = background[-1] if background else "Kick-off seed idea from analysis."
|
|
2751
|
+
core = "Seed idea derived from current analysis and background knowledge."
|
|
2752
|
+
method = "Synthesize referenced methods and expose unresolved bottlenecks."
|
|
2753
|
+
risks = "Need fairness checks and failure-mode surfacing."
|
|
2754
|
+
tags = ["seed"]
|
|
2755
|
+
rationale = "Starting point from existing analysis and background knowledge."
|
|
2756
|
+
|
|
2757
|
+
profile_payload = context.get("scientific_intervention_profile")
|
|
2758
|
+
existing_intervention = context.get("scientific_intervention")
|
|
2759
|
+
if not isinstance(existing_intervention, dict):
|
|
2760
|
+
existing_intervention = latest_payload.get("scientific_intervention")
|
|
2761
|
+
profile_resolution: Dict[str, Any] = {}
|
|
2762
|
+
if isinstance(profile_payload, dict):
|
|
2763
|
+
profile_resolution.update(profile_payload)
|
|
2764
|
+
if isinstance(existing_intervention, dict) and existing_intervention.get("profile_id"):
|
|
2765
|
+
profile_resolution.setdefault("profile_id", existing_intervention.get("profile_id"))
|
|
2766
|
+
profile_resolution.update(discipline_resolution)
|
|
2767
|
+
profile = resolve_scientific_intervention_profile(
|
|
2768
|
+
profile_resolution,
|
|
2769
|
+
root_domains=root_domains,
|
|
2770
|
+
project_context=(
|
|
2771
|
+
context.get("project_context")
|
|
2772
|
+
if isinstance(context.get("project_context"), dict)
|
|
2773
|
+
else None
|
|
2774
|
+
),
|
|
2775
|
+
)
|
|
2776
|
+
if profile is None:
|
|
2777
|
+
profile = get_scientific_intervention_profile("generic_scientific")
|
|
2778
|
+
if profile is None: # pragma: no cover - registry invariant
|
|
2779
|
+
raise RuntimeError("generic_scientific intervention profile is unavailable")
|
|
2780
|
+
if profile.profile_id != "computational_algorithmic" and components:
|
|
2781
|
+
forbidden_algorithm_components = set(profile.forbidden_default_patterns)
|
|
2782
|
+
idea_text = " ".join(
|
|
2783
|
+
str(value or "")
|
|
2784
|
+
for value in (title, abstract, core, method)
|
|
2785
|
+
).lower()
|
|
2786
|
+
high_precision_algorithm_terms = re.search(
|
|
2787
|
+
r"\b(?:algorithm(?:ic)?|machine\s+learning|deep\s+learning|neural\s+(?:network|model)|model\s+training|training\s+(?:a|the)\s+(?:model|network)|model\s+inference|inference[- ]time|trained\s+model|learned\s+(?:model|representation))\b",
|
|
2788
|
+
idea_text,
|
|
2789
|
+
)
|
|
2790
|
+
algorithmic_compound_terms = re.search(
|
|
2791
|
+
r"\b(?:algorithmic|machine[- ]learning|deep[- ]learning|neural|trained|learned)\s+(?:predictor|classifier|optimizer|surrogate\s+model|computational\s+(?:model|method))\b",
|
|
2792
|
+
idea_text,
|
|
2793
|
+
)
|
|
2794
|
+
algorithm_explicit = bool(high_precision_algorithm_terms or algorithmic_compound_terms)
|
|
2795
|
+
if not algorithm_explicit:
|
|
2796
|
+
components = [
|
|
2797
|
+
component
|
|
2798
|
+
for component in components
|
|
2799
|
+
if component not in forbidden_algorithm_components
|
|
2800
|
+
]
|
|
2801
|
+
if not defect_tags:
|
|
2802
|
+
defect_tags = list(profile.defect_tags) or ["unexplored_gap"]
|
|
2803
|
+
if not components:
|
|
2804
|
+
components = profile.default_component_names()
|
|
2805
|
+
component_explanations = normalize_component_explanations(
|
|
2806
|
+
components,
|
|
2807
|
+
context_component_explanations,
|
|
2808
|
+
)
|
|
2809
|
+
scientific_intervention = build_scientific_intervention_payload(
|
|
2810
|
+
profile,
|
|
2811
|
+
components,
|
|
2812
|
+
component_explanations,
|
|
2813
|
+
existing_intervention,
|
|
2814
|
+
)
|
|
2815
|
+
seed_identity = str(
|
|
2816
|
+
context.get("seed_id")
|
|
2817
|
+
or context.get("idea_id")
|
|
2818
|
+
or (mature_record.get("idea_id") if isinstance(mature_record, Mapping) else "")
|
|
2819
|
+
or ""
|
|
2820
|
+
).strip()
|
|
2821
|
+
route_identity = str(context.get("route_id") or "").strip()
|
|
2822
|
+
seed_gap_ids = context.get("target_gap_ids")
|
|
2823
|
+
if not isinstance(seed_gap_ids, list) and isinstance(mature_record, Mapping):
|
|
2824
|
+
seed_gap_ids = mature_record.get("target_gap_ids")
|
|
2825
|
+
seed_gap_ids = [str(item).strip() for item in (seed_gap_ids or []) if str(item).strip()]
|
|
2826
|
+
hypothesis_contract = dict(scientific_intervention.get("hypothesis_contract") or {})
|
|
2827
|
+
if seed_gap_ids:
|
|
2828
|
+
hypothesis_contract["target_gap_ids"] = seed_gap_ids
|
|
2829
|
+
if isinstance(mature_record, Mapping):
|
|
2830
|
+
for field_name, source_name in (
|
|
2831
|
+
("central_hypothesis", "central_hypothesis"),
|
|
2832
|
+
("scientific_object", "scientific_object"),
|
|
2833
|
+
("mechanism_or_relation", "mechanism_or_relation"),
|
|
2834
|
+
("assumptions", "assumptions"),
|
|
2835
|
+
("evidence_basis", "evidence_basis"),
|
|
2836
|
+
):
|
|
2837
|
+
value = mature_record.get(source_name)
|
|
2838
|
+
if value not in (None, "", [], {}):
|
|
2839
|
+
hypothesis_contract.setdefault(field_name, value)
|
|
2840
|
+
if hypothesis_contract:
|
|
2841
|
+
scientific_intervention["hypothesis_contract"] = hypothesis_contract
|
|
2842
|
+
if seed_identity:
|
|
2843
|
+
scientific_intervention["seed_id"] = seed_identity
|
|
2844
|
+
if route_identity:
|
|
2845
|
+
scientific_intervention["route_id"] = route_identity
|
|
2846
|
+
scientific_intervention["route_policy"] = context.get("route_policy") or {}
|
|
2847
|
+
routed_gap_context = {
|
|
2848
|
+
key: [dict(item) for item in context.get(key, []) if isinstance(item, dict)]
|
|
2849
|
+
for key in (
|
|
2850
|
+
"active_gaps",
|
|
2851
|
+
"provisional_gaps",
|
|
2852
|
+
"supporting_constraints",
|
|
2853
|
+
"verification_only_gaps",
|
|
2854
|
+
"future_work_seeds",
|
|
2855
|
+
)
|
|
2856
|
+
if isinstance(context.get(key), list) and context.get(key)
|
|
2857
|
+
}
|
|
2858
|
+
if routed_gap_context:
|
|
2859
|
+
scientific_intervention["gap_routing"] = routed_gap_context
|
|
2860
|
+
gap_hypothesis_seeds = [
|
|
2861
|
+
dict(seed)
|
|
2862
|
+
for seed in context.get("gap_hypothesis_seeds", [])
|
|
2863
|
+
if isinstance(seed, dict) and str(seed.get("seed_id") or "").strip()
|
|
2864
|
+
]
|
|
2865
|
+
if gap_hypothesis_seeds:
|
|
2866
|
+
hypothesis_seed_refs = []
|
|
2867
|
+
for seed in gap_hypothesis_seeds:
|
|
2868
|
+
seed_ref = {
|
|
2869
|
+
"seed_id": str(seed.get("seed_id") or "").strip(),
|
|
2870
|
+
"gap_id": str(seed.get("gap_id") or "").strip(),
|
|
2871
|
+
"gap_route": str(seed.get("gap_route") or "").strip(),
|
|
2872
|
+
"seed_status": str(seed.get("seed_status") or "").strip(),
|
|
2873
|
+
"target_slot": str(seed.get("target_slot") or "").strip(),
|
|
2874
|
+
}
|
|
2875
|
+
if is_data_anchored(seed):
|
|
2876
|
+
seed_ref.update(
|
|
2877
|
+
{
|
|
2878
|
+
"analysis_priority": DATA_ANCHORED_PRIORITY,
|
|
2879
|
+
"data_anchor_refs": list(seed.get("data_anchor_refs") or []),
|
|
2880
|
+
"literature_reconciliation_status": str(
|
|
2881
|
+
seed.get("literature_reconciliation_status") or "unresolved"
|
|
2882
|
+
),
|
|
2883
|
+
}
|
|
2884
|
+
)
|
|
2885
|
+
hypothesis_seed_refs.append(seed_ref)
|
|
2886
|
+
scientific_intervention["hypothesis_seed_refs"] = hypothesis_seed_refs
|
|
2887
|
+
scientific_intervention["gap_seed_status"] = (
|
|
2888
|
+
dict(context.get("gap_seed_status"))
|
|
2889
|
+
if isinstance(context.get("gap_seed_status"), dict)
|
|
2890
|
+
else {}
|
|
2891
|
+
)
|
|
2892
|
+
data_anchored_context = context.get("data_anchored_context")
|
|
2893
|
+
if not isinstance(data_anchored_context, Mapping) and isinstance(mature_record, Mapping):
|
|
2894
|
+
data_anchored_context = mature_record
|
|
2895
|
+
if is_data_anchored(data_anchored_context):
|
|
2896
|
+
scientific_intervention["data_anchored_contract"] = {
|
|
2897
|
+
"data_anchor_refs": list(data_anchored_context.get("data_anchor_refs") or []),
|
|
2898
|
+
"literature_reconciliation_status": str(
|
|
2899
|
+
data_anchored_context.get("literature_reconciliation_status") or "unresolved"
|
|
2900
|
+
),
|
|
2901
|
+
"competing_explanations": list(
|
|
2902
|
+
data_anchored_context.get("competing_explanations") or []
|
|
2903
|
+
),
|
|
2904
|
+
"measurement_needs": list(data_anchored_context.get("measurement_needs") or []),
|
|
2905
|
+
"claim_limits": list(data_anchored_context.get("claim_limits") or []),
|
|
2906
|
+
"mcts_depth_multiplier": float(
|
|
2907
|
+
data_anchored_context.get("mcts_depth_multiplier") or 1.75
|
|
2908
|
+
),
|
|
2909
|
+
"coverage_branches": list(
|
|
2910
|
+
data_anchored_context.get("coverage_branches") or []
|
|
2911
|
+
),
|
|
2912
|
+
}
|
|
2913
|
+
coverage_assignment = data_anchored_context.get(
|
|
2914
|
+
"data_anchored_coverage_assignment"
|
|
2915
|
+
)
|
|
2916
|
+
if isinstance(coverage_assignment, Mapping):
|
|
2917
|
+
scientific_intervention["data_anchored_contract"][
|
|
2918
|
+
"coverage_assignment"
|
|
2919
|
+
] = dict(coverage_assignment)
|
|
2920
|
+
|
|
2921
|
+
return idea_state_cls(
|
|
2922
|
+
title=str(title),
|
|
2923
|
+
abstract=str(abstract),
|
|
2924
|
+
core_contribution=str(core),
|
|
2925
|
+
method=str(method),
|
|
2926
|
+
risks=str(risks),
|
|
2927
|
+
tags=[str(t) for t in tags] if isinstance(tags, list) else [str(tags)],
|
|
2928
|
+
operator="seed",
|
|
2929
|
+
target_defects=[str(tag) for tag in defect_tags],
|
|
2930
|
+
rationale=str(rationale),
|
|
2931
|
+
memory_refs=[],
|
|
2932
|
+
components=components,
|
|
2933
|
+
component_explanations=component_explanations,
|
|
2934
|
+
root_domains=[str(domain).strip() for domain in root_domains if str(domain).strip()][:2],
|
|
2935
|
+
discipline_resolution=discipline_resolution,
|
|
2936
|
+
scientific_intervention=scientific_intervention,
|
|
2937
|
+
paper_graph_context=str(context.get("paper_context") or ""),
|
|
2938
|
+
)
|
|
2939
|
+
|
|
2940
|
+
|
|
2941
|
+
def best_candidate(root: Any, candidate_cls: Any) -> Optional[Any]:
|
|
2942
|
+
candidates: List[Any] = []
|
|
2943
|
+
stack = [root]
|
|
2944
|
+
visited: Set[int] = set()
|
|
2945
|
+
while stack:
|
|
2946
|
+
node = stack.pop()
|
|
2947
|
+
if node.node_id in visited:
|
|
2948
|
+
continue
|
|
2949
|
+
visited.add(node.node_id)
|
|
2950
|
+
if node.evaluation:
|
|
2951
|
+
candidates.append(candidate_cls(node=node, evaluation=node.evaluation))
|
|
2952
|
+
stack.extend(node.children)
|
|
2953
|
+
if not candidates:
|
|
2954
|
+
return None
|
|
2955
|
+
return max(candidates, key=lambda c: c.evaluation.composite)
|
|
2956
|
+
|
|
2957
|
+
|
|
2958
|
+
def pareto_candidates(root: Any, candidate_cls: Any) -> Dict[str, Optional[Any]]:
|
|
2959
|
+
by_metric = {
|
|
2960
|
+
"novel": lambda ev: ev.novelty,
|
|
2961
|
+
"feasible": lambda ev: ev.feasibility,
|
|
2962
|
+
"concise": lambda ev: ev.conciseness,
|
|
2963
|
+
}
|
|
2964
|
+
pareto: Dict[str, Optional[Any]] = {k: None for k in by_metric}
|
|
2965
|
+
stack = [root]
|
|
2966
|
+
visited_ids: Set[int] = set()
|
|
2967
|
+
visited: List[Any] = []
|
|
2968
|
+
while stack:
|
|
2969
|
+
node = stack.pop()
|
|
2970
|
+
if node.node_id in visited_ids:
|
|
2971
|
+
continue
|
|
2972
|
+
visited_ids.add(node.node_id)
|
|
2973
|
+
if node.evaluation:
|
|
2974
|
+
visited.append(candidate_cls(node=node, evaluation=node.evaluation))
|
|
2975
|
+
stack.extend(node.children)
|
|
2976
|
+
|
|
2977
|
+
for label, scorer in by_metric.items():
|
|
2978
|
+
if visited:
|
|
2979
|
+
pareto[label] = max(visited, key=lambda c, s=scorer: s(c.evaluation))
|
|
2980
|
+
return pareto
|
|
2981
|
+
|
|
2982
|
+
|
|
2983
|
+
def harvest_experience(
|
|
2984
|
+
node: Any,
|
|
2985
|
+
evaluation: Any,
|
|
2986
|
+
path_summary_text: str,
|
|
2987
|
+
min_confidence_for_memory: float,
|
|
2988
|
+
) -> Optional[Dict[str, Any]]:
|
|
2989
|
+
if evaluation.confidence > min_confidence_for_memory:
|
|
2990
|
+
experience = {
|
|
2991
|
+
"defect": ", ".join(node.state.target_defects) or evaluation.defect_fix_summary,
|
|
2992
|
+
"action": node.state.operator,
|
|
2993
|
+
"idea": node.state.title,
|
|
2994
|
+
"context": path_summary_text,
|
|
2995
|
+
"feedback": evaluation.feedback,
|
|
2996
|
+
"tags": node.state.tags + ["defect_fix"],
|
|
2997
|
+
"edit_plan": node.state.edit_plan,
|
|
2998
|
+
}
|
|
2999
|
+
return experience
|
|
3000
|
+
return None
|