orizu 0.5.21 → 0.5.23

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 (87) hide show
  1. package/README.md +46 -15
  2. package/dist/cleanup.d.ts +6 -0
  3. package/dist/cleanup.d.ts.map +1 -0
  4. package/dist/cleanup.js +27 -0
  5. package/dist/gepa-engine-dispatch.d.ts.map +1 -1
  6. package/dist/gepa-engine-dispatch.js +20 -1
  7. package/dist/gepa-python-paths.d.ts.map +1 -1
  8. package/dist/gepa-python-paths.js +25 -6
  9. package/dist/help.d.ts.map +1 -1
  10. package/dist/help.js +37 -15
  11. package/dist/hosted-commands.d.ts +13 -0
  12. package/dist/hosted-commands.d.ts.map +1 -0
  13. package/dist/hosted-commands.js +10 -0
  14. package/dist/hosted-gepa-cli.d.ts +11 -0
  15. package/dist/hosted-gepa-cli.d.ts.map +1 -0
  16. package/dist/hosted-gepa-cli.js +110 -0
  17. package/dist/hosted-gepa-numeric-values.d.ts +3 -0
  18. package/dist/hosted-gepa-numeric-values.d.ts.map +1 -0
  19. package/dist/hosted-gepa-numeric-values.js +49 -0
  20. package/dist/hosted-provider-settings.d.ts +3 -0
  21. package/dist/hosted-provider-settings.d.ts.map +1 -0
  22. package/dist/hosted-provider-settings.js +69 -0
  23. package/dist/hosted-session-cli.d.ts.map +1 -1
  24. package/dist/hosted-session-cli.js +6 -1
  25. package/dist/http.d.ts +1 -0
  26. package/dist/http.d.ts.map +1 -1
  27. package/dist/http.js +7 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +72 -74
  30. package/dist/instruction-set-loader.d.ts.map +1 -1
  31. package/dist/instruction-set-loader.js +57 -5
  32. package/dist/instruction-sets-cli.d.ts +5 -2
  33. package/dist/instruction-sets-cli.d.ts.map +1 -1
  34. package/dist/instruction-sets-cli.js +134 -24
  35. package/dist/optimizations-promote-cli.d.ts +3 -0
  36. package/dist/optimizations-promote-cli.d.ts.map +1 -1
  37. package/dist/optimizations-promote-cli.js +75 -7
  38. package/dist/runner-dir-verify.d.ts.map +1 -1
  39. package/dist/runner-dir-verify.js +4 -7
  40. package/dist/skilled-proposer-bake-report.d.ts +2 -0
  41. package/dist/skilled-proposer-bake-report.d.ts.map +1 -0
  42. package/dist/skilled-proposer-bake-report.js +1 -0
  43. package/dist/skilled-proposer-config.d.ts +2 -0
  44. package/dist/skilled-proposer-config.d.ts.map +1 -0
  45. package/dist/skilled-proposer-config.js +212 -0
  46. package/dist/skilled-proposer-launch.d.ts +26 -0
  47. package/dist/skilled-proposer-launch.d.ts.map +1 -1
  48. package/dist/skilled-proposer-launch.js +103 -6
  49. package/package.json +1 -1
  50. package/scripts/vendor-gepa-python.mjs +81 -12
  51. package/src/cleanup.ts +37 -0
  52. package/src/gepa-engine-dispatch.ts +21 -1
  53. package/src/gepa-python-paths.ts +24 -6
  54. package/src/help.ts +37 -15
  55. package/src/hosted-commands.ts +4 -0
  56. package/src/hosted-gepa-cli.ts +107 -0
  57. package/src/hosted-gepa-numeric-values.ts +60 -0
  58. package/src/hosted-provider-settings.ts +69 -0
  59. package/src/hosted-session-cli.ts +4 -1
  60. package/src/http.ts +8 -0
  61. package/src/index.ts +112 -89
  62. package/src/instruction-set-loader.ts +55 -5
  63. package/src/instruction-sets-cli.ts +173 -19
  64. package/src/optimizations-promote-cli.ts +85 -7
  65. package/src/runner-dir-verify.ts +11 -6
  66. package/src/skilled-proposer-bake-report.ts +1 -0
  67. package/src/skilled-proposer-config.ts +191 -0
  68. package/src/skilled-proposer-launch.ts +96 -7
  69. package/src/skilled-proposer-venv-manager.mjs +44 -27
  70. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +36 -3
  71. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +31 -3
  72. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +7 -2
  73. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +354 -5
  74. package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
  75. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +15 -0
  76. package/vendor/skills/orizu-cli/SKILL.md +94 -168
  77. package/vendor/skills/orizu-cli/references/assess-and-plan.md +107 -0
  78. package/vendor/skills/orizu-cli/references/building-judges.md +442 -180
  79. package/vendor/skills/orizu-cli/references/cli-reference.md +69 -39
  80. package/vendor/skills/orizu-cli/references/dataset-design.md +100 -0
  81. package/vendor/skills/orizu-cli/references/eval-strategy.md +64 -0
  82. package/vendor/skills/orizu-cli/references/instructions-after-prompts.md +115 -0
  83. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +150 -82
  84. package/vendor/skills/orizu-cli/references/optimization-reports.md +196 -104
  85. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +57 -37
  86. package/vendor/skills/orizu-cli/references/primer.md +43 -18
  87. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +199 -131
@@ -1,14 +1,78 @@
1
- import { spawnSync } from 'child_process';
1
+ import { spawn, spawnSync } from 'child_process';
2
+ import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'fs';
3
+ import { tmpdir } from 'os';
2
4
  import { join } from 'path';
3
5
  import { fileURLToPath } from 'url';
4
- import { bundledOfficialGepaPythonPath } from './gepa-python-paths.js';
6
+ import { bundledOfficialGepaPythonPath, getGepaPythonPathEntries } from './gepa-python-paths.js';
5
7
  import { sanitizeTerminalText } from './json-response.js';
