orizu 0.5.19 → 0.5.21

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 (378) hide show
  1. package/README.md +23 -0
  2. package/dist/archive-artifacts-cli.d.ts +50 -0
  3. package/dist/archive-artifacts-cli.d.ts.map +1 -0
  4. package/dist/artifact-archive.d.ts +48 -0
  5. package/dist/artifact-archive.d.ts.map +1 -0
  6. package/dist/artifact-pull.d.ts +22 -0
  7. package/dist/artifact-pull.d.ts.map +1 -0
  8. package/dist/banner.d.ts +3 -0
  9. package/dist/banner.d.ts.map +1 -0
  10. package/dist/connectors-cli.d.ts +31 -0
  11. package/dist/connectors-cli.d.ts.map +1 -0
  12. package/dist/credentials.d.ts +46 -0
  13. package/dist/credentials.d.ts.map +1 -0
  14. package/dist/csv.d.ts +49 -0
  15. package/dist/csv.d.ts.map +1 -0
  16. package/dist/dataset-download.d.ts +6 -0
  17. package/dist/dataset-download.d.ts.map +1 -0
  18. package/dist/dataset-edit-rows.d.ts +10 -0
  19. package/dist/dataset-edit-rows.d.ts.map +1 -0
  20. package/dist/dataset-upload-snapshot.d.ts +13 -0
  21. package/dist/dataset-upload-snapshot.d.ts.map +1 -0
  22. package/dist/daytona-slice-rehearsal.d.ts +89 -0
  23. package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
  24. package/dist/daytona-slice.d.ts +106 -0
  25. package/dist/daytona-slice.d.ts.map +1 -0
  26. package/dist/deploy-key-merge-core.d.ts +121 -0
  27. package/dist/deploy-key-merge-core.d.ts.map +1 -0
  28. package/dist/diff-comments-cli.d.ts +12 -0
  29. package/dist/diff-comments-cli.d.ts.map +1 -0
  30. package/dist/diff-comments-cli.js +21 -2
  31. package/dist/egress-allowlist-cli.d.ts +23 -0
  32. package/dist/egress-allowlist-cli.d.ts.map +1 -0
  33. package/dist/egress-policy.d.ts +108 -0
  34. package/dist/egress-policy.d.ts.map +1 -0
  35. package/dist/error-response.d.ts +9 -0
  36. package/dist/error-response.d.ts.map +1 -0
  37. package/dist/file-parser.d.ts +5 -0
  38. package/dist/file-parser.d.ts.map +1 -0
  39. package/dist/gepa-engine-dispatch.d.ts +9 -0
  40. package/dist/gepa-engine-dispatch.d.ts.map +1 -0
  41. package/dist/gepa-engine-dispatch.js +307 -0
  42. package/dist/gepa-python-command.d.ts +6 -0
  43. package/dist/gepa-python-command.d.ts.map +1 -0
  44. package/dist/gepa-python-command.js +26 -0
  45. package/dist/gepa-python-paths.d.ts +6 -0
  46. package/dist/gepa-python-paths.d.ts.map +1 -0
  47. package/dist/gepa-python-paths.js +42 -0
  48. package/dist/git-credential.d.ts +69 -0
  49. package/dist/git-credential.d.ts.map +1 -0
  50. package/dist/git-recovery-environment.d.ts +31 -0
  51. package/dist/git-recovery-environment.d.ts.map +1 -0
  52. package/dist/github-setup.d.ts +122 -0
  53. package/dist/github-setup.d.ts.map +1 -0
  54. package/dist/global-flags.d.ts +12 -0
  55. package/dist/global-flags.d.ts.map +1 -0
  56. package/dist/help.d.ts +46 -0
  57. package/dist/help.d.ts.map +1 -0
  58. package/dist/help.js +93 -25
  59. package/dist/hosted-boot.d.ts +273 -0
  60. package/dist/hosted-boot.d.ts.map +1 -0
  61. package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
  62. package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
  63. package/dist/hosted-bootstrap.d.ts +188 -0
  64. package/dist/hosted-bootstrap.d.ts.map +1 -0
  65. package/dist/hosted-broker-server.d.ts +50 -0
  66. package/dist/hosted-broker-server.d.ts.map +1 -0
  67. package/dist/hosted-git-http-server.d.ts +36 -0
  68. package/dist/hosted-git-http-server.d.ts.map +1 -0
  69. package/dist/hosted-harness-claude.d.ts +195 -0
  70. package/dist/hosted-harness-claude.d.ts.map +1 -0
  71. package/dist/hosted-harness-opencode.d.ts +227 -0
  72. package/dist/hosted-harness-opencode.d.ts.map +1 -0
  73. package/dist/hosted-harness.d.ts +118 -0
  74. package/dist/hosted-harness.d.ts.map +1 -0
  75. package/dist/hosted-harvest.d.ts +68 -0
  76. package/dist/hosted-harvest.d.ts.map +1 -0
  77. package/dist/hosted-headless.d.ts +72 -0
  78. package/dist/hosted-headless.d.ts.map +1 -0
  79. package/dist/hosted-loop-lifecycle.d.ts +206 -0
  80. package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
  81. package/dist/hosted-loop.d.ts +170 -0
  82. package/dist/hosted-loop.d.ts.map +1 -0
  83. package/dist/hosted-run-event-sink.d.ts +222 -0
  84. package/dist/hosted-run-event-sink.d.ts.map +1 -0
  85. package/dist/hosted-runtime-assets.d.ts +186 -0
  86. package/dist/hosted-runtime-assets.d.ts.map +1 -0
  87. package/dist/hosted-session-cli.d.ts +258 -0
  88. package/dist/hosted-session-cli.d.ts.map +1 -0
  89. package/dist/hosted-skill-staging.d.ts +73 -0
  90. package/dist/hosted-skill-staging.d.ts.map +1 -0
  91. package/dist/http.d.ts +8 -0
  92. package/dist/http.d.ts.map +1 -0
  93. package/dist/index.d.ts +22 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +119 -129
  96. package/dist/instruction-set-gepa-launch.d.ts +3 -0
  97. package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
  98. package/dist/instruction-set-gepa-launch.js +15 -0
  99. package/dist/instruction-set-loader.d.ts +6 -0
  100. package/dist/instruction-set-loader.d.ts.map +1 -0
  101. package/dist/instruction-set-loader.js +59 -0
  102. package/dist/instruction-set-manifest.d.ts +14 -0
  103. package/dist/instruction-set-manifest.d.ts.map +1 -0
  104. package/dist/instruction-set-manifest.js +162 -0
  105. package/dist/instruction-sets-cli.d.ts +40 -0
  106. package/dist/instruction-sets-cli.d.ts.map +1 -0
  107. package/dist/instruction-sets-cli.js +361 -0
  108. package/dist/json-response.d.ts +4 -0
  109. package/dist/json-response.d.ts.map +1 -0
  110. package/dist/jsonl-stream.d.ts +8 -0
  111. package/dist/jsonl-stream.d.ts.map +1 -0
  112. package/dist/kill-switch-cli.d.ts +30 -0
  113. package/dist/kill-switch-cli.d.ts.map +1 -0
  114. package/dist/loopback-serve.d.ts +25 -0
  115. package/dist/loopback-serve.d.ts.map +1 -0
  116. package/dist/manifests-cli.d.ts +22 -0
  117. package/dist/manifests-cli.d.ts.map +1 -0
  118. package/dist/markdown-report-input.d.ts +7 -0
  119. package/dist/markdown-report-input.d.ts.map +1 -0
  120. package/dist/markdown-report-input.js +15 -1
  121. package/dist/merge-job.d.ts +155 -0
  122. package/dist/merge-job.d.ts.map +1 -0
  123. package/dist/model-configs-cli.d.ts +19 -0
  124. package/dist/model-configs-cli.d.ts.map +1 -0
  125. package/dist/model-configs-cli.js +104 -0
  126. package/dist/optimization-export-cli.d.ts +19 -0
  127. package/dist/optimization-export-cli.d.ts.map +1 -0
  128. package/dist/optimizations-list-cli.d.ts +20 -0
  129. package/dist/optimizations-list-cli.d.ts.map +1 -0
  130. package/dist/optimizations-promote-cli.d.ts +8 -0
  131. package/dist/optimizations-promote-cli.d.ts.map +1 -0
  132. package/dist/optimizations-promote-cli.js +34 -0
  133. package/dist/option-validation.d.ts +15 -0
  134. package/dist/option-validation.d.ts.map +1 -0
  135. package/dist/preview-runtime.d.ts +63 -0
  136. package/dist/preview-runtime.d.ts.map +1 -0
  137. package/dist/prompt-draft-push.d.ts +60 -0
  138. package/dist/prompt-draft-push.d.ts.map +1 -0
  139. package/dist/prompt-label-cli.d.ts +11 -0
  140. package/dist/prompt-label-cli.d.ts.map +1 -0
  141. package/dist/prompt-label-cli.js +29 -0
  142. package/dist/prompt-length-wire.d.ts +28 -0
  143. package/dist/prompt-length-wire.d.ts.map +1 -0
  144. package/dist/prompt-length-wire.js +2 -2
  145. package/dist/prompt-report-cli.d.ts +10 -0
  146. package/dist/prompt-report-cli.d.ts.map +1 -0
  147. package/dist/prompt-report-cli.js +2 -2
  148. package/dist/prompt-summary-table.d.ts +15 -0
  149. package/dist/prompt-summary-table.d.ts.map +1 -0
  150. package/dist/prompt-summary-table.js +6 -0
  151. package/dist/registration-tag-warning.d.ts +32 -0
  152. package/dist/registration-tag-warning.d.ts.map +1 -0
  153. package/dist/report-comments-cli.d.ts +16 -0
  154. package/dist/report-comments-cli.d.ts.map +1 -0
  155. package/dist/report-markdown-blankness.d.ts +2 -0
  156. package/dist/report-markdown-blankness.d.ts.map +1 -0
  157. package/dist/report-markdown-blankness.js +6 -0
  158. package/dist/report-replacement-warning.d.ts +17 -0
  159. package/dist/report-replacement-warning.d.ts.map +1 -0
  160. package/dist/report-replacement-warning.js +31 -0
  161. package/dist/repository-backup-core.d.ts +225 -0
  162. package/dist/repository-backup-core.d.ts.map +1 -0
  163. package/dist/repository-recovery-manifest.d.ts +83 -0
  164. package/dist/repository-recovery-manifest.d.ts.map +1 -0
  165. package/dist/run-event-digest.d.ts +74 -0
  166. package/dist/run-event-digest.d.ts.map +1 -0
  167. package/dist/runner-dir-verify.d.ts +66 -0
  168. package/dist/runner-dir-verify.d.ts.map +1 -0
  169. package/dist/runner-instruction-set-contract.d.ts +24 -0
  170. package/dist/runner-instruction-set-contract.d.ts.map +1 -0
  171. package/dist/runner-instruction-set-contract.js +50 -0
  172. package/dist/sandbox-hygiene.d.ts +45 -0
  173. package/dist/sandbox-hygiene.d.ts.map +1 -0
  174. package/dist/sandbox-provider.d.ts +193 -0
  175. package/dist/sandbox-provider.d.ts.map +1 -0
  176. package/dist/scorer-draft-push.d.ts +50 -0
  177. package/dist/scorer-draft-push.d.ts.map +1 -0
  178. package/dist/scorers-cli.d.ts +20 -0
  179. package/dist/scorers-cli.d.ts.map +1 -0
  180. package/dist/scorers-cli.js +22 -0
  181. package/dist/scorers-verify-parity-bridge.d.ts +16 -0
  182. package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
  183. package/dist/scorers-verify-parity-bridge.js +90 -0
  184. package/dist/scorers-verify-parity.d.ts +81 -0
  185. package/dist/scorers-verify-parity.d.ts.map +1 -0
  186. package/dist/scorers-verify-parity.js +502 -0
  187. package/dist/scores-accept-cli.d.ts +21 -0
  188. package/dist/scores-accept-cli.d.ts.map +1 -0
  189. package/dist/secret-redaction.d.ts +47 -0
  190. package/dist/secret-redaction.d.ts.map +1 -0
  191. package/dist/session-timeline.d.ts +54 -0
  192. package/dist/session-timeline.d.ts.map +1 -0
  193. package/dist/setup-prompt.d.ts +5 -0
  194. package/dist/setup-prompt.d.ts.map +1 -0
  195. package/dist/skill-installer.d.ts +68 -0
  196. package/dist/skill-installer.d.ts.map +1 -0
  197. package/dist/skilled-proposer-launch.d.ts +7 -0
  198. package/dist/skilled-proposer-launch.d.ts.map +1 -0
  199. package/dist/skilled-proposer-launch.js +60 -0
  200. package/dist/smoke.d.ts +102 -0
  201. package/dist/smoke.d.ts.map +1 -0
  202. package/dist/start-intent.d.ts +12 -0
  203. package/dist/start-intent.d.ts.map +1 -0
  204. package/dist/task-assignment-manifest.d.ts +15 -0
  205. package/dist/task-assignment-manifest.d.ts.map +1 -0
  206. package/dist/task-create-error.d.ts +18 -0
  207. package/dist/task-create-error.d.ts.map +1 -0
  208. package/dist/team-connectors-cli.d.ts +29 -0
  209. package/dist/team-connectors-cli.d.ts.map +1 -0
  210. package/dist/types.d.ts +39 -0
  211. package/dist/types.d.ts.map +1 -0
  212. package/dist/vercel-sandbox-provider.d.ts +156 -0
  213. package/dist/vercel-sandbox-provider.d.ts.map +1 -0
  214. package/dist/workbench-cli.d.ts +233 -0
  215. package/dist/workbench-cli.d.ts.map +1 -0
  216. package/dist/workspace-sync.d.ts +207 -0
  217. package/dist/workspace-sync.d.ts.map +1 -0
  218. package/dist/workspace.d.ts +92 -0
  219. package/dist/workspace.d.ts.map +1 -0
  220. package/dist/zip-draft-push.d.ts +55 -0
  221. package/dist/zip-draft-push.d.ts.map +1 -0
  222. package/package.json +15 -2
  223. package/requirements/README.md +138 -0
  224. package/requirements/skilled-proposer.in +66 -0
  225. package/requirements/skilled-proposer.lock +265 -0
  226. package/scripts/build-cli-dist.mjs +30 -0
  227. package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
  228. package/scripts/orizu-source-checkout.d.mts +1 -0
  229. package/scripts/orizu-source-checkout.mjs +10 -0
  230. package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
  231. package/scripts/vendor-gepa-python.mjs +187 -4
  232. package/src/diff-comments-cli.ts +41 -3
  233. package/src/gepa-engine-dispatch.ts +340 -0
  234. package/src/gepa-python-command.ts +31 -0
  235. package/src/gepa-python-paths.ts +56 -0
  236. package/src/help.ts +95 -25
  237. package/src/index.ts +131 -166
  238. package/src/instruction-set-gepa-launch.ts +28 -0
  239. package/src/instruction-set-loader.ts +37 -0
  240. package/src/instruction-set-manifest.ts +120 -0
  241. package/src/instruction-sets-cli.ts +306 -0
  242. package/src/markdown-report-input.ts +19 -1
  243. package/src/model-configs-cli.ts +119 -0
  244. package/src/optimizations-promote-cli.ts +41 -0
  245. package/src/prompt-label-cli.ts +55 -0
  246. package/src/prompt-length-wire.ts +1 -1
  247. package/src/prompt-report-cli.ts +2 -2
  248. package/src/prompt-summary-table.ts +12 -0
  249. package/src/report-markdown-blankness.ts +6 -0
  250. package/src/report-replacement-warning.ts +35 -0
  251. package/src/runner-instruction-set-contract.ts +64 -0
  252. package/src/scorers-cli.ts +40 -0
  253. package/src/scorers-verify-parity-bridge.ts +90 -0
  254. package/src/scorers-verify-parity.ts +564 -0
  255. package/src/skilled-proposer-launch.ts +89 -0
  256. package/src/skilled-proposer-venv-manager.mjs +757 -0
  257. package/tsconfig.json +2 -0
  258. package/vendor/gepa-python/LICENSE +7 -0
  259. package/vendor/gepa-python/manifest.json +107 -0
  260. package/vendor/gepa-python/src/gepa/__init__.py +22 -0
  261. package/vendor/gepa-python/src/gepa/adapters/__init__.py +0 -0
  262. package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/__init__.py +1 -0
  263. package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  264. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/__init__.py +26 -0
  265. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/confidence_adapter.py +618 -0
  266. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/scoring.py +206 -0
  267. package/vendor/gepa-python/src/gepa/adapters/default_adapter/__init__.py +0 -0
  268. package/vendor/gepa-python/src/gepa/adapters/default_adapter/default_adapter.py +206 -0
  269. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/__init__.py +0 -0
  270. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/dspy_adapter.py +525 -0
  271. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/instruction_proposal.py +500 -0
  272. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/__init__.py +6 -0
  273. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  274. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/full_program_adapter.py +269 -0
  275. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/__init__.py +56 -0
  276. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  277. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  278. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  279. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  280. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  281. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  282. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  283. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  284. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  285. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  286. package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/__init__.py +11 -0
  287. package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/langchain_adapter.py +261 -0
  288. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/__init__.py +37 -0
  289. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_adapter.py +693 -0
  290. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_client.py +364 -0
  291. package/vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py +879 -0
  292. package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/__init__.py +0 -0
  293. package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  294. package/vendor/gepa-python/src/gepa/api.py +468 -0
  295. package/vendor/gepa-python/src/gepa/core/__init__.py +0 -0
  296. package/vendor/gepa-python/src/gepa/core/adapter.py +227 -0
  297. package/vendor/gepa-python/src/gepa/core/callbacks.py +551 -0
  298. package/vendor/gepa-python/src/gepa/core/data_loader.py +74 -0
  299. package/vendor/gepa-python/src/gepa/core/engine.py +1025 -0
  300. package/vendor/gepa-python/src/gepa/core/result.py +271 -0
  301. package/vendor/gepa-python/src/gepa/core/state.py +706 -0
  302. package/vendor/gepa-python/src/gepa/examples/__init__.py +0 -0
  303. package/vendor/gepa-python/src/gepa/examples/aime.py +24 -0
  304. package/vendor/gepa-python/src/gepa/examples/anymaths-bench/eval_default.py +111 -0
  305. package/vendor/gepa-python/src/gepa/examples/anymaths-bench/train_anymaths.py +177 -0
  306. package/vendor/gepa-python/src/gepa/examples/mcp_adapter/__init__.py +4 -0
  307. package/vendor/gepa-python/src/gepa/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  308. package/vendor/gepa-python/src/gepa/examples/rag_adapter/__init__.py +9 -0
  309. package/vendor/gepa-python/src/gepa/examples/rag_adapter/rag_optimization.py +824 -0
  310. package/vendor/gepa-python/src/gepa/examples/terminal-bench/train_terminus.py +161 -0
  311. package/vendor/gepa-python/src/gepa/gepa_utils.py +117 -0
  312. package/vendor/gepa-python/src/gepa/gskill/__init__.py +1 -0
  313. package/vendor/gepa-python/src/gepa/gskill/gskill/__init__.py +0 -0
  314. package/vendor/gepa-python/src/gepa/gskill/gskill/cost_tracker.py +194 -0
  315. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/__init__.py +0 -0
  316. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code.py +1117 -0
  317. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code_skills.py +613 -0
  318. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/mini_swe_agent.py +440 -0
  319. package/vendor/gepa-python/src/gepa/gskill/gskill/experiment_logger.py +367 -0
  320. package/vendor/gepa-python/src/gepa/gskill/gskill/swe_fitness_fn.py +209 -0
  321. package/vendor/gepa-python/src/gepa/gskill/gskill/swe_harness.py +352 -0
  322. package/vendor/gepa-python/src/gepa/gskill/gskill/train_optimize_anything.py +830 -0
  323. package/vendor/gepa-python/src/gepa/image.py +104 -0
  324. package/vendor/gepa-python/src/gepa/lm.py +261 -0
  325. package/vendor/gepa-python/src/gepa/logging/__init__.py +0 -0
  326. package/vendor/gepa-python/src/gepa/logging/experiment_tracker.py +457 -0
  327. package/vendor/gepa-python/src/gepa/logging/logger.py +75 -0
  328. package/vendor/gepa-python/src/gepa/logging/utils.py +130 -0
  329. package/vendor/gepa-python/src/gepa/optimize_anything.py +1701 -0
  330. package/vendor/gepa-python/src/gepa/proposer/__init__.py +0 -0
  331. package/vendor/gepa-python/src/gepa/proposer/base.py +53 -0
  332. package/vendor/gepa-python/src/gepa/proposer/merge.py +403 -0
  333. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/__init__.py +0 -0
  334. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/base.py +64 -0
  335. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflection_lm.py +178 -0
  336. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflective_mutation.py +641 -0
  337. package/vendor/gepa-python/src/gepa/py.typed +0 -0
  338. package/vendor/gepa-python/src/gepa/strategies/__init__.py +0 -0
  339. package/vendor/gepa-python/src/gepa/strategies/acceptance.py +66 -0
  340. package/vendor/gepa-python/src/gepa/strategies/batch_sampler.py +103 -0
  341. package/vendor/gepa-python/src/gepa/strategies/candidate_selector.py +82 -0
  342. package/vendor/gepa-python/src/gepa/strategies/component_selector.py +36 -0
  343. package/vendor/gepa-python/src/gepa/strategies/eval_policy.py +64 -0
  344. package/vendor/gepa-python/src/gepa/strategies/instruction_proposal.py +153 -0
  345. package/vendor/gepa-python/src/gepa/strategies/proposal_sampling.py +128 -0
  346. package/vendor/gepa-python/src/gepa/strategies/proposal_selection.py +84 -0
  347. package/vendor/gepa-python/src/gepa/utils/__init__.py +63 -0
  348. package/vendor/gepa-python/src/gepa/utils/code_execution.py +626 -0
  349. package/vendor/gepa-python/src/gepa/utils/stdio_capture.py +145 -0
  350. package/vendor/gepa-python/src/gepa/utils/stop_condition.py +230 -0
  351. package/vendor/gepa-python/src/gepa/visualization.py +359 -0
  352. package/vendor/orizu-gepa/pyproject.toml +18 -0
  353. package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +9 -0
  354. package/vendor/orizu-gepa/src/orizu_gepa_connector/__main__.py +5 -0
  355. package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +138 -0
  356. package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +666 -0
  357. package/vendor/orizu-gepa/src/orizu_gepa_connector/cli.py +7 -0
  358. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +269 -0
  359. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
  360. package/vendor/orizu-gepa/src/orizu_gepa_connector/preflight.py +64 -0
  361. package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +186 -0
  362. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +721 -0
  363. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
  364. package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +55 -0
  365. package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +203 -0
  366. package/vendor/orizu-gepa-python/pyproject.toml +1 -1
  367. package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
  368. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
  369. package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +52 -0
  370. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
  371. package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
  372. package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
  373. package/vendor/skills/orizu-cli/SKILL.md +3 -1
  374. package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
  375. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
  376. package/vendor/skills/orizu-cli/references/optimization-reports.md +1 -1
  377. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +26 -1
  378. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +113 -10
