orizu 0.5.20 → 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 (260) hide show
  1. package/dist/archive-artifacts-cli.d.ts +50 -0
  2. package/dist/archive-artifacts-cli.d.ts.map +1 -0
  3. package/dist/artifact-archive.d.ts +48 -0
  4. package/dist/artifact-archive.d.ts.map +1 -0
  5. package/dist/artifact-pull.d.ts +22 -0
  6. package/dist/artifact-pull.d.ts.map +1 -0
  7. package/dist/banner.d.ts +3 -0
  8. package/dist/banner.d.ts.map +1 -0
  9. package/dist/connectors-cli.d.ts +31 -0
  10. package/dist/connectors-cli.d.ts.map +1 -0
  11. package/dist/credentials.d.ts +46 -0
  12. package/dist/credentials.d.ts.map +1 -0
  13. package/dist/csv.d.ts +49 -0
  14. package/dist/csv.d.ts.map +1 -0
  15. package/dist/dataset-download.d.ts +6 -0
  16. package/dist/dataset-download.d.ts.map +1 -0
  17. package/dist/dataset-edit-rows.d.ts +10 -0
  18. package/dist/dataset-edit-rows.d.ts.map +1 -0
  19. package/dist/dataset-upload-snapshot.d.ts +13 -0
  20. package/dist/dataset-upload-snapshot.d.ts.map +1 -0
  21. package/dist/daytona-slice-rehearsal.d.ts +89 -0
  22. package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
  23. package/dist/daytona-slice.d.ts +106 -0
  24. package/dist/daytona-slice.d.ts.map +1 -0
  25. package/dist/deploy-key-merge-core.d.ts +121 -0
  26. package/dist/deploy-key-merge-core.d.ts.map +1 -0
  27. package/dist/diff-comments-cli.d.ts +12 -0
  28. package/dist/diff-comments-cli.d.ts.map +1 -0
  29. package/dist/diff-comments-cli.js +3 -0
  30. package/dist/egress-allowlist-cli.d.ts +23 -0
  31. package/dist/egress-allowlist-cli.d.ts.map +1 -0
  32. package/dist/egress-policy.d.ts +108 -0
  33. package/dist/egress-policy.d.ts.map +1 -0
  34. package/dist/error-response.d.ts +9 -0
  35. package/dist/error-response.d.ts.map +1 -0
  36. package/dist/file-parser.d.ts +5 -0
  37. package/dist/file-parser.d.ts.map +1 -0
  38. package/dist/gepa-engine-dispatch.d.ts +9 -0
  39. package/dist/gepa-engine-dispatch.d.ts.map +1 -0
  40. package/dist/gepa-engine-dispatch.js +48 -2
  41. package/dist/gepa-python-command.d.ts +6 -0
  42. package/dist/gepa-python-command.d.ts.map +1 -0
  43. package/dist/gepa-python-paths.d.ts +6 -0
  44. package/dist/gepa-python-paths.d.ts.map +1 -0
  45. package/dist/gepa-python-paths.js +6 -3
  46. package/dist/git-credential.d.ts +69 -0
  47. package/dist/git-credential.d.ts.map +1 -0
  48. package/dist/git-recovery-environment.d.ts +31 -0
  49. package/dist/git-recovery-environment.d.ts.map +1 -0
  50. package/dist/github-setup.d.ts +122 -0
  51. package/dist/github-setup.d.ts.map +1 -0
  52. package/dist/global-flags.d.ts +12 -0
  53. package/dist/global-flags.d.ts.map +1 -0
  54. package/dist/help.d.ts +46 -0
  55. package/dist/help.d.ts.map +1 -0
  56. package/dist/help.js +91 -23
  57. package/dist/hosted-boot.d.ts +273 -0
  58. package/dist/hosted-boot.d.ts.map +1 -0
  59. package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
  60. package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
  61. package/dist/hosted-bootstrap.d.ts +188 -0
  62. package/dist/hosted-bootstrap.d.ts.map +1 -0
  63. package/dist/hosted-broker-server.d.ts +50 -0
  64. package/dist/hosted-broker-server.d.ts.map +1 -0
  65. package/dist/hosted-git-http-server.d.ts +36 -0
  66. package/dist/hosted-git-http-server.d.ts.map +1 -0
  67. package/dist/hosted-harness-claude.d.ts +195 -0
  68. package/dist/hosted-harness-claude.d.ts.map +1 -0
  69. package/dist/hosted-harness-opencode.d.ts +227 -0
  70. package/dist/hosted-harness-opencode.d.ts.map +1 -0
  71. package/dist/hosted-harness.d.ts +118 -0
  72. package/dist/hosted-harness.d.ts.map +1 -0
  73. package/dist/hosted-harvest.d.ts +68 -0
  74. package/dist/hosted-harvest.d.ts.map +1 -0
  75. package/dist/hosted-headless.d.ts +72 -0
  76. package/dist/hosted-headless.d.ts.map +1 -0
  77. package/dist/hosted-loop-lifecycle.d.ts +206 -0
  78. package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
  79. package/dist/hosted-loop.d.ts +170 -0
  80. package/dist/hosted-loop.d.ts.map +1 -0
  81. package/dist/hosted-run-event-sink.d.ts +222 -0
  82. package/dist/hosted-run-event-sink.d.ts.map +1 -0
  83. package/dist/hosted-runtime-assets.d.ts +186 -0
  84. package/dist/hosted-runtime-assets.d.ts.map +1 -0
  85. package/dist/hosted-session-cli.d.ts +258 -0
  86. package/dist/hosted-session-cli.d.ts.map +1 -0
  87. package/dist/hosted-skill-staging.d.ts +73 -0
  88. package/dist/hosted-skill-staging.d.ts.map +1 -0
  89. package/dist/http.d.ts +8 -0
  90. package/dist/http.d.ts.map +1 -0
  91. package/dist/index.d.ts +22 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +88 -92
  94. package/dist/instruction-set-gepa-launch.d.ts +3 -0
  95. package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
  96. package/dist/instruction-set-gepa-launch.js +15 -0
  97. package/dist/instruction-set-loader.d.ts +6 -0
  98. package/dist/instruction-set-loader.d.ts.map +1 -0
  99. package/dist/instruction-set-loader.js +59 -0
  100. package/dist/instruction-set-manifest.d.ts +14 -0
  101. package/dist/instruction-set-manifest.d.ts.map +1 -0
  102. package/dist/instruction-set-manifest.js +162 -0
  103. package/dist/instruction-sets-cli.d.ts +40 -0
  104. package/dist/instruction-sets-cli.d.ts.map +1 -0
  105. package/dist/instruction-sets-cli.js +361 -0
  106. package/dist/json-response.d.ts +4 -0
  107. package/dist/json-response.d.ts.map +1 -0
  108. package/dist/jsonl-stream.d.ts +8 -0
  109. package/dist/jsonl-stream.d.ts.map +1 -0
  110. package/dist/kill-switch-cli.d.ts +30 -0
  111. package/dist/kill-switch-cli.d.ts.map +1 -0
  112. package/dist/loopback-serve.d.ts +25 -0
  113. package/dist/loopback-serve.d.ts.map +1 -0
  114. package/dist/manifests-cli.d.ts +22 -0
  115. package/dist/manifests-cli.d.ts.map +1 -0
  116. package/dist/markdown-report-input.d.ts +7 -0
  117. package/dist/markdown-report-input.d.ts.map +1 -0
  118. package/dist/merge-job.d.ts +155 -0
  119. package/dist/merge-job.d.ts.map +1 -0
  120. package/dist/model-configs-cli.d.ts +19 -0
  121. package/dist/model-configs-cli.d.ts.map +1 -0
  122. package/dist/model-configs-cli.js +104 -0
  123. package/dist/optimization-export-cli.d.ts +19 -0
  124. package/dist/optimization-export-cli.d.ts.map +1 -0
  125. package/dist/optimizations-list-cli.d.ts +20 -0
  126. package/dist/optimizations-list-cli.d.ts.map +1 -0
  127. package/dist/optimizations-promote-cli.d.ts +8 -0
  128. package/dist/optimizations-promote-cli.d.ts.map +1 -0
  129. package/dist/optimizations-promote-cli.js +34 -0
  130. package/dist/option-validation.d.ts +15 -0
  131. package/dist/option-validation.d.ts.map +1 -0
  132. package/dist/preview-runtime.d.ts +63 -0
  133. package/dist/preview-runtime.d.ts.map +1 -0
  134. package/dist/prompt-draft-push.d.ts +60 -0
  135. package/dist/prompt-draft-push.d.ts.map +1 -0
  136. package/dist/prompt-label-cli.d.ts +11 -0
  137. package/dist/prompt-label-cli.d.ts.map +1 -0
  138. package/dist/prompt-label-cli.js +29 -0
  139. package/dist/prompt-length-wire.d.ts +28 -0
  140. package/dist/prompt-length-wire.d.ts.map +1 -0
  141. package/dist/prompt-report-cli.d.ts +10 -0
  142. package/dist/prompt-report-cli.d.ts.map +1 -0
  143. package/dist/prompt-summary-table.d.ts +15 -0
  144. package/dist/prompt-summary-table.d.ts.map +1 -0
  145. package/dist/prompt-summary-table.js +6 -0
  146. package/dist/registration-tag-warning.d.ts +32 -0
  147. package/dist/registration-tag-warning.d.ts.map +1 -0
  148. package/dist/report-comments-cli.d.ts +16 -0
  149. package/dist/report-comments-cli.d.ts.map +1 -0
  150. package/dist/report-markdown-blankness.d.ts +2 -0
  151. package/dist/report-markdown-blankness.d.ts.map +1 -0
  152. package/dist/report-replacement-warning.d.ts +17 -0
  153. package/dist/report-replacement-warning.d.ts.map +1 -0
  154. package/dist/repository-backup-core.d.ts +225 -0
  155. package/dist/repository-backup-core.d.ts.map +1 -0
  156. package/dist/repository-recovery-manifest.d.ts +83 -0
  157. package/dist/repository-recovery-manifest.d.ts.map +1 -0
  158. package/dist/run-event-digest.d.ts +74 -0
  159. package/dist/run-event-digest.d.ts.map +1 -0
  160. package/dist/runner-dir-verify.d.ts +66 -0
  161. package/dist/runner-dir-verify.d.ts.map +1 -0
  162. package/dist/runner-instruction-set-contract.d.ts +24 -0
  163. package/dist/runner-instruction-set-contract.d.ts.map +1 -0
  164. package/dist/runner-instruction-set-contract.js +50 -0
  165. package/dist/sandbox-hygiene.d.ts +45 -0
  166. package/dist/sandbox-hygiene.d.ts.map +1 -0
  167. package/dist/sandbox-provider.d.ts +193 -0
  168. package/dist/sandbox-provider.d.ts.map +1 -0
  169. package/dist/scorer-draft-push.d.ts +50 -0
  170. package/dist/scorer-draft-push.d.ts.map +1 -0
  171. package/dist/scorers-cli.d.ts +20 -0
  172. package/dist/scorers-cli.d.ts.map +1 -0
  173. package/dist/scorers-cli.js +22 -0
  174. package/dist/scorers-verify-parity-bridge.d.ts +16 -0
  175. package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
  176. package/dist/scorers-verify-parity-bridge.js +90 -0
  177. package/dist/scorers-verify-parity.d.ts +81 -0
  178. package/dist/scorers-verify-parity.d.ts.map +1 -0
  179. package/dist/scorers-verify-parity.js +502 -0
  180. package/dist/scores-accept-cli.d.ts +21 -0
  181. package/dist/scores-accept-cli.d.ts.map +1 -0
  182. package/dist/secret-redaction.d.ts +47 -0
  183. package/dist/secret-redaction.d.ts.map +1 -0
  184. package/dist/session-timeline.d.ts +54 -0
  185. package/dist/session-timeline.d.ts.map +1 -0
  186. package/dist/setup-prompt.d.ts +5 -0
  187. package/dist/setup-prompt.d.ts.map +1 -0
  188. package/dist/skill-installer.d.ts +68 -0
  189. package/dist/skill-installer.d.ts.map +1 -0
  190. package/dist/skilled-proposer-launch.d.ts +7 -0
  191. package/dist/skilled-proposer-launch.d.ts.map +1 -0
  192. package/dist/skilled-proposer-launch.js +60 -0
  193. package/dist/smoke.d.ts +102 -0
  194. package/dist/smoke.d.ts.map +1 -0
  195. package/dist/start-intent.d.ts +12 -0
  196. package/dist/start-intent.d.ts.map +1 -0
  197. package/dist/task-assignment-manifest.d.ts +15 -0
  198. package/dist/task-assignment-manifest.d.ts.map +1 -0
  199. package/dist/task-create-error.d.ts +18 -0
  200. package/dist/task-create-error.d.ts.map +1 -0
  201. package/dist/team-connectors-cli.d.ts +29 -0
  202. package/dist/team-connectors-cli.d.ts.map +1 -0
  203. package/dist/types.d.ts +39 -0
  204. package/dist/types.d.ts.map +1 -0
  205. package/dist/vercel-sandbox-provider.d.ts +156 -0
  206. package/dist/vercel-sandbox-provider.d.ts.map +1 -0
  207. package/dist/workbench-cli.d.ts +233 -0
  208. package/dist/workbench-cli.d.ts.map +1 -0
  209. package/dist/workspace-sync.d.ts +207 -0
  210. package/dist/workspace-sync.d.ts.map +1 -0
  211. package/dist/workspace.d.ts +92 -0
  212. package/dist/workspace.d.ts.map +1 -0
  213. package/dist/zip-draft-push.d.ts +55 -0
  214. package/dist/zip-draft-push.d.ts.map +1 -0
  215. package/package.json +14 -1
  216. package/requirements/README.md +138 -0
  217. package/requirements/skilled-proposer.in +66 -0
  218. package/requirements/skilled-proposer.lock +265 -0
  219. package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
  220. package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
  221. package/src/diff-comments-cli.ts +4 -0
  222. package/src/gepa-engine-dispatch.ts +42 -2
  223. package/src/gepa-python-paths.ts +6 -3
  224. package/src/help.ts +93 -23
  225. package/src/index.ts +99 -126
  226. package/src/instruction-set-gepa-launch.ts +28 -0
  227. package/src/instruction-set-loader.ts +37 -0
  228. package/src/instruction-set-manifest.ts +120 -0
  229. package/src/instruction-sets-cli.ts +306 -0
  230. package/src/model-configs-cli.ts +119 -0
  231. package/src/optimizations-promote-cli.ts +41 -0
  232. package/src/prompt-label-cli.ts +55 -0
  233. package/src/prompt-summary-table.ts +12 -0
  234. package/src/runner-instruction-set-contract.ts +64 -0
  235. package/src/scorers-cli.ts +40 -0
  236. package/src/scorers-verify-parity-bridge.ts +90 -0
  237. package/src/scorers-verify-parity.ts +564 -0
  238. package/src/skilled-proposer-launch.ts +89 -0
  239. package/src/skilled-proposer-venv-manager.mjs +757 -0
  240. package/tsconfig.json +2 -0
  241. package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +2 -1
  242. package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +8 -2
  243. package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +17 -3
  244. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +100 -8
  245. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
  246. package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +92 -12
  247. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +100 -22
  248. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
  249. package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +17 -0
  250. package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +2 -1
  251. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
  252. package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +4 -1
  253. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
  254. package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
  255. package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
  256. package/vendor/skills/orizu-cli/SKILL.md +2 -0
  257. package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
  258. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
  259. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +25 -0
  260. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +108 -8