6
- function ensureSelectedSkilledProposerVenv(python, vendoredGepaPath) {
8
+ import { SKILLED_PROPOSER_BAKE_REPORT_FIELDS } from './skilled-proposer-bake-report.js';
9
+ function materializeConfigPayload(engine, environment) {
10
+ const payload = environment.ORIZU_SKILLED_PROPOSER_CONFIG;
11
+ if (engine !== 'official' || environment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer' || payload === undefined)
12
+ return undefined;
13
+ const root = mkdtempSync(join(tmpdir(), 'orizu-skilled-proposer-payload-'));
14
+ try {
15
+ const path = join(root, 'config.json');
16
+ writeFileSync(path, payload, { encoding: 'utf8', mode: 0o600 });
17
+ environment.ORIZU_SKILLED_PROPOSER_CONFIG = `@${path}`;
18
+ }
19
+ catch (error) {
20
+ rmSync(root, { recursive: true, force: true });
21
+ throw error;
22
+ }
23
+ return () => rmSync(root, { recursive: true, force: true });
24
+ }
25
+ export async function spawnSkilledProposerChild(command, args, engine, environment) {
26
+ const childEnvironment = { ...environment };
27
+ const shouldMaterialize = engine === 'official'
28
+ && childEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
29
+ && childEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG !== undefined;
30
+ if (!shouldMaterialize)
31
+ return spawnSync(command, args, { stdio: 'inherit', env: childEnvironment });
32
+ let child;
33
+ let cleanup = () => { };
34
+ const signals = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'];
35
+ const handlers = signals.map(signal => ({
36
+ signal,
37
+ handle: () => {
38
+ try {
39
+ cleanup();
40
+ }
41
+ finally {
42
+ try {
43
+ child?.kill(signal);
44
+ }
45
+ finally {
46
+ for (const entry of handlers)
47
+ process.off(entry.signal, entry.handle);
48
+ process.kill(process.pid, signal);
49
+ }
50
+ }
51
+ },
52
+ }));
53
+ for (const entry of handlers)
54
+ process.once(entry.signal, entry.handle);
55
+ try {
56
+ cleanup = materializeConfigPayload(engine, childEnvironment) ?? cleanup;
57
+ await new Promise(resolve => setImmediate(resolve));
58
+ return await new Promise(resolve => {
59
+ let error;
60
+ child = spawn(command, args, { stdio: 'inherit', env: childEnvironment });
61
+ child.once('error', cause => { error = cause; });
62
+ child.once('close', status => resolve(error ? { status, error } : { status }));
63
+ });
64
+ }
65
+ finally {
66
+ for (const entry of handlers)
67
+ process.off(entry.signal, entry.handle);
68
+ cleanup();
69
+ }
70
+ }
71
+ function ensureSelectedSkilledProposerVenv(python, vendoredGepaPath, cacheRoot = existsSync('/opt/orizu/prebaked.json')
72
+ ? '/opt/orizu/cache/skilled-proposer'
73
+ : join(process.cwd(), '.orizu', 'cache', 'skilled-proposer')) {
7
74
  const manager = fileURLToPath(new URL('../scripts/ensure-skilled-proposer-venv.mjs', import.meta.url));
8
75
  const lock = fileURLToPath(new URL('../requirements/skilled-proposer.lock', import.meta.url));
9
- // This is a regenerable workspace cache. The manager's publish key further
10
- // separates interpreters, platforms, and lock revisions inside it.
11
- const cacheRoot = join(process.cwd(), '.orizu', 'cache', 'skilled-proposer');
12
76
  const result = spawnSync(process.execPath, [manager, '--python', python, '--cache-root', cacheRoot, '--lock', lock,
13
77
  '--vendored-gepa-path', vendoredGepaPath, '--json'], {
14
78
  env: process.env,
@@ -34,6 +98,38 @@ function ensureSelectedSkilledProposerVenv(python, vendoredGepaPath) {
34
98
  }
35
99
  return report;
36
100
  }
101
+ const packageRoot = join(fileURLToPath(new URL('.', import.meta.url)), '..');
102
+ const managerPath = join(packageRoot, 'scripts/ensure-skilled-proposer-venv.mjs');
103
+ const lockPath = join(packageRoot, 'requirements/skilled-proposer.lock');
104
+ const vendoredGepaPath = join(packageRoot, 'vendor/gepa-python/src');
105
+ const packagedPythonPathEntries = ['vendor/orizu-gepa/src', 'vendor/orizu-gepa-python/src', 'vendor/gepa-python/src'].map(path => join(packageRoot, path));
106
+ const bakedCacheRoot = '/opt/orizu/cache/skilled-proposer';
107
+ export function skilledProposerBakeCommand(io, verify = false) {
108
+ try {
109
+ const python = process.env.PYTHON || 'python3';
110
+ if (!verify) {
111
+ io.print(JSON.stringify(ensureSelectedSkilledProposerVenv(python, vendoredGepaPath, bakedCacheRoot)));
112
+ return 0;
113
+ }
114
+ if (!readdirSync(join(bakedCacheRoot, 'venvs'), { withFileTypes: true }).some(entry => entry.isDirectory() && existsSync(join(bakedCacheRoot, 'venvs', entry.name, '.orizu-skilled-proposer-venv.json'))))
115
+ throw new Error('ALI_1588_VENV_MARKER_MISSING');
116
+ const environment = { ...process.env, ORIZU_CANDIDATE_PROPOSER: 'skilled-proposer' };
117
+ const launch = prepareSkilledProposerLaunch(python, 'official', environment);
118
+ const report = launch.report;
119
+ if (report.publishedVenv !== false || report.executedPipArgv !== null)
120
+ throw new Error('ALI_1588_DEPENDENCY_SETUP_NOT_REUSED');
121
+ const pythonPathEntries = getGepaPythonPathEntries(undefined);
122
+ if (JSON.stringify(pythonPathEntries) !== JSON.stringify(packagedPythonPathEntries))
123
+ throw new Error('ALI_1588_PYTHON_ROOT_MISSING');
124
+ const completeReport = { ...report, pythonPathEntries, marker: 'ALI_1588_DEPENDENCY_SETUP_REUSED', python: launch.python, packageRoot, manager: managerPath, lock: lockPath, vendoredGepaPath, launcherVenv: launch.environment.ORIZU_SKILLED_PROPOSER_VENV ?? null, launcherSslCertFile: launch.environment.SSL_CERT_FILE ?? null };
125
+ io.print(JSON.stringify(Object.fromEntries(SKILLED_PROPOSER_BAKE_REPORT_FIELDS.map(key => [key, completeReport[key]]))));
126
+ return 0;
127
+ }
128
+ catch (error) {
129
+ io.printErr?.(`${error instanceof Error ? error.message : String(error)}\n`);
130
+ return 1;
131
+ }
132
+ }
37
133
  export function prepareSkilledProposerLaunch(python, engine, environment) {
38
134
  if (engine !== 'official' || environment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer') {
39
135
  return { python, environment };
@@ -56,5 +152,6 @@ export function prepareSkilledProposerLaunch(python, engine, environment) {
56
152
  ? join(report.venv, 'Scripts', 'python.exe')
57
153
  : join(report.venv, 'bin', 'python'),
58
154
  environment,
155
+ report,
59
156
  };
60
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,6 +19,10 @@ const archiveArgumentIndex = process.argv.indexOf('--archive')
19
19
  const suppliedArchive = archiveArgumentIndex === -1
20
20
  ? null
21
21
  : process.argv[archiveArgumentIndex + 1]
22
+ const destinationArgumentIndex = process.argv.indexOf('--destination')
23
+ const suppliedDestination = destinationArgumentIndex === -1
24
+ ? null
25
+ : process.argv[destinationArgumentIndex + 1]
22
26
  const sourceSrc = resolve(sourceRoot, 'src')
23
27
  const sourcePyproject = resolve(sourceRoot, 'pyproject.toml')
24
28
  const sourceManifest = resolve(sourceRoot, 'manifest.json')
@@ -31,6 +35,7 @@ const GEPA_VERSION = '0.1.4'
31
35
  const GEPA_RELEASE_COMMIT = '8b0ce6cd99a234f6b74daf37558a2ac0ce18f975'
32
36
  const GEPA_WHEEL_FILENAME = 'gepa-0.1.4-py3-none-any.whl'
33
37
  const GEPA_WHEEL_SHA256 = '12b971039599625c156d2231f6d72a29c31a22e9c237689459b5f1a3c353f532'
38
+ const GEPA_TREE_SHA256 = '8cb2ef66ca4212f416d1c4b23421f0de377b4997c1c50fd41ac16ca31015de99'
34
39
  const hasSourcePackage = existsSync(sourceSrc)
35
40
  && existsSync(sourcePyproject)
36
41
  && existsSync(sourceManifest)
@@ -75,19 +80,55 @@ function collectFileHashes(root, prefix = '') {
75
80
  return files
76
81
  }
77
82
 
78
- function assertOfficialVendoredManifest() {
83
+ function fileInventoryDigest(files) {
84
+ const entries = Object.entries(files).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
85
+ return createHash('sha256').update(JSON.stringify(entries)).digest('hex')
86
+ }
87
+
88
+ function assertOfficialVendoredManifest(root = officialVendorRoot) {
89
+ const manifestPath = resolve(root, 'manifest.json')
79
90
  let manifest
80
91
  try {
81
- manifest = JSON.parse(readFileSync(officialVendoredManifest, 'utf8'))
92
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
82
93
  } catch (error) {
83
94
  throw new Error(`Official GEPA vendored manifest is unreadable: ${error.message}`)
84
95
  }
85
96
 
97
+ if (manifest.schemaVersion !== 'gepa-vendor.v1') {
98
+ throw new Error('Official GEPA vendored manifest identity mismatch: schemaVersion')
99
+ }
100
+ if (!manifest.upstream || typeof manifest.upstream !== 'object' || Array.isArray(manifest.upstream)) {
101
+ throw new Error('Official GEPA vendored manifest identity mismatch: upstream')
102
+ }
103
+ for (const [field, expected] of [
104
+ ['package', 'gepa'],
105
+ ['version', GEPA_VERSION],
106
+ ['repositoryCommit', GEPA_RELEASE_COMMIT],
107
+ ]) {
108
+ if (manifest.upstream[field] !== expected) {
109
+ throw new Error(`Official GEPA vendored manifest identity mismatch: ${field}`)
110
+ }
111
+ }
112
+ if (!manifest.upstream.wheel || typeof manifest.upstream.wheel !== 'object' || Array.isArray(manifest.upstream.wheel)) {
113
+ throw new Error('Official GEPA vendored manifest identity mismatch: wheel')
114
+ }
115
+ for (const [field, expected] of [['filename', GEPA_WHEEL_FILENAME], ['sha256', GEPA_WHEEL_SHA256]]) {
116
+ if (manifest.upstream.wheel[field] !== expected) {
117
+ throw new Error(`Official GEPA vendored manifest identity mismatch: wheel.${field}`)
118
+ }
119
+ }
120
+
86
121
  if (!manifest.files || typeof manifest.files !== 'object' || Array.isArray(manifest.files)) {
87
122
  throw new Error('Official GEPA vendored manifest is missing file hashes')
88
123
  }
124
+ if (!Object.hasOwn(manifest.files, 'LICENSE')) {
125
+ throw new Error('Official GEPA vendored manifest is missing required LICENSE')
126
+ }
127
+ if (!Object.hasOwn(manifest.files, 'src/gepa/__init__.py')) {
128
+ throw new Error('Official GEPA vendored manifest is missing required GEPA package entry')
129
+ }
89
130
 
90
- const actualFiles = collectFileHashes(officialVendorRoot)
131
+ const actualFiles = collectFileHashes(root)
91
132
  delete actualFiles['manifest.json']
92
133
  const expectedEntries = Object.entries(manifest.files).sort(([left], [right]) => left.localeCompare(right))
93
134
  const actualEntries = Object.entries(actualFiles).sort(([left], [right]) => left.localeCompare(right))
@@ -102,9 +143,28 @@ function assertOfficialVendoredManifest() {
102
143
  throw new Error(`Official GEPA vendored manifest mismatch at ${expectedPath}`)
103
144
  }
104
145
  }
146
+ const treeSha256 = fileInventoryDigest(actualFiles)
147
+ if (treeSha256 !== GEPA_TREE_SHA256) {
148
+ throw new Error(`Official GEPA vendored tree digest mismatch: expected ${GEPA_TREE_SHA256}, got ${treeSha256}`)
149
+ }
150
+
151
+ return {
152
+ marker: 'ALI_1588_GEPA_VENDOR_VERIFIED',
153
+ schemaVersion: manifest.schemaVersion,
154
+ wheelSha256: manifest.upstream.wheel.sha256,
155
+ treeSha256,
156
+ fileCount: actualEntries.length,
157
+ }
105
158
  }
106
159
 
107
- function extractPinnedGepa(archivePath) {
160
+ function writeValidationReceipt(receipt) {
161
+ if (!receipt || receipt.marker !== 'ALI_1588_GEPA_VENDOR_VERIFIED') {
162
+ throw new Error('Official GEPA vendored manifest validation produced no receipt')
163
+ }
164
+ process.stdout.write(`${JSON.stringify(receipt)}\n`)
165
+ }
166
+
167
+ function extractPinnedGepa(archivePath, destinationRoot) {
108
168
  const extractor = String.raw`
109
169
  from pathlib import Path, PurePosixPath
110
170
  import shutil
@@ -132,7 +192,7 @@ with zipfile.ZipFile(archive_path) as archive:
132
192
  with archive.open(member) as source, destination.open('wb') as output:
133
193
  shutil.copyfileobj(source, output)
134
194
  `
135
- const result = spawnSync('python3', ['-c', extractor, archivePath, officialVendorRoot], {
195
+ const result = spawnSync('python3', ['-c', extractor, archivePath, destinationRoot], {
136
196
  encoding: 'utf8',
137
197
  })
138
198
  if (result.status !== 0) {
@@ -141,14 +201,14 @@ with zipfile.ZipFile(archive_path) as archive:
141
201
  }
142
202
  }
143
203
 
144
- function vendorOfficialGepa(archivePath) {
204
+ function vendorOfficialGepa(archivePath, destinationRoot = officialVendorRoot) {
145
205
  assertPinnedGepaArchive(archivePath)
146
- rmSync(officialVendorRoot, { recursive: true, force: true })
147
- mkdirSync(officialVendorRoot, { recursive: true })
148
- extractPinnedGepa(archivePath)
206
+ rmSync(destinationRoot, { recursive: true, force: true })
207
+ mkdirSync(destinationRoot, { recursive: true })
208
+ extractPinnedGepa(archivePath, destinationRoot)
149
209
 
150
- const files = collectFileHashes(officialVendorRoot)
151
- writeFileSync(officialVendoredManifest, `${JSON.stringify({
210
+ const files = collectFileHashes(destinationRoot)
211
+ writeFileSync(resolve(destinationRoot, 'manifest.json'), `${JSON.stringify({
152
212
  schemaVersion: 'gepa-vendor.v1',
153
213
  upstream: {
154
214
  package: 'gepa',
@@ -161,11 +221,20 @@ function vendorOfficialGepa(archivePath) {
161
221
  },
162
222
  files,
163
223
  }, null, 2)}\n`)
224
+ return assertOfficialVendoredManifest(destinationRoot)
164
225
  }
165
226
 
166
227
  if (archiveArgumentIndex !== -1 && (!suppliedArchive || suppliedArchive.startsWith('--'))) {
167
228
  throw new Error('Expected a source archive path after --archive')
168
229
  }
230
+ if (destinationArgumentIndex !== -1 && (!suppliedDestination || suppliedDestination.startsWith('--'))) {
231
+ throw new Error('Expected a destination directory after --destination')
232
+ }
233
+
234
+ if (suppliedArchive && suppliedDestination) {
235
+ writeValidationReceipt(vendorOfficialGepa(suppliedArchive, suppliedDestination))
236
+ process.exit(0)
237
+ }
169
238
 
170
239
  if (shouldRefresh) {
171
240
  if (!suppliedArchive) {
@@ -190,7 +259,7 @@ if (shouldClean) {
190
259
 
191
260
  if (!hasSourcePackage || !hasConnectorSourcePackage) {
192
261
  if (hasVendoredPackage && hasVendoredConnectorPackage && hasOfficialVendoredPackage) {
193
- assertOfficialVendoredManifest()
262
+ writeValidationReceipt(assertOfficialVendoredManifest())
194
263
  process.exit(0)
195
264
  }
196
265
  if (hasVendoredPackage || hasVendoredConnectorPackage || hasOfficialVendoredPackage) {
package/src/cleanup.ts ADDED
@@ -0,0 +1,37 @@
1
+ export type Cleanup = () => void
2
+
3
+ function collectCleanupFailures(cleanups: readonly Cleanup[]): unknown[] {
4
+ const failures: unknown[] = []
5
+ for (const cleanup of cleanups) {
6
+ try {
7
+ cleanup()
8
+ } catch (error) {
9
+ failures.push(error)
10
+ }
11
+ }
12
+ return failures
13
+ }
14
+
15
+ /** Attempt every disposer even when an earlier cleanup fails. */
16
+ export function cleanupAll(
17
+ cleanups: readonly Cleanup[],
18
+ message: string
19
+ ): void {
20
+ const failures = collectCleanupFailures(cleanups)
21
+ if (failures.length > 0) {
22
+ throw new AggregateError(failures, message)
23
+ }
24
+ }
25
+
26
+ /** Preserve the operation failure together with every cleanup failure. */
27
+ export function throwWithCleanup(
28
+ operationError: unknown,
29
+ cleanups: readonly Cleanup[],
30
+ message: string
31
+ ): never {
32
+ const cleanupFailures = collectCleanupFailures(cleanups)
33
+ if (cleanupFailures.length > 0) {
34
+ throw new AggregateError([operationError, ...cleanupFailures], message)
35
+ }
36
+ throw operationError
37
+ }
@@ -1,5 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'fs'
2
2
 
3
+ import { normalizedSkilledProposerConfig } from './skilled-proposer-config.js'
4
+
3
5
  export type GepaEngine = 'official' | 'legacy'
4
6
 
5
7
  export interface GepaEngineDispatch {
@@ -179,6 +181,7 @@ const CONTROLLED_ENVIRONMENT_NAMES = new Set([
179
181
  // translation prevents an inherited environment from selecting a proposer
180
182
  // on an otherwise byte-for-byte normal official-GEPA launch.
181
183
  'ORIZU_CANDIDATE_PROPOSER',
184
+ 'ORIZU_SKILLED_PROPOSER_CONFIG',
182
185
  'ORIZU_COMPONENT_SELECTOR',
183
186
  ])
184
187
 
@@ -208,6 +211,7 @@ function translateOfficialOptions(args: string[], project: string, environment:
208
211
  connectorEnvironment.ORIZU_PROJECT = project
209
212
 
210
213
  let metadata: string | undefined
214
+ let candidateProposerConfig: string | undefined
211
215
  let hasInstructionSet = false
212
216
  let hasCandidateVersion = false
213
217
  const budgetFlags: string[] = []
@@ -240,6 +244,12 @@ function translateOfficialOptions(args: string[], project: string, environment:
240
244
  index = parsed.nextIndex
241
245
  continue
242
246
  }
247
+ if (flag === '--candidate-proposer-config') {
248
+ const parsed = optionValueOrThrow(args, index, flag)
249
+ candidateProposerConfig = parsed.value
250
+ index = parsed.nextIndex
251
+ continue
252
+ }
243
253
  const option = VALUE_OPTIONS_BY_FLAG.get(flag)
244
254
  if (option) {
245
255
  const parsed = optionValueOrThrow(args, index, flag, option.allowEmpty)
@@ -297,6 +307,16 @@ function translateOfficialOptions(args: string[], project: string, environment:
297
307
  && connectorEnvironment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer') {
298
308
  throw new Error('--proposal-max-calls and --proposal-max-tokens require --candidate-proposer skilled-proposer')
299
309
  }
310
+ if (candidateProposerConfig !== undefined) {
311
+ if (connectorEnvironment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer') {
312
+ throw new Error('--candidate-proposer-config requires --candidate-proposer skilled-proposer')
313
+ }
314
+ connectorEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG = normalizedSkilledProposerConfig(candidateProposerConfig)
315
+ }
316
+ if (connectorEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
317
+ && connectorEnvironment.ORIZU_REFLECTION_PROMPT_TEMPLATE !== undefined) {
318
+ throw new Error('--reflection-prompt-template is incompatible with --candidate-proposer skilled-proposer')
319
+ }
300
320
 
301
321
  const reflectionModel = connectorEnvironment.ORIZU_REFLECTION_MODEL ?? 'anthropic/claude-opus-4-7'
302
322
  if (!reflectionModel.startsWith('openai/') && connectorEnvironment.ORIZU_REFLECTION_MAX_TOKENS === undefined) {
@@ -321,7 +341,7 @@ function validatedMetadata(raw: string | undefined): string {
321
341
  export function dispatchGepaEngine(args: string[], project: string, environment: NodeJS.ProcessEnv): GepaEngineDispatch {
322
342
  const selected = removeEngine(args)
323
343
  if (selected.engine === 'legacy') {
324
- for (const flag of ['--instruction-set', '--model-config', '--component-selector', '--max-candidate-proposals', '--candidate-proposer', '--proposal-max-calls', '--proposal-max-tokens']) {
344
+ for (const flag of ['--instruction-set', '--model-config', '--component-selector', '--max-candidate-proposals', '--candidate-proposer', '--candidate-proposer-config', '--proposal-max-calls', '--proposal-max-tokens']) {
325
345
  if (selected.args.some(argument => argument === flag || argument.startsWith(`${flag}=`))) {
326
346
  throw new Error(`${flag} is supported by the official GEPA engine only`)
327
347
  }
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from 'fs'
2
+ import { execFileSync } from 'child_process'
2
3
  import { fileURLToPath } from 'url'
3
4
 
4
5
  import { isOrizuSourceCheckout } from '../scripts/orizu-source-checkout.mjs'
@@ -34,14 +35,31 @@ export function bundledOrizuGepaConnectorPythonPath(): string | null {
34
35
  }
35
36
 
36
37
  export function bundledOfficialGepaPythonPath(): string | null {
37
- const sourceArchive = fileURLToPath(new URL('../gepa-python-source.zip', import.meta.url))
38
38
  const vendor = fileURLToPath(new URL('../vendor/gepa-python/src', import.meta.url))
39
- // The checked-in pinned wheel is directly importable from PYTHONPATH in a
40
- // source checkout. Published packages intentionally omit that archive and
41
- // must resolve only the verified tree materialized by prepack.
42
- const candidates = prefersLiveSource() ? [sourceArchive, vendor] : [vendor]
39
+ if (prefersLiveSource()) {
40
+ const requiredVendorFiles = [
41
+ fileURLToPath(new URL('../vendor/gepa-python/manifest.json', import.meta.url)),
42
+ fileURLToPath(new URL('../vendor/gepa-python/src/gepa/__init__.py', import.meta.url)),
43
+ fileURLToPath(new URL('../vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py', import.meta.url)),
44
+ ]
45
+ if (!requiredVendorFiles.every(existsSync)) {
46
+ const vendoringScript = fileURLToPath(new URL('../scripts/vendor-gepa-python.mjs', import.meta.url))
47
+ try {
48
+ execFileSync(process.execPath, [vendoringScript], { stdio: 'pipe' })
49
+ } catch (error) {
50
+ const details = error instanceof Error ? error.message : String(error)
51
+ throw new Error(`Unable to materialize bundled official GEPA: ${details}`)
52
+ }
53
+ if (!requiredVendorFiles.every(existsSync)) {
54
+ throw new Error('Unable to materialize bundled official GEPA: extracted tree is incomplete')
55
+ }
56
+ }
57
+ }
43
58
 
44
- return resolveGepaPythonPath(candidates)
59
+ // zipimport cannot resolve GEPA's namespace-package adapters on every
60
+ // supported Python minor. Both source and published CLIs execute only the
61
+ // verified tree materialized from the pinned wheel.
62
+ return resolveGepaPythonPath([vendor])
45
63
  }
46
64
 
47
65
  export function getGepaPythonPathEntries(existingPythonPath: string | undefined): string[] {
package/src/help.ts CHANGED
@@ -89,6 +89,40 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
89
89
  },
90
90
  { path: ['instructions', 'archive'], aliases: [['instruction-sets', 'archive']], usage: 'orizu instructions archive <slug-or-exact-name> --project <team/project> [--json]', summary: 'Archive an instruction set without changing resolution or sync.', group: 'Instruction sets', options: [{ name: '--project <team/project>', help: 'Project slug.', required: true }, { name: '--json', help: 'Print JSON.' }], examples: ['orizu instructions archive planner --project core/evals'] },
91
91
  { path: ['instructions', 'restore'], aliases: [['instruction-sets', 'restore']], usage: 'orizu instructions restore <slug-or-exact-name> --project <team/project> [--json]', summary: 'Restore an archived instruction set.', group: 'Instruction sets', options: [{ name: '--project <team/project>', help: 'Project slug.', required: true }, { name: '--json', help: 'Print JSON.' }], examples: ['orizu instructions restore planner --project core/evals'] },
92
+ {
93
+ path: ['instructions', 'scorers', 'set-headline'],
94
+ aliases: [['instruction-sets', 'scorers', 'set-headline']],
95
+ usage: 'orizu instructions scorers set-headline <set> --key <component-key> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
96
+ summary: 'Bind the headline scorer for one instruction-set component.',
97
+ group: 'Instruction sets',
98
+ options: [
99
+ { name: '--key <component-key>', help: 'Instruction-set component key.', required: true },
100
+ { name: '--scorer-version <id>', help: 'Scorer version to bind.', required: true },
101
+ { name: '--dataset-version <id>', help: 'Default dataset version.' },
102
+ { name: '--split-set <id>', help: 'Default dataset split set.' },
103
+ { name: '--split <name>', help: 'Default split name.' },
104
+ { name: '--project <team/project>', help: 'Project slug.' },
105
+ { name: '--json', help: 'Print JSON.' },
106
+ ],
107
+ examples: ['orizu instructions scorers set-headline planner --key system --scorer-version <id> --project core/evals'],
108
+ },
109
+ {
110
+ path: ['instructions', 'scorers', 'add'],
111
+ aliases: [['instruction-sets', 'scorers', 'add']],
112
+ usage: 'orizu instructions scorers add <set> --key <component-key> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
113
+ summary: 'Track an additional scorer for one instruction-set component.',
114
+ group: 'Instruction sets',
115
+ options: [
116
+ { name: '--key <component-key>', help: 'Instruction-set component key.', required: true },
117
+ { name: '--scorer-version <id>', help: 'Scorer version to bind.', required: true },
118
+ { name: '--dataset-version <id>', help: 'Default dataset version.' },
119
+ { name: '--split-set <id>', help: 'Default dataset split set.' },
120
+ { name: '--split <name>', help: 'Default split name.' },
121
+ { name: '--project <team/project>', help: 'Project slug.' },
122
+ { name: '--json', help: 'Print JSON.' },
123
+ ],
124
+ examples: ['orizu instructions scorers add planner --key system --scorer-version <id> --project core/evals'],
125
+ },
92
126
  {
93
127
  path: ['login'],
94
128
  usage: 'orizu login [--no-prompt-if-logged-in]',
@@ -119,7 +153,7 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
119
153
  },
120
154
  {
121
155
  path: ['log'],
122
- usage: 'orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json',
156
+ usage: 'orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json [--event-id <id>] [--event-layer <layer>] [--optimizer-family <family>] [--iteration <n>] [--candidate-id <id>] [--parent-candidate-id <id>] [--child-candidate-id <id>]',
123
157
  summary: 'Submit an optimization event payload.',
124
158
  group: 'Optimizations',
125
159
  },
@@ -413,18 +447,6 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
413
447
  summary: 'Move or set a non-production prompt label; use instructions profiles promote for production.',
414
448
  group: 'Prompts and judges',
415
449
  },
416
- {
417
- path: ['prompts', 'scorers', 'set-headline'],
418
- usage: 'orizu prompts scorers set-headline <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
419
- summary: 'Bind the headline scorer for a prompt.',
420
- group: 'Scorers and runners',
421
- },
422
- {
423
- path: ['prompts', 'scorers', 'add'],
424
- usage: 'orizu prompts scorers add <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
425
- summary: 'Track an additional scorer for a prompt.',
426
- group: 'Scorers and runners',
427
- },
428
450
  {
429
451
  path: ['judges', 'list'],
430
452
  usage: 'orizu judges list --project <team/project> [--status active|archived|all]',
@@ -578,8 +600,8 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
578
600
  },
579
601
  {
580
602
  path: ['optimizations', 'run-gepa'],
581
- usage: 'orizu optimizations run-gepa --project <team/project> --optimizer-version-id <id> (--candidate-version-id <id> | --instruction-set <name> --model-config <identity> [--component-selector round-robin|all]) --runner-version-id <id> --candidate-runner-dir <dir> --scorer-version-id <id> --scorer-runner-version-id <id> --scorer-runner-dir <dir> [--engine official|legacy] [--scorer-input-contract gepa|flat_row] [--scorer-candidate-field <row-field>] [--allow-degenerate-seed] --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--budget auto|light|medium|heavy | --max-metric-calls <n> | --max-full-evals <n> | --max-iterations <n> | --max-candidate-proposals <n>] [--num-threads auto|N] [--reflection-retry-attempts N] [--reflection-http-timeout-seconds N] [--log-dir logs]',
582
- summary: 'Run official GEPA locally and stream events; pass --engine legacy only for the frozen safety hatch. --instruction-set requires --model-config and cannot be combined with --candidate-version-id; round-robin updates one component-map entry per round and all updates every component. --max-candidate-proposals is official-engine-only. Validates the scorer contract on the seed before iterating; judge runners built for flat-row score runs need --scorer-input-contract flat_row (ALI-1158).',
603
+ usage: 'orizu optimizations run-gepa [--hosted [--launch-intent-id <uuid>]] --project <team/project> --optimizer-version-id <id> (--candidate-version-id <id> | --instruction-set <name> --model-config <identity> [--component-selector round-robin|all]) --runner-version-id <id> [--candidate-runner-dir <dir>] --scorer-version-id <id> --scorer-runner-version-id <id> [--scorer-runner-dir <dir>] [--engine official|legacy] [--candidate-proposer skilled-proposer [--candidate-proposer-config <@file|path>] [--proposal-max-calls <n>] [--proposal-max-tokens <n>]] [--scorer-input-contract gepa|flat_row] [--scorer-candidate-field <row-field>] [--allow-degenerate-seed] [--seed <n>] --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--minibatch-size <n>] [--candidate-selection-strategy pareto|current_best|epsilon_greedy] [--epsilon <n>] [--objective <text>] [--budget auto|light|medium|heavy | --max-metric-calls <n> | --max-full-evals <n> | --max-iterations <n> | --max-candidate-proposals <n>] [--num-threads auto|N] [--reflection-model <provider/model>] [--reflection-temperature <n>] [--reflection-prompt-template <text|@file>] [--reflection-max-tokens N] [--reflection-provider-settings <json|@file>] [--reflection-retry-attempts N] [--reflection-http-timeout-seconds N] [--skip-perfect-parent-reflection|--no-skip-perfect-parent-reflection] [--disable-evaluation-cache] [--auto-promote] [--promotion-label <label>] [--log-row-snapshots] [--metadata <json>] [--python <command>] [--log-dir logs] [--no-local-log] [--json]',
604
+ summary: 'Run official GEPA locally and stream events, or use --hosted for a queued Orizu launch. Hosted mode does not require --candidate-runner-dir or --scorer-runner-dir, accepts only a named --budget preset, and supports response-loss recovery with --launch-intent-id. Local runner version ids materialize registered bytes, while optional runner dirs are additionally byte-verified before execution. Pass --engine legacy only for the frozen safety hatch. Skilled-proposer is official-only, its config requires that selection, and the selection cannot be combined with --reflection-prompt-template. --instruction-set requires --model-config and cannot be combined with --candidate-version-id; round-robin updates one component-map entry per round and all updates every component. --max-candidate-proposals is official-engine-only. Validates the scorer contract on the seed before iterating; judge runners built for flat-row score runs need --scorer-input-contract flat_row (ALI-1158).',
583
605
  group: 'Optimizations',
584
606
  },
585
607
  {
@@ -0,0 +1,4 @@
1
+ import { runHostedGepaOptimization } from './hosted-gepa-cli.js'
2
+ import { hostedCommand } from './hosted-session-cli.js'
3
+ interface HostedCommandContext { environment: NodeJS.ProcessEnv; getArg: (name: string) => string | null; json: boolean; printJson: (value: Record<string, unknown>) => void; printLine: (value?: string) => void; printErr: (value: string) => void; resolveProjectSlug: (projectArg: string | null) => Promise<string>; setExitCode: (code: number) => void }
4
+ export async function dispatchHostedCommands(args: string[], context: HostedCommandContext): Promise<boolean> { const [command, subcommand] = args; if (command === 'optimizations' && subcommand === 'run-gepa' && args.includes('--hosted')) { const project = context.getArg('--project') || await context.resolveProjectSlug(null); await runHostedGepaOptimization({ args: args.slice(2), project, environment: context.environment, json: context.json, printJson: context.printJson, printLine: context.printLine }); return true } if (command === 'internal' || (command === 'session' && subcommand === 'start' && args.includes('--hosted'))) { context.setExitCode(await hostedCommand(args, { json: context.json, print: context.printLine, printErr: context.printErr })); return true } return false }
@@ -0,0 +1,107 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
3
+ import { dispatchGepaEngine } from './gepa-engine-dispatch.js'
4
+ import { parseHostedGepaNumericValue } from './hosted-gepa-numeric-values.js'
5
+ import { authedFetch } from './http.js'
6
+ import { hostedProviderSettingsError } from './hosted-provider-settings.js'
7
+ import { parseJsonResponse } from './json-response.js'
8
+
9
+ interface HostedGepaOptions {
10
+ args: string[]
11
+ project: string
12
+ environment: NodeJS.ProcessEnv
13
+ json: boolean
14
+ printJson: (value: Record<string, unknown>) => void
15
+ printLine: (value: string) => void
16
+ }
17
+
18
+ function argumentValue(args: string[], name: string): string | null {
19
+ const index = args.indexOf(name)
20
+ if (index >= 0 && index + 1 < args.length) return args[index + 1]
21
+ return args.find(argument => argument.startsWith(`${name}=`))?.slice(name.length + 1) ?? null
22
+ }
23
+
24
+ function dispatchArgs(args: string[]): string[] {
25
+ const filtered: string[] = []
26
+ for (let index = 0; index < args.length; index += 1) {
27
+ if (args[index] === '--hosted') continue
28
+ if (args[index] === '--launch-intent-id') { index += 1; continue }
29
+ if (args[index].startsWith('--launch-intent-id=')) continue
30
+ filtered.push(args[index])
31
+ }
32
+ return filtered
33
+ }
34
+
35
+ export async function runHostedGepaOptimization(options: HostedGepaOptions): Promise<void> {
36
+ const hasLaunchIntent = options.args.some(argument =>
37
+ argument === '--launch-intent-id' || argument.startsWith('--launch-intent-id='))
38
+ const explicitLaunchIntent = argumentValue(options.args, '--launch-intent-id')
39
+ if (hasLaunchIntent && (!explicitLaunchIntent || explicitLaunchIntent.startsWith('-')
40
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(explicitLaunchIntent))) {
41
+ throw new Error('--launch-intent-id must be a UUIDv4 value when provided.')
42
+ }
43
+ const args = dispatchArgs(options.args)
44
+ if (['--max-iterations', '--max-full-evals', '--max-metric-calls', '--max-candidate-proposals']
45
+ .some(flag => args.some(argument => argument === flag || argument.startsWith(`${flag}=`)))) {
46
+ throw new Error('Hosted optimization requires a named --budget preset; numeric budget controls are not accepted.')
47
+ }
48
+ const dispatch = dispatchGepaEngine(args, options.project, options.environment)
49
+ if (dispatch.engine !== 'official') throw new Error('Hosted optimization requires the official GEPA engine.')
50
+ const env = dispatch.environment
51
+ const required = [
52
+ ['ORIZU_OPTIMIZER_VERSION_ID', '--optimizer-version-id'], ['ORIZU_RUNNER_VERSION_ID', '--runner-version-id'],
53
+ ['ORIZU_SCORER_VERSION_ID', '--scorer-version-id'], ['ORIZU_SCORER_RUNNER_VERSION_ID', '--scorer-runner-version-id'],
54
+ ['ORIZU_DATASET_VERSION_ID', '--dataset-version-id'], ['ORIZU_SPLIT_SET_ID', '--split-set-id'],
55
+ ] as const
56
+ for (const [name, flag] of required) if (!env[name]) throw new Error(`${flag} is required for hosted optimization.`)
57
+ const providerSettings = JSON.parse(env.ORIZU_REFLECTION_PROVIDER_SETTINGS ?? '{}') as Record<string, unknown>
58
+ const providerSettingsError = hostedProviderSettingsError(providerSettings,
59
+ env.ORIZU_REFLECTION_MODEL ?? 'anthropic/', env.ORIZU_REFLECTION_TEMPERATURE)
60
+ if (providerSettingsError) throw new Error(providerSettingsError)
61
+ const optional: Record<string, unknown> = {}
62
+ const fields: Array<[string, string, 'number' | 'json' | 'boolean' | 'string']> = [
63
+ ['candidateProposer', 'ORIZU_CANDIDATE_PROPOSER', 'string'], ['candidateProposerConfig', 'ORIZU_SKILLED_PROPOSER_CONFIG', 'json'],
64
+ ['proposalMaxCalls', 'ORIZU_PROPOSAL_MAX_CALLS', 'number'], ['proposalMaxTokens', 'ORIZU_PROPOSAL_MAX_TOKENS', 'number'],
65
+ ['minibatchSize', 'ORIZU_MINIBATCH_SIZE', 'number'], ['numThreads', 'ORIZU_NUM_THREADS', 'number'],
66
+ ['candidateSelectionStrategy', 'ORIZU_CANDIDATE_SELECTION_STRATEGY', 'string'], ['epsilon', 'ORIZU_EPSILON', 'number'],
67
+ ['reflectionModel', 'ORIZU_REFLECTION_MODEL', 'string'], ['reflectionTemperature', 'ORIZU_REFLECTION_TEMPERATURE', 'number'],
68
+ ['reflectionMaxTokens', 'ORIZU_REFLECTION_MAX_TOKENS', 'number'], ['reflectionRetryAttempts', 'ORIZU_REFLECTION_RETRY_ATTEMPTS', 'number'],
69
+ ['reflectionHttpTimeoutSeconds', 'ORIZU_REFLECTION_HTTP_TIMEOUT_SECONDS', 'number'],
70
+ ['reflectionPromptTemplate', 'ORIZU_REFLECTION_PROMPT_TEMPLATE', 'string'],
71
+ ['reflectionProviderSettings', 'ORIZU_REFLECTION_PROVIDER_SETTINGS', 'json'], ['objective', 'ORIZU_OBJECTIVE', 'string'],
72
+ ['seed', 'ORIZU_SEED', 'number'], ['disableEvaluationCache', 'ORIZU_DISABLE_EVALUATION_CACHE', 'boolean'],
73
+ ['allowDegenerateSeed', 'ORIZU_ALLOW_DEGENERATE_SEED', 'boolean'],
74
+ ['skipPerfectParentReflection', 'ORIZU_SKIP_PERFECT_PARENT_REFLECTION', 'boolean'],
75
+ ]
76
+ for (const [field, name, kind] of fields) {
77
+ if (env[name] === undefined) continue
78
+ optional[field] = kind === 'number' ? parseHostedGepaNumericValue(field, env[name]!)
79
+ : kind === 'json' ? JSON.parse(env[name]!) : kind === 'boolean' ? env[name] === '1' : env[name]
80
+ }
81
+ const launchIntentId = explicitLaunchIntent || randomUUID()
82
+ const body = {
83
+ hosted: true, launchIntentId, optimizerVersionId: env.ORIZU_OPTIMIZER_VERSION_ID,
84
+ promptVersionIds: env.ORIZU_PROMPT_VERSION_ID ? [env.ORIZU_PROMPT_VERSION_ID] : [],
85
+ ...(env.ORIZU_INSTRUCTION_SET_NAME ? { instructionSetName: env.ORIZU_INSTRUCTION_SET_NAME,
86
+ modelConfigIdentity: env.ORIZU_MODEL_CONFIG_IDENTITY, componentSelector: env.ORIZU_COMPONENT_SELECTOR } : {}),
87
+ runnerVersionId: env.ORIZU_RUNNER_VERSION_ID,
88
+ scorers: [{ scorerVersionId: env.ORIZU_SCORER_VERSION_ID,
89
+ scorerRunnerVersionId: env.ORIZU_SCORER_RUNNER_VERSION_ID, role: 'selection', scorerConfig: {} }],
90
+ datasetVersionId: env.ORIZU_DATASET_VERSION_ID, splitSetId: env.ORIZU_SPLIT_SET_ID,
91
+ trainSplitName: env.ORIZU_TRAIN_SPLIT ?? 'train', validationSplitName: env.ORIZU_VALIDATION_SPLIT ?? 'validation',
92
+ engine: 'official', scorerInputContract: env.ORIZU_SCORER_INPUT_CONTRACT ?? 'gepa',
93
+ scorerCandidateField: env.ORIZU_SCORER_CANDIDATE_FIELD ?? null, budget: env.ORIZU_BUDGET ?? 'auto', ...optional,
94
+ }
95
+ const response = await authedFetch(`/api/cli/optimization-runs?project=${encodeURIComponent(options.project)}`, {
96
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
97
+ })
98
+ const data = await parseJsonResponse<Record<string, unknown>>(response, 'Hosted optimization launch')
99
+ if (!response.ok) {
100
+ if (options.json) { options.printJson(data); process.exitCode = 1; return }
101
+ throw new Error(JSON.stringify(data))
102
+ }
103
+ if (options.json) { options.printJson(data); return }
104
+ options.printLine('Queued hosted optimization.')
105
+ options.printLine(`Launch intent: ${launchIntentId}`)
106
+ options.printLine(String(data.monitorUrl))
107
+ }