qwen-sci 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (549) hide show
  1. qwen_sci-0.2.2.dist-info/METADATA +492 -0
  2. qwen_sci-0.2.2.dist-info/RECORD +549 -0
  3. qwen_sci-0.2.2.dist-info/WHEEL +4 -0
  4. qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
  5. qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
  6. src/1.txt +1 -0
  7. src/__init__.py +1 -0
  8. src/__main__.py +5 -0
  9. src/agents/__init__.py +1 -0
  10. src/agents/experiment_design_agent/__init__.py +268 -0
  11. src/agents/experiment_design_agent/artifacts.py +747 -0
  12. src/agents/experiment_design_agent/cache.py +319 -0
  13. src/agents/experiment_design_agent/completeness.py +130 -0
  14. src/agents/experiment_design_agent/contracts.py +771 -0
  15. src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
  16. src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
  17. src/agents/experiment_design_agent/discipline_catalog.py +178 -0
  18. src/agents/experiment_design_agent/evidence_cards.py +338 -0
  19. src/agents/experiment_design_agent/evidence_planner.py +452 -0
  20. src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
  21. src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
  22. src/agents/experiment_design_agent/idea_adapter.py +100 -0
  23. src/agents/experiment_design_agent/idea_intake.py +87 -0
  24. src/agents/experiment_design_agent/llm_json.py +328 -0
  25. src/agents/experiment_design_agent/orchestrator.py +1220 -0
  26. src/agents/experiment_design_agent/reasoning_context.py +183 -0
  27. src/agents/experiment_design_agent/reasoning_validation.py +546 -0
  28. src/agents/experiment_design_agent/run.py +481 -0
  29. src/agents/experiment_design_agent/run_logging.py +292 -0
  30. src/agents/experiment_design_agent/scope_gate.py +198 -0
  31. src/agents/experiment_design_agent/study_type_composer.py +1360 -0
  32. src/agents/experiment_design_agent/survey_evidence.py +1381 -0
  33. src/agents/experiment_design_agent/template_router.py +195 -0
  34. src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
  35. src/agents/idea_agent/__init__.py +1 -0
  36. src/agents/idea_agent/agent/__init__.py +6 -0
  37. src/agents/idea_agent/agent/artifacts.py +308 -0
  38. src/agents/idea_agent/agent/base.py +122 -0
  39. src/agents/idea_agent/agent/ligagent.py +488 -0
  40. src/agents/idea_agent/agent/mcts.py +2060 -0
  41. src/agents/idea_agent/agent/prompts/__init__.py +48 -0
  42. src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
  43. src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
  44. src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
  45. src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
  46. src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
  47. src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
  48. src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
  49. src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
  50. src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
  51. src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
  52. src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
  53. src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
  54. src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
  55. src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
  56. src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
  57. src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
  58. src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
  59. src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
  60. src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
  61. src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
  62. src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
  63. src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
  64. src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
  65. src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
  66. src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
  67. src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
  68. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
  69. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
  70. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
  71. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
  72. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
  73. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
  74. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
  75. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
  76. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
  77. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
  78. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
  79. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
  80. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
  81. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
  82. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
  83. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
  84. src/agents/idea_agent/example/idea_result.json +900 -0
  85. src/agents/idea_agent/run.py +276 -0
  86. src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
  87. src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
  88. src/agents/idea_agent/tests/test_idea_debate.py +480 -0
  89. src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
  90. src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
  91. src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
  92. src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
  93. src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
  94. src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
  95. src/agents/idea_agent/tests/test_result_persistence.py +183 -0
  96. src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
  97. src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
  98. src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
  99. src/agents/idea_agent/utils/core/__init__.py +1 -0
  100. src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
  101. src/agents/idea_agent/utils/core/chat_errors.py +95 -0
  102. src/agents/idea_agent/utils/core/chat_router.py +130 -0
  103. src/agents/idea_agent/utils/core/chat_transport.py +188 -0
  104. src/agents/idea_agent/utils/core/config_loader.py +166 -0
  105. src/agents/idea_agent/utils/core/json_utils.py +28 -0
  106. src/agents/idea_agent/utils/core/logger.py +287 -0
  107. src/agents/idea_agent/utils/core/progress.py +58 -0
  108. src/agents/idea_agent/utils/core/response_parsing.py +57 -0
  109. src/agents/idea_agent/utils/core/run_inputs.py +198 -0
  110. src/agents/idea_agent/utils/mcts/__init__.py +1 -0
  111. src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
  112. src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
  113. src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
  114. src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
  115. src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
  116. src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
  117. src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
  118. src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
  119. src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
  120. src/agents/idea_agent/utils/papers/__init__.py +1 -0
  121. src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
  122. src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
  123. src/agents/idea_agent/utils/prompting/__init__.py +1 -0
  124. src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
  125. src/agents/idea_agent/utils/workflow/__init__.py +1 -0
  126. src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
  127. src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
  128. src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
  129. src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
  130. src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
  131. src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
  132. src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
  133. src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
  134. src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
  135. src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
  136. src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
  137. src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
  138. src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
  139. src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
  140. src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
  141. src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
  142. src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
  143. src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
  144. src/agents/research_plan_author/__init__.py +50 -0
  145. src/agents/research_plan_author/artifacts.py +270 -0
  146. src/agents/research_plan_author/authoring_blueprint.py +1246 -0
  147. src/agents/research_plan_author/bibtex_renderer.py +252 -0
  148. src/agents/research_plan_author/contract_repair.py +131 -0
  149. src/agents/research_plan_author/contracts.py +745 -0
  150. src/agents/research_plan_author/cross_section_editor.py +232 -0
  151. src/agents/research_plan_author/document_quality.py +987 -0
  152. src/agents/research_plan_author/idea_evolution.py +223 -0
  153. src/agents/research_plan_author/input_loader.py +76 -0
  154. src/agents/research_plan_author/latex_compiler.py +260 -0
  155. src/agents/research_plan_author/latex_safety.py +159 -0
  156. src/agents/research_plan_author/llm_json.py +74 -0
  157. src/agents/research_plan_author/markdown_renderer.py +275 -0
  158. src/agents/research_plan_author/pdf_validator.py +149 -0
  159. src/agents/research_plan_author/render.py +166 -0
  160. src/agents/research_plan_author/render_artifacts.py +187 -0
  161. src/agents/research_plan_author/run.py +765 -0
  162. src/agents/research_plan_author/run_logging.py +209 -0
  163. src/agents/research_plan_author/section_cache.py +184 -0
  164. src/agents/research_plan_author/section_composer.py +1764 -0
  165. src/agents/research_plan_author/section_router.py +209 -0
  166. src/agents/research_plan_author/semantic_validator.py +254 -0
  167. src/agents/research_plan_author/source_bundle.py +46 -0
  168. src/agents/research_plan_author/source_registry.py +208 -0
  169. src/agents/research_plan_author/survey_source_loader.py +137 -0
  170. src/agents/research_plan_author/template_adapter.py +173 -0
  171. src/agents/research_plan_author/template_profile.py +204 -0
  172. src/agents/research_plan_author/tex_renderer.py +496 -0
  173. src/agents/research_plan_author/theory_presentation.py +134 -0
  174. src/agents/research_plan_author/theory_spine.py +703 -0
  175. src/agents/survey_agent/Dockerfile +54 -0
  176. src/agents/survey_agent/README.md +350 -0
  177. src/agents/survey_agent/config/deep_survey.yaml +217 -0
  178. src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
  179. src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
  180. src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
  181. src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
  182. src/agents/survey_agent/env_report.md +348 -0
  183. src/agents/survey_agent/modules/__init__.py +0 -0
  184. src/agents/survey_agent/modules/code_collector.py +2194 -0
  185. src/agents/survey_agent/modules/code_report_generator.py +1282 -0
  186. src/agents/survey_agent/modules/data_manager.py +1713 -0
  187. src/agents/survey_agent/modules/database.py +167 -0
  188. src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
  189. src/agents/survey_agent/modules/fulltext_http.py +134 -0
  190. src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
  191. src/agents/survey_agent/modules/judge.py +434 -0
  192. src/agents/survey_agent/modules/outcome_RAG.py +236 -0
  193. src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
  194. src/agents/survey_agent/modules/pe.py +1729 -0
  195. src/agents/survey_agent/modules/pseudo_creator.py +782 -0
  196. src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
  197. src/agents/survey_agent/modules/refine_agent.py +1952 -0
  198. src/agents/survey_agent/modules/survey_generator.py +5691 -0
  199. src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
  200. src/agents/survey_agent/modules/visual_prompts.py +327 -0
  201. src/agents/survey_agent/modules/work_analyzer.py +2750 -0
  202. src/agents/survey_agent/modules/work_collector.py +5649 -0
  203. src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
  204. src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
  205. src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
  206. src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
  207. src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
  208. src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
  209. src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
  210. src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
  211. src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
  212. src/agents/survey_agent/utils/__init__.py +0 -0
  213. src/agents/survey_agent/utils/ablation_utils.py +317 -0
  214. src/agents/survey_agent/utils/api_call.py +2518 -0
  215. src/agents/survey_agent/utils/chat_utils.py +82 -0
  216. src/agents/survey_agent/utils/config_utils.py +70 -0
  217. src/agents/survey_agent/utils/convert_to_md.py +29 -0
  218. src/agents/survey_agent/utils/err_info.py +24 -0
  219. src/agents/survey_agent/utils/file_utils.py +176 -0
  220. src/agents/survey_agent/utils/gpu_utils.py +145 -0
  221. src/agents/survey_agent/utils/graph_visualizer.py +279 -0
  222. src/agents/survey_agent/utils/html_utils.py +181 -0
  223. src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
  224. src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
  225. src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
  226. src/agents/survey_agent/utils/mineru_utils.py +396 -0
  227. src/agents/survey_agent/utils/paper.py +144 -0
  228. src/agents/survey_agent/utils/read_yaml.py +15 -0
  229. src/agents/survey_agent/utils/repo_utils.py +167 -0
  230. src/agents/survey_agent/utils/rich_logger.py +77 -0
  231. src/agents/survey_agent/utils/step2v2.py +874 -0
  232. src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
  233. src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
  234. src/agents/survey_agent/utils/utils.py +118 -0
  235. src/agents/survey_agent/utils/work.py +2 -0
  236. src/cli.py +1611 -0
  237. src/config/__init__.py +213 -0
  238. src/config/default.yaml +1122 -0
  239. src/harness/LICENSE +21 -0
  240. src/harness/VENDORED.md +14 -0
  241. src/harness/src/__init__.py +0 -0
  242. src/harness/src/openharness/__init__.py +0 -0
  243. src/harness/src/openharness/__main__.py +6 -0
  244. src/harness/src/openharness/api/__init__.py +21 -0
  245. src/harness/src/openharness/api/client.py +281 -0
  246. src/harness/src/openharness/api/codex_client.py +407 -0
  247. src/harness/src/openharness/api/copilot_auth.py +240 -0
  248. src/harness/src/openharness/api/copilot_client.py +134 -0
  249. src/harness/src/openharness/api/errors.py +19 -0
  250. src/harness/src/openharness/api/openai_client.py +577 -0
  251. src/harness/src/openharness/api/provider.py +186 -0
  252. src/harness/src/openharness/api/registry.py +437 -0
  253. src/harness/src/openharness/api/usage.py +17 -0
  254. src/harness/src/openharness/auth/__init__.py +29 -0
  255. src/harness/src/openharness/auth/external.py +610 -0
  256. src/harness/src/openharness/auth/flows.py +189 -0
  257. src/harness/src/openharness/auth/manager.py +482 -0
  258. src/harness/src/openharness/auth/storage.py +269 -0
  259. src/harness/src/openharness/autopilot/__init__.py +23 -0
  260. src/harness/src/openharness/autopilot/service.py +2239 -0
  261. src/harness/src/openharness/autopilot/types.py +91 -0
  262. src/harness/src/openharness/bridge/__init__.py +20 -0
  263. src/harness/src/openharness/bridge/manager.py +106 -0
  264. src/harness/src/openharness/bridge/session_runner.py +46 -0
  265. src/harness/src/openharness/bridge/types.py +37 -0
  266. src/harness/src/openharness/bridge/work_secret.py +41 -0
  267. src/harness/src/openharness/channels/UPSTREAM +6 -0
  268. src/harness/src/openharness/channels/__init__.py +22 -0
  269. src/harness/src/openharness/channels/adapter.py +131 -0
  270. src/harness/src/openharness/channels/bus/__init__.py +6 -0
  271. src/harness/src/openharness/channels/bus/events.py +38 -0
  272. src/harness/src/openharness/channels/bus/queue.py +44 -0
  273. src/harness/src/openharness/channels/impl/__init__.py +6 -0
  274. src/harness/src/openharness/channels/impl/base.py +141 -0
  275. src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
  276. src/harness/src/openharness/channels/impl/discord.py +311 -0
  277. src/harness/src/openharness/channels/impl/email.py +410 -0
  278. src/harness/src/openharness/channels/impl/feishu.py +1342 -0
  279. src/harness/src/openharness/channels/impl/manager.py +257 -0
  280. src/harness/src/openharness/channels/impl/matrix.py +700 -0
  281. src/harness/src/openharness/channels/impl/mochat.py +897 -0
  282. src/harness/src/openharness/channels/impl/qq.py +141 -0
  283. src/harness/src/openharness/channels/impl/slack.py +284 -0
  284. src/harness/src/openharness/channels/impl/telegram.py +525 -0
  285. src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
  286. src/harness/src/openharness/cli.py +2551 -0
  287. src/harness/src/openharness/commands/__init__.py +21 -0
  288. src/harness/src/openharness/commands/registry.py +2783 -0
  289. src/harness/src/openharness/config/__init__.py +34 -0
  290. src/harness/src/openharness/config/paths.py +160 -0
  291. src/harness/src/openharness/config/schema.py +119 -0
  292. src/harness/src/openharness/config/settings.py +1104 -0
  293. src/harness/src/openharness/coordinator/__init__.py +12 -0
  294. src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
  295. src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
  296. src/harness/src/openharness/engine/__init__.py +80 -0
  297. src/harness/src/openharness/engine/cost_tracker.py +24 -0
  298. src/harness/src/openharness/engine/messages.py +222 -0
  299. src/harness/src/openharness/engine/query.py +1188 -0
  300. src/harness/src/openharness/engine/query_engine.py +313 -0
  301. src/harness/src/openharness/engine/stream_events.py +90 -0
  302. src/harness/src/openharness/hooks/__init__.py +50 -0
  303. src/harness/src/openharness/hooks/events.py +20 -0
  304. src/harness/src/openharness/hooks/executor.py +242 -0
  305. src/harness/src/openharness/hooks/hot_reload.py +31 -0
  306. src/harness/src/openharness/hooks/loader.py +68 -0
  307. src/harness/src/openharness/hooks/schemas.py +66 -0
  308. src/harness/src/openharness/hooks/types.py +38 -0
  309. src/harness/src/openharness/keybindings/__init__.py +14 -0
  310. src/harness/src/openharness/keybindings/default_bindings.py +11 -0
  311. src/harness/src/openharness/keybindings/loader.py +22 -0
  312. src/harness/src/openharness/keybindings/parser.py +18 -0
  313. src/harness/src/openharness/keybindings/resolver.py +13 -0
  314. src/harness/src/openharness/mcp/__init__.py +79 -0
  315. src/harness/src/openharness/mcp/client.py +298 -0
  316. src/harness/src/openharness/mcp/config.py +16 -0
  317. src/harness/src/openharness/mcp/types.py +76 -0
  318. src/harness/src/openharness/memory/__init__.py +25 -0
  319. src/harness/src/openharness/memory/agent.py +91 -0
  320. src/harness/src/openharness/memory/manager.py +183 -0
  321. src/harness/src/openharness/memory/memdir.py +52 -0
  322. src/harness/src/openharness/memory/migrate.py +137 -0
  323. src/harness/src/openharness/memory/paths.py +22 -0
  324. src/harness/src/openharness/memory/relevance.py +145 -0
  325. src/harness/src/openharness/memory/scan.py +116 -0
  326. src/harness/src/openharness/memory/schema.py +443 -0
  327. src/harness/src/openharness/memory/search.py +71 -0
  328. src/harness/src/openharness/memory/team.py +90 -0
  329. src/harness/src/openharness/memory/types.py +33 -0
  330. src/harness/src/openharness/memory/usage.py +153 -0
  331. src/harness/src/openharness/output_styles/__init__.py +5 -0
  332. src/harness/src/openharness/output_styles/loader.py +42 -0
  333. src/harness/src/openharness/permissions/__init__.py +26 -0
  334. src/harness/src/openharness/permissions/checker.py +200 -0
  335. src/harness/src/openharness/permissions/modes.py +13 -0
  336. src/harness/src/openharness/personalization/__init__.py +6 -0
  337. src/harness/src/openharness/personalization/extractor.py +138 -0
  338. src/harness/src/openharness/personalization/rules.py +64 -0
  339. src/harness/src/openharness/personalization/session_hook.py +65 -0
  340. src/harness/src/openharness/platforms.py +86 -0
  341. src/harness/src/openharness/plugins/__init__.py +53 -0
  342. src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
  343. src/harness/src/openharness/plugins/installer.py +39 -0
  344. src/harness/src/openharness/plugins/loader.py +730 -0
  345. src/harness/src/openharness/plugins/schemas.py +24 -0
  346. src/harness/src/openharness/plugins/types.py +59 -0
  347. src/harness/src/openharness/prompts/__init__.py +14 -0
  348. src/harness/src/openharness/prompts/claudemd.py +48 -0
  349. src/harness/src/openharness/prompts/context.py +187 -0
  350. src/harness/src/openharness/prompts/environment.py +135 -0
  351. src/harness/src/openharness/prompts/system_prompt.py +109 -0
  352. src/harness/src/openharness/sandbox/Dockerfile +6 -0
  353. src/harness/src/openharness/sandbox/__init__.py +33 -0
  354. src/harness/src/openharness/sandbox/adapter.py +148 -0
  355. src/harness/src/openharness/sandbox/docker_backend.py +232 -0
  356. src/harness/src/openharness/sandbox/docker_image.py +103 -0
  357. src/harness/src/openharness/sandbox/path_validator.py +37 -0
  358. src/harness/src/openharness/sandbox/session.py +63 -0
  359. src/harness/src/openharness/services/__init__.py +30 -0
  360. src/harness/src/openharness/services/autodream/__init__.py +34 -0
  361. src/harness/src/openharness/services/autodream/backup.py +104 -0
  362. src/harness/src/openharness/services/autodream/lock.py +138 -0
  363. src/harness/src/openharness/services/autodream/prompt.py +128 -0
  364. src/harness/src/openharness/services/autodream/service.py +313 -0
  365. src/harness/src/openharness/services/compact/__init__.py +1725 -0
  366. src/harness/src/openharness/services/cron.py +137 -0
  367. src/harness/src/openharness/services/cron_scheduler.py +595 -0
  368. src/harness/src/openharness/services/lsp/__init__.py +216 -0
  369. src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
  370. src/harness/src/openharness/services/oauth/__init__.py +0 -0
  371. src/harness/src/openharness/services/session_backend.py +97 -0
  372. src/harness/src/openharness/services/session_memory/__init__.py +139 -0
  373. src/harness/src/openharness/services/session_storage.py +230 -0
  374. src/harness/src/openharness/services/token_estimation.py +15 -0
  375. src/harness/src/openharness/services/tool_outputs.py +55 -0
  376. src/harness/src/openharness/skills/__init__.py +44 -0
  377. src/harness/src/openharness/skills/_frontmatter.py +97 -0
  378. src/harness/src/openharness/skills/bundled/__init__.py +75 -0
  379. src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
  380. src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
  381. src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
  382. src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
  383. src/harness/src/openharness/skills/bundled/content/review.md +33 -0
  384. src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
  385. src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
  386. src/harness/src/openharness/skills/bundled/content/test.md +38 -0
  387. src/harness/src/openharness/skills/loader.py +229 -0
  388. src/harness/src/openharness/skills/registry.py +29 -0
  389. src/harness/src/openharness/skills/types.py +24 -0
  390. src/harness/src/openharness/state/__init__.py +6 -0
  391. src/harness/src/openharness/state/app_state.py +30 -0
  392. src/harness/src/openharness/state/store.py +40 -0
  393. src/harness/src/openharness/swarm/__init__.py +70 -0
  394. src/harness/src/openharness/swarm/in_process.py +693 -0
  395. src/harness/src/openharness/swarm/lockfile.py +24 -0
  396. src/harness/src/openharness/swarm/mailbox.py +522 -0
  397. src/harness/src/openharness/swarm/permission_sync.py +1168 -0
  398. src/harness/src/openharness/swarm/registry.py +410 -0
  399. src/harness/src/openharness/swarm/spawn_utils.py +229 -0
  400. src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
  401. src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
  402. src/harness/src/openharness/swarm/types.py +398 -0
  403. src/harness/src/openharness/swarm/worktree.py +315 -0
  404. src/harness/src/openharness/tasks/__init__.py +18 -0
  405. src/harness/src/openharness/tasks/local_agent_task.py +28 -0
  406. src/harness/src/openharness/tasks/local_shell_task.py +17 -0
  407. src/harness/src/openharness/tasks/manager.py +475 -0
  408. src/harness/src/openharness/tasks/stop_task.py +11 -0
  409. src/harness/src/openharness/tasks/types.py +32 -0
  410. src/harness/src/openharness/themes/__init__.py +21 -0
  411. src/harness/src/openharness/themes/builtin.py +89 -0
  412. src/harness/src/openharness/themes/loader.py +55 -0
  413. src/harness/src/openharness/themes/schema.py +54 -0
  414. src/harness/src/openharness/tools/__init__.py +107 -0
  415. src/harness/src/openharness/tools/agent_tool.py +141 -0
  416. src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
  417. src/harness/src/openharness/tools/base.py +80 -0
  418. src/harness/src/openharness/tools/bash_tool.py +259 -0
  419. src/harness/src/openharness/tools/brief_tool.py +33 -0
  420. src/harness/src/openharness/tools/config_tool.py +54 -0
  421. src/harness/src/openharness/tools/cron_create_tool.py +105 -0
  422. src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
  423. src/harness/src/openharness/tools/cron_list_tool.py +69 -0
  424. src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
  425. src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
  426. src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
  427. src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
  428. src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
  429. src/harness/src/openharness/tools/file_edit_tool.py +95 -0
  430. src/harness/src/openharness/tools/file_read_tool.py +72 -0
  431. src/harness/src/openharness/tools/file_write_tool.py +87 -0
  432. src/harness/src/openharness/tools/glob_tool.py +175 -0
  433. src/harness/src/openharness/tools/grep_tool.py +374 -0
  434. src/harness/src/openharness/tools/image_generation_tool.py +447 -0
  435. src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
  436. src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
  437. src/harness/src/openharness/tools/lsp_tool.py +154 -0
  438. src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
  439. src/harness/src/openharness/tools/mcp_tool.py +72 -0
  440. src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
  441. src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
  442. src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
  443. src/harness/src/openharness/tools/send_message_tool.py +58 -0
  444. src/harness/src/openharness/tools/skill_tool.py +43 -0
  445. src/harness/src/openharness/tools/sleep_tool.py +32 -0
  446. src/harness/src/openharness/tools/task_create_tool.py +56 -0
  447. src/harness/src/openharness/tools/task_get_tool.py +33 -0
  448. src/harness/src/openharness/tools/task_list_tool.py +35 -0
  449. src/harness/src/openharness/tools/task_output_tool.py +35 -0
  450. src/harness/src/openharness/tools/task_stop_tool.py +30 -0
  451. src/harness/src/openharness/tools/task_update_tool.py +50 -0
  452. src/harness/src/openharness/tools/team_create_tool.py +31 -0
  453. src/harness/src/openharness/tools/team_delete_tool.py +30 -0
  454. src/harness/src/openharness/tools/todo_write_tool.py +46 -0
  455. src/harness/src/openharness/tools/tool_search_tool.py +38 -0
  456. src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
  457. src/harness/src/openharness/tools/web_search_tool.py +119 -0
  458. src/harness/src/openharness/ui/__init__.py +5 -0
  459. src/harness/src/openharness/ui/app.py +320 -0
  460. src/harness/src/openharness/ui/backend_host.py +941 -0
  461. src/harness/src/openharness/ui/coordinator_drain.py +198 -0
  462. src/harness/src/openharness/ui/input.py +32 -0
  463. src/harness/src/openharness/ui/output.py +265 -0
  464. src/harness/src/openharness/ui/permission_dialog.py +14 -0
  465. src/harness/src/openharness/ui/protocol.py +247 -0
  466. src/harness/src/openharness/ui/react_launcher.py +179 -0
  467. src/harness/src/openharness/ui/runtime.py +799 -0
  468. src/harness/src/openharness/ui/textual_app.py +495 -0
  469. src/harness/src/openharness/utils/__init__.py +0 -0
  470. src/harness/src/openharness/utils/file_lock.py +86 -0
  471. src/harness/src/openharness/utils/fs.py +98 -0
  472. src/harness/src/openharness/utils/helpers.py +77 -0
  473. src/harness/src/openharness/utils/network_guard.py +340 -0
  474. src/harness/src/openharness/utils/shell.py +147 -0
  475. src/harness/src/openharness/vim/__init__.py +5 -0
  476. src/harness/src/openharness/vim/transitions.py +8 -0
  477. src/harness/src/openharness/voice/__init__.py +7 -0
  478. src/harness/src/openharness/voice/keyterms.py +10 -0
  479. src/harness/src/openharness/voice/stream_stt.py +8 -0
  480. src/harness/src/openharness/voice/voice_mode.py +44 -0
  481. src/llm/__init__.py +29 -0
  482. src/llm/image_generation.py +402 -0
  483. src/llm/provider_registry.py +419 -0
  484. src/llm/runtime_env.py +64 -0
  485. src/llm/structured_output.py +131 -0
  486. src/llm/vision.py +198 -0
  487. src/memory/__init__.py +2 -0
  488. src/memory/api/__init__.py +1 -0
  489. src/memory/api/base_memory_system_api.py +98 -0
  490. src/memory/api/base_symbolic_memory_system_api.py +190 -0
  491. src/memory/api/base_vector_memory_system_api.py +116 -0
  492. src/memory/api/faiss_memory_system_api.py +228 -0
  493. src/memory/api/slot_process_api.py +827 -0
  494. src/memory/api/symbolic_memory_system_api.py +572 -0
  495. src/memory/memory_system/__init__.py +48 -0
  496. src/memory/memory_system/component_taxonomy.py +430 -0
  497. src/memory/memory_system/llm.py +437 -0
  498. src/memory/memory_system/models.py +168 -0
  499. src/memory/memory_system/user_prompt.py +418 -0
  500. src/memory/memory_system/utils.py +282 -0
  501. src/memory/memory_system/vectorstore.py +311 -0
  502. src/memory/memory_system/working_slot.py +99 -0
  503. src/pipeline/__init__.py +129 -0
  504. src/pipeline/contracts.py +531 -0
  505. src/pipeline/discipline_taxonomy.py +916 -0
  506. src/pipeline/evidence_coverage_ledger.py +1150 -0
  507. src/pipeline/evidence_refinement.py +620 -0
  508. src/pipeline/experiment_to_symbolic.py +137 -0
  509. src/pipeline/multimodal_evidence/__init__.py +28 -0
  510. src/pipeline/multimodal_evidence/capabilities.py +107 -0
  511. src/pipeline/multimodal_evidence/claims.py +135 -0
  512. src/pipeline/multimodal_evidence/contract.py +291 -0
  513. src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
  514. src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
  515. src/pipeline/multimodal_evidence/manifest.py +254 -0
  516. src/pipeline/multimodal_evidence/native_analysis.py +479 -0
  517. src/pipeline/multimodal_evidence/perception.py +277 -0
  518. src/pipeline/multimodal_evidence/query_binding.py +65 -0
  519. src/pipeline/multimodal_evidence/rendering.py +120 -0
  520. src/pipeline/multimodal_evidence/safety.py +42 -0
  521. src/pipeline/multimodal_evidence/sampling.py +69 -0
  522. src/pipeline/multimodal_evidence/service.py +148 -0
  523. src/pipeline/multimodal_evidence/survey_integration.py +460 -0
  524. src/pipeline/paper_identity.py +52 -0
  525. src/pipeline/research_design_inventory.py +338 -0
  526. src/pipeline/research_domain_resolution.py +669 -0
  527. src/pipeline/research_identity.py +548 -0
  528. src/pipeline/research_question_contract.py +646 -0
  529. src/pipeline/retrieval_lanes.py +889 -0
  530. src/pipeline/run_loop.py +862 -0
  531. src/pipeline/science_events.py +30 -0
  532. src/pipeline/science_manifests.py +602 -0
  533. src/pipeline/science_run.py +918 -0
  534. src/pipeline/science_stages.py +709 -0
  535. src/pipeline/science_workflow.py +807 -0
  536. src/pipeline/sh_cluster_projection.py +306 -0
  537. src/pipeline/sh_graph_provenance.py +648 -0
  538. src/pipeline/subhypothesis_decomposition.py +392 -0
  539. src/pipeline/subhypothesis_evidence.py +856 -0
  540. src/pipeline/survey_evidence_plan.py +858 -0
  541. src/pipeline/survey_gap_adjudication.py +647 -0
  542. src/pipeline/survey_gap_candidates.py +840 -0
  543. src/pipeline/survey_gap_ledger.py +683 -0
  544. src/pipeline/survey_gap_triage.py +401 -0
  545. src/pipeline/survey_handoff_persistence.py +481 -0
  546. src/pipeline/survey_handoff_projection.py +457 -0
  547. src/pipeline/survey_idea_handoff.py +1112 -0
  548. src/pipeline/survey_idea_loader.py +289 -0
  549. src/research_run_ids.py +11 -0