@@ -0,0 +1,303 @@
1
+ # Migrating an existing GEPA setup into Orizu
2
+
3
+ For a customer already running official GEPA from a plain script — inline
4
+ `trainset`/`valset`, a seed prompt in a dict, their own `metric(row, output)`.
5
+
6
+ The ordering rule: **nothing is optimized until parity is proven.** The last
7
+ step before `run-gepa` is always `orizu scorers verify-parity`, which runs the
8
+ migrated scorer runner under the exact payload `run-gepa` sends it and the
9
+ customer's original metric over the same rows, and exits 0 only if they agree.
10
+
11
+ Commands are shown with `--local`; drop it against a hosted server.
12
+
13
+ ## 0. Read their script
14
+
15
+ Note the row shape and which field is the stable id, `trainset`/`valset`
16
+ membership, the seed candidate text, the metric's `module:function`, and what
17
+ it returns (a float, a dict with `score`, or GEPA's `EvaluationResult` — all
18
+ three are accepted; `objective_scores` is ignored). If rows have no stable id,
19
+ add one now: every step below pairs on it.
20
+
21
+ ## 1. Set up, then export the rows
22
+
23
+ ```bash
24
+ orizu setup --team <team-slug>
25
+ ```
26
+
27
+ One dataset holds every row; the split set decides train vs validation. Dump
28
+ rows verbatim from their script, and mirror their lists exactly — a resampled
29
+ split makes every later number incomparable with the numbers they have.
30
+
31
+ Build the dataset from the **id-deduplicated union**: a row reused in both
32
+ `TRAINSET` and `VALSET` would otherwise be written twice, and the upload route
33
+ rejects duplicate canonical row ids (`app/api/datasets/route.ts:206-211`,
34
+ "csvData must contain unique canonical row ids"). The partition lists keep the
35
+ original ids independently, so a shared row can still appear in both.
36
+
37
+ ```python
38
+ import json
39
+ seen, rows = set(), []
40
+ for row in TRAINSET + VALSET:
41
+ if row["id"] in seen:
42
+ continue
43
+ seen.add(row["id"])
44
+ rows.append(row)
45
+ with open("dataset.jsonl", "w") as handle:
46
+ for row in rows:
47
+ handle.write(json.dumps(row) + "\n")
48
+ with open("split.json", "w") as handle:
49
+ json.dump({"name": "default", "strategy": "predefined", "seed": 42, "partitions": [
50
+ {"name": "train", "row_ids": [row["id"] for row in TRAINSET]},
51
+ {"name": "validation", "row_ids": [row["id"] for row in VALSET]},
52
+ {"name": "test", "row_ids": []},
53
+ ]}, handle)
54
+ ```
55
+
56
+ ## 2. Upload, version, split
57
+
58
+ ```bash
59
+ orizu --local datasets upload --file ./dataset.jsonl \
60
+ --project <team>/<project> --name "GEPA migration rows" --json
61
+
62
+ orizu --local datasets versions create <dataset-id-or-name> \
63
+ --project <team>/<project> --label v1 --json # -> dataset_version_id
64
+
65
+ orizu --local datasets splits create <dataset-version-id> \
66
+ --from-file ./split.json --json # -> split_set_id
67
+ ```
68
+
69
+ `--from-file` preserves their partitions; the ratio flags
70
+ (`--train/--validation/--test`) resample and must not be used here.
71
+
72
+ ## 3. Candidate runner and seed prompt
73
+
74
+ The candidate runner does what `task_lm` did: candidate prompt + row -> output.
75
+
76
+ ```bash
77
+ orizu --local runners push ./candidate-runner \
78
+ --project <team>/<project> --name gepa-candidate-runner --label default --json
79
+
80
+ orizu --local prompts push ./prompt \
81
+ --project <team>/<project> --runner-version <candidate-runner-version-id> --json
82
+ ```
83
+
84
+ `./prompt` holds `orizu.prompt.json` plus the body file it names. Only `name`
85
+ is enforced (`app/api/cli/prompts/route.ts:220-221`); `body_file` defaults to
86
+ `prompt.md`, `body_kind` to `text` (`packages/cli/src/index.ts:3966-3985`) and
87
+ `provider_settings` to `{}`. Write all four anyway — a migration whose model
88
+ settings are implicit is a migration nobody can reproduce:
89
+
90
+ ```json
91
+ {
92
+ "name": "gepa-seed-prompt",
93
+ "body_file": "prompt.md",
94
+ "body_kind": "text",
95
+ "provider_settings": { "model": "claude-sonnet-4-6", "temperature": 0 }
96
+ }
97
+ ```
98
+
99
+ `prompt.md` carries their seed candidate text verbatim. Returns the seed
100
+ `prompt_version_id`.
101
+
102
+ ## 4. Wrap their metric in a scorer runner
103
+
104
+ Do not rewrite the metric — wrap it, or parity is unachievable by construction.
105
+ `scorer-runner/manifest.json` declares
106
+ `"command": ["python3", "runner.py"]` and `"scorer_input_contract": "flat_row"`.
107
+ Under `flat_row`, `input["row"]` is the dataset row with the candidate output
108
+ injected at `model_output` (or the `--scorer-candidate-field` field) and
109
+ `input["model_output"]` is that same output, so `runner.py` is two lines:
110
+ `result = metric(payload["row"], payload["model_output"])`, then write the score
111
+ to `ORIZU_RUNNER_OUTPUT_PATH`. Extract it the same three ways `verify-parity`
112
+ extracts the original metric's result, or the two sides disagree on a shape step
113
+ 0 says is supported:
114
+
115
+ ```python
116
+ score = result["score"] if isinstance(result, dict) else getattr(result, "score", result)
117
+ json.dump({"score": float(score)}, open(os.environ["ORIZU_RUNNER_OUTPUT_PATH"], "w"))
118
+ ```
119
+
120
+ A bare `float(getattr(result, "score", result))` raises `TypeError` on the dict
121
+ form, failing every row here and again under `run-gepa`.
122
+
123
+ **If the metric lives inside their script, extract it first.** The one-file
124
+ shape in step 0 usually defines `metric` inside `optimize.py` next to
125
+ `import gepa`; vendoring that file into the runner drags the `gepa` import into
126
+ the scrubbed runner environment and every row raises `ModuleNotFoundError`. Move
127
+ the function to a `metric.py` with no optimizer imports and have `optimize.py`
128
+ do `from metric import metric` — their script keeps working unchanged, the
129
+ runner copy carries no dependencies, and `--original metric:metric` is valid for
130
+ both. (`docs/requirements/official-gepa/stranger/` ships this two-file shape.)
131
+
132
+ **Copy the metric module into `./scorer-runner/` before pushing.** The runner
133
+ executes with its working directory set to the *materialized runner version* and
134
+ a scrubbed environment, so the `metric.py` sitting next to their `optimize.py`
135
+ is not importable — a runner that does `import metric` raises
136
+ `ModuleNotFoundError` on every row, here and again under `run-gepa`. Vendor the
137
+ file (or the whole package) into the runner directory so the registered runner
138
+ bytes contain everything the metric needs:
139
+
140
+ ```bash
141
+ cp ./metric.py ./scorer-runner/metric.py # or: cp -R ./their_package ./scorer-runner/
142
+ ```
143
+
144
+ `verify-parity` still imports the customer's ORIGINAL metric from the current
145
+ directory, so the two copies must be the same file — re-copy whenever it changes,
146
+ or the parity you prove is against a stale runner.
147
+
148
+ A DSPy metric is `metric(example, pred)`; the 3-line adapter is:
149
+
150
+ ```python
151
+ def metric(row, model_output): # what verify-parity calls
152
+ import dspy
153
+ return dspy_metric(dspy.Example(**row), dspy.Prediction(answer=model_output))
154
+ ```
155
+
156
+ ```bash
157
+ orizu --local runners push ./scorer-runner \
158
+ --project <team>/<project> --name gepa-scorer-runner --label default --json
159
+
160
+ orizu --local prompts push ./scorer-prompt \
161
+ --project <team>/<project> --runner-version <scorer-runner-version-id> --json
162
+
163
+ orizu --local scorers register --project <team>/<project> \
164
+ --name gepa-migrated-metric --manifest ./scorer.manifest.json \
165
+ --prompt-version <scorer-prompt-version-id> \
166
+ --runner-version <scorer-runner-version-id> \
167
+ --label production --json # -> scorer_version_id
168
+ ```
169
+
170
+ `scorer.manifest.json` needs all four fields below;
171
+ `app/api/cli/scorers/route.ts` rejects a manifest missing `implementation_kind`
172
+ or `metric_key`, and `mode` must be `row` or `set`:
173
+
174
+ ```json
175
+ {
176
+ "name": "gepa-migrated-metric",
177
+ "mode": "row",
178
+ "implementation_kind": "prompt_runner",
179
+ "metric_key": "score"
180
+ }
181
+ ```
182
+
183
+ `prompt_runner` is the only kind `runners exec --scorer-version` and
184
+ `verify-parity` can execute, and it requires a backing prompt version. For a
185
+ pure-code metric with no judge text, `./scorer-prompt` is a short prompt
186
+ version documenting the rubric; the runner never reads its body.
187
+
188
+ ## 5. Produce `outputs.jsonl` by running the seed once
189
+
190
+ A scorer scores *(row, output)*, so parity needs one candidate output per row.
191
+
192
+ ```bash
193
+ orizu --local runners exec \
194
+ --prompt-version <seed-prompt-version-id> \
195
+ --runner-version <candidate-runner-version-id> \
196
+ --dataset-version <dataset-version-id> \
197
+ --split-set <split-set-id> --split validation \
198
+ --out ./results.jsonl
199
+
200
+ jq -c '{row_id: .row_id, model_output: .model_response}' ./results.jsonl > ./outputs.jsonl
201
+ ```
202
+
203
+ `outputs.jsonl` must cover exactly the rows being checked: a missing or extra
204
+ `row_id`, a duplicate, or a non-string `model_output` is refused before
205
+ anything runs.
206
+
207
+ ## 6. Prove parity
208
+
209
+ ```bash
210
+ orizu --local scorers verify-parity \
211
+ --scorer-version <scorer-version-id> \
212
+ --dataset-version <dataset-version-id> \
213
+ --split-set <split-set-id> --split validation \
214
+ --outputs ./outputs.jsonl \
215
+ --original metric:metric \
216
+ --json
217
+ ```
218
+
219
+ - `--original <module>:<function>` is imported from the **current directory**,
220
+ so run this where their script lives.
221
+ - The input contract and candidate field resolve exactly as `run-gepa` resolves
222
+ them: flag > runner manifest (`scorer_input_contract`,
223
+ `candidate_output_field`) > defaults (`gepa`, `model_output`). Because a
224
+ `gepa`-contract runner is fed a different payload shape entirely,
225
+ verify-parity refuses anything that does not resolve to `flat_row` — declare
226
+ `"scorer_input_contract": "flat_row"` in the runner manifest (step 4) or pass
227
+ `--scorer-input-contract flat_row`, and pass the SAME value to `run-gepa`.
228
+ - `--scorer-candidate-field <field>` when the judge reads the candidate from a
229
+ row field other than `model_output` — the same value you pass to `run-gepa`.
230
+ It may not be blank.
231
+ - `--tolerance <float>` defaults to `0` (exact). Raise it only for a genuinely
232
+ non-deterministic metric, and say so in the report.
233
+ - `--python <cmd>` picks the interpreter (else `PYTHON`, else `python3`). The
234
+ metric runs in the customer's own unscrubbed environment, so an LLM-judge
235
+ metric still sees its API keys.
236
+ - `--runner-dir <dir>` runs local scorer-runner bytes, but only if they hash to
237
+ the registered runner version; drifted bytes exit 2.
238
+ - `--limit <n>` checks the first N rows of the partition — a smoke check, not
239
+ the proof: it always reports `parity: false` with `scope: {compared, total}`,
240
+ so a limited run can never be mistaken for a full one. The same full
241
+ `outputs.jsonl` works: outputs must cover the limited rows and belong to the
242
+ split, and need not be trimmed to N.
243
+ - A repeated option takes its LAST value, matching run-gepa's scalar options.
244
+ - If a sampled row already has the candidate field as a key, the command exits 2
245
+ rather than overwriting a value the original metric reads.
246
+
247
+ Exit `0` parity proven (>= 1 row compared, no mismatch, no row error); `1` at
248
+ least one mismatch or row error — read `mismatches`/`errors` in the `--json`
249
+ report, fix the runner, repeat; `2` the check could not run (bad arguments, no
250
+ rows, unverified runner dir, original metric not importable).
251
+
252
+ Do not continue on exit 1 or 2. A scorer that disagrees with their metric
253
+ optimizes the wrong thing, and every number afterwards is wrong.
254
+
255
+ **Prove parity on BOTH partitions before declaring the scorer migrated.**
256
+ `run-gepa` scores `train` and `validation`, and training rows drive GEPA's
257
+ proposals: a scorer that agrees on validation but diverges on a training row
258
+ that exercises a different schema branch changes the experiment's result while
259
+ passing this gate. Repeat steps 5 and 6 with `--split train` (its own
260
+ `outputs.jsonl` from the same seed run) and require exit 0 on both.
261
+
262
+ A `parity: true` report means every row of that partition was compared and
263
+ agreed. A limited run reports `parity: false` with
264
+ `scope: {compared, total}` and the line "Smoke check passed on N of M rows"
265
+ — it exits 0, but it is not the proof.
266
+
267
+ ## 7. Optimizer and run
268
+
269
+ ```bash
270
+ orizu --local optimizers push ./optimizer \
271
+ --project <team>/<project> --name gepa-optimizer --label gepa-v1 --json
272
+
273
+ orizu --local optimizations run-gepa \
274
+ --project <team>/<project> \
275
+ --optimizer-version-id <optimizer-version-id> \
276
+ --candidate-version-id <seed-prompt-version-id> \
277
+ --runner-version-id <candidate-runner-version-id> \
278
+ --candidate-runner-dir ./candidate-runner \
279
+ --scorer-version-id <scorer-version-id> \
280
+ --scorer-runner-version-id <scorer-runner-version-id> \
281
+ --scorer-runner-dir ./scorer-runner \
282
+ --dataset-version-id <dataset-version-id> \
283
+ --split-set-id <split-set-id> \
284
+ --train-split train --val-split validation \
285
+ --scorer-input-contract flat_row \
286
+ --reflection-max-tokens 8192 \
287
+ --engine official
288
+ ```
289
+
290
+ `--reflection-max-tokens` is not optional here: with `--engine official` the
291
+ reflection model defaults to `anthropic/claude-opus-4-7`, and every non-`openai/`
292
+ reflection model is refused at launch without it
293
+ (`packages/cli/src/gepa-engine-dispatch.ts:301-304`, again at
294
+ `packages/orizu-gepa/src/orizu_gepa_connector/runtime.py:193-196`). Drop it only
295
+ if you also pass an `openai/...` `--reflection-model`.
296
+
297
+ `./optimizer` needs `manifest.json` with `"optimizer_family": "gepa"`. See
298
+ `references/optimization-with-gepa.md` for reflection-model and budget flags.
299
+ Pass `--scorer-candidate-field <field>` here too if you passed it to
300
+ `verify-parity`: the payload the runner sees must be the one parity was proven
301
+ under. Afterwards, `orizu optimizations promote <run-id> --candidate <id>
302
+ --label production` promotes the winner — and with parity proven, its score is
303
+ comparable to the seed score their own script reports.
@@ -2,6 +2,8 @@
2
2
 