@@ -0,0 +1,641 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
3
+
4
+ import traceback
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Any
7
+
8
+ from gepa.core.adapter import (
9
+ DataInst,
10
+ EvaluationBatch,
11
+ GEPAAdapter,
12
+ ProposalFn,
13
+ RolloutOutput,
14
+ Trajectory,
15
+ default_batch_evaluate,
16
+ )
17
+ from gepa.core.callbacks import (
18
+ CandidateSelectedEvent,
19
+ EvaluationEndEvent,
20
+ EvaluationSkippedEvent,
21
+ EvaluationStartEvent,
22
+ GEPACallback,
23
+ MinibatchSampledEvent,
24
+ ProposalEndEvent,
25
+ ProposalStartEvent,
26
+ ReflectiveDatasetBuiltEvent,
27
+ notify_callbacks,
28
+ )
29
+ from gepa.core.data_loader import DataId, DataLoader, ensure_loader
30
+ from gepa.core.state import GEPAState, _candidate_hash
31
+ from gepa.proposer.base import CandidateProposal, SubsampleEvaluation
32
+ from gepa.proposer.reflective_mutation.base import (
33
+ CandidateSelector,
34
+ LanguageModel,
35
+ ReflectionComponentSelector,
36
+ )
37
+ from gepa.proposer.reflective_mutation.reflection_lm import ReflectionLM, StatelessReflectionLM
38
+ from gepa.strategies.batch_sampler import BatchSampler
39
+ from gepa.strategies.instruction_proposal import InstructionProposalSignature
40
+ from gepa.strategies.proposal_sampling import ProposalTask, SamplingStrategy, SingleMutationSampling
41
+
42
+
43
+ class ReflectiveMutationProposer:
44
+ """Implements the reflective mutation flow.
45
+
46
+ Each iteration, the proposer:
47
+
48
+ 1. Samples one or more (parent, minibatch) tasks via ``sampling_strategy``
49
+ 2. Batch-evaluates all parents (deduplicated)
50
+ 3. For each task: builds a reflective dataset and proposes new texts
51
+ 4. Batch-evaluates all children
52
+ 5. Returns ALL evaluated proposals as :class:`CandidateProposal` objects —
53
+ acceptance and selection are applied by the engine, which is the single
54
+ accept+select authority
55
+
56
+ With the default ``SingleMutationSampling``, this produces exactly one
57
+ task per iteration — matching GEPA's original sequential behavior.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ logger: Any,
63
+ trainset: list[DataInst] | DataLoader[DataId, DataInst],
64
+ adapter: GEPAAdapter[DataInst, Trajectory, RolloutOutput],
65
+ candidate_selector: CandidateSelector,
66
+ module_selector: ReflectionComponentSelector,
67
+ batch_sampler: BatchSampler[DataId, DataInst],
68
+ perfect_score: float | None,
69
+ skip_perfect_score: bool,
70
+ experiment_tracker: Any,
71
+ reflection_lm: LanguageModel | None = None,
72
+ reflection_prompt_template: str | dict[str, str] | None = None,
73
+ custom_candidate_proposer: ProposalFn | None = None,
74
+ callbacks: list[GEPACallback] | None = None,
75
+ sampling_strategy: SamplingStrategy | None = None,
76
+ reflection_strategy: ReflectionLM | None = None,
77
+ ):
78
+ self.logger = logger
79
+ self.trainset = ensure_loader(trainset)
80
+ self.adapter = adapter
81
+ self.candidate_selector = candidate_selector
82
+ self.module_selector = module_selector
83
+ self.batch_sampler = batch_sampler
84
+ self.perfect_score = perfect_score
85
+ self.skip_perfect_score = skip_perfect_score
86
+ self.experiment_tracker = experiment_tracker
87
+ self.reflection_lm = reflection_lm
88
+ self.custom_candidate_proposer = custom_candidate_proposer
89
+ self.callbacks = callbacks
90
+ self.sampling_strategy: SamplingStrategy = sampling_strategy or SingleMutationSampling()
91
+
92
+ self.reflection_prompt_template = reflection_prompt_template
93
+
94
+ if isinstance(reflection_prompt_template, dict):
95
+ for _param_name, template in reflection_prompt_template.items():
96
+ InstructionProposalSignature.validate_prompt_template(template)
97
+ else:
98
+ InstructionProposalSignature.validate_prompt_template(reflection_prompt_template)
99
+
100
+ if reflection_strategy is not None and (
101
+ adapter.propose_new_texts is not None or custom_candidate_proposer is not None
102
+ ):
103
+ owner = (
104
+ "adapter.propose_new_texts"
105
+ if adapter.propose_new_texts is not None
106
+ else "custom_candidate_proposer"
107
+ )
108
+ raise ValueError(
109
+ f"reflection_strategy was provided, but {owner} owns proposal generation "
110
+ "and the reflection strategy would be silently ignored. Remove one of the two."
111
+ )
112
+
113
+ # Reflection LM (#329 Phase 1); None when an adapter/custom proposer owns
114
+ # reflection. An injected reflection_strategy — any ReflectionLM
115
+ # implementation, e.g. session-based or ComBEE-style aggregating
116
+ # reflectors (#329 Phase 2/3) — takes precedence over the stateless
117
+ # default built from the raw reflection_lm callable.
118
+ self._reflection_lm: ReflectionLM | None = reflection_strategy or (
119
+ StatelessReflectionLM(reflection_lm, reflection_prompt_template, logger)
120
+ if reflection_lm is not None
121
+ else None
122
+ )
123
+
124
+ if self.skip_perfect_score and self.perfect_score is None:
125
+ raise ValueError(
126
+ "perfect_score must be provided when skip_perfect_score is True. "
127
+ "If you do not have a perfect target score, set skip_perfect_score=False."
128
+ )
129
+
130
+ def propose_new_texts(
131
+ self,
132
+ candidate: dict[str, str],
133
+ reflective_dataset: Mapping[str, Sequence[Mapping[str, Any]]],
134
+ components_to_update: list[str],
135
+ ) -> tuple[dict[str, str], dict[str, str | list[dict[str, Any]]], dict[str, str], dict[str, Any]]:
136
+ """Propose new instruction texts for the given components.
137
+
138
+ Returns:
139
+ A tuple of (new_texts, prompts, raw_lm_outputs, reflection_metadata)
140
+ where the first three are dicts keyed by component name and
141
+ ``reflection_metadata`` is the ReflectionLM's free-form diagnostics
142
+ (empty for single-call reflectors; multi-call strategies such as
143
+ ComBEE record per-call intermediates here).
144
+ """
145
+ empty: dict[str, str | list[dict[str, Any]]] = {}
146
+ if self.adapter.propose_new_texts is not None:
147
+ return self.adapter.propose_new_texts(candidate, reflective_dataset, components_to_update), empty, {}, {}
148
+
149
+ if self.custom_candidate_proposer is not None:
150
+ return self.custom_candidate_proposer(candidate, reflective_dataset, components_to_update), empty, {}, {}
151
+
152
+ if self._reflection_lm is None:
153
+ raise ValueError("reflection_lm must be provided when adapter.propose_new_texts is None.")
154
+
155
+ # Delegate to the ReflectionLM (#329 Phase 1). Stateful implementations
156
+ # return a successor carrying accumulated context; chain it so session
157
+ # state actually persists (stateless implementations return self,
158
+ # making this a no-op).
159
+ proposal, next_lm = self._reflection_lm.reflect(candidate, reflective_dataset, components_to_update)
160
+ self._reflection_lm = next_lm
161
+ return proposal.new_texts, proposal.prompts, proposal.raw_lm_outputs, proposal.metadata
162
+
163
+ def _propose_texts_batch(
164
+ self, jobs: list[tuple[dict[str, str], Mapping[str, Sequence[Mapping[str, Any]]], list[str]]]
165
+ ) -> list[tuple[dict[str, str], dict[str, str | list[dict[str, Any]]], dict[str, str], dict[str, Any]]]:
166
+ """Propose new texts for many tasks, batching the reflection LM calls.
167
+
168
+ ReflectionLM implementations that provide ``reflect_many`` (e.g. the
169
+ stateless default, which issues one ``litellm.batch_completion``
170
+ covering every task/component) are batched; implementations that only
171
+ provide ``reflect()`` are called once per task. When an adapter
172
+ proposer or custom proposer owns the call, fall back to one invocation
173
+ per task — their batching, if any, is their concern.
174
+ """
175
+ if (
176
+ self.adapter.propose_new_texts is not None
177
+ or self.custom_candidate_proposer is not None
178
+ or self._reflection_lm is None
179
+ ):
180
+ return [self.propose_new_texts(cand, refds, comps) for cand, refds, comps in jobs]
181
+
182
+ reflect_many = getattr(self._reflection_lm, "reflect_many", None)
183
+ if reflect_many is not None:
184
+ results = list(reflect_many(jobs))
185
+ if len(results) != len(jobs):
186
+ raise ValueError(
187
+ f"ReflectionLM.reflect_many returned {len(results)} results for {len(jobs)} jobs"
188
+ )
189
+ else:
190
+ results = []
191
+ for cand, refds, comps in jobs:
192
+ proposal, next_lm = self._reflection_lm.reflect(cand, refds, comps)
193
+ self._reflection_lm = next_lm # chain stateful reflectors
194
+ results.append((proposal, next_lm))
195
+ if results:
196
+ # For batched reflection, chain to the final returned successor.
197
+ self._reflection_lm = results[-1][1]
198
+ return [
199
+ (proposal.new_texts, proposal.prompts, proposal.raw_lm_outputs, proposal.metadata)
200
+ for proposal, _next_lm in results
201
+ ]
202
+
203
+ def _propose_texts_batch_safe(
204
+ self, jobs: list[tuple[dict[str, str], Mapping[str, Sequence[Mapping[str, Any]]], list[str]]]
205
+ ) -> list[tuple[dict[str, str], dict[str, str | list[dict[str, Any]]], dict[str, str], dict[str, Any]] | None]:
206
+ """Like :meth:`_propose_texts_batch`, but isolates per-task failures.
207
+
208
+ Returns ``None`` in the slot of any task whose reflection raised, so one
209
+ bad task (or a failed batch) does not sink the whole iteration.
210
+ """
211
+ if not jobs:
212
+ return []
213
+ try:
214
+ return list(self._propose_texts_batch(jobs))
215
+ except Exception as e:
216
+ self.logger.log(f"Batched reflection failed ({e}); retrying per task.")
217
+ self.logger.log(traceback.format_exc())
218
+ out: list[tuple[dict[str, str], dict[str, str | list[dict[str, Any]]], dict[str, str], dict[str, Any]] | None] = []
219
+ for cand, refds, comps in jobs:
220
+ try:
221
+ out.append(self.propose_new_texts(cand, refds, comps))
222
+ except Exception as e2:
223
+ self.logger.log(f"Per-task reflection failed: {e2}")
224
+ out.append(None)
225
+ return out
226
+
227
+ # ------------------------------------------------------------------
228
+ # Batch evaluate helper
229
+ # ------------------------------------------------------------------
230
+
231
+ def _batch_evaluate(self, items: list[tuple[dict[str, str], list]]) -> list[EvaluationBatch]:
232
+ """Evaluate (candidate, batch) pairs via the adapter's batch_evaluate or fallback."""
233
+ batch_fn = getattr(self.adapter, "batch_evaluate", None)
234
+ if batch_fn is not None:
235
+ return batch_fn(items)
236
+ return default_batch_evaluate(self.adapter, items)
237
+
238
+ # ------------------------------------------------------------------
239
+ # Main proposal method
240
+ # ------------------------------------------------------------------
241
+
242
+ def propose(self, state: GEPAState) -> list[CandidateProposal]:
243
+ """Run the reflective mutation pipeline and return all evaluated proposals.
244
+
245
+ The proposer generates and minibatch-evaluates candidates; acceptance and
246
+ selection (which to keep) are the engine's job. With the default
247
+ ``SingleMutationSampling`` this returns at most one proposal — identical to
248
+ the original sequential behavior.
249
+ """
250
+ i = state.i + 1
251
+
252
+ # Stage 1: Sample (parent, minibatch) tasks
253
+ tasks = self.sampling_strategy.sample_tasks(state, self.candidate_selector, self.batch_sampler, self.trainset)
254
+ if not tasks:
255
+ return []
256
+
257
+ # Fire callbacks for each sampled task
258
+ for task in tasks:
259
+ notify_callbacks(
260
+ self.callbacks,
261
+ "on_candidate_selected",
262
+ CandidateSelectedEvent(
263
+ iteration=i,
264
+ candidate_idx=task.parent_idx,
265
+ candidate=task.parent_candidate,
266
+ score=state.program_full_scores_val_set[task.parent_idx],
267
+ ),
268
+ )
269
+ notify_callbacks(
270
+ self.callbacks,
271
+ "on_minibatch_sampled",
272
+ MinibatchSampledEvent(
273
+ iteration=i,
274
+ minibatch_ids=task.minibatch_ids,
275
+ trainset_size=len(self.trainset),
276
+ ),
277
+ )
278
+
279
+ # Stage 2: Batch evaluate parents (deduplicated)
280
+ unique_keys: dict[tuple[str, tuple], tuple[dict[str, str], list[Any]]] = {}
281
+ task_to_key: list[tuple[str, tuple]] = []
282
+ for task in tasks:
283
+ key = (_candidate_hash(task.parent_candidate), tuple(task.minibatch_ids))
284
+ unique_keys.setdefault(key, (task.parent_candidate, task.minibatch))
285
+ task_to_key.append(key)
286
+
287
+ key_list = list(unique_keys.keys())
288
+ items = [unique_keys[k] for k in key_list]
289
+
290
+ # Fire evaluation start callbacks for each task
291
+ for task in tasks:
292
+ notify_callbacks(
293
+ self.callbacks,
294
+ "on_evaluation_start",
295
+ EvaluationStartEvent(
296
+ iteration=i,
297
+ candidate_idx=task.parent_idx,
298
+ batch_size=len(task.minibatch),
299
+ capture_traces=True,
300
+ parent_ids=[p for p in state.parent_program_for_candidate[task.parent_idx] if p is not None],
301
+ inputs=task.minibatch,
302
+ is_seed_candidate=task.parent_idx == 0,
303
+ ),
304
+ )
305
+
306
+ parent_evals = self._batch_evaluate(items)
307
+ key_to_eval: dict[tuple[str, tuple], EvaluationBatch] = dict(zip(key_list, parent_evals, strict=True))
308
+
309
+ # Fire evaluation end callbacks for each task
310
+ for task, key in zip(tasks, task_to_key, strict=True):
311
+ eval_curr = key_to_eval[key]
312
+ notify_callbacks(
313
+ self.callbacks,
314
+ "on_evaluation_end",
315
+ EvaluationEndEvent(
316
+ iteration=i,
317
+ candidate_idx=task.parent_idx,
318
+ scores=eval_curr.scores,
319
+ has_trajectories=bool(eval_curr.trajectories),
320
+ parent_ids=[p for p in state.parent_program_for_candidate[task.parent_idx] if p is not None],
321
+ outputs=eval_curr.outputs,
322
+ trajectories=eval_curr.trajectories,
323
+ objective_scores=eval_curr.objective_scores,
324
+ is_seed_candidate=task.parent_idx == 0,
325
+ ),
326
+ )
327
+
328
+ total_parent_evals = sum(
329
+ e.num_metric_calls if e.num_metric_calls is not None else len(items[idx][1])
330
+ for idx, e in enumerate(parent_evals)
331
+ )
332
+ state.increment_evals(total_parent_evals)
333
+
334
+ # Update evaluation cache for parents
335
+ if state.evaluation_cache is not None:
336
+ for task, key in zip(tasks, task_to_key, strict=True):
337
+ eval_curr = key_to_eval[key]
338
+ objective_scores_list = list(eval_curr.objective_scores) if eval_curr.objective_scores else None
339
+ state.evaluation_cache.put_batch(
340
+ task.parent_candidate,
341
+ task.minibatch_ids,
342
+ eval_curr.outputs,
343
+ eval_curr.scores,
344
+ objective_scores_list,
345
+ )
346
+
347
+ # Trace: legacy first-task keys (pre-#329 tooling compatibility) plus
348
+ # full per-task records — multi-task iterations record every task, not
349
+ # just the first. Tasks that get skipped later simply never receive
350
+ # score keys.
351
+ first_task = tasks[0]
352
+ state.full_program_trace[-1]["selected_program_candidate"] = first_task.parent_idx
353
+ state.full_program_trace[-1]["subsample_ids"] = first_task.minibatch_ids
354
+ state.full_program_trace[-1]["n_tasks"] = len(tasks)
355
+ state.full_program_trace[-1]["tasks"] = [
356
+ {"parent_idx": task.parent_idx, "subsample_ids": list(task.minibatch_ids)} for task in tasks
357
+ ]
358
+ self.logger.log(
359
+ f"Iteration {i}: Selected program {first_task.parent_idx} "
360
+ f"score: {state.program_full_scores_val_set[first_task.parent_idx]}"
361
+ )
362
+
363
+ self.experiment_tracker.log_metrics(
364
+ {
365
+ "iteration": i,
366
+ "selected_program_candidate": first_task.parent_idx,
367
+ "total_metric_calls": state.total_num_evals,
368
+ },
369
+ step=i,
370
+ )
371
+
372
+ # Stage 3a: Build reflective datasets + fire pre-reflection callbacks (per task).
373
+ # ``prepared`` holds one slot per task (None = skipped); ``jobs`` is the
374
+ # subset that will reflect, in order, so the reflection LM can batch them.
375
+ prepared: list[tuple[ProposalTask, EvaluationBatch, list[str], Any] | None] = []
376
+ for task, key in zip(tasks, task_to_key, strict=True):
377
+ eval_curr = key_to_eval[key]
378
+
379
+ if not eval_curr.trajectories:
380
+ self.logger.log(f"Iteration {i}: No trajectories for parent {task.parent_idx}. Skipping.")
381
+ notify_callbacks(
382
+ self.callbacks,
383
+ "on_evaluation_skipped",
384
+ EvaluationSkippedEvent(
385
+ iteration=i,
386
+ candidate_idx=task.parent_idx,
387
+ reason="no_trajectories",
388
+ scores=eval_curr.scores,
389
+ is_seed_candidate=task.parent_idx == 0,
390
+ ),
391
+ )
392
+ prepared.append(None)
393
+ continue
394
+
395
+ if (
396
+ self.skip_perfect_score
397
+ and self.perfect_score is not None
398
+ and all(s is not None and s >= self.perfect_score for s in eval_curr.scores)
399
+ ):
400
+ self.logger.log(f"Iteration {i}: All subsample scores perfect for parent {task.parent_idx}. Skipping.")
401
+ notify_callbacks(
402
+ self.callbacks,
403
+ "on_evaluation_skipped",
404
+ EvaluationSkippedEvent(
405
+ iteration=i,
406
+ candidate_idx=task.parent_idx,
407
+ reason="all_scores_perfect",
408
+ scores=eval_curr.scores,
409
+ is_seed_candidate=task.parent_idx == 0,
410
+ ),
411
+ )
412
+ prepared.append(None)
413
+ continue
414
+
415
+ predictor_names = self.module_selector(
416
+ state, eval_curr.trajectories, eval_curr.scores, task.parent_idx, task.parent_candidate
417
+ )
418
+
419
+ try:
420
+ reflective_dataset = self.adapter.make_reflective_dataset(
421
+ task.parent_candidate, eval_curr, predictor_names
422
+ )
423
+ reflective_dataset_concrete: dict[str, list[dict[str, Any]]] = {
424
+ k: [dict(item) for item in v] for k, v in reflective_dataset.items()
425
+ }
426
+ notify_callbacks(
427
+ self.callbacks,
428
+ "on_reflective_dataset_built",
429
+ ReflectiveDatasetBuiltEvent(
430
+ iteration=i,
431
+ candidate_idx=task.parent_idx,
432
+ components=predictor_names,
433
+ dataset=reflective_dataset_concrete,
434
+ ),
435
+ )
436
+ notify_callbacks(
437
+ self.callbacks,
438
+ "on_proposal_start",
439
+ ProposalStartEvent(
440
+ iteration=i,
441
+ parent_candidate=task.parent_candidate,
442
+ components=predictor_names,
443
+ reflective_dataset=reflective_dataset_concrete,
444
+ ),
445
+ )
446
+ except Exception as e:
447
+ self.logger.log(f"Iteration {i}: Exception building reflective dataset: {e}")
448
+ self.logger.log(traceback.format_exc())
449
+ prepared.append(None)
450
+ continue
451
+
452
+ prepared.append((task, eval_curr, predictor_names, reflective_dataset))
453
+
454
+ # Stage 3b: Reflect across all prepared tasks — one batched LM call when the
455
+ # reflection LM supports it (litellm.batch_completion), else per task.
456
+ jobs = [(p[0].parent_candidate, p[3], p[2]) for p in prepared if p is not None]
457
+ batch_texts = iter(self._propose_texts_batch_safe(jobs))
458
+
459
+ # Stage 3c: Build each child candidate from its proposed texts.
460
+ children: list[tuple[ProposalTask, dict[str, str], EvaluationBatch, dict[str, Any]] | None] = []
461
+ for p in prepared:
462
+ if p is None:
463
+ children.append(None)
464
+ continue
465
+ task, eval_curr, _predictor_names, _reflective_dataset = p
466
+ texts = next(batch_texts)
467
+ if texts is None:
468
+ children.append(None)
469
+ continue
470
+ new_texts, prompts, raw_outputs, reflection_metadata = texts
471
+
472
+ if not new_texts:
473
+ # Reflection produced no text updates (e.g. every requested
474
+ # component was missing from the reflective dataset). A child
475
+ # would be byte-identical to its parent: don't burn minibatch
476
+ # metric calls evaluating it or emit proposal/rejection events
477
+ # for a proposal that never happened.
478
+ self.logger.log(
479
+ f"Iteration {i}: Reflection returned no text updates; skipping proposal for this task."
480
+ )
481
+ children.append(None)
482
+ continue
483
+
484
+ notify_callbacks(
485
+ self.callbacks,
486
+ "on_proposal_end",
487
+ ProposalEndEvent(
488
+ iteration=i,
489
+ new_instructions=new_texts,
490
+ prompts=prompts,
491
+ raw_lm_outputs=raw_outputs,
492
+ ),
493
+ )
494
+
495
+ _lm_metadata: dict[str, Any] = {}
496
+ # Stable per-proposal identifier (iteration-taskindex): downstream
497
+ # consumers (run manifests, #346's per-proposal state anchors) can
498
+ # key on this instead of positional inference.
499
+ _lm_metadata["proposal_id"] = f"{i}-{len(children)}"
500
+ for comp in new_texts:
501
+ _lm_metadata[f"prompt:{comp}"] = prompts.get(comp, "")
502
+ _lm_metadata[f"raw_lm_output:{comp}"] = raw_outputs.get(comp, "")
503
+ # Multi-call reflection diagnostics (e.g. ComBEE per-call
504
+ # intermediates) flow into the proposal metadata for callbacks,
505
+ # trackers, and the run manifest. Keys that would collide with the
506
+ # reserved prompt:/raw_lm_output: namespaces are remapped so a
507
+ # reflector cannot inject phantom components into proposal tables.
508
+ for meta_key, meta_val in (reflection_metadata or {}).items():
509
+ if meta_key.startswith(("prompt:", "raw_lm_output:")):
510
+ _lm_metadata[f"reflection_meta:{meta_key}"] = meta_val
511
+ else:
512
+ _lm_metadata[meta_key] = meta_val
513
+
514
+ for pname, text in new_texts.items():
515
+ self.logger.log(f"Iteration {i}: Proposed new text for {pname}: {text}")
516
+
517
+ new_candidate = task.parent_candidate.copy()
518
+ for name, text in new_texts.items():
519
+ assert name in new_candidate, f"{name} missing in candidate"
520
+ new_candidate[name] = text
521
+
522
+ children.append((task, new_candidate, eval_curr, _lm_metadata))
523
+
524
+ # Stage 4: Batch evaluate children
525
+ valid_children = [(idx, c) for idx, c in enumerate(children) if c is not None]
526
+ if not valid_children:
527
+ return []
528
+
529
+ child_items = [(c[1], c[0].minibatch) for _, c in valid_children]
530
+
531
+ # Fire evaluation start callbacks for each child candidate (parity with
532
+ # the pre-batch sequential path, which emitted these around the new
533
+ # candidate's minibatch evaluation; candidate_idx is None because the
534
+ # child is not in the candidate pool yet)
535
+ for _, (task, _new_candidate, _eval_curr, _meta) in valid_children:
536
+ notify_callbacks(
537
+ self.callbacks,
538
+ "on_evaluation_start",
539
+ EvaluationStartEvent(
540
+ iteration=i,
541
+ candidate_idx=None,
542
+ batch_size=len(task.minibatch),
543
+ capture_traces=True,
544
+ parent_ids=[task.parent_idx],
545
+ inputs=task.minibatch,
546
+ is_seed_candidate=False,
547
+ ),
548
+ )
549
+
550
+ child_evals = self._batch_evaluate(child_items)
551
+
552
+ # Fire evaluation end callbacks for each child candidate
553
+ for (_, (task, _new_candidate, _eval_curr, _meta)), child_eval in zip(valid_children, child_evals, strict=True):
554
+ notify_callbacks(
555
+ self.callbacks,
556
+ "on_evaluation_end",
557
+ EvaluationEndEvent(
558
+ iteration=i,
559
+ candidate_idx=None,
560
+ scores=child_eval.scores,
561
+ has_trajectories=bool(child_eval.trajectories),
562
+ parent_ids=[task.parent_idx],
563
+ outputs=child_eval.outputs,
564
+ trajectories=child_eval.trajectories,
565
+ objective_scores=child_eval.objective_scores,
566
+ is_seed_candidate=False,
567
+ ),
568
+ )
569
+
570
+ total_child_evals = sum(
571
+ e.num_metric_calls if e.num_metric_calls is not None else len(child_items[idx][1])
572
+ for idx, e in enumerate(child_evals)
573
+ )
574
+ state.increment_evals(total_child_evals)
575
+
576
+ # Update evaluation cache for children
577
+ if state.evaluation_cache is not None:
578
+ for (_, (task, new_candidate, _, _)), child_eval in zip(valid_children, child_evals, strict=True):
579
+ new_obj_scores = list(child_eval.objective_scores) if child_eval.objective_scores else None
580
+ state.evaluation_cache.put_batch(
581
+ new_candidate, task.minibatch_ids, child_eval.outputs, child_eval.scores, new_obj_scores
582
+ )
583
+
584
+ # Trace: per-task before/after scores (children is index-aligned with tasks)
585
+ trace_tasks = state.full_program_trace[-1].get("tasks")
586
+ if trace_tasks is not None:
587
+ for (child_idx, (_task, _nc, eval_curr, _md)), child_eval in zip(valid_children, child_evals, strict=True):
588
+ trace_tasks[child_idx]["subsample_scores"] = list(eval_curr.scores)
589
+ trace_tasks[child_idx]["new_subsample_scores"] = list(child_eval.scores)
590
+
591
+ # Log subsample scores for first task (trace compatibility)
592
+ if valid_children:
593
+ first_child_idx = valid_children[0][0]
594
+ first_child = children[first_child_idx]
595
+ if first_child is not None:
596
+ state.full_program_trace[-1]["subsample_scores"] = key_to_eval[task_to_key[first_child_idx]].scores
597
+ state.full_program_trace[-1]["new_subsample_scores"] = child_evals[0].scores
598
+
599
+ subsample_before = sum(key_to_eval[task_to_key[first_child_idx]].scores)
600
+ subsample_after = sum(child_evals[0].scores)
601
+ self.experiment_tracker.log_metrics(
602
+ {
603
+ # pre-#329 key names, kept for existing dashboards
604
+ "subsample_score": subsample_before,
605
+ "new_subsample_score": subsample_after,
606
+ "subsample/before": subsample_before,
607
+ "subsample/after": subsample_after,
608
+ "total_metric_calls": state.total_num_evals,
609
+ },
610
+ step=i,
611
+ )
612
+
613
+ # Stage 5: Build proposals and filter
614
+ proposals: list[CandidateProposal] = []
615
+ for (_, (task, new_candidate, eval_curr, _lm_metadata)), child_eval in zip(
616
+ valid_children, child_evals, strict=True
617
+ ):
618
+ proposal = CandidateProposal(
619
+ candidate=new_candidate,
620
+ parent_program_ids=[task.parent_idx],
621
+ subsample_indices=task.minibatch_ids,
622
+ subsample_scores_before=eval_curr.scores,
623
+ subsample_scores_after=child_eval.scores,
624
+ eval_before=SubsampleEvaluation(
625
+ scores=eval_curr.scores,
626
+ outputs=eval_curr.outputs,
627
+ objective_scores=list(eval_curr.objective_scores) if eval_curr.objective_scores else None,
628
+ trajectories=eval_curr.trajectories,
629
+ ),
630
+ eval_after=SubsampleEvaluation(
631
+ scores=child_eval.scores,
632
+ outputs=child_eval.outputs,
633
+ objective_scores=list(child_eval.objective_scores) if child_eval.objective_scores else None,
634
+ trajectories=child_eval.trajectories,
635
+ ),
636
+ tag="reflective_mutation",
637
+ metadata=_lm_metadata,
638
+ )
639
+ proposals.append(proposal)
640
+
641
+ return proposals
File without changes