headlesscode 1.2.2 → 1.3.0

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.
@@ -0,0 +1,424 @@
1
+ import { createHash, randomUUID } from "node:crypto"
2
+ import { readFileSync } from "node:fs"
3
+ import { Pool, type PoolClient } from "pg"
4
+ import type { ResourceClass } from "./types.js"
5
+ import { createRsiArtifactStore, type RsiArtifactReference, type RsiArtifactStore } from "./artifact-store.js"
6
+
7
+ const MIGRATIONS = [
8
+ { version: 1, sql: readFileSync(new URL("./migrations/001_postgres_fleet_queue.sql", import.meta.url), "utf8") },
9
+ { version: 2, sql: readFileSync(new URL("./migrations/002_external_artifacts_and_job_leases.sql", import.meta.url), "utf8") },
10
+ { version: 3, sql: readFileSync(new URL("./migrations/003_model_training_jobs.sql", import.meta.url), "utf8") },
11
+ ]
12
+
13
+ export interface FleetQueuePolicy {
14
+ queueName: string
15
+ maxInFlight: number
16
+ leaseMs: number
17
+ maxAttempts: number
18
+ classCapacity?: Partial<Record<ResourceClass, number>>
19
+ concurrencyKeyCapacity?: Record<string, number>
20
+ }
21
+
22
+ function parseCapacityMap(raw: string | undefined, name: string): Record<string, number> {
23
+ if (!raw?.trim()) return {}
24
+ let value: unknown
25
+ try { value = JSON.parse(raw) } catch { throw new Error(`${name} must be a JSON object of positive integer capacities`) }
26
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be a JSON object of positive integer capacities`)
27
+ const result: Record<string, number> = {}
28
+ for (const [key, limit] of Object.entries(value)) {
29
+ if (!key || !Number.isInteger(limit) || Number(limit) < 1) throw new Error(`${name} must contain only positive integer capacities`)
30
+ result[key] = Number(limit)
31
+ }
32
+ return result
33
+ }
34
+
35
+ export function fleetQueuePolicy(queueName: string, maxInFlight: number, env: NodeJS.ProcessEnv = process.env): FleetQueuePolicy {
36
+ const leaseMs = env.HEADLESSCODE_RSI_LEASE_MS?.trim() ? Number(env.HEADLESSCODE_RSI_LEASE_MS) : 60 * 60_000
37
+ const maxAttempts = env.HEADLESSCODE_RSI_MAX_ATTEMPTS?.trim() ? Number(env.HEADLESSCODE_RSI_MAX_ATTEMPTS) : 2
38
+ return validatePolicy({
39
+ queueName,
40
+ maxInFlight,
41
+ leaseMs,
42
+ maxAttempts,
43
+ classCapacity: parseCapacityMap(env.HEADLESSCODE_RSI_CLASS_CAPACITY, "HEADLESSCODE_RSI_CLASS_CAPACITY") as FleetQueuePolicy["classCapacity"],
44
+ concurrencyKeyCapacity: parseCapacityMap(env.HEADLESSCODE_RSI_KEY_CAPACITY, "HEADLESSCODE_RSI_KEY_CAPACITY"),
45
+ })
46
+ }
47
+
48
+ export interface FleetWorkerRegistration {
49
+ workerId: string
50
+ gatewayId: string
51
+ hostname: string
52
+ maxActive: number
53
+ resourceClasses: ResourceClass[]
54
+ }
55
+
56
+ export interface FleetJobPayload {
57
+ schemaVersion: 1
58
+ snapshotSha256: string
59
+ snapshotBytes: number
60
+ snapshotCommit: string
61
+ task: string
62
+ model: string
63
+ maxIterations: number
64
+ timeoutMs: number
65
+ protectedPaths: string[]
66
+ candidate: Record<string, unknown>
67
+ regressionCommand?: string
68
+ evalCommands?: string[]
69
+ modelTask?: {
70
+ kind: "qlora-train" | "model-evaluation"
71
+ datasetVersion: string
72
+ datasetSha256: string
73
+ manifestSha256: string
74
+ baseModelId: string
75
+ baseModelPath: "/workspace/.rsi-base-model"
76
+ seed: 1337
77
+ maxSteps?: 8
78
+ modelCandidateId?: string
79
+ adapterPath?: string
80
+ modelKind?: "base" | "adapter"
81
+ cellSetSha256: string
82
+ cellIds: string[]
83
+ }
84
+ }
85
+
86
+ export interface FleetJob {
87
+ jobId: string
88
+ idempotencyKey: string
89
+ runId: string
90
+ candidateId: string
91
+ jobKind: "mutation" | "evaluation" | "training" | "model-evaluation"
92
+ resourceClass: ResourceClass
93
+ concurrencyKey?: string
94
+ artifactSha256: string
95
+ payload: FleetJobPayload
96
+ status: "queued" | "leased" | "completed" | "failed" | "cancelled"
97
+ attempts: number
98
+ maxAttempts: number
99
+ leaseDurationMs: number
100
+ leaseOwner?: string
101
+ leaseToken?: string
102
+ leaseExpiresAt?: string
103
+ result?: unknown
104
+ error?: string
105
+ createdAt: string
106
+ updatedAt: string
107
+ }
108
+
109
+ export interface FleetJobLease { job: FleetJob; token: string; workerId: string }
110
+ export interface QueueMutationResult { accepted: boolean; duplicate: boolean; job?: FleetJob }
111
+ export type FleetJobIdentity = Pick<FleetJob, "idempotencyKey" | "runId" | "candidateId" | "jobKind" | "resourceClass" | "concurrencyKey" | "artifactSha256" | "payload">
112
+
113
+ function stableJson(value: unknown): string {
114
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`
115
+ if (value && typeof value === "object") {
116
+ const object = value as Record<string, unknown>
117
+ return `{${Object.keys(object).filter((key) => object[key] !== undefined).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`
118
+ }
119
+ const encoded = JSON.stringify(value)
120
+ return encoded === undefined ? "undefined" : encoded
121
+ }
122
+
123
+ /** Compare every queued execution input before a coordinator reuses an idempotent job. */
124
+ export function fleetJobMatches(job: FleetJob, expected: FleetJobIdentity): boolean {
125
+ return job.idempotencyKey === expected.idempotencyKey
126
+ && job.runId === expected.runId
127
+ && job.candidateId === expected.candidateId
128
+ && job.jobKind === expected.jobKind
129
+ && job.resourceClass === expected.resourceClass
130
+ && (job.concurrencyKey ?? undefined) === (expected.concurrencyKey ?? undefined)
131
+ && job.artifactSha256 === expected.artifactSha256
132
+ && stableJson(job.payload) === stableJson(expected.payload)
133
+ }
134
+
135
+ function validatePolicy(policy: FleetQueuePolicy): FleetQueuePolicy {
136
+ if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(policy.queueName)) throw new Error("queueName must be a simple lowercase identifier")
137
+ for (const [name, value, minimum] of [["maxInFlight", policy.maxInFlight, 1], ["leaseMs", policy.leaseMs, 1000], ["maxAttempts", policy.maxAttempts, 1]] as const) {
138
+ if (!Number.isInteger(value) || value < minimum) throw new Error(`queue ${name} must be an integer of at least ${minimum}`)
139
+ }
140
+ for (const [key, value] of Object.entries({ ...(policy.classCapacity ?? {}), ...(policy.concurrencyKeyCapacity ?? {}) })) {
141
+ if (!Number.isInteger(value) || value < 1) throw new Error(`queue capacity ${key} must be a positive integer`)
142
+ }
143
+ return {
144
+ ...policy,
145
+ classCapacity: policy.classCapacity ?? {},
146
+ concurrencyKeyCapacity: policy.concurrencyKeyCapacity ?? {},
147
+ }
148
+ }
149
+
150
+ function rowToJob(row: Record<string, unknown>): FleetJob {
151
+ return {
152
+ jobId: String(row.job_id),
153
+ idempotencyKey: String(row.idempotency_key),
154
+ runId: String(row.run_id),
155
+ candidateId: String(row.candidate_id),
156
+ jobKind: row.job_kind as FleetJob["jobKind"],
157
+ resourceClass: row.resource_class as ResourceClass,
158
+ ...(typeof row.concurrency_key === "string" ? { concurrencyKey: row.concurrency_key } : {}),
159
+ artifactSha256: String(row.artifact_sha256).trim(),
160
+ payload: row.payload as FleetJobPayload,
161
+ status: row.status as FleetJob["status"],
162
+ attempts: Number(row.attempts),
163
+ maxAttempts: Number(row.max_attempts),
164
+ leaseDurationMs: Number(row.lease_duration_ms),
165
+ ...(typeof row.lease_owner === "string" ? { leaseOwner: row.lease_owner } : {}),
166
+ ...(typeof row.lease_token === "string" ? { leaseToken: row.lease_token } : {}),
167
+ ...(row.lease_expires_at ? { leaseExpiresAt: new Date(String(row.lease_expires_at)).toISOString() } : {}),
168
+ ...(row.result !== null && row.result !== undefined ? { result: row.result } : {}),
169
+ ...(typeof row.error === "string" ? { error: row.error } : {}),
170
+ createdAt: new Date(String(row.created_at)).toISOString(),
171
+ updatedAt: new Date(String(row.updated_at)).toISOString(),
172
+ }
173
+ }
174
+
175
+ /** Worst-case synchronous OpenShell CLI budget, including setup and teardown. */
176
+ export function requiredFleetJobLeaseMs(jobKind: FleetJob["jobKind"], timeoutMs: number, visibleCommandCount = 0): number {
177
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || !Number.isSafeInteger(visibleCommandCount) || visibleCommandCount < 0) throw new Error("invalid fleet job timeout or visible command count")
178
+ const sessions = jobKind === "evaluation" ? visibleCommandCount + 1 : 1
179
+ const required = sessions * (timeoutMs * 3 + 180_000)
180
+ if (!Number.isSafeInteger(required) || required > 2_147_483_647) throw new Error("RSI job timeout budget exceeds PostgreSQL's 24-day lease range")
181
+ return required
182
+ }
183
+
184
+ async function inTransaction<T>(pool: Pool, run: (client: PoolClient) => Promise<T>): Promise<T> {
185
+ const client = await pool.connect()
186
+ try {
187
+ await client.query("BEGIN")
188
+ const result = await run(client)
189
+ await client.query("COMMIT")
190
+ return result
191
+ } catch (error) {
192
+ await client.query("ROLLBACK").catch(() => undefined)
193
+ throw error
194
+ } finally {
195
+ client.release()
196
+ }
197
+ }
198
+
199
+ /** PostgreSQL is the source of truth for workers, admission, leases and results. */
200
+ export class PostgresRsiJobQueue {
201
+ readonly policy: FleetQueuePolicy
202
+ readonly artifactBackend: RsiArtifactStore["backend"]
203
+ constructor(readonly pool: Pool, policy: FleetQueuePolicy, private readonly artifactStore: RsiArtifactStore) {
204
+ this.policy = validatePolicy(policy)
205
+ this.artifactBackend = artifactStore.backend
206
+ }
207
+
208
+ static fromEnvironment(policy: FleetQueuePolicy, env: NodeJS.ProcessEnv = process.env): PostgresRsiJobQueue {
209
+ const connectionString = env.HEADLESSCODE_RSI_DATABASE_URL?.trim()
210
+ if (!connectionString) throw new Error("HEADLESSCODE_RSI_DATABASE_URL is required for RSI fleet execution")
211
+ return new PostgresRsiJobQueue(new Pool({ connectionString, max: 8, application_name: `headlesscode-rsi-${policy.queueName}` }), policy, createRsiArtifactStore(env))
212
+ }
213
+
214
+ async migrate(): Promise<void> {
215
+ await this.pool.query(`CREATE TABLE IF NOT EXISTS headlesscode_rsi_schema_migrations (
216
+ version integer PRIMARY KEY,
217
+ applied_at timestamptz NOT NULL DEFAULT clock_timestamp()
218
+ )`)
219
+ for (const migration of MIGRATIONS) {
220
+ const applied = await this.pool.query("SELECT 1 FROM headlesscode_rsi_schema_migrations WHERE version=$1", [migration.version])
221
+ if (applied.rowCount) continue
222
+ await inTransaction(this.pool, async (client) => {
223
+ await client.query(migration.sql)
224
+ await client.query("INSERT INTO headlesscode_rsi_schema_migrations(version) VALUES($1) ON CONFLICT DO NOTHING", [migration.version])
225
+ })
226
+ }
227
+ const inserted = await this.pool.query(
228
+ `INSERT INTO headlesscode_rsi_queue_policy(queue_name, max_in_flight, lease_ms, max_attempts, class_capacity, key_capacity)
229
+ VALUES($1,$2,$3,$4,$5::jsonb,$6::jsonb) ON CONFLICT(queue_name) DO NOTHING`,
230
+ [this.policy.queueName, this.policy.maxInFlight, this.policy.leaseMs, this.policy.maxAttempts, JSON.stringify(this.policy.classCapacity), JSON.stringify(this.policy.concurrencyKeyCapacity)],
231
+ )
232
+ void inserted
233
+ const persisted = await this.pool.query("SELECT max_in_flight, lease_ms, max_attempts, class_capacity, key_capacity FROM headlesscode_rsi_queue_policy WHERE queue_name=$1", [this.policy.queueName])
234
+ const row = persisted.rows[0]
235
+ if (!row || Number(row.max_in_flight) !== this.policy.maxInFlight || Number(row.lease_ms) !== this.policy.leaseMs || Number(row.max_attempts) !== this.policy.maxAttempts || stableJson(row.class_capacity) !== stableJson(this.policy.classCapacity) || stableJson(row.key_capacity) !== stableJson(this.policy.concurrencyKeyCapacity)) {
236
+ throw new Error(`persisted RSI queue policy for ${this.policy.queueName} differs from requested admission settings`)
237
+ }
238
+ }
239
+
240
+ async registerWorker(registration: FleetWorkerRegistration): Promise<void> {
241
+ if (!registration.workerId.trim() || !registration.gatewayId.trim() || !registration.hostname.trim()) throw new Error("worker id, gateway id and hostname are required")
242
+ if (!Number.isInteger(registration.maxActive) || registration.maxActive < 1) throw new Error("worker maxActive must be a positive integer")
243
+ await this.pool.query(
244
+ `INSERT INTO headlesscode_rsi_workers(queue_name,worker_id,gateway_id,hostname,max_active,resource_classes,active,registered_at,heartbeat_at)
245
+ VALUES($1,$2,$3,$4,$5,$6::jsonb,true,clock_timestamp(),clock_timestamp())
246
+ ON CONFLICT(queue_name,worker_id) DO UPDATE SET gateway_id=EXCLUDED.gateway_id, hostname=EXCLUDED.hostname, max_active=EXCLUDED.max_active,
247
+ resource_classes=EXCLUDED.resource_classes, active=true, heartbeat_at=clock_timestamp()`,
248
+ [this.policy.queueName, registration.workerId, registration.gatewayId, registration.hostname, registration.maxActive, JSON.stringify(registration.resourceClasses)],
249
+ )
250
+ }
251
+
252
+ async heartbeatWorker(workerId: string): Promise<boolean> {
253
+ const result = await this.pool.query("UPDATE headlesscode_rsi_workers SET heartbeat_at=clock_timestamp() WHERE queue_name=$1 AND worker_id=$2 AND active=true", [this.policy.queueName, workerId])
254
+ return result.rowCount === 1
255
+ }
256
+
257
+ async activeWorkerCount(resourceClass?: ResourceClass): Promise<number> {
258
+ const result = await this.pool.query(
259
+ `SELECT count(*)::int AS count FROM headlesscode_rsi_workers
260
+ WHERE queue_name=$1 AND active=true AND heartbeat_at > clock_timestamp()-($2::text || ' milliseconds')::interval
261
+ AND ($3::text IS NULL OR resource_classes @> jsonb_build_array($3::text))`,
262
+ [this.policy.queueName, this.policy.leaseMs * 2, resourceClass ?? null],
263
+ )
264
+ return Number(result.rows[0]?.count ?? 0)
265
+ }
266
+
267
+ async stopWorker(workerId: string): Promise<void> {
268
+ await this.pool.query("UPDATE headlesscode_rsi_workers SET active=false, heartbeat_at=clock_timestamp() WHERE queue_name=$1 AND worker_id=$2", [this.policy.queueName, workerId])
269
+ }
270
+
271
+ async putArtifact(content: Buffer): Promise<RsiArtifactReference> {
272
+ const reference = await this.artifactStore.put(content)
273
+ await this.pool.query("INSERT INTO headlesscode_rsi_artifacts(sha256,storage_id,object_key,backend,byte_length) VALUES($1,$2,$3,$4,$5) ON CONFLICT(sha256) DO NOTHING", [reference.sha256, reference.storageId, reference.objectKey, reference.backend, reference.byteLength])
274
+ const verified = await this.pool.query("SELECT storage_id,object_key,backend,byte_length FROM headlesscode_rsi_artifacts WHERE sha256=$1", [reference.sha256])
275
+ if (verified.rows[0]?.storage_id !== reference.storageId || verified.rows[0]?.object_key !== reference.objectKey || Number(verified.rows[0]?.byte_length) !== reference.byteLength || verified.rows[0]?.backend !== reference.backend) throw new Error("stored RSI artifact metadata did not match uploaded content")
276
+ return reference
277
+ }
278
+
279
+ async getArtifact(sha256: string): Promise<Buffer> {
280
+ const result = await this.pool.query("SELECT storage_id,object_key,backend,byte_length FROM headlesscode_rsi_artifacts WHERE sha256=$1", [sha256])
281
+ if (result.rowCount !== 1) throw new Error(`RSI artifact not found: ${sha256}`)
282
+ const row = result.rows[0]
283
+ const reference: RsiArtifactReference = { sha256, storageId: row.storage_id, objectKey: row.object_key, byteLength: Number(row.byte_length), backend: row.backend }
284
+ if (reference.backend !== this.artifactStore.backend) throw new Error("RSI artifact backend differs from the configured worker artifact store")
285
+ return this.artifactStore.get(reference)
286
+ }
287
+
288
+ async enqueue(input: { idempotencyKey: string; runId: string; candidateId: string; jobKind: FleetJob["jobKind"]; resourceClass: ResourceClass; concurrencyKey?: string; artifactSha256: string; payload: FleetJobPayload }): Promise<FleetJob> {
289
+ if (!input.idempotencyKey.trim()) throw new Error("job idempotencyKey must not be empty")
290
+ if (input.payload.schemaVersion !== 1 || input.payload.snapshotSha256 !== input.artifactSha256 || !/^[0-9a-f]{40,64}$/.test(input.payload.snapshotCommit)) throw new Error("job payload must bind to its sanitized snapshot artifact and commit")
291
+ if (input.payload.snapshotBytes < 1) throw new Error("job payload declares an invalid snapshot size")
292
+ return inTransaction(this.pool, async (client) => {
293
+ const jobId = randomUUID()
294
+ const inserted = await client.query(
295
+ `INSERT INTO headlesscode_rsi_jobs(job_id,queue_name,idempotency_key,run_id,candidate_id,job_kind,resource_class,concurrency_key,artifact_sha256,payload,status,max_attempts,lease_duration_ms)
296
+ VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,'queued',$11,$12) ON CONFLICT(queue_name,idempotency_key) DO NOTHING RETURNING *`,
297
+ [jobId, this.policy.queueName, input.idempotencyKey, input.runId, input.candidateId, input.jobKind, input.resourceClass, input.concurrencyKey ?? null, input.artifactSha256, JSON.stringify(input.payload), this.policy.maxAttempts, Math.max(this.policy.leaseMs, requiredFleetJobLeaseMs(input.jobKind, input.payload.timeoutMs, input.payload.evalCommands?.length ?? 0))],
298
+ )
299
+ if (inserted.rowCount === 1) return rowToJob(inserted.rows[0])
300
+ const existing = await client.query("SELECT * FROM headlesscode_rsi_jobs WHERE queue_name=$1 AND idempotency_key=$2 FOR UPDATE", [this.policy.queueName, input.idempotencyKey])
301
+ const row = existing.rows[0]
302
+ if (!row || row.run_id !== input.runId || row.candidate_id !== input.candidateId || row.job_kind !== input.jobKind || row.resource_class !== input.resourceClass || row.concurrency_key !== (input.concurrencyKey ?? null) || row.artifact_sha256.trim() !== input.artifactSha256 || stableJson(row.payload) !== stableJson(input.payload)) {
303
+ throw new Error(`idempotency key ${input.idempotencyKey} was reused for different RSI job contents`)
304
+ }
305
+ return rowToJob(row)
306
+ })
307
+ }
308
+
309
+ async claim(workerId: string): Promise<FleetJobLease | undefined> {
310
+ return inTransaction(this.pool, async (client) => {
311
+ const policyResult = await client.query("SELECT * FROM headlesscode_rsi_queue_policy WHERE queue_name=$1 FOR UPDATE", [this.policy.queueName])
312
+ const policy = policyResult.rows[0]
313
+ if (!policy) throw new Error("RSI fleet queue has not been migrated")
314
+ const workerResult = await client.query(
315
+ `SELECT *, heartbeat_at > clock_timestamp()-($3::text || ' milliseconds')::interval AS heartbeat_fresh
316
+ FROM headlesscode_rsi_workers WHERE queue_name=$1 AND worker_id=$2 FOR UPDATE`,
317
+ [this.policy.queueName, workerId, Number(policy.lease_ms) * 2],
318
+ )
319
+ const worker = workerResult.rows[0]
320
+ if (!worker?.active || worker.heartbeat_fresh !== true || !Array.isArray(worker.resource_classes)) return undefined
321
+
322
+ await client.query(
323
+ `WITH expired AS (
324
+ SELECT job_id FROM headlesscode_rsi_jobs WHERE queue_name=$1 AND status='leased' AND lease_expires_at <= clock_timestamp()
325
+ ORDER BY lease_expires_at LIMIT 100 FOR UPDATE SKIP LOCKED
326
+ )
327
+ UPDATE headlesscode_rsi_jobs AS jobs SET status=CASE WHEN jobs.attempts >= jobs.max_attempts THEN 'failed' ELSE 'queued' END,
328
+ error='worker lease expired before completion', lease_owner=NULL, lease_token=NULL, lease_expires_at=NULL, updated_at=clock_timestamp()
329
+ FROM expired WHERE jobs.job_id=expired.job_id`,
330
+ [this.policy.queueName],
331
+ )
332
+ const capacities = await client.query(
333
+ `SELECT count(*)::int AS total,
334
+ count(*) FILTER (WHERE lease_owner=$1)::int AS worker_total
335
+ FROM headlesscode_rsi_jobs WHERE queue_name=$2 AND status='leased' AND lease_expires_at > clock_timestamp()`,
336
+ [workerId, this.policy.queueName],
337
+ )
338
+ const active = capacities.rows[0]
339
+ if (Number(active.total) >= Number(policy.max_in_flight) || Number(active.worker_total) >= Number(worker.max_active)) return undefined
340
+ const allowedClasses = worker.resource_classes as ResourceClass[]
341
+ const next = await client.query(
342
+ `SELECT job.* FROM headlesscode_rsi_jobs AS job
343
+ JOIN headlesscode_rsi_queue_policy AS policy ON policy.queue_name=$1
344
+ WHERE job.queue_name=$1 AND job.status='queued' AND job.available_at <= clock_timestamp()
345
+ AND job.resource_class = ANY($2::text[])
346
+ AND (COALESCE((policy.class_capacity->>job.resource_class)::int, policy.max_in_flight) >
347
+ (SELECT count(*) FROM headlesscode_rsi_jobs AS active WHERE active.queue_name=$1 AND active.status='leased' AND active.lease_expires_at > clock_timestamp() AND active.resource_class=job.resource_class))
348
+ AND (job.concurrency_key IS NULL OR COALESCE((policy.key_capacity->>job.concurrency_key)::int, policy.max_in_flight) >
349
+ (SELECT count(*) FROM headlesscode_rsi_jobs AS active WHERE active.queue_name=$1 AND active.status='leased' AND active.lease_expires_at > clock_timestamp() AND active.concurrency_key=job.concurrency_key))
350
+ ORDER BY job.created_at, job.job_id LIMIT 1 FOR UPDATE OF job SKIP LOCKED`,
351
+ [this.policy.queueName, allowedClasses],
352
+ )
353
+ const jobRow = next.rows[0]
354
+ if (!jobRow) return undefined
355
+ const token = randomUUID()
356
+ const updated = await client.query(
357
+ `UPDATE headlesscode_rsi_jobs SET status='leased', attempts=attempts+1, lease_owner=$2, lease_token=$3,
358
+ lease_expires_at=clock_timestamp()+(lease_duration_ms::text || ' milliseconds')::interval, updated_at=clock_timestamp()
359
+ WHERE job_id=$1 AND queue_name=$4 AND status='queued' RETURNING *`,
360
+ [jobRow.job_id, workerId, token, this.policy.queueName],
361
+ )
362
+ return { job: rowToJob(updated.rows[0]), token, workerId }
363
+ })
364
+ }
365
+
366
+ async heartbeat(lease: FleetJobLease): Promise<boolean> {
367
+ const result = await this.pool.query(
368
+ `UPDATE headlesscode_rsi_jobs SET lease_expires_at=clock_timestamp()+(lease_duration_ms::text || ' milliseconds')::interval, updated_at=clock_timestamp()
369
+ WHERE job_id=$1 AND queue_name=$4 AND lease_owner=$2 AND lease_token=$3 AND status='leased' AND lease_expires_at > clock_timestamp()`,
370
+ [lease.job.jobId, lease.workerId, lease.token, this.policy.queueName],
371
+ )
372
+ return result.rowCount === 1
373
+ }
374
+
375
+ async complete(lease: FleetJobLease, resultValue: unknown): Promise<QueueMutationResult> {
376
+ const resultJson = JSON.stringify(resultValue)
377
+ return inTransaction(this.pool, async (client) => {
378
+ const updated = await client.query(
379
+ `UPDATE headlesscode_rsi_jobs SET status='completed', result=$4::jsonb, completed_at=clock_timestamp(), updated_at=clock_timestamp(),
380
+ lease_owner=NULL, lease_token=NULL, lease_expires_at=NULL
381
+ WHERE job_id=$1 AND queue_name=$5 AND lease_owner=$2 AND lease_token=$3 AND status='leased' AND lease_expires_at > clock_timestamp() RETURNING *`,
382
+ [lease.job.jobId, lease.workerId, lease.token, resultJson, this.policy.queueName],
383
+ )
384
+ if (updated.rowCount === 1) return { accepted: true, duplicate: false, job: rowToJob(updated.rows[0]) }
385
+ const existing = await client.query("SELECT * FROM headlesscode_rsi_jobs WHERE job_id=$1 AND queue_name=$2", [lease.job.jobId, this.policy.queueName])
386
+ const row = existing.rows[0]
387
+ if (row?.status === "completed" && stableJson(row.result) === stableJson(resultValue)) return { accepted: true, duplicate: true, job: rowToJob(row) }
388
+ return { accepted: false, duplicate: false, ...(row ? { job: rowToJob(row) } : {}) }
389
+ })
390
+ }
391
+
392
+ async fail(lease: FleetJobLease, error: string): Promise<boolean> {
393
+ const result = await this.pool.query(
394
+ `UPDATE headlesscode_rsi_jobs SET status=CASE WHEN attempts >= max_attempts THEN 'failed' ELSE 'queued' END, error=$4,
395
+ updated_at=clock_timestamp(), lease_owner=NULL, lease_token=NULL, lease_expires_at=NULL
396
+ WHERE job_id=$1 AND queue_name=$5 AND lease_owner=$2 AND lease_token=$3 AND status='leased' AND lease_expires_at > clock_timestamp()`,
397
+ [lease.job.jobId, lease.workerId, lease.token, error.slice(0, 4000), this.policy.queueName],
398
+ )
399
+ return result.rowCount === 1
400
+ }
401
+
402
+ async getJob(jobId: string): Promise<FleetJob | undefined> {
403
+ const result = await this.pool.query("SELECT * FROM headlesscode_rsi_jobs WHERE job_id=$1 AND queue_name=$2", [jobId, this.policy.queueName])
404
+ return result.rowCount === 1 ? rowToJob(result.rows[0]) : undefined
405
+ }
406
+
407
+ async getJobByIdempotencyKey(idempotencyKey: string): Promise<FleetJob | undefined> {
408
+ const result = await this.pool.query("SELECT * FROM headlesscode_rsi_jobs WHERE queue_name=$1 AND idempotency_key=$2", [this.policy.queueName, idempotencyKey])
409
+ return result.rowCount === 1 ? rowToJob(result.rows[0]) : undefined
410
+ }
411
+
412
+ async cancel(jobId: string, reason: string): Promise<boolean> {
413
+ const result = await this.pool.query(
414
+ `UPDATE headlesscode_rsi_jobs SET status='cancelled', error=$3, updated_at=clock_timestamp(), lease_owner=NULL, lease_token=NULL, lease_expires_at=NULL
415
+ WHERE queue_name=$1 AND job_id=$2 AND status='queued'`,
416
+ [this.policy.queueName, jobId, reason.slice(0, 4000)],
417
+ )
418
+ return result.rowCount === 1
419
+ }
420
+
421
+ async close(): Promise<void> {
422
+ await Promise.all([this.pool.end(), Promise.resolve(this.artifactStore.close?.())])
423
+ }
424
+ }
@@ -0,0 +1,21 @@
1
+ import { promoteArchivedCurriculumTask } from "./curriculum.js"
2
+
3
+ function argument(flag: string): string | undefined {
4
+ const index = process.argv.indexOf(flag)
5
+ return index >= 0 ? process.argv[index + 1] : undefined
6
+ }
7
+
8
+ async function main(): Promise<void> {
9
+ const archiveDir = argument("--archive-dir")
10
+ const taskId = argument("--task-id")
11
+ if (!archiveDir || !taskId || process.argv.some((value, index) => value.startsWith("--") && !["--archive-dir", "--task-id"].includes(value)) || process.argv.filter((value) => value === "--archive-dir").length !== 1 || process.argv.filter((value) => value === "--task-id").length !== 1) {
12
+ throw new Error("usage: headlesscode-rsi-promote-curriculum --archive-dir <path> --task-id <id>")
13
+ }
14
+ const task = await promoteArchivedCurriculumTask(archiveDir, taskId)
15
+ process.stdout.write(`${task.id} promoted at ${task.promotedAt}\n`)
16
+ }
17
+
18
+ main().catch((error: unknown) => {
19
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
20
+ process.exitCode = 1
21
+ })
@@ -5,7 +5,8 @@ export function formatCandidate(candidate: CandidateRecord): string {
5
5
  const parent = candidate.parentSelection ? `, parent=${candidate.parent} (${candidate.parentSelection.strategy})` : ""
6
6
  const mutation = candidate.mutationKind ? `, mutation=${candidate.mutationKind}` : ""
7
7
  const metrics = fitness ? `, metrics=${JSON.stringify(fitness.metrics)}` : ""
8
- return `${candidate.id}: ${candidate.status}${parent}${mutation}${fitness ? `, score=${fitness.score}, ${fitness.reason}` : ""}${metrics}`
8
+ const comparison = candidate.pairedComparison ? `, paired=${candidate.pairedComparison.reason} (${candidate.pairedComparison.baselinePasses}/${candidate.pairedComparison.trialCount} → ${candidate.pairedComparison.candidatePasses}/${candidate.pairedComparison.trialCount}; duration ratio ${candidate.pairedComparison.durationRatio.toFixed(3)})` : ""
9
+ return `${candidate.id}: ${candidate.status}${parent}${mutation}${fitness ? `, score=${fitness.score}, ${fitness.reason}` : ""}${comparison}${metrics}`
9
10
  }