3
3
  How to optimize text candidates against validated judges/scorers. For Orizu-tracked runs, prefer `prompt-control-plane.md` and the bundled `orizu optimizations run-gepa` command when optimizing one text candidate. Use this reference for GEPA mechanics, custom optimizer implementations, and optional DSPy context for customers already using DSPy.
4
4
 
5
+ For an instruction set, select the profile explicitly with `--instruction-set <name> --model-config <identity>` rather than `--prompt` or `--prompt-version` (those selectors are mutually exclusive). The connector resolves that model config's production profile, or the set default, and optimizes its complete component map. `--component-selector round-robin` is the default and updates one component per round; `--component-selector all` updates every component per round. A multi-component candidate is sent as a component map, while a set of one keeps the existing prompt-body contract. Git-pinned components and malformed component maps are refused before a run starts; multi-component auto-promotion is deliberately refused until component-map promotion is available.
6
+
5
7
  ## Inputs
6
8
 
7
9
  You should arrive here with:
@@ -66,6 +68,29 @@ Reflection output contract:
66
68
 
67
69
  Full command syntax and event contracts: `prompt-control-plane.md`.
68
70
 
71
+ ## Migrating from the legacy engine (release cli-v0.5.20+)
72
+
73
+ Since release `cli-v0.5.20` (`orizu@0.5.20` on npm), `run-gepa` defaults to the official GEPA engine. The flag surface and validation rules match legacy almost everywhere — a valid legacy command runs unchanged, with two exceptions: the reflection-cap requirement below, and stricter `=`-form parsing (`--flag=` with an empty value, or an `=`-attached value that itself starts with `--`, is refused pre-launch; legacy's argparse accepted both — a scripted `--scorer-candidate-field=$FIELD` with `FIELD` unset now fails with the flag named). What actually differs:
74
+
75
+ - **Validation happens before launch.** Legacy validated flags inside the python process (an argparse exit); the official dispatcher enforces the flag-shape rules below before anything spawns and names the offending flag in the error (the budget-conflict error lists the exclusive set rather than the flags you passed; value errors — e.g. a non-integer or non-positive budget number — are still reported by the engine and name the underlying setting). The rules carried over from legacy: `--budget` / `--candidate-selection-strategy` / `--scorer-input-contract` validate their choices, boolean flags take no value, abbreviated flag spellings are rejected, and budget controls are mutually exclusive — at most one of `--budget`, `--max-metric-calls`, `--max-full-evals`, `--max-iterations`. The official-only `--max-candidate-proposals` joins that exclusion set; passing no budget control defaults to `--budget auto`.
76
+ - **A reflection cap is required for non-OpenAI reflection models.** `--reflection-max-tokens` is required at launch whenever the reflection model does not start with `openai/` — including the default (`anthropic/claude-opus-4-7`) and bare names like `gpt-4o` — so the run refuses immediately instead of failing every reflection call mid-run.
77
+ - **The skilled proposer is an opt-in proposal path.** Reach for `--candidate-proposer skilled-proposer` when you want to evaluate the upstream proposer's alternative reflection process and can accept its first-use network, CPython, and managed-dependency requirements. Evaluate the shipped empty-skills proposer through the controlled A/B below, and ground quality or generalization claims in that evidence. Environment setup and recovery live in `prompt-control-plane.md` ("Skilled proposer").
78
+ - **Seed preflight: same gate, better evidence.** Both engines refuse a degenerate seed (every valid row pinned to the same bound — the worst score, or uniformly `0.0` under a lower-is-better scorer, where the perfect bound is indistinguishable from a scorer silently zeroing on a mismatched input shape) or an all-errored seed at launch, and both accept `--allow-degenerate-seed` to bypass. The bypass covers *both* refusals — including the broken-scorer one — so a bypassed run can spend budget on a scorer that never works; use it only when a weak seed is expected. New on the official engine: the refusal message carries per-row evidence (row ids, scores, scorer output and errors); `<log-dir>/preflight-refused-<uuid>/preflight.json` (not written under `--no-local-log`) instead stores redacted `{row_id, score, error_class}` entries unless `--log-row-snapshots` is passed — a sibling of the run directories, since no run record exists when a launch is refused.
79
+ - **Metric accounting differs.** Official counts include every evaluated row; legacy excluded some cached work. Do not compare raw metric-call totals across engines (details in ADR-019).
80
+ - **Artifacts.** Both engines write `events.jsonl`, `evaluations.jsonl`, `reflections.jsonl`, and `result.json` locally; `lm_stats.json` (reflection usage) is official-only. Dashboard rendering is identical, and run metadata records `engine: official` or `engine: legacy`.
81
+
82
+ Terminal semantics are unchanged between engines: exhausting any budget control other than `--max-iterations` ends the run `paused` (`pause_reason: budget_exhausted`) with no auto-promotion, while completing the configured `--max-iterations` is a normal `succeeded` finish that may auto-promote. A paused run's validated candidates can still be promoted manually — only automatic promotion is gated on how the run ended. Falling back to `--engine legacy` after a budget-exhausted pause reproduces the same paused outcome; it will not turn the run into a `succeeded` one.
83
+
84
+ **Fallback:** rerun with `--engine legacy` for the previous engine's exact behavior, dropping `--max-candidate-proposals` first if you used it (it is official-only; the CLI refuses it under legacy before launch). Legacy is frozen (no new features) and will be removed at the M3 milestone (ADR-019) — if you fall back because the official engine misbehaved, capture both run ids and report the pair.
85
+
86
+ ### Compare the skilled proposer with the default
87
+
88
+ Run two arms with the same candidate prompt/version, dataset version and split set, candidate and scorer runners, seed, reflection model/settings, thread count, and one shared overall budget such as `--max-candidate-proposals 1`. Keep the default arm unselected; add `--candidate-proposer skilled-proposer` plus bounded `--proposal-max-calls` and `--proposal-max-tokens` only to the selected arm. The comparison is ready when both terminal states, retained candidates, scores, proposal evidence, and total run usage are captured. A fixed GEPA seed aligns optimizer sampling but does not make provider or scorer calls deterministic, so treat the pair as process and outcome evidence rather than an intrinsic quality claim.
89
+
90
+ For the selected arm, each provider-bearing proposal call appends `proposal-observability/events.jsonl`. Success records contain `source`, `status`, `attempt`, `correlation_id`, `provider`, `model`, nullable `request_id`, `latency_ms`, `cache_state`, and `usage.{input_tokens,output_tokens,total_tokens}`. Failure records contain `source`, `status`, `attempt`, `correlation_id`, `provider`, `cache_state`, `failure_code`, and optional `usage`; the latest durable failure is also written to `proposal-failures/latest.json` with `source`, `code`, `detail`, and optional `correlation_id`.
91
+
92
+ With local logging, those paths sit under `<log-dir>/<run-id>/`, and terminal `proposal-observability/lm_stats.json` is a flat object with `total_tokens_in`, `total_tokens_out`, and `total_tokens`. Under `--no-local-log`, per-call and failure records move under `.orizu/proposal-observability/<run-id>/`, and no terminal `lm_stats.json` is written. Finish the comparison after transport-bearing calls that reached provider completion or failure have records, `proposal_observability_event_failed` is absent, and, when local logging is enabled, terminal usage totals reconcile with those records.
93
+
69
94
  ## Step 1: Wrap your application as an Orizu runner
70
95
 
71
96
  For Orizu-tracked optimization, the candidate runner receives one dataset row and one candidate text body through the file contract. The scorer runner, by default, receives a GEPA-shaped `row` — `{source_row, candidate_id, candidate_output, candidate_raw_response, candidate_error}` — and returns a score and feedback. See `prompt-control-plane.md` ("Scorer-Runner Input Contracts") for the exact runner I/O shapes.
@@ -22,16 +22,80 @@ Use this reference for the Phase 0 prompt, judge, scorer, runner, score, run, an
22
22
  7. For common text-candidate optimization, prefer `orizu optimizations run-gepa`; it starts the run and logs events for you.
23
23
  8. After `run-gepa`, inspect `logs/<optimization_run_id>` first; it is the complete local trace for coding-agent analysis.
24
24
  9. Use `orizu optimizations export <run-id> --out <run-id>.optimization.json` when the local log is missing or the run happened elsewhere.
25
- 10. Write and attach a markdown report for finished, failed, or cancelled runs; use `optimization-reports.md` for structure and diagnostic guidance.
26
- 11. For custom optimizers, start an optimization run before local execution, then stream events into that run.
27
- 12. Use bare HTTP for optimization events; use `orizu log` only as a shell fallback.
28
- 13. Promote only accepted candidates; rejected candidates stay in optimization events.
25
+ 10. Promote an accepted candidate with `orizu optimizations promote <run-id> --candidate <id> [--label production]`; tuple output reports its profile version and each component as changed or carried.
26
+ 11. Write and attach a markdown report for finished, failed, or cancelled runs; use `optimization-reports.md` for structure and diagnostic guidance.
27
+ 12. For custom optimizers, start an optimization run before local execution, then stream events into that run.
28
+ 13. Use bare HTTP for optimization events; use `orizu log` only as a shell fallback.
29
+ 14. Promote only accepted candidates; rejected candidates stay in optimization events.
29
30
 
30
31
  Customer model-provider secrets stay local. Do not upload Anthropic/OpenAI/etc. API keys to Orizu.
31
32
 
33
+ ## Instruction sets
34
+
35
+ An instruction set is a set of named components (key → text); order carries no meaning.
36
+ It has one profile per model config. Existing prompts appear as sets of one component. Inspect or
37
+ write them with:
38
+
39
+ ```bash
40
+ orizu instruction-sets list --project core/evals --status active --json
41
+ orizu instruction-sets show planner --project core/evals --status active --json
42
+ orizu instruction-sets create ./orizu.instruction-set.json --project core/evals --model-config anthropic/claude-haiku
43
+ orizu instruction-sets push ./orizu.instruction-set.json --project core/evals
44
+ orizu instruction-sets profiles new planner --project core/evals --model-config anthropic/claude-haiku
45
+ orizu instruction-sets profiles promote planner --project core/evals --model-config anthropic/claude-haiku --version 2
46
+ orizu instruction-sets profiles rollback planner --project core/evals --model-config anthropic/claude-haiku --to 1
47
+ orizu instruction-sets default show planner --project core/evals
48
+ orizu instruction-sets default move planner --project core/evals --model-config anthropic/claude-haiku --version 2
49
+ orizu instruction-sets shape add planner --project core/evals --key safety --from ./orizu.instruction-set.json
50
+ orizu instruction-sets shape remove planner --project core/evals --key safety
51
+ ```
52
+
53
+ The manifest contains `name`, `shape` (the set of component keys; order carries no meaning), and `components`; every shape key
54
+ needs a set-wide component (`key` plus `text` or a manifest-relative `path`). A
55
+ component may additionally specify `modelConfig` to override that key in a named
56
+ profile. Create materializes each named profile with its complete base-plus-
57
+ override component map. The default starts on the seed profile version. A profile with a
58
+ production version resolves to that component map; a profile without one resolves to the
59
+ default.
60
+
61
+ Shape changes create unpromoted profile heads but leave the default and
62
+ production pointers in place. The instruction set does not resolve for affected
63
+ model configs until those pointers move to their new shape-change versions; use
64
+ the follow-up commands printed by the text CLI.
65
+
66
+ Before a human moves a default, `default show` lists the model configs whose
67
+ resolution would change. A move targets a version by model-config identity and
68
+ version number, and the database rejects an unsealed or non-commit-anchored
69
+ component map. Shape changes create a new `shape_change` version for every profile;
70
+ they do not repoint the default or any production label.
71
+
72
+ Sync an offline runner directory with `orizu instruction-sets sync planner
73
+ --out ./instructions --project core/evals`. The layout is `manifest.json`,
74
+ `default/<key>.md`, and `profiles/<identity-slug>/<key>.md`; loaders are
75
+ `loadInstructionSet(dir, name, modelConfigIdentity)` in TypeScript and
76
+ `load_instruction_set(dir, name, model_config_identity)` in Python. A
77
+ Git-pinned component is recorded in the manifest but is intentionally not
78
+ downloaded; either loader raises `instruction_set_component_unavailable`. A
79
+ missing or unreadable local component file is distinct:
80
+ `instruction_set_component_unreadable`.
81
+
82
+ Use `profiles new` to seed an additional model-config profile from the default;
83
+ hosted agents may do this because it does not move production. Use `profiles
84
+ promote` to move only that profile's production label; use `profiles rollback
85
+ --to <n>` to create a new rollback version from the target version's component
86
+ component map **and settings version** before moving it. Promotion and rollback are
87
+ human-only because they move production labels.
88
+
89
+ For a single-component instruction set that wraps an existing prompt, only a
90
+ promotion or rollback of the set's default profile also moves that prompt's
91
+ `production` label in the same transaction. The coupling is intentionally
92
+ one-way: a later direct prompt-label move does not change the instruction-set
93
+ profile label. Human CLI output names the prompt as `default profile → prompt
94
+ label` when a profile command performed this mirror.
95
+
32
96
  Execution/privacy defaults:
33
97
 
34
- - Runner subprocesses receive only the file-contract paths plus a small allowlist of provider/runtime environment variables. Orizu API tokens are not passed into runner processes.
98
+ - Runner subprocesses receive the input/output file-contract paths plus a small allowlist of provider/runtime environment variables. Orizu API tokens are not passed into runner processes. When an execution carries an instruction set, `ORIZU_INSTRUCTION_SET_DIR` is set explicitly to a fresh per-row synced layout; it is absent for legacy executions and is never inherited from the shell.
35
99
  - `orizu optimizations run-gepa` redacts dataset row payloads and reflection text in logged events by default. Use `--log-row-snapshots` only when the customer explicitly wants raw row and prompt text in the optimization event stream.
36
100
  - `run-gepa` still writes complete local traces under `logs/<optimization_run_id>` by default. Treat those logs as sensitive: they include row inputs, model outputs, scores, feedback, scorer responses, reflection prompts, reflection responses, and candidate text.
37
101
  - Runner artifacts, runner output, score result uploads, and optimization event payloads are size-capped. If a run needs larger observability payloads, store the large artifact separately and log a pointer.
@@ -52,7 +116,7 @@ Required file: `manifest.json`.
52
116
  }
53
117
  ```
54
118
 
55
- For `runners exec`, the command must read input JSON from `ORIZU_RUNNER_INPUT_PATH` and write output JSON to `ORIZU_RUNNER_OUTPUT_PATH`.
119
+ For `runners exec`, the command must read input JSON from `ORIZU_RUNNER_INPUT_PATH` and write output JSON to `ORIZU_RUNNER_OUTPUT_PATH`. When present, `ORIZU_INSTRUCTION_SET_DIR` contains the same layout written by `instruction-sets sync`; use `loadInstructionSet`/`load_instruction_set` to read materialized component text. Pinned components are represented in the manifest and are intentionally not materialized as `.md` files.
56
120
 
57
121
  Input shape:
58
122
 
@@ -68,12 +132,24 @@ Input shape:
68
132
  "max_tokens": 4096
69
133
  }
