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.
- package/README.md +46 -15
- package/dist/cleanup.d.ts +6 -0
- package/dist/cleanup.d.ts.map +1 -0
- package/dist/cleanup.js +27 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -1
- package/dist/gepa-engine-dispatch.js +20 -1
- package/dist/gepa-python-paths.d.ts.map +1 -1
- package/dist/gepa-python-paths.js +25 -6
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +37 -15
- package/dist/hosted-commands.d.ts +13 -0
- package/dist/hosted-commands.d.ts.map +1 -0
- package/dist/hosted-commands.js +10 -0
- package/dist/hosted-gepa-cli.d.ts +11 -0
- package/dist/hosted-gepa-cli.d.ts.map +1 -0
- package/dist/hosted-gepa-cli.js +110 -0
- package/dist/hosted-gepa-numeric-values.d.ts +3 -0
- package/dist/hosted-gepa-numeric-values.d.ts.map +1 -0
- package/dist/hosted-gepa-numeric-values.js +49 -0
- package/dist/hosted-provider-settings.d.ts +3 -0
- package/dist/hosted-provider-settings.d.ts.map +1 -0
- package/dist/hosted-provider-settings.js +69 -0
- package/dist/hosted-session-cli.d.ts.map +1 -1
- package/dist/hosted-session-cli.js +6 -1
- package/dist/http.d.ts +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -74
- package/dist/instruction-set-loader.d.ts.map +1 -1
- package/dist/instruction-set-loader.js +57 -5
- package/dist/instruction-sets-cli.d.ts +5 -2
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +134 -24
- package/dist/optimizations-promote-cli.d.ts +3 -0
- package/dist/optimizations-promote-cli.d.ts.map +1 -1
- package/dist/optimizations-promote-cli.js +75 -7
- package/dist/runner-dir-verify.d.ts.map +1 -1
- package/dist/runner-dir-verify.js +4 -7
- package/dist/skilled-proposer-bake-report.d.ts +2 -0
- package/dist/skilled-proposer-bake-report.d.ts.map +1 -0
- package/dist/skilled-proposer-bake-report.js +1 -0
- package/dist/skilled-proposer-config.d.ts +2 -0
- package/dist/skilled-proposer-config.d.ts.map +1 -0
- package/dist/skilled-proposer-config.js +212 -0
- package/dist/skilled-proposer-launch.d.ts +26 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -1
- package/dist/skilled-proposer-launch.js +103 -6
- package/package.json +1 -1
- package/scripts/vendor-gepa-python.mjs +81 -12
- package/src/cleanup.ts +37 -0
- package/src/gepa-engine-dispatch.ts +21 -1
- package/src/gepa-python-paths.ts +24 -6
- package/src/help.ts +37 -15
- package/src/hosted-commands.ts +4 -0
- package/src/hosted-gepa-cli.ts +107 -0
- package/src/hosted-gepa-numeric-values.ts +60 -0
- package/src/hosted-provider-settings.ts +69 -0
- package/src/hosted-session-cli.ts +4 -1
- package/src/http.ts +8 -0
- package/src/index.ts +112 -89
- package/src/instruction-set-loader.ts +55 -5
- package/src/instruction-sets-cli.ts +173 -19
- package/src/optimizations-promote-cli.ts +85 -7
- package/src/runner-dir-verify.ts +11 -6
- package/src/skilled-proposer-bake-report.ts +1 -0
- package/src/skilled-proposer-config.ts +191 -0
- package/src/skilled-proposer-launch.ts +96 -7
- package/src/skilled-proposer-venv-manager.mjs +44 -27
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +36 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +31 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +7 -2
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +354 -5
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +15 -0
- package/vendor/skills/orizu-cli/SKILL.md +94 -168
- package/vendor/skills/orizu-cli/references/assess-and-plan.md +107 -0
- package/vendor/skills/orizu-cli/references/building-judges.md +442 -180
- package/vendor/skills/orizu-cli/references/cli-reference.md +69 -39
- package/vendor/skills/orizu-cli/references/dataset-design.md +100 -0
- package/vendor/skills/orizu-cli/references/eval-strategy.md +64 -0
- package/vendor/skills/orizu-cli/references/instructions-after-prompts.md +115 -0
- package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +150 -82
- package/vendor/skills/orizu-cli/references/optimization-reports.md +196 -104
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +57 -37
- package/vendor/skills/orizu-cli/references/primer.md +43 -18
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +199 -131
|
@@ -1,34 +1,97 @@
|
|
|
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
6
|
|
|
5
7
|
import type { GepaEngine } from './gepa-engine-dispatch.js'
|
|
6
|
-
import { bundledOfficialGepaPythonPath } from './gepa-python-paths.js'
|
|
8
|
+
import { bundledOfficialGepaPythonPath, getGepaPythonPathEntries } from './gepa-python-paths.js'
|
|
7
9
|
import { sanitizeTerminalText } from './json-response.js'
|
|
10
|
+
import { SKILLED_PROPOSER_BAKE_REPORT_FIELDS } from './skilled-proposer-bake-report.js'
|
|
8
11
|
|
|
9
12
|
interface SkilledProposerVenvReport {
|
|
10
13
|
venv: string
|
|
14
|
+
publishKey: string
|
|
11
15
|
sslCertFile: string | null
|
|
12
16
|
pythonPathEntries: string[]
|
|
13
|
-
warnings
|
|
17
|
+
warnings: Array<{ code: string, message: string }>
|
|
18
|
+
waitedForPublishLock: boolean
|
|
14
19
|
error?: string
|
|
15
20
|
message?: string
|
|
21
|
+
publishedVenv?: boolean | null
|
|
22
|
+
executedPipArgv?: string[] | null
|
|
16
23
|
}
|
|
17
|
-
|
|
18
24
|
export interface SkilledProposerLaunch {
|
|
19
25
|
python: string
|
|
20
26
|
environment: NodeJS.ProcessEnv
|
|
27
|
+
report?: SkilledProposerVenvReport
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function materializeConfigPayload(engine: GepaEngine, environment: NodeJS.ProcessEnv): (() => void) | undefined {
|
|
31
|
+
const payload = environment.ORIZU_SKILLED_PROPOSER_CONFIG
|
|
32
|
+
if (engine !== 'official' || environment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer' || payload === undefined) return undefined
|
|
33
|
+
const root = mkdtempSync(join(tmpdir(), 'orizu-skilled-proposer-payload-'))
|
|
34
|
+
try {
|
|
35
|
+
const path = join(root, 'config.json')
|
|
36
|
+
writeFileSync(path, payload, { encoding: 'utf8', mode: 0o600 })
|
|
37
|
+
environment.ORIZU_SKILLED_PROPOSER_CONFIG = `@${path}`
|
|
38
|
+
} catch (error) {
|
|
39
|
+
rmSync(root, { recursive: true, force: true })
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
42
|
+
return () => rmSync(root, { recursive: true, force: true })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function spawnSkilledProposerChild(
|
|
46
|
+
command: string, args: string[], engine: GepaEngine, environment: NodeJS.ProcessEnv,
|
|
47
|
+
) {
|
|
48
|
+
const childEnvironment = { ...environment }
|
|
49
|
+
const shouldMaterialize = engine === 'official'
|
|
50
|
+
&& childEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
|
|
51
|
+
&& childEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG !== undefined
|
|
52
|
+
if (!shouldMaterialize) return spawnSync(command, args, { stdio: 'inherit', env: childEnvironment })
|
|
53
|
+
|
|
54
|
+
let child: ReturnType<typeof spawn> | undefined
|
|
55
|
+
let cleanup = () => {}
|
|
56
|
+
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'] as const
|
|
57
|
+
const handlers = signals.map(signal => ({
|
|
58
|
+
signal,
|
|
59
|
+
handle: () => {
|
|
60
|
+
try { cleanup() }
|
|
61
|
+
finally {
|
|
62
|
+
try { child?.kill(signal) }
|
|
63
|
+
finally {
|
|
64
|
+
for (const entry of handlers) process.off(entry.signal, entry.handle)
|
|
65
|
+
process.kill(process.pid, signal)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}))
|
|
70
|
+
for (const entry of handlers) process.once(entry.signal, entry.handle)
|
|
71
|
+
try {
|
|
72
|
+
cleanup = materializeConfigPayload(engine, childEnvironment) ?? cleanup
|
|
73
|
+
await new Promise<void>(resolve => setImmediate(resolve))
|
|
74
|
+
return await new Promise<{ status: number | null, error?: Error }>(resolve => {
|
|
75
|
+
let error: Error | undefined
|
|
76
|
+
child = spawn(command, args, { stdio: 'inherit', env: childEnvironment })
|
|
77
|
+
child.once('error', cause => { error = cause })
|
|
78
|
+
child.once('close', status => resolve(error ? { status, error } : { status }))
|
|
79
|
+
})
|
|
80
|
+
} finally {
|
|
81
|
+
for (const entry of handlers) process.off(entry.signal, entry.handle)
|
|
82
|
+
cleanup()
|
|
83
|
+
}
|
|
21
84
|
}
|
|
22
85
|
|
|
23
86
|
function ensureSelectedSkilledProposerVenv(
|
|
24
87
|
python: string,
|
|
25
88
|
vendoredGepaPath: string,
|
|
89
|
+
cacheRoot = existsSync('/opt/orizu/prebaked.json')
|
|
90
|
+
? '/opt/orizu/cache/skilled-proposer'
|
|
91
|
+
: join(process.cwd(), '.orizu', 'cache', 'skilled-proposer'),
|
|
26
92
|
): SkilledProposerVenvReport {
|
|
27
93
|
const manager = fileURLToPath(new URL('../scripts/ensure-skilled-proposer-venv.mjs', import.meta.url))
|
|
28
94
|
const lock = fileURLToPath(new URL('../requirements/skilled-proposer.lock', import.meta.url))
|
|
29
|
-
// This is a regenerable workspace cache. The manager's publish key further
|
|
30
|
-
// separates interpreters, platforms, and lock revisions inside it.
|
|
31
|
-
const cacheRoot = join(process.cwd(), '.orizu', 'cache', 'skilled-proposer')
|
|
32
95
|
const result = spawnSync(
|
|
33
96
|
process.execPath,
|
|
34
97
|
[manager, '--python', python, '--cache-root', cacheRoot, '--lock', lock,
|
|
@@ -58,6 +121,31 @@ function ensureSelectedSkilledProposerVenv(
|
|
|
58
121
|
return report
|
|
59
122
|
}
|
|
60
123
|
|
|
124
|
+
interface BakeCommandIo { print: (value: string) => void; printErr?: (value: string) => void }
|
|
125
|
+
const packageRoot = join(fileURLToPath(new URL('.', import.meta.url)), '..')
|
|
126
|
+
const managerPath = join(packageRoot, 'scripts/ensure-skilled-proposer-venv.mjs')
|
|
127
|
+
const lockPath = join(packageRoot, 'requirements/skilled-proposer.lock')
|
|
128
|
+
const vendoredGepaPath = join(packageRoot, 'vendor/gepa-python/src')
|
|
129
|
+
const packagedPythonPathEntries = ['vendor/orizu-gepa/src', 'vendor/orizu-gepa-python/src', 'vendor/gepa-python/src'].map(path => join(packageRoot, path))
|
|
130
|
+
const bakedCacheRoot = '/opt/orizu/cache/skilled-proposer'
|
|
131
|
+
|
|
132
|
+
export function skilledProposerBakeCommand(io: BakeCommandIo, verify = false): number {
|
|
133
|
+
try {
|
|
134
|
+
const python = process.env.PYTHON || 'python3'
|
|
135
|
+
if (!verify) { io.print(JSON.stringify(ensureSelectedSkilledProposerVenv(python, vendoredGepaPath, bakedCacheRoot))); return 0 }
|
|
136
|
+
if (!readdirSync(join(bakedCacheRoot, 'venvs'), { withFileTypes: true }).some(entry => entry.isDirectory() && existsSync(join(bakedCacheRoot, 'venvs', entry.name, '.orizu-skilled-proposer-venv.json')))) throw new Error('ALI_1588_VENV_MARKER_MISSING')
|
|
137
|
+
const environment = { ...process.env, ORIZU_CANDIDATE_PROPOSER: 'skilled-proposer' }
|
|
138
|
+
const launch = prepareSkilledProposerLaunch(python, 'official', environment)
|
|
139
|
+
const report = launch.report!
|
|
140
|
+
if (report.publishedVenv !== false || report.executedPipArgv !== null) throw new Error('ALI_1588_DEPENDENCY_SETUP_NOT_REUSED')
|
|
141
|
+
const pythonPathEntries = getGepaPythonPathEntries(undefined)
|
|
142
|
+
if (JSON.stringify(pythonPathEntries) !== JSON.stringify(packagedPythonPathEntries)) throw new Error('ALI_1588_PYTHON_ROOT_MISSING')
|
|
143
|
+
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 }
|
|
144
|
+
io.print(JSON.stringify(Object.fromEntries(SKILLED_PROPOSER_BAKE_REPORT_FIELDS.map(key => [key, completeReport[key]]))))
|
|
145
|
+
return 0
|
|
146
|
+
} catch (error) { io.printErr?.(`${error instanceof Error ? error.message : String(error)}\n`); return 1 }
|
|
147
|
+
}
|
|
148
|
+
|
|
61
149
|
export function prepareSkilledProposerLaunch(
|
|
62
150
|
python: string,
|
|
63
151
|
engine: GepaEngine,
|
|
@@ -85,5 +173,6 @@ export function prepareSkilledProposerLaunch(
|
|
|
85
173
|
? join(report.venv, 'Scripts', 'python.exe')
|
|
86
174
|
: join(report.venv, 'bin', 'python'),
|
|
87
175
|
environment,
|
|
176
|
+
report,
|
|
88
177
|
}
|
|
89
178
|
}
|
|
@@ -100,15 +100,14 @@ finally:
|
|
|
100
100
|
const MANAGER_SCHEMA_VERSION = 1
|
|
101
101
|
const MANIFEST_NAME = '.orizu-skilled-proposer-venv.json'
|
|
102
102
|
const LOCK_WAIT_MILLISECONDS = 25
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const LOCK_TIMEOUT_MILLISECONDS = 20 * 60 * 1000
|
|
107
|
-
const PYTHON_PROBE_TIMEOUT_MILLISECONDS = 15_000
|
|
103
|
+
export const LOCK_TIMEOUT_MILLISECONDS = 20 * 60 * 1000
|
|
104
|
+
export const PYTHON_PROBE_TIMEOUT_MILLISECONDS = 15_000
|
|
105
|
+
export const LOCK_RELEASE_TIMEOUT_MILLISECONDS = 15_000
|
|
108
106
|
const VENV_CREATE_TIMEOUT_MILLISECONDS = 2 * 60 * 1000
|
|
109
|
-
const VALIDATION_TIMEOUT_MILLISECONDS = 60_000
|
|
107
|
+
export const VALIDATION_TIMEOUT_MILLISECONDS = 60_000
|
|
110
108
|
const PIP_INSTALL_TIMEOUT_MILLISECONDS = 10 * 60 * 1000
|
|
111
109
|
const TEST_BARRIER_TIMEOUT_MILLISECONDS = 60_000
|
|
110
|
+
export const SKILLED_PROPOSER_MANAGER_MAXIMUM_MILLISECONDS = (2 * PYTHON_PROBE_TIMEOUT_MILLISECONDS) + LOCK_RELEASE_TIMEOUT_MILLISECONDS + LOCK_TIMEOUT_MILLISECONDS + VENV_CREATE_TIMEOUT_MILLISECONDS + PIP_INSTALL_TIMEOUT_MILLISECONDS + (6 * VALIDATION_TIMEOUT_MILLISECONDS) + (2 * 60 * 1000)
|
|
112
111
|
const ABANDONED_STAGE_MAX_AGE_MILLISECONDS = 24 * 60 * 60 * 1000
|
|
113
112
|
const SSL_CERT_FILE_UNRESOLVED_WARNING = {
|
|
114
113
|
code: 'ALI_1505_SSL_CERT_FILE_UNRESOLVED',
|
|
@@ -203,11 +202,6 @@ function versionAtLeast(actual, required) {
|
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
function assertSupportedWheelTarget(identity) {
|
|
206
|
-
// `platform.libc_ver()` is not merely cache identity on Linux: it is also a
|
|
207
|
-
// conservative admission check for the locked glibc wheel matrix. A bad
|
|
208
|
-
// guess can refuse a compatible host (named, before mutation), but cannot
|
|
209
|
-
// admit an sdist or make pip install an incompatible wheel because the
|
|
210
|
-
// customer install remains hash-locked and binary-only.
|
|
211
205
|
const machine = String(identity.machine).toLowerCase()
|
|
212
206
|
const isSupportedMac = identity.system === 'Darwin'
|
|
213
207
|
&& machine === 'arm64'
|
|
@@ -272,7 +266,7 @@ export function characterizePython(python, probeEnv = isolatedPythonProbeEnv())
|
|
|
272
266
|
}
|
|
273
267
|
}
|
|
274
268
|
|
|
275
|
-
function readAndValidateHashLock(lockPath) {
|
|
269
|
+
export function readAndValidateHashLock(lockPath) {
|
|
276
270
|
let contents
|
|
277
271
|
try {
|
|
278
272
|
contents = readFileSync(lockPath, 'utf8')
|
|
@@ -305,7 +299,8 @@ function readAndValidateHashLock(lockPath) {
|
|
|
305
299
|
)
|
|
306
300
|
}
|
|
307
301
|
for (const requirement of requirements) {
|
|
308
|
-
const
|
|
302
|
+
const requirementSpecifier = requirement.split(/\s+--hash=/, 1)[0].trim()
|
|
303
|
+
const isExactPin = /^[A-Za-z0-9_.-]+(?:\[[^\]]+\])?==[^\s,;*]+(?:\s*;\s*.+)?$/.test(requirementSpecifier)
|
|
309
304
|
const hashes = [...requirement.matchAll(/(?:^|\s)--hash=([^\s]+)/g)].map(match => match[1])
|
|
310
305
|
const hashTokens = [...requirement.matchAll(/(?:^|\s)(--hash(?:=[^\s]*)?)(?=\s|$)/g)]
|
|
311
306
|
.map(match => match[1])
|
|
@@ -343,7 +338,7 @@ function resolveSslCertFile(pythonIdentity, probeEnv) {
|
|
|
343
338
|
return null
|
|
344
339
|
}
|
|
345
340
|
|
|
346
|
-
function buildPublishKey(pythonIdentity, lockContents) {
|
|
341
|
+
export function buildPublishKey(pythonIdentity, lockContents) {
|
|
347
342
|
const lockDigest = createHash('sha256').update(lockContents).digest('hex')
|
|
348
343
|
const identity = {
|
|
349
344
|
managerSchemaVersion: MANAGER_SCHEMA_VERSION,
|
|
@@ -355,7 +350,6 @@ function buildPublishKey(pythonIdentity, lockContents) {
|
|
|
355
350
|
soabi: pythonIdentity.soabi,
|
|
356
351
|
system: pythonIdentity.system,
|
|
357
352
|
machine: pythonIdentity.machine,
|
|
358
|
-
osVersion: pythonIdentity.osVersion,
|
|
359
353
|
libc: pythonIdentity.libc,
|
|
360
354
|
lockDigest,
|
|
361
355
|
}
|
|
@@ -523,21 +517,35 @@ function acquirePublishLock(python, lockFile, env) {
|
|
|
523
517
|
})
|
|
524
518
|
}
|
|
525
519
|
|
|
526
|
-
async function releasePublishLock(lock) {
|
|
520
|
+
export async function releasePublishLock(lock) {
|
|
527
521
|
if (!lock?.child) return
|
|
522
|
+
const deadline = Date.now() + LOCK_RELEASE_TIMEOUT_MILLISECONDS
|
|
523
|
+
const remaining = () => Math.max(0, deadline - Date.now())
|
|
524
|
+
const waitForExit = timeout => lock.child.exitCode !== null || lock.child.signalCode !== null ? Promise.resolve(true) : new Promise(resolve => {
|
|
525
|
+
const onExit = () => { clearTimeout(timer); resolve(true) }
|
|
526
|
+
const timer = setTimeout(() => { lock.child.off('exit', onExit); resolve(false) }, timeout)
|
|
527
|
+
lock.child.once('exit', onExit)
|
|
528
|
+
})
|
|
529
|
+
const guardianAlive = () => { try { process.kill(lock.guardianPid, 0); return true } catch (error) { if (error?.code === 'ESRCH') return false; throw error } }
|
|
530
|
+
const waitForGuardianExit = async () => { while (guardianAlive() && remaining() > 0) await wait(Math.min(25, remaining())); return !guardianAlive() }
|
|
531
|
+
const signalGuardian = signal => {
|
|
532
|
+
if (lock.guardianPid === lock.child.pid) return
|
|
533
|
+
try { process.kill(lock.guardianPid, signal) } catch (error) { if (error?.code !== 'ESRCH') throw error }
|
|
534
|
+
}
|
|
535
|
+
const phase = LOCK_RELEASE_TIMEOUT_MILLISECONDS / 3
|
|
536
|
+
const finishGuardian = async () => { if (guardianAlive()) signalGuardian('SIGKILL'); return await waitForGuardianExit() }
|
|
528
537
|
if (lock.child.exitCode !== null || lock.child.signalCode !== null) {
|
|
529
|
-
|
|
530
|
-
process.kill(lock.guardianPid, 'SIGTERM')
|
|
531
|
-
} catch (error) {
|
|
532
|
-
if (error?.code !== 'ESRCH') throw error
|
|
533
|
-
}
|
|
538
|
+
if (!await finishGuardian()) throw new SkilledProposerVenvError('ALI_1505_PUBLISH_LOCK_RELEASE_TIMEOUT', 'publish-lock guardian did not exit after SIGKILL')
|
|
534
539
|
return
|
|
535
540
|
}
|
|
536
|
-
const exited = new Promise(resolve => lock.child.once('exit', resolve))
|
|
537
541
|
lock.child.stdin.end('release\n')
|
|
538
|
-
|
|
539
|
-
await
|
|
540
|
-
|
|
542
|
+
signalGuardian('SIGTERM')
|
|
543
|
+
if (await waitForExit(Math.min(phase, remaining()))) { if (await finishGuardian()) return }
|
|
544
|
+
lock.child.kill('SIGTERM')
|
|
545
|
+
if (await waitForExit(Math.min(phase, remaining()))) { if (await finishGuardian()) return }
|
|
546
|
+
signalGuardian('SIGKILL'); lock.child.kill('SIGKILL')
|
|
547
|
+
const [holderExited, guardianExited] = await Promise.all([waitForExit(remaining()), waitForGuardianExit()])
|
|
548
|
+
if (!holderExited || !guardianExited) throw new SkilledProposerVenvError('ALI_1505_PUBLISH_LOCK_RELEASE_TIMEOUT', 'publish-lock holder or guardian did not exit after SIGKILL')
|
|
541
549
|
}
|
|
542
550
|
|
|
543
551
|
async function killPublishLockLeaderAtTestHook(lock) {
|
|
@@ -603,7 +611,7 @@ function isolatedChildEnv(sslCertFile) {
|
|
|
603
611
|
return env
|
|
604
612
|
}
|
|
605
613
|
|
|
606
|
-
export async function ensureSkilledProposerVenv(options) {
|
|
614
|
+
export async function ensureSkilledProposerVenv(options, dependencies = {}) {
|
|
607
615
|
// This must stay first: unsupported interpreters may not create cache state or
|
|
608
616
|
// invoke a mutating `python -m` command.
|
|
609
617
|
const probeEnv = isolatedPythonProbeEnv()
|
|
@@ -643,6 +651,7 @@ export async function ensureSkilledProposerVenv(options) {
|
|
|
643
651
|
const lockedReport = { ...baseReport, waitedForPublishLock: lock.waitedForPublishLock }
|
|
644
652
|
|
|
645
653
|
const stage = join(options.cacheRoot, 'venvs', `.${publishKey}-${process.pid}-${randomUUID()}.tmp`)
|
|
654
|
+
let primaryError, hasPrimaryError = false
|
|
646
655
|
try {
|
|
647
656
|
// Holding the per-key publish lock proves that no live builder for this key
|
|
648
657
|
// can own one of these sibling staging directories.
|
|
@@ -726,9 +735,17 @@ export async function ensureSkilledProposerVenv(options) {
|
|
|
726
735
|
)
|
|
727
736
|
}
|
|
728
737
|
return { ...installingReport, publishedVenv: true }
|
|
738
|
+
} catch (error) {
|
|
739
|
+
primaryError = error
|
|
740
|
+
hasPrimaryError = true
|
|
741
|
+
throw error
|
|
729
742
|
} finally {
|
|
730
743
|
rmSync(stage, { recursive: true, force: true })
|
|
731
|
-
await releasePublishLock(lock)
|
|
744
|
+
try { await (dependencies.releasePublishLock || releasePublishLock)(lock) }
|
|
745
|
+
catch (cleanupError) {
|
|
746
|
+
if (hasPrimaryError) throw new AggregateError([primaryError, cleanupError], 'ALI_1505_PRIMARY_AND_PUBLISH_LOCK_RELEASE_FAILED')
|
|
747
|
+
throw cleanupError
|
|
748
|
+
}
|
|
732
749
|
}
|
|
733
750
|
}
|
|
734
751
|
|
|
@@ -219,6 +219,32 @@ def run_official_gepa(
|
|
|
219
219
|
owned_stop_callbacks = list(stop_callbacks)
|
|
220
220
|
if proposal_budget is not None:
|
|
221
221
|
owned_stop_callbacks.append(ProposalBudgetStopper(proposal_budget))
|
|
222
|
+
proposer_failures: list[Exception] = []
|
|
223
|
+
gepa_candidate_proposer = custom_candidate_proposer
|
|
224
|
+
if custom_candidate_proposer is not None:
|
|
225
|
+
delegate = custom_candidate_proposer
|
|
226
|
+
|
|
227
|
+
def capture_proposer_failure(
|
|
228
|
+
candidate: dict[str, str], reflective_dataset: Any, components_to_update: list[str],
|
|
229
|
+
) -> dict[str, str]:
|
|
230
|
+
if proposer_failures:
|
|
231
|
+
raise proposer_failures[0]
|
|
232
|
+
try:
|
|
233
|
+
return delegate(candidate, reflective_dataset, components_to_update)
|
|
234
|
+
except Exception as error:
|
|
235
|
+
if not proposer_failures:
|
|
236
|
+
proposer_failures.append(error)
|
|
237
|
+
raise
|
|
238
|
+
|
|
239
|
+
gepa_candidate_proposer = capture_proposer_failure
|
|
240
|
+
def raise_proposer_failure(_state: Any) -> bool:
|
|
241
|
+
if proposer_failures:
|
|
242
|
+
raise proposer_failures[0]
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
# Failure must win over every normal stopper in CompositeStopper(any),
|
|
246
|
+
# otherwise a simultaneous budget stop emits terminal success first.
|
|
247
|
+
owned_stop_callbacks.insert(0, raise_proposer_failure)
|
|
222
248
|
|
|
223
249
|
reflection_minibatch_size = config.minibatch_size
|
|
224
250
|
if custom_candidate_proposer is not None and max_metric_calls is not None:
|
|
@@ -252,11 +278,18 @@ def run_official_gepa(
|
|
|
252
278
|
# The connector implements GEPA's component-aware strategy protocol;
|
|
253
279
|
# passing it as a bare callable would discard the selected component.
|
|
254
280
|
kwargs["reflection_strategy"] = reflection_lm
|
|
255
|
-
if
|
|
256
|
-
kwargs["custom_candidate_proposer"] =
|
|
281
|
+
if gepa_candidate_proposer is not None:
|
|
282
|
+
kwargs["custom_candidate_proposer"] = gepa_candidate_proposer
|
|
257
283
|
kwargs["trainset"] = _with_row_identities(trainset)
|
|
258
284
|
kwargs["valset"] = _with_row_identities(valset)
|
|
259
|
-
|
|
285
|
+
try:
|
|
286
|
+
result = optimize(**kwargs)
|
|
287
|
+
except Exception:
|
|
288
|
+
if proposer_failures:
|
|
289
|
+
raise proposer_failures[0]
|
|
290
|
+
raise
|
|
291
|
+
if proposer_failures:
|
|
292
|
+
raise proposer_failures[0]
|
|
260
293
|
if custom_candidate_proposer is not None and preflight_evaluation is not None:
|
|
261
294
|
# GEPA's state correctly counts its seed evaluation, but this run
|
|
262
295
|
# served that evaluation from the already-accounted launch preflight.
|
|
@@ -18,10 +18,38 @@ def _inside(root: Path, candidate: Path) -> bool:
|
|
|
18
18
|
return False
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
if not
|
|
21
|
+
def _resolve_set_root(directory: str, reference: str) -> Path:
|
|
22
|
+
directory_root = Path(directory).resolve()
|
|
23
|
+
if not directory_root.is_dir():
|
|
24
24
|
raise InstructionSetLoaderError("instruction_set_not_synced")
|
|
25
|
+
direct = (directory_root / reference).resolve()
|
|
26
|
+
if _inside(directory_root, direct) and direct.is_dir():
|
|
27
|
+
try:
|
|
28
|
+
direct_manifest = json.loads((direct / "manifest.json").read_text())
|
|
29
|
+
except (OSError, json.JSONDecodeError):
|
|
30
|
+
# Preserve the exact-path loader's specific missing/invalid manifest error.
|
|
31
|
+
return direct
|
|
32
|
+
if direct_manifest.get("name") == reference or direct_manifest.get("slug") == reference:
|
|
33
|
+
return direct
|
|
34
|
+
matches: list[Path] = []
|
|
35
|
+
for child in directory_root.iterdir():
|
|
36
|
+
candidate = child.resolve()
|
|
37
|
+
if child.name.startswith(".") or not child.is_dir() or not _inside(directory_root, candidate):
|
|
38
|
+
continue
|
|
39
|
+
try:
|
|
40
|
+
manifest = json.loads((candidate / "manifest.json").read_text())
|
|
41
|
+
except (OSError, json.JSONDecodeError):
|
|
42
|
+
continue
|
|
43
|
+
if manifest.get("name") == reference or manifest.get("slug") == reference:
|
|
44
|
+
matches.append(candidate)
|
|
45
|
+
if len(matches) != 1:
|
|
46
|
+
code = "instruction_set_reference_ambiguous" if len(matches) > 1 else "instruction_set_not_synced"
|
|
47
|
+
raise InstructionSetLoaderError(code)
|
|
48
|
+
return matches[0]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_instruction_set(directory: str, name: str, model_config_identity: str) -> dict[str, str]:
|
|
52
|
+
root = _resolve_set_root(directory, name)
|
|
25
53
|
manifest_path = root / "manifest.json"
|
|
26
54
|
if not manifest_path.is_file():
|
|
27
55
|
raise InstructionSetLoaderError("instruction_set_manifest_missing")
|
|
@@ -406,13 +406,17 @@ def run_from_environment() -> dict[str, Any]:
|
|
|
406
406
|
|
|
407
407
|
ALI-1502 supplies these variables after verifying runner bytes. Scorer
|
|
408
408
|
contract resolution and seed validation intentionally happen before
|
|
409
|
-
``start_run`` so either refusal leaves no stranded running record.
|
|
409
|
+
``start_run`` so either refusal leaves no stranded running record. Hosted
|
|
410
|
+
attach identity is resolved before even those runner-backed preflights.
|
|
410
411
|
"""
|
|
411
412
|
env = _required_environment()
|
|
412
413
|
train_split = os.environ.get("ORIZU_TRAIN_SPLIT", "train")
|
|
413
414
|
validation_split = os.environ.get("ORIZU_VALIDATION_SPLIT", "validation")
|
|
414
415
|
config = build_config_from_environment()
|
|
415
416
|
client = OrizuClient.from_env()
|
|
417
|
+
hosted_run_id = os.environ.get("ORIZU_HOSTED_OPTIMIZATION_RUN_ID", "").strip() or None
|
|
418
|
+
if hosted_run_id is not None:
|
|
419
|
+
client.require_hosted_optimization_run(run_id=hosted_run_id, project=env["ORIZU_PROJECT"])
|
|
416
420
|
profile_version_id = os.environ.get("ORIZU_INSTRUCTION_SET_PROFILE_VERSION_ID")
|
|
417
421
|
prompt_context, trainset = client.fetch_exec_context(
|
|
418
422
|
prompt_version_id=os.environ.get("ORIZU_PROMPT_VERSION_ID"), runner_version_id=env["ORIZU_RUNNER_VERSION_ID"],
|
|
@@ -492,7 +496,7 @@ def run_from_environment() -> dict[str, Any]:
|
|
|
492
496
|
is_multi_component_instruction_set = bool(
|
|
493
497
|
profile_version_id and isinstance(shape, list) and len(shape) > 1
|
|
494
498
|
)
|
|
495
|
-
run_id = client.start_run(
|
|
499
|
+
run_id = hosted_run_id or client.start_run(
|
|
496
500
|
project=env["ORIZU_PROJECT"], optimizer_version_id=env["ORIZU_OPTIMIZER_VERSION_ID"],
|
|
497
501
|
# A set of one is the legacy prompt subject. Only a genuine component
|
|
498
502
|
# map is attributed to the profile version instead of a prompt version.
|
|
@@ -506,6 +510,7 @@ def run_from_environment() -> dict[str, Any]:
|
|
|
506
510
|
dataset_version_id=env["ORIZU_DATASET_VERSION_ID"], split_set_id=env["ORIZU_SPLIT_SET_ID"],
|
|
507
511
|
train_split=train_split, validation_split=validation_split, metadata=metadata,
|
|
508
512
|
model_config_settings_version_id=instruction_set.get("model_config_settings_version_id") if isinstance(instruction_set, dict) else None,
|
|
513
|
+
runner_version_id=prompt_context.runner_version_id,
|
|
509
514
|
)
|
|
510
515
|
print(json.dumps({"optimization_run_id": run_id}), flush=True)
|
|
511
516
|
# The frozen sink treats ``None`` as no local artifact writer. Keep that
|