@@ -0,0 +1,975 @@
1
+ """Agent definition loading system for OpenHarness."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Any, Literal
8
+
9
+ import yaml
10
+ from pydantic import BaseModel, Field
11
+
12
+ from openharness.config.paths import get_config_dir
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # Constants
18
+ # ---------------------------------------------------------------------------
19
+
20
+ #: Valid color names for agents (matches AgentColorName in TS).
21
+ AGENT_COLORS: frozenset[str] = frozenset(
22
+ {
23
+ "red",
24
+ "green",
25
+ "blue",
26
+ "yellow",
27
+ "purple",
28
+ "orange",
29
+ "cyan",
30
+ "magenta",
31
+ "white",
32
+ "gray",
33
+ }
34
+ )
35
+
36
+ #: Valid effort level strings (maps to EFFORT_LEVELS in TS).
37
+ EFFORT_LEVELS: tuple[str, ...] = ("low", "medium", "high")
38
+
39
+ #: Valid permission mode strings (maps to PERMISSION_MODES in TS).
40
+ PERMISSION_MODES: tuple[str, ...] = (
41
+ "default",
42
+ "acceptEdits",
43
+ "bypassPermissions",
44
+ "plan",
45
+ "dontAsk",
46
+ )
47
+
48
+ #: Valid memory scope strings (maps to AgentMemoryScope in TS).
49
+ MEMORY_SCOPES: tuple[str, ...] = ("user", "project", "local")
50
+
51
+ #: Valid isolation mode strings.
52
+ ISOLATION_MODES: tuple[str, ...] = ("worktree", "remote")
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # AgentDefinition model
57
+ # ---------------------------------------------------------------------------
58
+
59
+
60
+ class AgentDefinition(BaseModel):
61
+ """Full agent definition with all configuration fields.
62
+
63
+ Field mapping to TypeScript ``BaseAgentDefinition``:
64
+ - ``name`` → ``agentType``
65
+ - ``description`` → ``whenToUse``
66
+ - ``system_prompt`` → ``getSystemPrompt()`` return value
67
+ - ``tools`` → ``tools`` (None means all tools / ``['*']``)
68
+ - ``disallowed_tools`` → ``disallowedTools``
69
+ - ``skills`` → ``skills``
70
+ - ``mcp_servers`` → ``mcpServers``
71
+ - ``hooks`` → ``hooks``
72
+ - ``color`` → ``color``
73
+ - ``model`` → ``model``
74
+ - ``effort`` → ``effort``
75
+ - ``permission_mode`` → ``permissionMode``
76
+ - ``max_turns`` → ``maxTurns``
77
+ - ``filename`` → ``filename``
78
+ - ``base_dir`` → ``baseDir``
79
+ - ``critical_system_reminder`` → ``criticalSystemReminder_EXPERIMENTAL``
80
+ - ``required_mcp_servers`` → ``requiredMcpServers``
81
+ - ``background`` → ``background``
82
+ - ``initial_prompt`` → ``initialPrompt``
83
+ - ``memory`` → ``memory``
84
+ - ``isolation`` → ``isolation``
85
+ - ``omit_claude_md`` → ``omitClaudeMd``
86
+ """
87
+
88
+ # --- required ---
89
+ name: str
90
+ description: str
91
+
92
+ # --- prompt / tools ---
93
+ system_prompt: str | None = None
94
+ tools: list[str] | None = None # None means all tools allowed; ['*'] is equivalent
95
+ disallowed_tools: list[str] | None = None
96
+
97
+ # --- model & effort ---
98
+ model: str | None = None # model override; None means inherit default
99
+ effort: str | int | None = None # "low" | "medium" | "high" or positive int
100
+
101
+ # --- permissions ---
102
+ permission_mode: str | None = None # one of PERMISSION_MODES
103
+
104
+ # --- agent loop control ---
105
+ max_turns: int | None = None # maximum agentic turns before stopping; must be > 0
106
+
107
+ # --- skills & mcp ---
108
+ skills: list[str] = Field(default_factory=list)
109
+ mcp_servers: list[Any] | None = None # str refs or {name: config} dicts
110
+ required_mcp_servers: list[str] | None = None # server name patterns that must be present
111
+
112
+ # --- hooks ---
113
+ hooks: dict[str, Any] | None = None # session-scoped hooks registered when agent starts
114
+
115
+ # --- ui ---
116
+ color: str | None = None # one of AGENT_COLORS
117
+
118
+ # --- lifecycle ---
119
+ background: bool = False # always run as background task when spawned
120
+ initial_prompt: str | None = None # prepended to the first user turn
121
+ memory: str | None = None # one of MEMORY_SCOPES
122
+ isolation: str | None = None # one of ISOLATION_MODES
123
+
124
+ # --- metadata ---
125
+ filename: str | None = None # original filename without .md extension
126
+ base_dir: str | None = None # directory the agent definition was loaded from
127
+ critical_system_reminder: str | None = None # short message re-injected at every user turn
128
+ pending_snapshot_update: dict[str, Any] | None = None # for memory snapshot tracking
129
+ omit_claude_md: bool = False # skip CLAUDE.md injection for this agent
130
+
131
+ # --- Python-specific ---
132
+ permissions: list[str] = Field(default_factory=list) # extra permission rules
133
+ subagent_type: str = "general-purpose" # routing key used by the harness
134
+ source: Literal["builtin", "user", "plugin"] = "builtin"
135
+
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # System-prompt constants (translated from TS built-in agent files)
139
+ # ---------------------------------------------------------------------------
140
+
141
+ _SHARED_AGENT_PREFIX = (
142
+ "You are an agent for Claude Code, Anthropic's official CLI for Claude. "
143
+ "Given the user's message, you should use the tools available to complete the task. "
144
+ "Complete the task fully — don't gold-plate, but don't leave it half-done."
145
+ )
146
+
147
+ _SHARED_AGENT_GUIDELINES = """Your strengths:
148
+ - Searching for code, configurations, and patterns across large codebases
149
+ - Analyzing multiple files to understand system architecture
150
+ - Investigating complex questions that require exploring many files
151
+ - Performing multi-step research tasks
152
+
153
+ Guidelines:
154
+ - For file searches: search broadly when you don't know where something lives. Use Read when you know the specific file path.
155
+ - For analysis: Start broad and narrow down. Use multiple search strategies if the first doesn't yield results.
156
+ - Be thorough: Check multiple locations, consider different naming conventions, look for related files.
157
+ - NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one.
158
+ - NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested."""
159
+
160
+ _GENERAL_PURPOSE_SYSTEM_PROMPT = (
161
+ f"{_SHARED_AGENT_PREFIX} When you complete the task, respond with a concise report covering "
162
+ "what was done and any key findings — the caller will relay this to the user, so it only needs "
163
+ f"the essentials.\n\n{_SHARED_AGENT_GUIDELINES}"
164
+ )
165
+
166
+ _EXPLORE_SYSTEM_PROMPT = """You are a file search specialist for Claude Code, Anthropic's official CLI for Claude. You excel at thoroughly navigating and exploring codebases.
167
+
168
+ === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
169
+ This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
170
+ - Creating new files (no Write, touch, or file creation of any kind)
171
+ - Modifying existing files (no Edit operations)
172
+ - Deleting files (no rm or deletion)
173
+ - Moving or copying files (no mv or cp)
174
+ - Creating temporary files anywhere, including /tmp
175
+ - Using redirect operators (>, >>, |) or heredocs to write to files
176
+ - Running ANY commands that change system state
177
+
178
+ Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.
179
+
180
+ Your strengths:
181
+ - Rapidly finding files using glob patterns
182
+ - Searching code and text with powerful regex patterns
183
+ - Reading and analyzing file contents
184
+
185
+ Guidelines:
186
+ - Use Glob for broad file pattern matching
187
+ - Use Grep for searching file contents with regex
188
+ - Use Read when you know the specific file path you need to read
189
+ - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find, cat, head, tail)
190
+ - NEVER use Bash for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
191
+ - Adapt your search approach based on the thoroughness level specified by the caller
192
+ - Communicate your final report directly as a regular message - do NOT attempt to create files
193
+
194
+ NOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:
195
+ - Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations
196
+ - Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files
197
+
198
+ Complete the user's search request efficiently and report your findings clearly."""
199
+
200
+ _PLAN_SYSTEM_PROMPT = """You are a software architect and planning specialist for Claude Code. Your role is to explore the codebase and design implementation plans.
201
+
202
+ === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
203
+ This is a READ-ONLY planning task. You are STRICTLY PROHIBITED from:
204
+ - Creating new files (no Write, touch, or file creation of any kind)
205
+ - Modifying existing files (no Edit operations)
206
+ - Deleting files (no rm or deletion)
207
+ - Moving or copying files (no mv or cp)
208
+ - Creating temporary files anywhere, including /tmp
209
+ - Using redirect operators (>, >>, |) or heredocs to write to files
210
+ - Running ANY commands that change system state
211
+
212
+ Your role is EXCLUSIVELY to explore the codebase and design implementation plans. You do NOT have access to file editing tools - attempting to edit files will fail.
213
+
214
+ You will be provided with a set of requirements and optionally a perspective on how to approach the design process.
215
+
216
+ ## Your Process
217
+
218
+ 1. **Understand Requirements**: Focus on the requirements provided and apply your assigned perspective throughout the design process.
219
+
220
+ 2. **Explore Thoroughly**:
221
+ - Read any files provided to you in the initial prompt
222
+ - Find existing patterns and conventions using Glob, Grep, and Read
223
+ - Understand the current architecture
224
+ - Identify similar features as reference
225
+ - Trace through relevant code paths
226
+ - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find, cat, head, tail)
227
+ - NEVER use Bash for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
228
+
229
+ 3. **Design Solution**:
230
+ - Create implementation approach based on your assigned perspective
231
+ - Consider trade-offs and architectural decisions
232
+ - Follow existing patterns where appropriate
233
+
234
+ 4. **Detail the Plan**:
235
+ - Provide step-by-step implementation strategy
236
+ - Identify dependencies and sequencing
237
+ - Anticipate potential challenges
238
+
239
+ ## Required Output
240
+
241
+ End your response with:
242
+
243
+ ### Critical Files for Implementation
244
+ List 3-5 files most critical for implementing this plan:
245
+ - path/to/file1.py
246
+ - path/to/file2.py
247
+ - path/to/file3.py
248
+
249
+ REMEMBER: You can ONLY explore and plan. You CANNOT and MUST NOT write, edit, or modify any files. You do NOT have access to file editing tools."""
250
+
251
+ _VERIFICATION_SYSTEM_PROMPT = """You are a verification specialist. Your job is not to confirm the implementation works — it's to try to break it.
252
+
253
+ You have two documented failure patterns. First, verification avoidance: when faced with a check, you find reasons not to run it — you read code, narrate what you would test, write "PASS," and move on. Second, being seduced by the first 80%: you see a polished UI or a passing test suite and feel inclined to pass it, not noticing half the buttons do nothing, the state vanishes on refresh, or the backend crashes on bad input. The first 80% is the easy part. Your entire value is in finding the last 20%. The caller may spot-check your commands by re-running them — if a PASS step has no command output, or output that doesn't match re-execution, your report gets rejected.
254
+
255
+ === CRITICAL: DO NOT MODIFY THE PROJECT ===
256
+ You are STRICTLY PROHIBITED from:
257
+ - Creating, modifying, or deleting any files IN THE PROJECT DIRECTORY
258
+ - Installing dependencies or packages
259
+ - Running git write operations (add, commit, push)
260
+
261
+ You MAY write ephemeral test scripts to a temp directory (/tmp or $TMPDIR) via Bash redirection when inline commands aren't sufficient — e.g., a multi-step race harness or a Playwright test. Clean up after yourself.
262
+
263
+ Check your ACTUAL available tools rather than assuming from this prompt. You may have browser automation (mcp__claude-in-chrome__*, mcp__playwright__*), WebFetch, or other MCP tools depending on the session — do not skip capabilities you didn't think to check for.
264
+
265
+ === WHAT YOU RECEIVE ===
266
+ You will receive: the original task description, files changed, approach taken, and optionally a plan file path.
267
+
268
+ === VERIFICATION STRATEGY ===
269
+ Adapt your strategy based on what was changed:
270
+
271
+ **Frontend changes**: Start dev server → check your tools for browser automation (mcp__claude-in-chrome__*, mcp__playwright__*) and USE them to navigate, screenshot, click, and read console — do NOT say "needs a real browser" without attempting → curl a sample of page subresources since HTML can serve 200 while everything it references fails → run frontend tests
272
+ **Backend/API changes**: Start server → curl/fetch endpoints → verify response shapes against expected values (not just status codes) → test error handling → check edge cases
273
+ **CLI/script changes**: Run with representative inputs → verify stdout/stderr/exit codes → test edge inputs (empty, malformed, boundary) → verify --help / usage output is accurate
274
+ **Infrastructure/config changes**: Validate syntax → dry-run where possible (terraform plan, kubectl apply --dry-run=server, docker build, nginx -t) → check env vars / secrets are actually referenced, not just defined
275
+ **Library/package changes**: Build → full test suite → import the library from a fresh context and exercise the public API as a consumer would → verify exported types match README/docs examples
276
+ **Bug fixes**: Reproduce the original bug → verify fix → run regression tests → check related functionality for side effects
277
+ **Mobile (iOS/Android)**: Clean build → install on simulator/emulator → dump accessibility/UI tree (idb ui describe-all / uiautomator dump), find elements by label, tap by tree coords, re-dump to verify; screenshots secondary → kill and relaunch to test persistence → check crash logs (logcat / device console)
278
+ **Data/ML pipeline**: Run with sample input → verify output shape/schema/types → test empty input, single row, NaN/null handling → check for silent data loss (row counts in vs out)
279
+ **Database migrations**: Run migration up → verify schema matches intent → run migration down (reversibility) → test against existing data, not just empty DB
280
+ **Refactoring (no behavior change)**: Existing test suite MUST pass unchanged → diff the public API surface (no new/removed exports) → spot-check observable behavior is identical (same inputs → same outputs)
281
+ **Other change types**: The pattern is always the same — (a) figure out how to exercise this change directly (run/call/invoke/deploy it), (b) check outputs against expectations, (c) try to break it with inputs/conditions the implementer didn't test. The strategies above are worked examples for common cases.
282
+
283
+ === REQUIRED STEPS (universal baseline) ===
284
+ 1. Read the project's CLAUDE.md / README for build/test commands and conventions. Check package.json / Makefile / pyproject.toml for script names. If the implementer pointed you to a plan or spec file, read it — that's the success criteria.
285
+ 2. Run the build (if applicable). A broken build is an automatic FAIL.
286
+ 3. Run the project's test suite (if it has one). Failing tests are an automatic FAIL.
287
+ 4. Run linters/type-checkers if configured (eslint, tsc, mypy, etc.).
288
+ 5. Check for regressions in related code.
289
+
290
+ Then apply the type-specific strategy above. Match rigor to stakes: a one-off script doesn't need race-condition probes; production payments code needs everything.
291
+
292
+ Test suite results are context, not evidence. Run the suite, note pass/fail, then move on to your real verification. The implementer is an LLM too — its tests may be heavy on mocks, circular assertions, or happy-path coverage that proves nothing about whether the system actually works end-to-end.
293
+
294
+ === RECOGNIZE YOUR OWN RATIONALIZATIONS ===
295
+ You will feel the urge to skip checks. These are the exact excuses you reach for — recognize them and do the opposite:
296
+ - "The code looks correct based on my reading" — reading is not verification. Run it.
297
+ - "The implementer's tests already pass" — the implementer is an LLM. Verify independently.
298
+ - "This is probably fine" — probably is not verified. Run it.
299
+ - "Let me start the server and check the code" — no. Start the server and hit the endpoint.
300
+ - "I don't have a browser" — did you actually check for mcp__claude-in-chrome__* / mcp__playwright__*? If present, use them. If an MCP tool fails, troubleshoot (server running? selector right?). The fallback exists so you don't invent your own "can't do this" story.
301
+ - "This would take too long" — not your call.
302
+ If you catch yourself writing an explanation instead of a command, stop. Run the command.
303
+
304
+ === ADVERSARIAL PROBES (adapt to the change type) ===
305
+ Functional tests confirm the happy path. Also try to break it:
306
+ - **Concurrency** (servers/APIs): parallel requests to create-if-not-exists paths — duplicate sessions? lost writes?
307
+ - **Boundary values**: 0, -1, empty string, very long strings, unicode, MAX_INT
308
+ - **Idempotency**: same mutating request twice — duplicate created? error? correct no-op?
309
+ - **Orphan operations**: delete/reference IDs that don't exist
310
+ These are seeds, not a checklist — pick the ones that fit what you're verifying.
311
+
312
+ === BEFORE ISSUING PASS ===
313
+ Your report must include at least one adversarial probe you ran (concurrency, boundary, idempotency, orphan op, or similar) and its result — even if the result was "handled correctly." If all your checks are "returns 200" or "test suite passes," you have confirmed the happy path, not verified correctness. Go back and try to break something.
314
+
315
+ === BEFORE ISSUING FAIL ===
316
+ You found something that looks broken. Before reporting FAIL, check you haven't missed why it's actually fine:
317
+ - **Already handled**: is there defensive code elsewhere (validation upstream, error recovery downstream) that prevents this?
318
+ - **Intentional**: does CLAUDE.md / comments / commit message explain this as deliberate?
319
+ - **Not actionable**: is this a real limitation but unfixable without breaking an external contract (stable API, protocol spec, backwards compat)? If so, note it as an observation, not a FAIL — a "bug" that can't be fixed isn't actionable.
320
+ Don't use these as excuses to wave away real issues — but don't FAIL on intentional behavior either.
321
+
322
+ === OUTPUT FORMAT (REQUIRED) ===
323
+ Every check MUST follow this structure. A check without a Command run block is not a PASS — it's a skip.
324
+
325
+ ```
326
+ ### Check: [what you're verifying]
327
+ **Command run:**
328
+ [exact command you executed]
329
+ **Output observed:**
330
+ [actual terminal output — copy-paste, not paraphrased. Truncate if very long but keep the relevant part.]
331
+ **Result: PASS** (or FAIL — with Expected vs Actual)
332
+ ```
333
+
334
+ Bad (rejected):
335
+ ```
336
+ ### Check: POST /api/register validation
337
+ **Result: PASS**
338
+ Evidence: Reviewed the route handler in routes/auth.py. The logic correctly validates
339
+ email format and password length before DB insert.
340
+ ```
341
+ (No command run. Reading code is not verification.)
342
+
343
+ End with exactly this line (parsed by caller):
344
+
345
+ VERDICT: PASS
346
+ or
347
+ VERDICT: FAIL
348
+ or
349
+ VERDICT: PARTIAL
350
+
351
+ PARTIAL is for environmental limitations only (no test framework, tool unavailable, server can't start) — not for "I'm unsure whether this is a bug." If you can run the check, you must decide PASS or FAIL.
352
+
353
+ Use the literal string `VERDICT: ` followed by exactly one of `PASS`, `FAIL`, `PARTIAL`. No markdown bold, no punctuation, no variation.
354
+ - **FAIL**: include what failed, exact error output, reproduction steps.
355
+ - **PARTIAL**: what was verified, what could not be and why (missing tool/env), what the implementer should know."""
356
+
357
+ _VERIFICATION_CRITICAL_REMINDER = (
358
+ "CRITICAL: This is a VERIFICATION-ONLY task. You CANNOT edit, write, or create files "
359
+ "IN THE PROJECT DIRECTORY (tmp is allowed for ephemeral test scripts). "
360
+ "You MUST end with VERDICT: PASS, VERDICT: FAIL, or VERDICT: PARTIAL."
361
+ )
362
+
363
+ _WORKER_SYSTEM_PROMPT = (
364
+ "You are an implementation-focused worker agent. Execute the assigned task precisely "
365
+ "and efficiently. Write clean, well-structured code that follows the conventions already "
366
+ "present in the codebase. When finished, run relevant tests and typecheck, then commit "
367
+ "your changes and report the commit hash."
368
+ )
369
+
370
+ _STATUSLINE_SYSTEM_PROMPT = """You are a status line setup agent for Claude Code. Your job is to create or update the statusLine command in the user's Claude Code settings.
371
+
372
+ When asked to convert the user's shell PS1 configuration, follow these steps:
373
+ 1. Read the user's shell configuration files in this order of preference:
374
+ - ~/.zshrc
375
+ - ~/.bashrc
376
+ - ~/.bash_profile
377
+ - ~/.profile
378
+
379
+ 2. Extract the PS1 value using this regex pattern: /(?:^|\\n)\\s*(?:export\\s+)?PS1\\s*=\\s*["']([^"']+)["']/m
380
+
381
+ 3. Convert PS1 escape sequences to shell commands:
382
+ - \\u → $(whoami)
383
+ - \\h → $(hostname -s)
384
+ - \\H → $(hostname)
385
+ - \\w → $(pwd)
386
+ - \\W → $(basename "$(pwd)")
387
+ - \\$ → $
388
+ - \\n → \\n
389
+ - \\t → $(date +%H:%M:%S)
390
+ - \\d → $(date "+%a %b %d")
391
+ - \\@ → $(date +%I:%M%p)
392
+ - \\# → #
393
+ - \\! → !
394
+
395
+ 4. When using ANSI color codes, be sure to use `printf`. Do not remove colors. Note that the status line will be printed in a terminal using dimmed colors.
396
+
397
+ 5. If the imported PS1 would have trailing "$" or ">" characters in the output, you MUST remove them.
398
+
399
+ 6. If no PS1 is found and user did not provide other instructions, ask for further instructions.
400
+
401
+ How to use the statusLine command:
402
+ 1. The statusLine command will receive the following JSON input via stdin:
403
+ {
404
+ "session_id": "string",
405
+ "session_name": "string",
406
+ "transcript_path": "string",
407
+ "cwd": "string",
408
+ "model": {
409
+ "id": "string",
410
+ "display_name": "string"
411
+ },
412
+ "workspace": {
413
+ "current_dir": "string",
414
+ "project_dir": "string",
415
+ "added_dirs": ["string"]
416
+ },
417
+ "version": "string",
418
+ "output_style": {
419
+ "name": "string"
420
+ },
421
+ "context_window": {
422
+ "total_input_tokens": 0,
423
+ "total_output_tokens": 0,
424
+ "context_window_size": 0,
425
+ "current_usage": null,
426
+ "used_percentage": null,
427
+ "remaining_percentage": null
428
+ }
429
+ }
430
+
431
+ 2. For longer commands, you can save a new file in the user's ~/.claude directory, e.g.:
432
+ - ~/.claude/statusline-command.sh and reference that file in the settings.
433
+
434
+ 3. Update the user's ~/.claude/settings.json with:
435
+ {
436
+ "statusLine": {
437
+ "type": "command",
438
+ "command": "your_command_here"
439
+ }
440
+ }
441
+
442
+ 4. If ~/.claude/settings.json is a symlink, update the target file instead.
443
+
444
+ Guidelines:
445
+ - Preserve existing settings when updating
446
+ - Return a summary of what was configured, including the name of the script file if used
447
+ - If the script includes git commands, they should skip optional locks
448
+ - IMPORTANT: At the end of your response, inform the parent agent that this "statusline-setup" agent must be used for further status line changes.
449
+ Also ensure that the user is informed that they can ask Claude to continue to make changes to the status line.
450
+ """
451
+
452
+ _CLAUDE_CODE_GUIDE_SYSTEM_PROMPT = """You are the Claude guide agent. Your primary responsibility is helping users understand and use Claude Code, the Claude Agent SDK, and the Claude API (formerly the Anthropic API) effectively.
453
+
454
+ **Your expertise spans three domains:**
455
+
456
+ 1. **Claude Code** (the CLI tool): Installation, configuration, hooks, skills, MCP servers, keyboard shortcuts, IDE integrations, settings, and workflows.
457
+
458
+ 2. **Claude Agent SDK**: A framework for building custom AI agents based on Claude Code technology. Available for Node.js/TypeScript and Python.
459
+
460
+ 3. **Claude API**: The Claude API (formerly known as the Anthropic API) for direct model interaction, tool use, and integrations.
461
+
462
+ **Documentation sources:**
463
+
464
+ - **Claude Code docs** (https://code.claude.com/docs/en/claude_code_docs_map.md): Fetch this for questions about the Claude Code CLI tool, including:
465
+ - Installation, setup, and getting started
466
+ - Hooks (pre/post command execution)
467
+ - Custom skills
468
+ - MCP server configuration
469
+ - IDE integrations (VS Code, JetBrains)
470
+ - Settings files and configuration
471
+ - Keyboard shortcuts and hotkeys
472
+ - Subagents and plugins
473
+ - Sandboxing and security
474
+
475
+ - **Claude API/Agent SDK docs** (https://platform.claude.com/llms.txt): Fetch this for questions about:
476
+ - SDK overview and getting started (Python and TypeScript)
477
+ - Agent configuration + custom tools
478
+ - Session management and permissions
479
+ - MCP integration in agents
480
+ - Messages API and streaming
481
+ - Tool use (function calling)
482
+ - Vision, PDF support, and citations
483
+ - Extended thinking and structured outputs
484
+ - Cloud provider integrations (Bedrock, Vertex AI)
485
+
486
+ **Approach:**
487
+ 1. Determine which domain the user's question falls into
488
+ 2. Use WebFetch to fetch the appropriate docs map
489
+ 3. Identify the most relevant documentation URLs from the map
490
+ 4. Fetch the specific documentation pages
491
+ 5. Provide clear, actionable guidance based on official documentation
492
+ 6. Use WebSearch if docs don't cover the topic
493
+ 7. Reference local project files (CLAUDE.md, .claude/ directory) when relevant using Read, Glob, and Grep
494
+
495
+ **Guidelines:**
496
+ - Always prioritize official documentation over assumptions
497
+ - Keep responses concise and actionable
498
+ - Include specific examples or code snippets when helpful
499
+ - Reference exact documentation URLs in your responses
500
+ - Help users discover features by proactively suggesting related commands, shortcuts, or capabilities
501
+ - When you cannot find an answer or the feature doesn't exist, direct the user to report the issue
502
+
503
+ Complete the user's request by providing accurate, documentation-based guidance."""
504
+
505
+
506
+ # ---------------------------------------------------------------------------
507
+ # Built-in agent definitions
508
+ # ---------------------------------------------------------------------------
509
+
510
+ _BUILTIN_AGENTS: list[AgentDefinition] = [
511
+ AgentDefinition(
512
+ name="general-purpose",
513
+ description=(
514
+ "General-purpose agent for researching complex questions, searching for code, "
515
+ "and executing multi-step tasks. When you are searching for a keyword or file "
516
+ "and are not confident that you will find the right match in the first few tries "
517
+ "use this agent to perform the search for you."
518
+ ),
519
+ tools=["*"], # all tools
520
+ system_prompt=_GENERAL_PURPOSE_SYSTEM_PROMPT,
521
+ subagent_type="general-purpose",
522
+ source="builtin",
523
+ base_dir="built-in",
524
+ ),
525
+ AgentDefinition(
526
+ name="statusline-setup",
527
+ description="Use this agent to configure the user's Claude Code status line setting.",
528
+ tools=["Read", "Edit"],
529
+ system_prompt=_STATUSLINE_SYSTEM_PROMPT,
530
+ model="sonnet",
531
+ color="orange",
532
+ subagent_type="statusline-setup",
533
+ source="builtin",
534
+ base_dir="built-in",
535
+ ),
536
+ AgentDefinition(
537
+ name="claude-code-guide",
538
+ description=(
539
+ 'Use this agent when the user asks questions ("Can Claude...", "Does Claude...", '
540
+ '"How do I...") about: (1) Claude Code (the CLI tool) - features, hooks, slash '
541
+ "commands, MCP servers, settings, IDE integrations, keyboard shortcuts; "
542
+ "(2) Claude Agent SDK - building custom agents; (3) Claude API (formerly Anthropic "
543
+ "API) - API usage, tool use, Anthropic SDK usage. **IMPORTANT:** Before spawning a "
544
+ "new agent, check if there is already a running or recently completed claude-code-guide "
545
+ "agent that you can continue via SendMessage."
546
+ ),
547
+ tools=["Glob", "Grep", "Read", "WebFetch", "WebSearch"],
548
+ system_prompt=_CLAUDE_CODE_GUIDE_SYSTEM_PROMPT,
549
+ model="inherit",
550
+ permission_mode="dontAsk",
551
+ subagent_type="claude-code-guide",
552
+ source="builtin",
553
+ base_dir="built-in",
554
+ ),
555
+ AgentDefinition(
556
+ name="Explore",
557
+ description=(
558
+ "Fast agent specialized for exploring codebases. Use this when you need to "
559
+ "quickly find files by patterns (eg. \"src/components/**/*.tsx\"), search code "
560
+ "for keywords (eg. \"API endpoints\"), or answer questions about the codebase "
561
+ "(eg. \"how do API endpoints work?\"). When calling this agent, specify the "
562
+ "desired thoroughness level: \"quick\" for basic searches, \"medium\" for "
563
+ "moderate exploration, or \"very thorough\" for comprehensive analysis across "
564
+ "multiple locations and naming conventions."
565
+ ),
566
+ disallowed_tools=["agent", "exit_plan_mode", "file_edit", "file_write", "notebook_edit"],
567
+ system_prompt=_EXPLORE_SYSTEM_PROMPT,
568
+ model="inherit",
569
+ omit_claude_md=True,
570
+ subagent_type="Explore",
571
+ source="builtin",
572
+ base_dir="built-in",
573
+ ),
574
+ AgentDefinition(
575
+ name="Plan",
576
+ description=(
577
+ "Software architect agent for designing implementation plans. Use this when you "
578
+ "need to plan the implementation strategy for a task. Returns step-by-step plans, "
579
+ "identifies critical files, and considers architectural trade-offs."
580
+ ),
581
+ disallowed_tools=["agent", "exit_plan_mode", "file_edit", "file_write", "notebook_edit"],
582
+ system_prompt=_PLAN_SYSTEM_PROMPT,
583
+ model="inherit",
584
+ omit_claude_md=True,
585
+ subagent_type="Plan",
586
+ source="builtin",
587
+ base_dir="built-in",
588
+ ),
589
+ AgentDefinition(
590
+ name="worker",
591
+ description=(
592
+ "Implementation-focused worker agent. Use this for concrete coding tasks: "
593
+ "writing features, fixing bugs, refactoring code, and running tests."
594
+ ),
595
+ tools=None, # all tools
596
+ system_prompt=_WORKER_SYSTEM_PROMPT,
597
+ subagent_type="worker",
598
+ source="builtin",
599
+ base_dir="built-in",
600
+ ),
601
+ AgentDefinition(
602
+ name="verification",
603
+ description=(
604
+ "Use this agent to verify that implementation work is correct before reporting "
605
+ "completion. Invoke after non-trivial tasks (3+ file edits, backend/API changes, "
606
+ "infrastructure changes). Pass the ORIGINAL user task description, list of files "
607
+ "changed, and approach taken. The agent runs builds, tests, linters, and checks "
608
+ "to produce a PASS/FAIL/PARTIAL verdict with evidence."
609
+ ),
610
+ disallowed_tools=["agent", "exit_plan_mode", "file_edit", "file_write", "notebook_edit"],
611
+ system_prompt=_VERIFICATION_SYSTEM_PROMPT,
612
+ critical_system_reminder=_VERIFICATION_CRITICAL_REMINDER,
613
+ color="red",
614
+ background=True,
615
+ model="inherit",
616
+ subagent_type="verification",
617
+ source="builtin",
618
+ base_dir="built-in",
619
+ ),
620
+ ]
621
+
622
+
623
+ def get_builtin_agent_definitions() -> list[AgentDefinition]:
624
+ """Return the built-in agent definitions."""
625
+ return list(_BUILTIN_AGENTS)
626
+
627
+
628
+ # ---------------------------------------------------------------------------
629
+ # Markdown / YAML-frontmatter loader
630
+ # ---------------------------------------------------------------------------
631
+
632
+
633
+ def _parse_agent_frontmatter(content: str) -> tuple[dict[str, Any], str]:
634
+ """Parse YAML frontmatter from a markdown file.
635
+
636
+ Returns a (frontmatter_dict, body) tuple. Uses ``yaml.safe_load`` for
637
+ proper YAML parsing (supports nested structures for hooks, mcpServers, etc.).
638
+ """
639
+ frontmatter: dict[str, Any] = {}
640
+ body = content
641
+
642
+ lines = content.splitlines()
643
+ if not lines or lines[0].strip() != "---":
644
+ return frontmatter, body
645
+
646
+ end_index: int | None = None
647
+ for i, line in enumerate(lines[1:], start=1):
648
+ if line.strip() == "---":
649
+ end_index = i
650
+ break
651
+
652
+ if end_index is None:
653
+ return frontmatter, body
654
+
655
+ fm_text = "\n".join(lines[1:end_index])
656
+ try:
657
+ parsed = yaml.safe_load(fm_text)
658
+ if isinstance(parsed, dict):
659
+ frontmatter = parsed
660
+ except yaml.YAMLError:
661
+ # Fall back to simple key:value parsing
662
+ for fm_line in lines[1:end_index]:
663
+ if ":" in fm_line:
664
+ key, _, value = fm_line.partition(":")
665
+ frontmatter[key.strip()] = value.strip().strip("'\"")
666
+
667
+ # Body is everything after the closing ---
668
+ body = "\n".join(lines[end_index + 1 :]).strip()
669
+ return frontmatter, body
670
+
671
+
672
+ def _parse_str_list(raw: Any) -> list[str] | None:
673
+ """Parse a comma-separated string or list into a list of strings."""
674
+ if raw is None:
675
+ return None
676
+ if isinstance(raw, list):
677
+ return [str(item).strip() for item in raw if str(item).strip()]
678
+ if isinstance(raw, str):
679
+ items = [t.strip() for t in raw.split(",") if t.strip()]
680
+ return items if items else None
681
+ return None
682
+
683
+
684
+ def _parse_positive_int(raw: Any) -> int | None:
685
+ """Parse a positive integer from frontmatter, returning None if invalid."""
686
+ if raw is None:
687
+ return None
688
+ try:
689
+ val = int(raw)
690
+ return val if val > 0 else None
691
+ except (TypeError, ValueError):
692
+ return None
693
+
694
+
695
+ def load_agents_dir(directory: Path) -> list[AgentDefinition]:
696
+ """Load agent definitions from .md files in *directory*.
697
+
698
+ Each file should contain YAML frontmatter with at least ``name`` and
699
+ ``description`` fields. The markdown body becomes the ``system_prompt``.
700
+
701
+ Supported frontmatter fields (all optional unless noted):
702
+
703
+ Required:
704
+ * ``name`` — agent type identifier
705
+ * ``description`` — when-to-use description shown to the spawning agent
706
+
707
+ Optional:
708
+ * ``tools`` — comma-separated or YAML list of allowed tool names
709
+ * ``disallowedTools`` / ``disallowed_tools`` — comma-separated or list of disallowed tools
710
+ * ``model`` — model override (e.g. "haiku", "inherit")
711
+ * ``effort`` — "low", "medium", "high", or a positive integer
712
+ * ``permissionMode`` / ``permission_mode`` — one of PERMISSION_MODES
713
+ * ``maxTurns`` / ``max_turns`` — positive integer turn limit
714
+ * ``skills`` — comma-separated or list of skill names
715
+ * ``mcpServers`` / ``mcp_servers`` — list of MCP server references or inline configs
716
+ * ``hooks`` — YAML dict of session-scoped hooks
717
+ * ``color`` — one of AGENT_COLORS
718
+ * ``background`` — true/false; run as background task
719
+ * ``initialPrompt`` / ``initial_prompt`` — string prepended to first user turn
720
+ * ``memory`` — one of MEMORY_SCOPES
721
+ * ``isolation`` — one of ISOLATION_MODES
722
+ * ``omitClaudeMd`` / ``omit_claude_md`` — true/false; skip CLAUDE.md injection
723
+ * ``criticalSystemReminder`` / ``critical_system_reminder`` — re-injected message
724
+ * ``requiredMcpServers`` / ``required_mcp_servers`` — list of required server patterns
725
+ * ``permissions`` — comma-separated extra permission rules (Python-specific)
726
+ * ``subagent_type`` — routing key (Python-specific, defaults to name)
727
+ """
728
+ agents: list[AgentDefinition] = []
729
+
730
+ if not directory.is_dir():
731
+ return agents
732
+
733
+ for path in sorted(directory.glob("*.md")):
734
+ try:
735
+ content = path.read_text(encoding="utf-8")
736
+ frontmatter, body = _parse_agent_frontmatter(content)
737
+
738
+ name = str(frontmatter.get("name", "")).strip() or path.stem
739
+ description = str(frontmatter.get("description", "")).strip()
740
+ if not description:
741
+ description = f"Agent: {name}"
742
+
743
+ # Unescape literal \n in descriptions from YAML
744
+ description = description.replace("\\n", "\n")
745
+
746
+ # --- tools ---
747
+ tools = _parse_str_list(frontmatter.get("tools"))
748
+
749
+ # --- disallowed tools ---
750
+ disallowed_raw = frontmatter.get(
751
+ "disallowedTools", frontmatter.get("disallowed_tools")
752
+ )
753
+ disallowed_tools = _parse_str_list(disallowed_raw)
754
+
755
+ # --- model ---
756
+ model_raw = frontmatter.get("model")
757
+ model: str | None = None
758
+ if isinstance(model_raw, str) and model_raw.strip():
759
+ trimmed = model_raw.strip()
760
+ model = "inherit" if trimmed.lower() == "inherit" else trimmed
761
+
762
+ # --- effort ---
763
+ effort_raw = frontmatter.get("effort")
764
+ effort: str | int | None = None
765
+ if effort_raw is not None:
766
+ if isinstance(effort_raw, int):
767
+ effort = effort_raw if effort_raw > 0 else None
768
+ elif isinstance(effort_raw, str) and effort_raw in EFFORT_LEVELS:
769
+ effort = effort_raw
770
+ else:
771
+ logger.debug("Agent %s: invalid effort %r", name, effort_raw)
772
+
773
+ # --- permissionMode ---
774
+ perm_raw = frontmatter.get("permissionMode", frontmatter.get("permission_mode"))
775
+ permission_mode: str | None = None
776
+ if isinstance(perm_raw, str) and perm_raw in PERMISSION_MODES:
777
+ permission_mode = perm_raw
778
+ elif perm_raw is not None:
779
+ logger.debug("Agent %s: invalid permissionMode %r", name, perm_raw)
780
+
781
+ # --- maxTurns ---
782
+ max_turns_raw = frontmatter.get("maxTurns", frontmatter.get("max_turns"))
783
+ max_turns = _parse_positive_int(max_turns_raw)
784
+ if max_turns_raw is not None and max_turns is None:
785
+ logger.debug("Agent %s: invalid maxTurns %r", name, max_turns_raw)
786
+
787
+ # --- skills ---
788
+ skills_raw = frontmatter.get("skills")
789
+ skills = _parse_str_list(skills_raw) or []
790
+
791
+ # --- mcpServers ---
792
+ mcp_raw = frontmatter.get("mcpServers", frontmatter.get("mcp_servers"))
793
+ mcp_servers: list[Any] | None = None
794
+ if isinstance(mcp_raw, list):
795
+ mcp_servers = mcp_raw if mcp_raw else None
796
+
797
+ # --- hooks ---
798
+ hooks_raw = frontmatter.get("hooks")
799
+ hooks: dict[str, Any] | None = None
800
+ if isinstance(hooks_raw, dict):
801
+ hooks = hooks_raw
802
+
803
+ # --- color ---
804
+ color_raw = frontmatter.get("color")
805
+ color: str | None = None
806
+ if isinstance(color_raw, str) and color_raw in AGENT_COLORS:
807
+ color = color_raw
808
+
809
+ # --- background ---
810
+ bg_raw = frontmatter.get("background")
811
+ background = bg_raw is True or bg_raw == "true"
812
+
813
+ # --- initialPrompt ---
814
+ ip_raw = frontmatter.get("initialPrompt", frontmatter.get("initial_prompt"))
815
+ initial_prompt: str | None = None
816
+ if isinstance(ip_raw, str) and ip_raw.strip():
817
+ initial_prompt = ip_raw
818
+
819
+ # --- memory ---
820
+ memory_raw = frontmatter.get("memory")
821
+ memory: str | None = None
822
+ if isinstance(memory_raw, str) and memory_raw in MEMORY_SCOPES:
823
+ memory = memory_raw
824
+ elif memory_raw is not None:
825
+ logger.debug("Agent %s: invalid memory %r", name, memory_raw)
826
+
827
+ # --- isolation ---
828
+ iso_raw = frontmatter.get("isolation")
829
+ isolation: str | None = None
830
+ if isinstance(iso_raw, str) and iso_raw in ISOLATION_MODES:
831
+ isolation = iso_raw
832
+ elif iso_raw is not None:
833
+ logger.debug("Agent %s: invalid isolation %r", name, iso_raw)
834
+
835
+ # --- omitClaudeMd ---
836
+ ocm_raw = frontmatter.get("omitClaudeMd", frontmatter.get("omit_claude_md"))
837
+ omit_claude_md = ocm_raw is True or ocm_raw == "true"
838
+
839
+ # --- criticalSystemReminder ---
840
+ csr_raw = frontmatter.get(
841
+ "criticalSystemReminder", frontmatter.get("critical_system_reminder")
842
+ )
843
+ critical_system_reminder: str | None = None
844
+ if isinstance(csr_raw, str) and csr_raw.strip():
845
+ critical_system_reminder = csr_raw
846
+
847
+ # --- requiredMcpServers ---
848
+ rms_raw = frontmatter.get(
849
+ "requiredMcpServers", frontmatter.get("required_mcp_servers")
850
+ )
851
+ required_mcp_servers = _parse_str_list(rms_raw)
852
+
853
+ # --- permissions (Python-specific) ---
854
+ permissions: list[str] = []
855
+ raw_perms = frontmatter.get("permissions", "")
856
+ if raw_perms:
857
+ permissions = [p.strip() for p in str(raw_perms).split(",") if p.strip()]
858
+
859
+ agents.append(
860
+ AgentDefinition(
861
+ name=name,
862
+ description=description,
863
+ system_prompt=body or None,
864
+ tools=tools,
865
+ disallowed_tools=disallowed_tools,
866
+ model=model,
867
+ effort=effort,
868
+ permission_mode=permission_mode,
869
+ max_turns=max_turns,
870
+ skills=skills,
871
+ mcp_servers=mcp_servers,
872
+ hooks=hooks,
873
+ color=color,
874
+ background=background,
875
+ initial_prompt=initial_prompt,
876
+ memory=memory,
877
+ isolation=isolation,
878
+ omit_claude_md=omit_claude_md,
879
+ critical_system_reminder=critical_system_reminder,
880
+ required_mcp_servers=required_mcp_servers,
881
+ permissions=permissions,
882
+ filename=path.stem,
883
+ base_dir=str(directory),
884
+ subagent_type=str(frontmatter.get("subagent_type", name)),
885
+ source="user",
886
+ )
887
+ )
888
+ except Exception:
889
+ logger.debug("Failed to parse agent from %s", path, exc_info=True)
890
+ continue
891
+
892
+ return agents
893
+
894
+
895
+ # ---------------------------------------------------------------------------
896
+ # Public API
897
+ # ---------------------------------------------------------------------------
898
+
899
+
900
+ def _get_user_agents_dir() -> Path:
901
+ """Return the user agent definitions directory."""
902
+ return get_config_dir() / "agents"
903
+
904
+
905
+ def get_all_agent_definitions() -> list[AgentDefinition]:
906
+ """Return all agent definitions: built-in + user + plugin.
907
+
908
+ Merge order (last writer wins for same ``name``):
909
+ 1. Built-in agents
910
+ 2. User agents (~/.openharness/agents/)
911
+ 3. Plugin agents (loaded from active plugins)
912
+
913
+ User definitions override built-ins with the same name; plugin definitions
914
+ override user definitions with the same name.
915
+ """
916
+ agent_map: dict[str, AgentDefinition] = {}
917
+
918
+ # 1. Built-ins (lowest priority)
919
+ for agent in get_builtin_agent_definitions():
920
+ agent_map[agent.name] = agent
921
+
922
+ # 2. User-defined agents
923
+ user_agents = load_agents_dir(_get_user_agents_dir())
924
+ for agent in user_agents:
925
+ agent_map[agent.name] = agent
926
+
927
+ # 3. Plugin agents — loaded lazily to avoid import cycles
928
+ try:
929
+ from openharness.plugins.loader import load_plugins # noqa: PLC0415
930
+ from openharness.config.settings import load_settings # noqa: PLC0415
931
+
932
+ settings = load_settings()
933
+ import os # noqa: PLC0415
934
+
935
+ cwd = os.getcwd()
936
+ for plugin in load_plugins(settings, cwd):
937
+ if not plugin.enabled:
938
+ continue
939
+ for agent_def in getattr(plugin, "agents", []):
940
+ if isinstance(agent_def, AgentDefinition):
941
+ agent_map[agent_def.name] = agent_def
942
+ except Exception:
943
+ pass
944
+
945
+ return list(agent_map.values())
946
+
947
+
948
+ def get_agent_definition(name: str) -> AgentDefinition | None:
949
+ """Return the agent definition for *name*, or ``None`` if not found."""
950
+ for agent in get_all_agent_definitions():
951
+ if agent.name == name:
952
+ return agent
953
+ return None
954
+
955
+
956
+ def has_required_mcp_servers(agent: AgentDefinition, available_servers: list[str]) -> bool:
957
+ """Return True if the agent's required MCP servers are all available.
958
+
959
+ Each pattern in ``required_mcp_servers`` must match (case-insensitive
960
+ substring) at least one server in ``available_servers``.
961
+ """
962
+ if not agent.required_mcp_servers:
963
+ return True
964
+ return all(
965
+ any(pattern.lower() in server.lower() for server in available_servers)
966
+ for pattern in agent.required_mcp_servers
967
+ )
968
+
969
+
970
+ def filter_agents_by_mcp_requirements(
971
+ agents: list[AgentDefinition],
972
+ available_servers: list[str],
973
+ ) -> list[AgentDefinition]:
974
+ """Return only agents whose required MCP servers are available."""
975
+ return [a for a in agents if has_required_mcp_servers(a, available_servers)]