70
134
  },
135
+ "instruction_set": {
136
+ "name": "planner",
137
+ "model_config": { "identity": "openai/gpt-5.4" },
138
+ "shape": ["system", "tools"],
139
+ "prompt_component_key": "system",
140
+ "components": { "system": "inline component bytes" },
141
+ "pinned_components": {
142
+ "tools": { "repoPath": "skills/tools.md", "contentSha": "…", "commitSha": "…" }
143
+ }
144
+ },
71
145
  "prompt_version_id": "uuid",
72
146
  "runner_version_id": "uuid",
73
147
  "run_id": null
74
148
  }
75
149
  ```
76
150
 
151
+ `instruction_set` is additive. `prompt_component_key` names the component that contains the requested legacy prompt version; candidate evaluation replaces that component's seed bytes with the candidate. `components` contains only inline text; Git-only components are absent there and appear in `pinned_components` with camelCase Git pins. Runners mirror whether the exec-context response contains `prompt.body`; the explicit `instructionSetProfileVersionId` parameter is a server surface that ALI-1536 sends. The route keeps byte-identical `prompt.body` for every implicit prompt-version caller and omits it only for an explicit multi-component map; explicit set-of-one responses retain it. Top-level JSON key order is not part of the contract.
152
+
77
153
  Output shape:
78
154
 
79
155
  ```json