10
11
 
11
12
  export function formatRunReport(run: RsiRunRecord, config: RsiConfig): string {
@@ -17,20 +18,46 @@ export function formatRunReport(run: RsiRunRecord, config: RsiConfig): string {
17
18
  `- Generations: ${run.generations}`,
18
19
  `- Started: ${run.startedAt}`,
19
20
  `- Finished: ${run.finishedAt ?? "in progress"}`,
20
- `- Baseline: ${run.baseline ? (run.baseline.ok ? "pass" : "fail") : "not run (dry-run)"}`,
21
+ `- Baseline: ${run.baseline ? (run.baseline.ok ? "pass" : "fail") : "not run (dry-run)"}${run.baselineFitness ? `, score=${run.baselineFitness.score}` : ""}`,
21
22
  `- Selected candidate: ${run.selected ?? "none"}`,
22
23
  `- Pareto candidates: ${run.selectedCandidates?.join(", ") || "none"}`,
23
24
  `- Parent policy: ${run.parentSelectionPolicy ?? config.parentSelectionPolicy ?? "champion-specialist-novelty"}`,
24
25
  `- Model candidates: ${run.modelCandidates?.map((model) => `${model.id} (${model.status})`).join(", ") || "none"}`,
25
26
  `- Experiment jobs: ${run.jobs?.length ?? 0}`,
26
27
  `- Trajectories: ${run.trajectoryRefs?.length ?? 0}`,
28
+ ...(config.computePolicy === "adaptive-independent" ? [
29
+ `- Adaptive budgets: trajectories ${config.maxTrajectories ?? config.population + 1}; total iterations ${config.maxTotalIterations ?? (config.population + 1) * config.maxIterations}; runtime admission ${config.maxRuntimeMs ?? 60 * 60_000} ms; max concurrent ${config.maxConcurrent}`,
30
+ ] : []),
27
31
  "",
28
32
  "## Candidates",
29
33
  "",
30
34
  ...run.candidates.map((candidate) => `- ${formatCandidate(candidate)}${candidate.failure ? `; failure=${candidate.failure}` : ""}`),
31
35
  "",
36
+ ...(run.adaptiveSearch?.length ? [
37
+ "## Adaptive allocation decisions",
38
+ "",
39
+ ...run.adaptiveSearch.map((decision) => `- Generation ${decision.generation}: ${decision.decision} (${decision.reason}); candidates=${decision.candidateIds.join(", ") || "none"}; evidence=${decision.evidence.map((entry) => `${entry.candidateId}:${entry.status}${entry.visiblePassRate === undefined ? "" : `:${entry.visiblePassRate.toFixed(3)}`}`).join(", ") || "none"}; allocated=${decision.allocatedTrajectories} trajectories/${decision.allocatedIterations} iterations; remaining=${decision.remainingTrajectories} trajectories/${decision.remainingIterations} iterations; elapsed=${decision.elapsedMs} ms.`),
40
+ "",
41
+ ] : []),
42
+ ...(run.curriculumTasks?.length ? [
43
+ "## Curriculum fixture status",
44
+ "",
45
+ ...run.curriculumTasks.map((task) => `- ${task.id}: ${task.promotedAt ? `promoted at ${task.promotedAt}` : task.validated ? "fixture-replay validated; not promoted" : "proposal; unvalidated"}; fixture=${task.fixtureId}; ${task.fixtureDescription}${task.validation ? `; base=${task.validation.baseCommit}; replays=${task.validation.replays.length}; reproducible=${task.validation.reproducible}; reason=${task.validation.reason}` : "; no executable validation recorded"}`),
46
+ "",
47
+ "Validated means the registered fixture reference passed, a known incorrect mutant was rejected, and two clean OpenShell replays produced identical output digests. It does not certify that task wording measures the named capability.",
48
+ "",
49
+ ] : []),
50
+ ...(run.adversarialReviews?.length ? [
51
+ "## Adversarial review",
52
+ "",
53
+ ...run.adversarialReviews.map((review) => `- ${review.candidateId}: ${review.status}; role=${review.role}; provider=${review.provider}; model=${review.model}; promptVersion=${review.promptVersion}; prompt=${review.promptSha256 || "unavailable"}; result=${review.resultSha256 || "unavailable"}; error=${review.errorSha256 || "none"}; tests=${review.testArtifactSha256 || "unavailable"}; OpenShell output=${review.testResults.map((entry) => entry.outputArtifactSha256).filter(Boolean).join(", ") || "none"}; penalty=${review.penaltyPoints}; findings=${review.findings.map((finding) => finding.severity + ":" + finding.message.replace(/\s+/g, " ").slice(0, 240)).join(" | ") || "none"}; ${review.testResults.map((entry) => entry.id + ":" + (entry.passed ? "pass" : "fail")).join(", ") || review.error || "no test result"}${review.summary ? `; summary=${review.summary.replace(/\s+/g, " ").slice(0, 400)}` : ""}`),
54
+ "",
55
+ "Adversarial tests run in a fresh OpenShell evaluation guest. The coordinator alone applies their pass/fail gate and bounded finding penalty.",
56
+ "",
57
+ ] : []),
32
58
  "## Guardrails",
33
59
  "",
60
+ `- Regression gate: ${config.regressionCommand}`,
34
61
  `- Visible evaluations: ${config.evalCommands.join("; ") || "none"}`,
35
62
  `- Hidden evaluations: ${config.hiddenEvalCommands.length || 0}`,
36
63
  `- Compute policy: ${config.computePolicy ?? "single"}`,
package/src/rsi/roles.ts CHANGED
@@ -28,9 +28,19 @@ export function resolveRoles(
28
28
  const roles: RsiRoleConfig = { ...defaultRoles(workerModel), ...configured }
29
29
  for (const role of ROLE_ENV_NAMES) {
30
30
  const model = env[envKey(role)]
31
- if (model) {
32
- const existing = roles[role]
33
- roles[role] = { ...(existing ?? { provider: "ollama" }), model } as RoleModelConfig
31
+ const prefix = `HEADLESSCODE_RSI_ROLE_${role.toUpperCase().replace(/-/g, "_")}_`
32
+ const provider = env[`${prefix}PROVIDER`]?.trim()
33
+ const baseUrl = env[`${prefix}BASE_URL`]?.trim()
34
+ const command = env[`${prefix}COMMAND`]?.trim()
35
+ if (provider && !["ollama", "openrouter", "command"].includes(provider)) throw new Error(`${prefix}PROVIDER must be ollama, openrouter, or command`)
36
+ if (model || provider || baseUrl || command) {
37
+ roles[role] = {
38
+ ...(roles[role] ?? { provider: "ollama" as const, model: workerModel }),
39
+ ...(model ? { model } : {}),
40
+ ...(provider ? { provider: provider as RoleModelConfig["provider"] } : {}),
41
+ ...(baseUrl ? { baseUrl } : {}),
42
+ ...(command ? { command } : {}),
43
+ }
34
44
  }
35
45
  }
36
46
  return roles
@@ -31,7 +31,13 @@ function metrics(candidate: CandidateRecord): MetricVector {
31
31
  }
32
32
 
33
33
  function eligible(candidates: CandidateRecord[]): CandidateRecord[] {
34
- return candidates.filter((candidate) => candidate.fitness && candidate.fitness.hardGates.committed !== false && candidate.commits[0])
34
+ return candidates.filter((candidate) =>
35
+ candidate.status === "accepted" &&
36
+ candidate.fitness &&
37
+ Object.values(candidate.fitness.hardGates).every(Boolean) &&
38
+ candidate.pairedComparison?.improved === true &&
39
+ candidate.commits.length > 0,
40
+ )
35
41
  }
36
42
 
37
43
  export interface ParentChoice {
@@ -0,0 +1,103 @@
1
+ import { createHash } from "node:crypto"
2
+ import { execFileSync } from "node:child_process"
3
+ import * as fs from "node:fs"
4
+ import * as path from "node:path"
5
+
6
+ export const RSI_TRAINING_FIXTURE_IDS = ["completion-discipline", "regression-recovery", "tool-efficiency"] as const
7
+ export const RSI_MODEL_EVAL_FIXTURE_IDS = ["generalization"] as const
8
+
9
+ export interface VerifiedTrainingDataset {
10
+ version: string
11
+ dataset: Buffer
12
+ datasetSha256: string
13
+ evaluationCells: Buffer
14
+ evaluationCellsSha256: string
15
+ manifest: Buffer
16
+ manifestSha256: string
17
+ trainingCellIds: string[]
18
+ evaluationCellIds: string[]
19
+ }
20
+
21
+ function digest(value: string | Buffer): string {
22
+ return createHash("sha256").update(value).digest("hex")
23
+ }
24
+
25
+ function sourceRecord(root: string, relativePath: string): { path: string; sha256: string } {
26
+ const content = fs.readFileSync(path.join(root, relativePath))
27
+ return { path: relativePath, sha256: digest(content) }
28
+ }
29
+
30
+ /** Build only from evaluator-owned fixtures whose reference passes and known mutant fails. */
31
+ export function buildVerifiedTrainingDataset(repoRoot: string): VerifiedTrainingDataset {
32
+ const fixtureRoot = path.join(repoRoot, "fixtures", "rsi-curriculum")
33
+ const validatorPath = path.join(fixtureRoot, "validate.mjs")
34
+ const sourceFiles = [sourceRecord(repoRoot, "fixtures/rsi-curriculum/validate.mjs")]
35
+ const trainingCellIds: string[] = []
36
+ const evaluationCellIds: string[] = []
37
+ const rows: Array<{ id: string; prompt: string; completion: string }> = []
38
+ const evaluationRows: Array<{ id: string; prompt: string; completion: string }> = []
39
+ const fixtureDigests: Record<string, string> = {}
40
+ for (const fixtureId of [...RSI_TRAINING_FIXTURE_IDS, ...RSI_MODEL_EVAL_FIXTURE_IDS]) {
41
+ execFileSync(process.execPath, [validatorPath, "--fixture", fixtureId], { cwd: repoRoot, timeout: 10_000, stdio: "pipe" })
42
+ const fixture = JSON.parse(fs.readFileSync(path.join(fixtureRoot, fixtureId + ".json"), "utf8")) as { id?: string; task?: string; cases?: Array<{ name?: string; input?: unknown[]; expected?: unknown }> }
43
+ if (fixture.id !== fixtureId || typeof fixture.task !== "string" || !Array.isArray(fixture.cases) || fixture.cases.length === 0) throw new Error("registered RSI training fixture is malformed: " + fixtureId)
44
+ const paths = [
45
+ "fixtures/rsi-curriculum/" + fixtureId + ".json",
46
+ "fixtures/rsi-curriculum/cases/" + fixtureId + ".mjs",
47
+ "fixtures/rsi-curriculum/mutants/" + fixtureId + ".mjs",
48
+ ]
49
+ for (const source of paths) sourceFiles.push(sourceRecord(repoRoot, source))
50
+ fixtureDigests[fixtureId] = digest(fs.readFileSync(path.join(repoRoot, paths[0]!)))
51
+ if ((RSI_TRAINING_FIXTURE_IDS as readonly string[]).includes(fixtureId)) {
52
+ const completion = fs.readFileSync(path.join(fixtureRoot, "cases", fixtureId + ".mjs"), "utf8").trim()
53
+ rows.push({
54
+ id: fixtureId,
55
+ prompt: "Implement this task as a JavaScript module exporting solve(...): " + fixture.task,
56
+ completion,
57
+ })
58
+ trainingCellIds.push(fixtureId)
59
+ } else {
60
+ const completion = fs.readFileSync(path.join(fixtureRoot, "cases", fixtureId + ".mjs"), "utf8").trim()
61
+ for (const item of fixture.cases) {
62
+ const id = fixtureId + ":" + (item.name ?? "")
63
+ evaluationCellIds.push(id)
64
+ evaluationRows.push({
65
+ id,
66
+ prompt: "Implement this task as a JavaScript module exporting solve(...): " + fixture.task + "\nCase input: " + JSON.stringify(item.input) + "\nExpected behavior: " + JSON.stringify(item.expected),
67
+ completion,
68
+ })
69
+ }
70
+ }
71
+ }
72
+ const dataset = Buffer.from(rows.map((row) => JSON.stringify(row)).join("\n") + "\n")
73
+ const datasetSha256 = digest(dataset)
74
+ const evaluationCells = Buffer.from(evaluationRows.map((row) => JSON.stringify(row)).join("\n") + "\n")
75
+ const evaluationCellsSha256 = digest(evaluationCells)
76
+ const manifestValue = {
77
+ schemaVersion: 1,
78
+ backend: "transformers-peft-qlora-v1",
79
+ trainingFixtureIds: [...RSI_TRAINING_FIXTURE_IDS],
80
+ evaluationFixtureIds: [...RSI_MODEL_EVAL_FIXTURE_IDS],
81
+ trainingCellIds,
82
+ evaluationCellIds,
83
+ fixtureDigests,
84
+ sourceFiles: sourceFiles.sort((a, b) => a.path.localeCompare(b.path)),
85
+ datasetSha256,
86
+ evaluationCellsSha256,
87
+ rowCount: rows.length,
88
+ maxSteps: 8,
89
+ seed: 1337,
90
+ }
91
+ const manifest = Buffer.from(JSON.stringify(manifestValue, null, 2) + "\n")
92
+ return {
93
+ version: digest(manifest),
94
+ dataset,
95
+ datasetSha256,
96
+ evaluationCells,
97
+ evaluationCellsSha256,
98
+ manifest,
99
+ manifestSha256: digest(manifest),
100
+ trainingCellIds,
101
+ evaluationCellIds,
102
+ }
103
+ }
@@ -59,7 +59,7 @@ export async function captureCandidateTrajectory(
59
59
  const messages = calls.flatMap((call) => call.messages ?? [])
60
60
  const toolCalls = calls.reduce((count, call) => count + (call.response?.message?.tool_calls?.length ?? 0), 0)
61
61
  const outcome = trajectoryOutcome(candidate)
62
- const trusted = outcome === "success" && candidate.fitness?.hardGates.noProtectedPathViolation === true
62
+ const trusted = outcome === "success" && candidate.fitness !== undefined && Object.values(candidate.fitness.hardGates).every(Boolean)
63
63
  const record: TrajectoryRecord = {
64
64
  id: `trajectory-${candidate.id}`,
65
65
  task: candidate.mutation,