@@ -714,13 +790,14 @@ orizu --local optimizations run-gepa \
714
790
 
715
791
  Useful GEPA flags:
716
792
 
717
- - `--engine official|legacy` defaults to `official`, which runs the bundled official GEPA connector. Use `legacy` only as the frozen recovery hatch; it keeps the historical Python loop and does not support `--max-candidate-proposals`.
793
+ - `--engine official|legacy` defaults to `official`, which runs the bundled official GEPA connector. Use `legacy` only as the frozen recovery hatch; it keeps the historical Python loop and does not support `--max-candidate-proposals` or the skilled proposer.
718
794
  - `--scorer-input-contract gepa|flat_row` selects the scorer-runner input shape. Use `flat_row` to reuse a judge runner written for `runners exec --scorer-version` without a hand-written adapter; add `--scorer-candidate-field <row-field>` when that judge reads the candidate output from a specific row field (e.g. `draft`). Passing a candidate field under the `gepa` contract is refused at launch, not silently ignored. See "Scorer-Runner Input Contracts" above.
719
795
  - `--allow-degenerate-seed` opts out of the launch-time refusal when the seed scores the worst possible value on every validation row. Leave it off by default — a uniformly-worst seed is almost always a scorer contract mismatch.
720
796
  - Budget controls are mutually exclusive: choose at most one of `--budget auto|light|medium|heavy`, `--max-metric-calls <n>`, `--max-full-evals <n>`, `--max-iterations <n>`, or `--max-candidate-proposals <n>`. With none provided, `run-gepa` defaults to `--budget auto`, the balanced medium preset. `--max-candidate-proposals` is available only with `--engine official`.
721
797
  - `--minibatch-size <n>` defaults to 3.
722
798
  - `--num-threads auto|N` defaults to `auto`; auto caps row-evaluation concurrency from mini-batch size, validation-set size, 2x CPU count, memory estimate, file-descriptor limit, and a 64-thread default ceiling. Set `ORIZU_GEPA_AUTO_THREADS_MAX` or use `--num-threads <n>` only when the runner/provider capacity is known.
723
- - `--candidate-selection-strategy pareto|current_best|epsilon_greedy`; default is `pareto`.
799
+ - `--candidate-selection-strategy pareto|current_best|epsilon_greedy`; default is `pareto`. With `epsilon_greedy`, `--epsilon <n>` controls the random-selection probability (default `0.1`, clamped to `0`–`1`).
800
+ - `--objective <text>` replaces the reflection objective when the optimization goal differs from the default instruction to maximize evaluator score while preserving intended behavior.
724
801
  - `--reflection-model <provider/model>`, `--reflection-temperature <n>`, `--reflection-prompt-template <text|@file>`.
725
802
  - `--reflection-max-tokens <n>` is explicit provider config, not a global default. It maps to Anthropic `max_tokens` and OpenAI `max_output_tokens`; Anthropic native Messages reflection requires it, while OpenAI can omit it unless the user wants a cap.
726
803
  - `--reflection-retry-attempts` and `--reflection-http-timeout-seconds` tune transient reflection-provider retries. Exhausted retryable failures log `reflection_failed`, count against candidate-proposal budget, and continue with the next iteration.
@@ -731,6 +808,24 @@ Useful GEPA flags:
731
808
  - `--log-dir <dir>` controls the local log root; default is `logs`.
732
809
  - `--no-local-log` disables local trace files. Use this only when the environment must not persist raw rows or reflection context.
733
810
 
811
+ ### Skilled proposer
812
+
813
+ `--candidate-proposer skilled-proposer` selects the skilled proposer; omission keeps the default official proposer. The flag is official-engine-only and accepts that one value. A missing value is refused before launch as `--candidate-proposer requires a value that does not start with --`; another value is refused as `--candidate-proposer must be skilled-proposer`; selecting it with `--engine legacy` is refused with `--candidate-proposer is supported by the official GEPA engine only`.
814
+
815
+ `--proposal-max-calls <positive-integer>` and `--proposal-max-tokens <positive-integer>` default to unset and are valid only with the skilled proposer. A missing operand names that flag; using either ceiling without the selection produces `--proposal-max-calls and --proposal-max-tokens require --candidate-proposer skilled-proposer`; the legacy-engine error names the individual flag. Integer and positive-value validation happens in the connector after launch and names `ORIZU_PROPOSAL_MAX_CALLS` or `ORIZU_PROPOSAL_MAX_TOKENS`.
816
+
817
+ The call ceiling counts each transport-bearing proposer bridge call after its success or failure is recorded; provider retries inside one bridge call remain one counted call. The token ceiling uses accumulated provider-derived `total_tokens`, including recorded usage from unsuccessful completions. Both ceilings stop new proposal work at the next safe boundary, so an in-flight call can meet or pass a ceiling. They govern only the skilled proposer and remain independent of overall GEPA controls such as `--max-metric-calls`; set at least one proposal ceiling and one overall GEPA ceiling, then start the run only when both limits match the intended experiment.
818
+
819
+ #### Managed environment lifecycle and recovery
820
+
821
+ The selected `run-gepa` invocation prepares the environment automatically. It preflights CPython 3.10–3.14, installs the checked-in exact-version and SHA-256 lock with binary-only selection on first use, smoke-tests the staged environment, and publishes it atomically. Use `--python <command>` to select that interpreter; otherwise a nonempty `PYTHON` is used, falling back to `python3`. Supported wheel targets are macOS arm64 on macOS 15 or later and glibc 2.28 or later on Linux x86_64. Run the selected command; managed-environment readiness is proven when optimizer execution begins without an `ALI_1505_*` environment diagnostic.
822
+
823
+ Published environments live under the working-directory-relative `.orizu/cache/skilled-proposer/venvs/<publish-key>`. The key covers the manager schema, lock digest, and interpreter/platform identity, so changing working directory, OS, kernel, interpreter, or lock can build another environment. Matching environments are validated before reuse, and concurrent first uses wait for the same atomic publication. Reclaim old environments by deleting the entire regenerable `.orizu/cache/skilled-proposer` directory, then rerun from the intended working directory until preflight completes.
824
+
825
+ A nonempty `SSL_CERT_FILE` is passed unchanged to both pip and provider traffic. Empty or absent values trigger best-effort system-bundle discovery. `ALI_1505_SSL_CERT_FILE_UNRESOLVED` means pip fell back to its bundled trust store and provider traffic may need an explicit PEM bundle; `ALI_1505_SSL_CONTEXT_FAILED` means the explicit path or PEM contents must be corrected. Set the working bundle and retry until the selected run passes TLS setup.
826
+
827
+ An interrupted first build leaves no published partial environment, so retry the same selected command. For `ALI_1505_UNSUPPORTED_CPYTHON`, choose an admitted interpreter with `--python` or `PYTHON`; for lock, wheel, install, validation, or publication diagnostics, preserve the named code and retry until the optimization starts. The CLI owns the lock and installation path; lock refresh remains a reviewed release operation.
828
+
734
829
  Local `run-gepa` logs:
735
830
 
736
831
  - The optimizer prints `[orizu-gepa] local log: <path>` after the run starts.
@@ -759,6 +854,11 @@ orizu --local optimizations export <optimization-run-id> \
759
854
 
760
855
  Use export when the local log is unavailable, the run happened on another machine, or a coding agent needs a portable single JSON artifact. The export fetches all optimization events, derives seed vs best, Pareto frontier, score-over-time, candidates, iterations, minibatch rows, and validation rows, and rehydrates row inputs from the dataset version artifact when possible. Server events may not contain row snapshots or reflection prompts unless the run used `--log-row-snapshots`; reflection responses are included for bundled `run-gepa` runs.
761
856
 
857
+ Both diff-comment read routes (`/api/optimization-runs/<run-id>/diff-comments` and
858
+ `/api/prompts/<prompt-id>/diff-comments`) return `componentKey` for tuple
859
+ comments. Preserve it on writes and use it in grouping keys: equal line ranges
860
+ from different instruction-set components are distinct comments.
861
+
762
862
  The v1 export keeps the run row's `best_candidate_id` in
763
863
  `summary.bestCandidateId` when event derivation rejects that identifier as an
764
864
  unknown candidate. Candidate detail can therefore be absent for the preserved