diff --git a/.agents/pm/features/pm-rl-whrd.toon b/.agents/pm/features/pm-rl-whrd.toon new file mode 100644 index 00000000..58f4814a --- /dev/null +++ b/.agents/pm/features/pm-rl-whrd.toon @@ -0,0 +1,93 @@ +id: pm-rl-whrd +title: Train the bounded LM in external Compose jobs across recursive generations +description: "Reuse durable process ownership, reservations, receipts and recovery for bounded causal LM fits in isolated resource-limited containers; retain held-out evaluation and promotion in the controller." +type: Feature +status: in_progress +priority: 2 +tags[3]: execution,llm-adapter,recursive-training +created_at: "2026-10-15T22:07:13.392Z" +updated_at: "2026-10-16T06:12:51.664Z" +author: codex-agent +acceptance_criteria: "Two real generations with successor collection; SIGKILL recovery, cancellation, corrupt checkpoint and limit refusals, one-launch contention; behavioural revert proofs and unchanged npm and Bun release gates." +parent: pm-rl-apvf +comments[23]{created_at,author,text}: + "2026-10-15T22:07:43.111Z",codex-agent,"Design: extend the existing process contract with a typed LM payload and checkpoint validator; share owner fences, atomic reservation, staged adoption, cancellation and settlement. Keep held-out examples in the controller. Bind immutable image, memory and CPU ceilings into programme and job identity; send only frozen weights, source adapter, training strings and collected rewards to a networkless non-root container. Add real recursive generations and crash/cancellation/limit/contention drills, then behavioural revert proofs and unchanged release gates." + "2026-10-15T22:16:06.750Z",codex-agent,"First real Compose evidence: generation 1 checkpoint 058aa3d2c08438abd18cf0e9b455aca99ddb95492de852201c55c79a0672130d -> 37e503748a170be32e1d506997b6c0cc5c9ee256b24ab01fb49cf0564da07b4c; held-out sampled reward 0.183854, six steps, 1152 target tokens, observed wall 5567 ms, fit CPU 820 ms, peak RSS 138629120 bytes, retained staging 88930 bytes. Generation 2 collected with that promoted digest and trained -> 086f78992283cba37929321abe3961ca6132a52c332bf8a8387a52f22141cbdb; reward 0.292396, six steps, 1152 tokens, wall 5654 ms, CPU 803 ms, peak RSS 143323136 bytes, staging 92364 bytes. Both promoted through the existing gate. Real controller SIGKILL after completion before receipt commit recovered the same checkpoint and original reservation with one launch. Mid-fit cancellation retained input/events and process_cancelled. Full drills and coverage verification continue. The first linked run reached its default 120-second timeout; rerun will explicitly allow the complete integration duration." + "2026-10-15T22:24:09.789Z",codex-agent,"Validated linked run: pm test pm-rl-whrd --run --progress --timeout 900 passed 8/8, zero failures/skips/cancellations/todo. Real kernel memory drill retained partial allocation evidence and daemon oom_killed=true at 201326592 bytes; the time drill began the real 1000-step fit before exceeding its 15000 ms allowance. Both refused promotion and successor collection. Two-generation execution took 54134.161 ms; independent verification/replay/status took 4470.981 ms. Canonical hashes and held-out objectives match the first measurement. Final fit resources: generation 1 wall 4297 ms, CPU 713 ms, RSS 138207232 bytes, staging 89057 bytes; generation 2 wall 4266 ms, CPU 759 ms, RSS 144183296 bytes, staging 92509 bytes; both six steps and 1152 target tokens. Daemon receipts verify memory=268435456, CPU=1, no network and non-root user. Existing Compose barrier variability is tracked in pm-rl-8nev. Revert proofs and complete release gates remain in progress." + "2026-10-15T22:29:39.962Z",codex-agent,"Behavioural revert proofs in isolated source snapshots, unchanged tests: (1) restore only executePendingGeneration from origin/main, then node --test --test-name-pattern=\"real Compose LM promotes two\" test/lm-compose.test.ts: 0/1, no external process jobs. (2) Change only executeReservedProcessJob planned-branch guard to relaunch compose-lm running jobs, then node --test --test-name-pattern=\"real Compose LM staged completion\" test/lm-compose.test.ts: 0/1, exclusive staged input already exists instead of recovery. (3) Remove only the measured memory/token refusal in validateLmProcessCheckpoint, then node --test --test-name-pattern=\"LM real fit publishes\" test/lm-compose.test.ts: 0/1, missing expected exception for invalid measured use. Restore those functions, then node --test --test-name-pattern=\"real Compose LM promotes two|real Compose LM staged completion|LM real fit publishes\" test/lm-compose.test.ts: 3/3, zero failures/skips/cancellations/todo. No live source, tests, thresholds or container memory ceilings were changed by the proofs." + "2026-10-15T22:41:43.334Z",codex-agent,The first full npm release attempt reached the existing decision-recovery SIGKILL-at-every-HTTP-to-PM-boundary test and hit its unchanged 300000 ms timeout. The case is outside the authored LM path; baseline attribution and a complete passing retry are still required. No deadline or coverage threshold has been raised. Focused LM 8/8 and restored behavioural proofs 3/3 remain passed. + "2026-10-15T23:04:35.050Z",codex-agent,"Gate follow-up pm-rl-bi4v now runs the two independent HTTP receipt datasets concurrently at each crash boundary, at most two scenarios active. No boundary, assertion, resource bound or test/coverage threshold changed. The paired covered case passed directly and through its PM-linked run (156944.936 ms, 1/1). This addresses the narrow serial timing margin observed in the first full release attempt. Complete fresh npm/Bun gates remain required." + "2026-10-15T23:09:56.615Z",codex-agent,"Stopped the first, already-failed ordinary-temp release attempt after its recorded HTTP receipt timeout. Fresh full gates now use an isolated executable RAM-backed temporary tracker directory, with isolated HOME and disabled global/system Git configuration. Tests, all four 100% thresholds, source inventory, deadlines and container limits are unchanged. The separate ordinary-filesystem real Compose LM run and all eight real-container drills passed before this environment change; their measured evidence remains in docs/lm-adapter.md. Do not interpret the full RAM-temp gates as power-loss or physical-disk durability evidence." + "2026-10-15T23:27:03.863Z",codex-agent,"The second full attempt exposed the existing orphan test startup barrier (pm-rl-8nev). Its final fix waits for the real running-job write and then starts the existing 15-second partial-artifact interval; the 30-second trainer allowance and recovery/accounting assertions remain. Direct covered and PM-linked checks pass. Image names and labels are unique per validation worker, allowing isolated npm/Bun gates without cross-worker image cleanup. The production trainer contract remains the implementation in ed4069c." + "2026-10-16T00:15:16.855Z",codex-agent,"The remaining HTTP full-gate timeout was traced to SIGKILL fixture children inheriting coverage flags/raw-counter output plus the code checkout cwd. The built child now reports and enforces that it is uninstrumented and runs in its scratch project; parent source coverage stays unchanged. Focused explicit-raw-directory run passes all eight boundaries, PM-linked run passes inside 300 seconds, and removing only child coverage isolation fails on the runtime observation. Production LM code remains unchanged. Full gate restarts will be staggered so their long HTTP crash drills do not contend." + "2026-10-16T00:28:06.054Z",codex-agent,"Additional receipt consistency regression: the pre-fix processLmTraining accepted a negative fit wall time, which participates in the combined generation deadline. An isolated pre-fix snapshot with the new assertions fails 0/1 (missing expected exception). Require fit wall >=0 and fit wall <= the completion process-wall measurement before evaluation. Both negative and larger-than-process durations are now refused as process_receipt_invalid. The fixed covered regression passes 1/1; normal measured fits remain valid. No resource allowance, deadline or threshold changed." + "2026-10-16T00:31:03.675Z",codex-agent,"Fit-clock guard validation: covered direct regression and PM-linked --only-last run both pass 1/1 with zero skips. Explicit behavioural revert in an isolated source snapshot removes only the processLmTraining clock guard; the unchanged regression fails 0/1 with missing expected exception. Restore the guard and it passes 1/1. Valid canonical tensors and original usage remain unchanged; both negative and greater-than-reported fit durations refuse before evaluation." + "2026-10-16T01:39:54.120Z",codex-agent,"Complete npm validation at df70cb0: all 590 tests pass, zero failed/cancelled/skipped. Line, branch and function coverage are 100%, but the unchanged statement gate correctly refuses 3977/3978 V8 blocks (99.97%). Raw counters identify the System One promotion-refusal null argument in executePendingGeneration. Extending the existing real-tracker terminal-refusal acceptance case across bandit and System One preserves every assertion and exercises compatibility with the shared LM lifecycle helper. The older Bun attempt was stopped before completion; it is not passing evidence. Both literal release gates will rerun after this coverage addition." + "2026-10-16T01:42:49.046Z",codex-agent,"Coverage acceptance addition passes directly 1/1 in 18.475 seconds and through PM-linked execution 1/1 in 39.849 seconds, zero skips. Both real bandit and HTTP System One programmes exhaust one permitted promotion, keep terminal refusal stable, create no external job, make no replay query and refuse approval switching. Raw counters explicitly enter the missing null-argument block. Diagnostic union with the completed 590/590 run is 3979/3979 blocks across all 30 authored sources; this union is not a completed release gate. Production source is byte-identical to df70cb0. Typecheck, lint and changelog checks pass. Restarting both literal release gates at the new committed test tree." + "2026-10-16T02:52:09.986Z",codex-agent,"At f405171 the completed npm attempt confirms all 8 new LM cases and the expanded System One compatibility case pass. Raw counters separately confirm 3979/3979 blocks across all 30 authored sources, with 100% line/branch/function coverage. The complete gate is rejected by the pre-existing packed CLI performance bound: 589/590 pass, measured 23890 ms versus 20000 ms. Reusing pm-rl-7c7m for exact evidence. An independent same-source Bun gate continues; a clean same-source npm validation checkout is prepared. This failed attempt and focused diagnostics are not gate approval." + "2026-10-16T03:30:45.008Z",codex-agent,"Completed literal bun run release:check at f405171 in an isolated regular checkout: exit 0, 590/590 tests passed, zero failed/cancelled/skipped. Exact source inventory 30 files and unchanged statements/lines/branches/functions thresholds all 100.00%; 272 documented declarations; zero duplicate clones; approved identity audit; zero production vulnerabilities; packed contents 105 files; generated changelog current. Node 24.19.0, Bun 1.3.5. Full gate scratch trackers used isolated executable RAM storage and HOME/Git configuration; the separate measured PM-linked 8/8 Compose suite used ordinary filesystem storage. The fresh literal npm gate at the identical committed source/test tree continues. Earlier failed attempts are excluded from this completed gate result." + "2026-10-16T03:47:16.337Z",codex-agent,"Final literal gates at source/test commit f405171 both exit 0: npm run release:check and bun run release:check each pass 590/590 tests with zero failed/cancelled/skipped. All 30 authored sources meet unchanged statements/lines/branches/functions 100.00%; 272 declarations documented; zero duplicate clones and production vulnerabilities; identity, publish attestation, 105-file package and generated changelog pass. Node 24.19.0, npm 11.17.0, Bun 1.3.5. Separate regular checkouts use isolated HOME/Git configuration and executable RAM-backed scratch trackers. The ordinary-filesystem PM-linked 8/8 real-container evidence and resource table remain separately identified. Primary behavioural reverts fail 0/1 each and restored controls pass 3/3; fit-clock and child-coverage guards have additional 0/1 to 1/1 proofs. Cleanup verified zero owned containers and retained one task-built trainer image. Documentation/tracker-only receipts follow; source and tests remain byte-identical to this gated commit. Release configuration and dependency pins remain unchanged." + "2026-10-16T03:50:51.809Z",codex-agent,"Final metadata checks: strict PM health with required merge drivers exits 0 under CI-like isolated HOME; integrity and history drift checks are clean. Existing advisory storage findings remain preserved. The default user environment separately reports unrelated global extension/host skew, so no broad default-environment health claim is made. No dependency, global extension, telemetry setting or historical provenance was changed. Updated documentation packages successfully as the same 105-file distribution. Claims remain released and all items unclosed." + "2026-10-16T04:19:43.232Z",codex-agent,"Review round 1: fetched origin; main is already an ancestor of the existing PR head, with no merge needed. Read the complete failed Node 26 job log. Investigating the bandit Compose controller winner exit after 31 seconds, including IPC continuation and process deadlines, and comparing shared contention/accounting changes against main. Will preserve exactly one launch/reservation assertions, run 20 repetitions per available Node 22/26 runtime, and run both unchanged release gates." + "2026-10-16T04:27:13.125Z",codex-agent,"Investigation: full Node 26 CI log fails on winner exit code 1 after 31157 ms, not a duplicate-launch assertion. Main-to-PR comparison leaves controller lease acquisition/renewal, reservation accounting and the bandit launch path unchanged. Twenty diagnostic Node 26.7.0 repetitions passed before synchronization changes; original CI refusal is absent from that log, so its exact cause is not established. The test previously paused at collect before a job existed. It now pauses after the committed running reservation, verifies exactly one unsettled one-step reservation before trainer startup, and retains winner exit 0, one launch, one job, one history create and idempotent resume. Added refusal message diagnostics and exact-job cleanup on failure. No production code, resource allowance, lease duration, timeout or coverage threshold changed." + "2026-10-16T04:27:17.535Z",codex-agent,"Behavioural revert proof: restoring only the original process-controller fixture with the new contention assertions retained fails deterministically at the reservation barrier (actual 0 jobs, expected 1; exit 1). Restored the fixture afterward. The new reservation state is therefore observable evidence rather than a scheduling delay. This proves the added synchronization invariant, not the historical CI timeout root cause." + "2026-10-16T04:36:54.188Z",codex-agent,"Final unchanged-allowance contention loops: Node 22.23.1 passes 20/20 and Node 26.7.0 passes 20/20, zero failures or skips; each invocation executes the real Compose contention test. Independently checked all 40 reporter logs. The new linked contention command also passes through pinned pm test --run --only-last --progress in the default schema context (1/1, exit 0). CI-style isolated HOME strict PM health with required merge drivers passes; default HOME separately reports the existing global pm-github version-skew gate failure and retained advisory findings. No global extension or history repair was performed. Next: full literal npm and Bun gates with isolated HOME/Git and executable RAM scratch trackers." + "2026-10-16T05:51:23.733Z",codex-agent,"Extended CI parity: the failed GitHub job used Node 26.12.0, while initially installed Node 26 was 26.7.0. Installed the exact 26.12.0 runtime with verified download checksum. Three diagnostic original-source contention probes also passed on 26.12.0, so the historical refusal remains unconfirmed. The final synchronized test now independently passes 20/20 on each of Node 22.23.1, 26.7.0 and 26.12.0 (60/60, zero failures/skips). Re-fetched main; it remains an ancestor, so no merge is needed. Literal Bun release gate at source 91d40fc passes 590/590, zero failures/skips/cancellations, all four coverage dimensions 100% across 30 sources, 272 documented declarations, and all audit/package/changelog checks. First npm attempt retains 589/590 with only the packed CLI child reaching its unchanged 25000 ms timeout; concurrent diagnostic probes added load, so that attempt is not passing gate evidence. Serial npm retry is now running alone on exact CI Node 26.12.0." + "2026-10-16T06:12:44.947Z",codex-agent,"Final local delivery at source/test commit 91d40fc: literal npm run release:check on Node 26.12.0 exits 0 with 590/590 passing, zero failed/skipped/cancelled, all four coverage dimensions 100% over all 30 sources, 272 documented declarations, and identity/audit/package/changelog checks passed. Literal bun run release:check on Node 26.7.0 and Bun 1.3.5 also exits 0 with 590/590 and the same unchanged four 100% thresholds. Packed CLI measured 10996.891 ms (npm retry) and 11086.851 ms (Bun). Preserve the first npm attempt: 589/590, sole unchanged 25000 ms packed CLI timeout; it is not passing gate evidence. Final contention loops are 20/20 each on Node 22.23.1, 26.7.0 and exact CI 26.12.0. The original CI winner refusal cannot be recovered from its log and did not reproduce; the reservation barrier invariant is proved, but the historical timeout root cause remains unconfirmed. Cleanup independently verifies zero repository Compose containers, and three owned validation images were removed after checking their exact owner label. Original history byte prefixes remain unchanged for both touched items. Main is already an ancestor; ready to push and wait for GitHub checks, without merging or resolving threads." +files[20]{path,scope}: + .dockerignore,project + compose.ts,project + index.ts,project + lm-trainer.Dockerfile,project + lm.ts,project + loop.ts,project + package.json,project + process-bandit.ts,project + process-lm-trainer.ts,project + process-lm.ts,project + process.ts,project + test/compose.test.ts,project + test/decision-recovery.test.ts,project + test/durable-loop.test.ts,project + test/fixtures/compose.Dockerfile,project + test/fixtures/decision-controller.ts,project + test/fixtures/lm-compose-trainer.ts,project + test/fixtures/lm-compose.Dockerfile,project + test/fixtures/process-controller.ts,project + test/lm-compose.test.ts,project +tests[4]: + - command: node --test test/lm-compose.test.ts + scope: project + provenance: + author: codex-agent + created_at: "2026-10-15T22:13:01.506Z" + source_kind: local_mutation + source_ref: feat/lm-compose-trainer + - command: "node --test --experimental-test-coverage --test-name-pattern=\"LM real fit publishes\" test/lm-compose.test.ts" + scope: project + timeout_seconds: 120 + provenance: + author: codex-agent + created_at: "2026-10-16T00:28:10.150Z" + source_kind: local_mutation + source_ref: feat/lm-compose-trainer + - command: node --test --experimental-test-coverage --test-coverage-include=index.ts --test-name-pattern='recorded promotion refusal stays terminal' test/durable-loop.test.ts + scope: project + provenance: + author: codex-agent + created_at: "2026-10-16T01:41:33.592Z" + source_kind: local_mutation + source_ref: feat/lm-compose-trainer + - command: "node --test --test-name-pattern=\"two real Compose controllers\" test/compose.test.ts" + scope: project + provenance: + author: codex-agent + created_at: "2026-10-16T04:27:20.119Z" + source_kind: local_mutation + source_ref: feat/lm-compose-trainer +docs[4]{path,scope}: + docs/lm-adapter.md,project + docs/process-trainer.md,project + README.md,project + RECURSIVE_TRAINING.md,project +body: "" diff --git a/.agents/pm/history/pm-rl-7c7m.jsonl b/.agents/pm/history/pm-rl-7c7m.jsonl index 9266ca78..5bfa86d5 100644 --- a/.agents/pm/history/pm-rl-7c7m.jsonl +++ b/.agents/pm/history/pm-rl-7c7m.jsonl @@ -1,3 +1,25 @@ {"hash_algorithm":"sha256","ts":"2026-10-15T18:17:46.948Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f5d0e8949390cc13c5995346","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-rl-7c7m"},{"op":"add","path":"/metadata/title","value":"Stabilize packed LM CLI timing under the unchanged acceptance bound"},{"op":"add","path":"/metadata/description","value":"The full release gate observed an existing packed LM CLI timing failure: measured CLI 22138 ms against its unchanged 20000 ms bound. The same unmodified test passed alone at 8955 ms. Investigate repeatable runtime and IO overhead under full coverage without hiding source or weakening the acceptance bound."},{"op":"add","path":"/metadata/type","value":"Task"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":3},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-10-15T18:17:46.948Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-15T18:17:46.948Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"Repeated full npm and Bun gates satisfy the existing 20-second packed LM CLI acceptance on declared runner resources; coverage scope and all four thresholds remain unchanged; measurements distinguish CLI time from total test setup time."},{"op":"add","path":"/metadata/parent","value":"pm-rl-mjo5"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"0fc84578a4fbba7c524ccc4b20b410bf09d194ef2beb95d1a6742ff2a13b9155","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"db2dba05a71d16c960aec9e05dc27985e3ca30bf0cebb9b81ea7c1f8793614e8"} {"hash_algorithm":"sha256","ts":"2026-10-15T18:19:08.367Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f5d0e8949390cc13c5995346","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T18:19:08.367Z"},{"op":"add","path":"/metadata/files","value":[{"path":"test/lm-cli.test.ts","scope":"project"}]}],"before_hash":"0fc84578a4fbba7c524ccc4b20b410bf09d194ef2beb95d1a6742ff2a13b9155","after_hash":"8be49d9af283b8287daa9fcb3521d0ffc33b55f2edc9676b9924cba414888c8a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"464a9c0bfb24681001a73bd8a0a00507bb752bd2d8c306a2c8c73cc5a4f8ed99"} {"hash_algorithm":"sha256","ts":"2026-10-15T18:19:13.038Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f5d0e8949390cc13c5995346","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T18:19:13.038Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/lm-adapter.md","scope":"project"}]}],"before_hash":"8be49d9af283b8287daa9fcb3521d0ffc33b55f2edc9676b9924cba414888c8a","after_hash":"f45f9d1ca6989ee6255adb8eea8a0bc697b3cc5d4cbe4cc48c256cd6dc5ccd73","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"6f29f6f065d4aa79e67b3884e083c668c24ed7609030dcc5a9cf4926b60f608a"} +{"hash_algorithm":"sha256","ts":"2026-10-16T02:20:21.723Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T02:20:21.723Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"f45f9d1ca6989ee6255adb8eea8a0bc697b3cc5d4cbe4cc48c256cd6dc5ccd73","after_hash":"e6e05bd04cd8038cc16814c9c16c163d2bb899269f6ae5159cf60397a5f1188f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"5068cc742c2b69b2c3ccee22019647695a09ac9da3411cdc0a8288f63c6e4e87"} +{"hash_algorithm":"sha256","ts":"2026-10-16T02:20:24.093Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T02:20:24.093Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"e6e05bd04cd8038cc16814c9c16c163d2bb899269f6ae5159cf60397a5f1188f","after_hash":"b7faa22780c0eabd853677ea25767dfe68db95ef907d7adb22ec9ff8cb581f61","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"3c6f191635824df7bac0fa572cfb0cf3cb511275d94ad5b69afc07a637c80c84"} +{"hash_algorithm":"sha256","ts":"2026-10-16T02:20:26.445Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T02:20:26.445Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-16T02:20:26.445Z","author":"codex-agent","text":"Reused for the existing packed LM CLI timing failure during the Compose LM release gate. At f405171 the unchanged test reports failure after 28607.147 ms total test time; exact failure detail awaits the completed Node reporter footer, so this is not asserted to be the measured CLI duration. The previous complete df70cb0 test run passed this unchanged case at measured CLI 14697.724 ms and all 590 tests passed (that gate separately refused one statement block, now covered). No deadline, allowance or threshold was changed. The current attempt continues only to retain complete diagnostics; it is not gate approval. New real-container LM execution, staged recovery and cancellation already pass in this attempt."}]}],"before_hash":"b7faa22780c0eabd853677ea25767dfe68db95ef907d7adb22ec9ff8cb581f61","after_hash":"8b025474929522aeea62a28613052ad205795b78f89b5ff94c4fd6886dc956df","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"2d86631ef5969fb473aaa770941dc5fe6c114304d4b42998c6791b0ab017024d"} +{"hash_algorithm":"sha256","ts":"2026-10-16T02:20:29.086Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T02:20:29.086Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/lm-cli.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-16T02:20:28.996Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}]}],"before_hash":"8b025474929522aeea62a28613052ad205795b78f89b5ff94c4fd6886dc956df","after_hash":"94c57cf41a6ff1304f13c118b3df910d522e12f97c0d5d7ed3ce7241ae7753bc","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"d760765a30312474916720e653da5586dc898338f5347c7829b6b59a4fe6c40d"} +{"hash_algorithm":"sha256","ts":"2026-10-16T02:52:07.374Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-16T02:52:07.374Z","author":"codex-agent","text":"Completed f405171 full npm diagnostics: 590 total, 589 pass, 1 fail, zero cancelled/skipped. The sole failure is measured real CLI 23890 ms versus unchanged 20000 ms. All new Compose LM cases pass; L/B/F are 100%; separate raw-counter diagnostic is 3979/3979 blocks across 30 authored sources. PM-linked direct command first fails before training because default schema PM_PATH redirects implicit SDK init to existing sandbox settings. Supported none context correctly requires snapshot/isolated workspace. With none plus snapshot the command reaches the real CLI and fails its unchanged 25000 ms child timeout. No timeout or threshold was weakened, no settings were forced, and no upstream PM changes or issues were filed. The context interaction and timing are separate observations."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T02:52:07.374Z"}],"before_hash":"94c57cf41a6ff1304f13c118b3df910d522e12f97c0d5d7ed3ce7241ae7753bc","after_hash":"75056b6f9e054677b4c484f3c12ce15a5c14dc649da0b41fbf38c746e3eb1d6e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e4a98628f9007f50c176314bfd80b054f94bbb075ea0448185c72541bd4c9cab"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:03:22.523Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-16T03:03:22.522Z","author":"codex-agent","text":"Node 26.7 focused packed acceptance also reaches the unchanged 25000 ms child timeout, so changing to the existing CI runtime does not resolve the observed timing. Inspecting a bounded numerical optimization in an isolated source snapshot: memoize at most 128 next-token logit rows within one fixed-policy held-out evaluation, with a fresh cache for each policy/evaluation. Keep all episodes, softmax calculations, LCG draws, rewards and gate inputs identical. Plan a deterministic real-weight access-count regression against the uncached sampler plus exact seeded reward equality, including a behavioural revert. No main worktree source or threshold has changed yet."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:03:22.523Z"}],"before_hash":"75056b6f9e054677b4c484f3c12ce15a5c14dc649da0b41fbf38c746e3eb1d6e","after_hash":"55a9bbbb30896b2e21b6d2cb395c8cb9ec550d25444d3d4f1144c90a48c815f8","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"fec0b8698a05087c6b18dfab1edd0b5321499aeff8a6d4057728877685d7d0ba"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:04:23.609Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-16T03:04:23.609Z","author":"codex-agent","text":"The independent f405171 Bun gate now passes the unchanged packed CLI case at measured 9620.706 ms (17643.969 ms total test time), with the same canonical checkpoints and held-out rewards. This follows the 23890 ms full npm failure and the focused timeout observations, so uncontrolled timing variation remains evidenced. The bounded cache idea was inspected but no sampling source or tests were changed; the prepared prototype contains no implementation changes and is discarded. Keep the product source/test tree frozen while completing both literal gates in isolated validation checkouts. This does not claim the timing follow-up is fixed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:04:23.609Z"}],"before_hash":"55a9bbbb30896b2e21b6d2cb395c8cb9ec550d25444d3d4f1144c90a48c815f8","after_hash":"197147a83fdd24095689919f27d58a78994771c4f178f2b1aff33e00d357a4aa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"c9c2a3f7923c178060aa517f14fbd631c9cb169e2634765bd8a837269eec51e4"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:30:46.801Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-16T03:30:46.801Z","author":"codex-agent","text":"The full literal Bun release gate at f405171 completes exit 0: all 590 tests pass, zero skips, every coverage dimension 100% over 30 sources, plus audit/pack/changelog. Its unchanged packed LM CLI measured 9620.706 ms. This successful run does not remove the retained 23890 ms npm failure or focused timeouts; timing variability remains an open follow-up. No sampling optimization or threshold change was applied."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:30:46.801Z"}],"before_hash":"197147a83fdd24095689919f27d58a78994771c4f178f2b1aff33e00d357a4aa","after_hash":"a6ad49488c083e55304461bb56f225550a184bd445cc6c5d22cc9847168e7943","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0487d33b4ccfdffece39b17d08a6e1d1391da5e4d6ee6e3305a4a90adb23e1c0"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:20.390Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-16T03:47:20.390Z","author":"codex-agent","text":"Final same-source isolated literal npm release gate passes 590/590 with zero skips and all four coverage dimensions 100%; packed LM CLI measured 11716.141 ms. Literal Bun also passes 590/590 and measured 9620.706 ms. Retain the observed 23890 ms failed attempt and focused timeouts as evidence of unresolved timing variability; no sampling optimization or threshold change was applied. PM test UX observations are separate: default schema PM_PATH collides with this fixture implicit init; none requires snapshot/isolated workspace, and none plus snapshot reaches the benchmark. Automatic result tracking is disabled, so results are recorded in comments. No upstream PM repository changes or issues were filed. Return this existing follow-up to open, without claiming its timing stability criterion solved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:20.390Z"}],"before_hash":"a6ad49488c083e55304461bb56f225550a184bd445cc6c5d22cc9847168e7943","after_hash":"8a60e44927bdd20fe53a672820510657433473779c2a2f3009d064c86a87c13e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"c56ee885b6dc616924fee31e6e0abefe0fb4c108ab7fe0e50763c6fec47f85d8"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:21.372Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:21.372Z"},{"op":"replace","path":"/metadata/status","value":"open"}],"before_hash":"8a60e44927bdd20fe53a672820510657433473779c2a2f3009d064c86a87c13e","after_hash":"708d3232bc5d1c4f877d77625fa4eac433c8737ac3451e3e520280878a855c65","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"c1038c0b8b175829a449307935b88f9bba6a22fd0c4accd8f3e85382627bb622"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:24.667Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:24.667Z"}],"before_hash":"708d3232bc5d1c4f877d77625fa4eac433c8737ac3451e3e520280878a855c65","after_hash":"d34b8288f1976396c35fcd89376ef44edb0a85f1fd9130987301cde0b2b90972","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ef26e8f0029dd1d8738b312d7871b66882c75b8a57b89a0d1e9f1b134df4306a"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:19:38.740Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:19:38.740Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"d34b8288f1976396c35fcd89376ef44edb0a85f1fd9130987301cde0b2b90972","after_hash":"b7b427ed0c10d5cb5dbe4e6053b498a594066ba46846fb9f7a056935d0d46040","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a4bc6ec370e0dcebea8ff8fd622dc6e1062ceadc263064c75856a772a9b6dedd"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:19:41.496Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:19:41.496Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"b7b427ed0c10d5cb5dbe4e6053b498a594066ba46846fb9f7a056935d0d46040","after_hash":"ccf8dd9e2d0ffd65934383d68397e25812bb025285f1f66e26af96f5512b248e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"dc9737802fe9958dcd4fcfa025f61aa0c7aebf49644cc1da6c351618b7cc6749"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:19:45.627Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-16T04:19:45.627Z","author":"codex-agent","text":"Review round 1: addressing the linked command failure under the normal PM test context. Will preserve append-only history and use pinned pm test remove/add, then run the linked acceptance through pm test without weakening its CLI timing assertion."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:19:45.627Z"}],"before_hash":"ccf8dd9e2d0ffd65934383d68397e25812bb025285f1f66e26af96f5512b248e","after_hash":"b909ae4c6496f2a1a450c9946e13d46cee9619205ed41e155199fafdea09cc31","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a0365245543b2279345f0b8ac81bf823f5008ff6d90900bd56c88741168987af"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:21:12.206Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_remove","patch":[{"op":"remove","path":"/metadata/tests"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:21:12.206Z"}],"before_hash":"b909ae4c6496f2a1a450c9946e13d46cee9619205ed41e155199fafdea09cc31","after_hash":"c4cb38e39189d338fc8f915a35829fd9994276b5674fbc14f91722807fbacda6","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"56c32550942d0bf2851b53f66819c594a8a08061d8dfc0b1edf2add62a35cd42"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:21:16.313Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:21:16.313Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/lm-cli.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-16T04:21:16.089Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}]}],"before_hash":"c4cb38e39189d338fc8f915a35829fd9994276b5674fbc14f91722807fbacda6","after_hash":"4b0dd976e591e3da2439fd25770d0b93269bb9b9a2ff631ba70f48d4979485c9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"53419a38c2bbb58ce876542141613a82665f2ee97dca48858d9c054352250f85"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:22:42.537Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-10-16T04:22:42.537Z","author":"codex-agent","text":"Revert proof: the original linked command under default schema context fails before training with init_existing_settings_requires_force. With only fixture init changed to pass its own explicit temporary pmRoot, the same normal pm test invocation reaches the unchanged CLI timing assertion: observed 22003 ms exceeds 20000 ms in this attempt. Pinned pm test remove/add replaced the link without editing existing history. Context collision is fixed; timing variability remains separate and the item stays active."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:22:42.537Z"}],"before_hash":"4b0dd976e591e3da2439fd25770d0b93269bb9b9a2ff631ba70f48d4979485c9","after_hash":"f292fc2be98d779baa735e0a0a4694eeb02f9d9234f1d04493a65cf59dfd3883","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"7558fa739067cab761fec135546b9c8d9fb04af47558646dc0911d40db6dc5ef"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:36:55.887Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/8","value":{"created_at":"2026-10-16T04:36:55.887Z","author":"codex-agent","text":"Further normal-schema linked runs reach training: observed CLI 23191 ms against the unchanged 20000 ms assertion, and a separate Node 26 attempt reaches the existing 25000 ms child timeout. Isolated HOME does not remove that timing variability on this host. These are benchmark failures, not fixture-init collisions; retain them without calling timing stability fixed. Automatic PM test-result recording remains disabled, so explicit comments retain the results."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:36:55.887Z"}],"before_hash":"f292fc2be98d779baa735e0a0a4694eeb02f9d9234f1d04493a65cf59dfd3883","after_hash":"be289e0146b95b6dd14a4d533417e084329f8d3fc2cf20829ae4fa682b3cd91d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e691a78e4edaee5f18f734a95c71e00aa6b8d24ca180e40ba062eb00da554950"} +{"hash_algorithm":"sha256","ts":"2026-10-16T05:51:25.100Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-10-16T05:51:25.100Z","author":"codex-agent","text":"The linked command now passes normally through pinned pm test pm-rl-7c7m --run --progress on Node 26.12.0, default schema context and source workspace, exit 0 (1/1). It retains the 20000 ms CLI assertion and 25000 ms child timeout. Its total PM-run time is 28377 ms, which includes package setup and must not be called measured CLI time. The literal Bun gate separately measures CLI 11086.851 ms and passes all 590 tests. Retain prior assertion failures and timeout observations as timing-variability evidence; fixture initialization is fixed by explicit temporary pmRoot, without forced settings or PM context overrides. Old history is unchanged and the test link was replaced through pinned remove/add."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T05:51:25.100Z"}],"before_hash":"be289e0146b95b6dd14a4d533417e084329f8d3fc2cf20829ae4fa682b3cd91d","after_hash":"003765ac5e451f41e13954b339724df5311af12ff80e3513913c17e1b4a5d434","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"6f62a59d42605200c8f196e53e81c7f5444c5a0a5d69806ac85f4e98d90d6af3"} +{"hash_algorithm":"sha256","ts":"2026-10-16T06:12:46.388Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-10-16T06:12:46.388Z","author":"codex-agent","text":"Final linked acceptance on exact CI Node 26.12.0 passes 1/1 through the normal pinned pm test --run --progress workflow: measured CLI 14597.809 ms, total linked-command time 28377 ms. Fixture initialization explicitly targets its own temporary pmRoot and does not require a PM context override or forced settings. Both final full literal gates pass 590/590 at unchanged thresholds; npm packed CLI measures 10996.891 ms and Bun 11086.851 ms. Retain earlier 22003/23191 ms assertion failures and 25000 ms child timeouts as unresolved timing variability rather than claiming stable performance. Return this timing follow-up to open; the linked-command context finding is fixed. Existing history is append-only and the original byte prefix was verified unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T06:12:46.388Z"}],"before_hash":"003765ac5e451f41e13954b339724df5311af12ff80e3513913c17e1b4a5d434","after_hash":"ac06fb80d8f50373619cc0796f21fc8821eb16dfbb1d6fd18c1c2f54890c3893","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"fecbc7bce114341aa483cfdde721208c80918d83151d55a19ae8d5fd860aa5d5"} +{"hash_algorithm":"sha256","ts":"2026-10-16T06:12:48.647Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T06:12:48.647Z"},{"op":"replace","path":"/metadata/status","value":"open"}],"before_hash":"ac06fb80d8f50373619cc0796f21fc8821eb16dfbb1d6fd18c1c2f54890c3893","after_hash":"f4771c7b94f34cbea5222310540918263b1145d8dc7c0f398f078d0abd97a146","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"197f62aacb4464727fca9daf7518cccdf545b5f51b55ceaf1edb44bd013f23ba"} +{"hash_algorithm":"sha256","ts":"2026-10-16T06:12:50.428Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T06:12:50.428Z"}],"before_hash":"f4771c7b94f34cbea5222310540918263b1145d8dc7c0f398f078d0abd97a146","after_hash":"ad5c8c18844098e852aabe75348e745735dfe3d04c4343127579c9dbbfbf447d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"65f1ec2037223bc5b6d00adf2eaf1418dc397670a3f68153b5ae4e605ea5cca5"} diff --git a/.agents/pm/history/pm-rl-8nev.jsonl b/.agents/pm/history/pm-rl-8nev.jsonl new file mode 100644 index 00000000..245771e4 --- /dev/null +++ b/.agents/pm/history/pm-rl-8nev.jsonl @@ -0,0 +1,9 @@ +{"hash_algorithm":"sha256","ts":"2026-10-15T22:23:36.056Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-rl-8nev"},{"op":"add","path":"/metadata/title","value":"Stabilize Compose cancellation barrier diagnostics under shared host load"},{"op":"add","path":"/metadata/description","value":"The existing cancellation-plus-cleanup-failure drill missed its partial-artifact barrier during concurrent package validation, then reported the wall watchdog refusal. Preserve current bounds while making trainer startup, timeout attribution and cleanup deterministic under shared host load. This predates the LM integration; its full release gate will independently verify the unchanged case."},{"op":"add","path":"/metadata/type","value":"Task"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":3},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-10-15T22:23:36.056Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-15T22:23:36.056Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"Reproduce the startup/barrier timing condition with real containers, retain cleanup evidence and distinguish startup delay from a trainer cancellation failure without weakening resource or coverage limits."},{"op":"add","path":"/metadata/parent","value":"pm-rl-mjo5"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"933e3d59e585617874755937e2e71fd0384541224aab814a982b3eeb38605fbe","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"73c5d00c8149d8aced971eee53adef75ffcfb83ba99ca9e604cead8764cc2b88"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:18:10.685Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:18:10.685Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"933e3d59e585617874755937e2e71fd0384541224aab814a982b3eeb38605fbe","after_hash":"9dd2f4053c53d2396a8e325aabf8a55540807aa223543846b7b4211e1a857512","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"aa8f8f946f08f66ec9b0f8d7e7116ffa1fe28a57f2a3a270499df861dcd30900"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:18:17.231Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:18:17.231Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"9dd2f4053c53d2396a8e325aabf8a55540807aa223543846b7b4211e1a857512","after_hash":"7c1c82f8b07304bac5187652a730a1714f3d70f2ccb374898e0a85ff7209ed78","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"4ea29061f9d0944adb297420bcb34dcfeedea380d9f013dafb84abb62bb12af2"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:18:24.790Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:18:24.790Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-15T23:18:24.790Z","author":"codex-agent","text":"The failed full Bun attempt reported Container did not publish its partial artifact in the existing orphan-recovery drill. It began the 15-second partial-artifact polling interval after first seeing a planned reservation, while the controller still committed its running transition. The revised drill waits on the existing real running-write IPC barrier, reads the job once, then starts the unchanged trainer startup barrier. Tighten fixture matching to a full process job ID so the loop collection step cannot satisfy the running barrier. Keep the 15-second barrier, 30-second trainer allowance and every orphan/recovery/accounting assertion; always kill the owned controller and clean its job container on assertion failure."}]}],"before_hash":"7c1c82f8b07304bac5187652a730a1714f3d70f2ccb374898e0a85ff7209ed78","after_hash":"06dbaaf7b01a8774bd8364fb0ac235cba1b178d8336fa055e0539c32d6cc86c8","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"04afcd2a1438723a10fd7058ee87c3e43a6a537eaa39dd25f7cd36620495417b"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:18:30.267Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:18:30.267Z"},{"op":"add","path":"/metadata/files","value":[{"path":"test/compose.test.ts","scope":"project"},{"path":"test/fixtures/process-controller.ts","scope":"project"}]}],"before_hash":"06dbaaf7b01a8774bd8364fb0ac235cba1b178d8336fa055e0539c32d6cc86c8","after_hash":"bfc0d4f78acd5ef5ab1a2e8e7ba25921089babd5a026ffbe848878e9893e4ee9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"0164f6edfb98830312f85f4b3228fa5db7ce33eae12f6b931a2d429be84202b4"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:18:36.261Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:18:36.261Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --experimental-test-coverage --test-name-pattern=\"real Compose recovery terminates an orphan\" test/compose.test.ts","scope":"project","timeout_seconds":180,"provenance":{"author":"codex-agent","created_at":"2026-10-15T23:18:36.042Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}]}],"before_hash":"bfc0d4f78acd5ef5ab1a2e8e7ba25921089babd5a026ffbe848878e9893e4ee9","after_hash":"4d6880c230915d9357e3ce65d5237c7bcd8e85b2c72d606f464f92a3b95d1ee6","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"3b702efbd5c87471f051a3304ee333eba0ccb6c165f40518693ad72e8e3b943e"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:26:52.822Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-15T23:26:52.822Z","author":"codex-agent","text":"The lifecycle-barrier drill passed under coverage directly (1/1) and through pm test pm-rl-8nev --run --progress (1/1, case 46658.868 ms, zero skips). An isolated check restoring the old broad fixture-ID predicate also passed (1/1, 51304.099 ms), so the extra predicate change was dropped; it is not claimed as a behavioural revert proof. The final test waits on the existing running-job barrier before the unchanged 15-second trainer-startup interval, reads the reservation once, and always cleans its owned controller/container. The observed failure was the startup-artifact assertion; no production recovery defect was demonstrated. Test image builds now use unique worker names/labels so cleanup cannot remove a peer validation image."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:26:52.822Z"}],"before_hash":"4d6880c230915d9357e3ce65d5237c7bcd8e85b2c72d606f464f92a3b95d1ee6","after_hash":"e78da8c88ff723b7f0819976d285f7bced4dcb6ddfaa42439f86740cb0a1e656","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e3bcb76e4f235d9f7ad89b56a98452219b411710c3282b633698045c3ec30522"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:17.620Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-16T03:47:17.620Z","author":"codex-agent","text":"Final verification: both complete npm and Bun release gates pass 590/590, zero skips, 100% across all four coverage dimensions. The real orphan drill passes in both. The fixture observes the persisted running state before the unchanged partial-artifact interval and preserves all SIGKILL, no-relaunch, retained-evidence and settlement assertions. Owned cleanup is verified; leave open for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:17.620Z"}],"before_hash":"e78da8c88ff723b7f0819976d285f7bced4dcb6ddfaa42439f86740cb0a1e656","after_hash":"c41f55fde8b50efe4bc3f461dc61b4cb942270d40c2be3d397496589c44bec08","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a079bc35c7500702bf1e2638da29047f07e97180918e7448e779920e9d2d99eb"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:22.975Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:22.975Z"}],"before_hash":"c41f55fde8b50efe4bc3f461dc61b4cb942270d40c2be3d397496589c44bec08","after_hash":"d39b1186d2bc41d26d1bbcd4e9faaeb3f423f458dfbdf09065bbb2cdf23048b2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"40f41917a55366be7beeefcdd059f77b39ce9db670f1462fa78388d9283a4854"} diff --git a/.agents/pm/history/pm-rl-bi4v.jsonl b/.agents/pm/history/pm-rl-bi4v.jsonl new file mode 100644 index 00000000..2784695d --- /dev/null +++ b/.agents/pm/history/pm-rl-bi4v.jsonl @@ -0,0 +1,15 @@ +{"hash_algorithm":"sha256","ts":"2026-10-15T22:41:40.256Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-rl-bi4v"},{"op":"add","path":"/metadata/title","value":"Keep the HTTP receipt crash drill within its unchanged timeout"},{"op":"add","path":"/metadata/description","value":"The existing SIGKILL-at-every-HTTP-to-PM-boundary integration case exceeded its unchanged 300-second timeout during full instrumented release validation. Compare the same case against the main baseline, identify startup, I/O or ownership waits, and preserve every crash boundary and assertion while bringing execution inside the bound."},{"op":"add","path":"/metadata/type","value":"Task"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-10-15T22:41:40.256Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-15T22:41:40.256Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"A baseline/candidate timing reproduction identifies the cause; the real eight-boundary drill and complete release gates pass with the existing 300-second timeout and unchanged coverage thresholds."},{"op":"add","path":"/metadata/parent","value":"pm-rl-apvf"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"c0b8de0febd09d747a32d3decdfd02f39dc95ed3f891f208459ec7c0b57dd884","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"f7857b79943c39c62c95a1e7b4c44e14e07d351f3327dfdafd854e51baa063d8"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:50:30.933Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:50:30.933Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-15T22:50:30.933Z","author":"codex-agent","text":"Diagnostic baseline: an isolated origin/main source archive, using the same pinned dependencies, passed node --test --test-concurrency=1 --experimental-test-coverage --test-coverage-include=*.ts --test-coverage-include=scripts/*.ts --test-name-pattern=\"SIGKILL at every HTTP-to-PM\" test/decision-recovery.test.ts in 276105.853 ms (1/1). The candidate full release run reached the unchanged 300000 ms timeout. This establishes a narrow baseline timing margin; it does not by itself attribute the full-run failure. Keep all eight boundaries and the original timeout. A complete passing candidate gate is still required."}]}],"before_hash":"c0b8de0febd09d747a32d3decdfd02f39dc95ed3f891f208459ec7c0b57dd884","after_hash":"e58b28dd5e461cf704ed45d3f1c0779a7b81eaff00351a8b6026a50e4d528d8a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"ca94e6e182cad649698965be874d96b1b0d50d7b1d5ede923a33a661eb1903e3"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:55:52.339Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:55:52.339Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"e58b28dd5e461cf704ed45d3f1c0779a7b81eaff00351a8b6026a50e4d528d8a","after_hash":"45d013c312b8caec66f6d090abdf755ec5b920e73ab27b08a5a431ad06a5a1d1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"769ce10ba71bcca52a19688b4340b68075cff24e740288897fa776c50e27657c"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:55:55.484Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:55:55.484Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"45d013c312b8caec66f6d090abdf755ec5b920e73ab27b08a5a431ad06a5a1d1","after_hash":"d4a22ac527b264bc52d9bf5a80cbed5dfbd4df6de09be3d3ea30c1e590947efa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b26256d3795702cea54eb3f85611bd1a85ccbc9b64fb4b3385cfc03abf21b44a"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:55:58.563Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-15T22:55:58.563Z","author":"codex-agent","text":"The eight crash scenarios use independent trackers and synthetic servers. Run the training/held-out pair concurrently for each of the four boundaries, with at most two scenarios active. Keep every real SIGKILL, replay, physical-request, token, identity and idempotency assertion and the original 300000 ms timeout. This changes scheduling only and avoids eight serial setup/replay intervals consuming the whole wall allowance. Verify the focused case under coverage, then require complete npm and Bun release gates."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:55:58.563Z"}],"before_hash":"d4a22ac527b264bc52d9bf5a80cbed5dfbd4df6de09be3d3ea30c1e590947efa","after_hash":"e41a81840c7aa1b89c9eea61042eb0af3ae5ad64f903cb6809d803290fa92afe","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"29abd4fe763eda54a0ed064e66e15cb684d56d0caf8991754dffd1f8d2e73a4b"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:56:02.765Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:56:02.765Z"},{"op":"add","path":"/metadata/files","value":[{"path":"test/decision-recovery.test.ts","scope":"project"}]}],"before_hash":"e41a81840c7aa1b89c9eea61042eb0af3ae5ad64f903cb6809d803290fa92afe","after_hash":"0d0e074dc64f4c99445adb00b5feb280e28c49bdd18593729845640948546c46","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"61449ab40d9c9071fef7595e914c700a36c21bbec28cfef67e9e32d438a9b945"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:56:05.797Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:56:05.797Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-concurrency=1 --experimental-test-coverage --test-name-pattern=\"SIGKILL at every HTTP-to-PM\" test/decision-recovery.test.ts","scope":"project","timeout_seconds":360,"provenance":{"author":"codex-agent","created_at":"2026-10-15T22:56:05.659Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}]}],"before_hash":"0d0e074dc64f4c99445adb00b5feb280e28c49bdd18593729845640948546c46","after_hash":"eed3c699365c66ff90621366d9ef6fc4e65a073868b27849ae69b30d61fa5da4","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"07857d2307a7b01f6d5d08a534d40e85e344959ba9e5420c6e71c5108580cfc6"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:01:06.478Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-15T23:01:06.478Z","author":"codex-agent","text":"The bounded pair schedule passed all eight real boundary scenarios under coverage in 254328.583 ms (1/1 test, zero skips). The isolated serial main-source diagnostic measured 276105.853 ms; the earlier serial full candidate hit 300000 ms. These are shared-host observations, not a controlled speedup claim. The diff changes only outer loop order and Promise.all of two independent metric cases; every assertion and the 300000 ms timeout remain."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:01:06.478Z"}],"before_hash":"eed3c699365c66ff90621366d9ef6fc4e65a073868b27849ae69b30d61fa5da4","after_hash":"1caa01894e8645f601361f226772e4fb3de95e35745868f6da43622581911d64","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"23460a2328b6baabc94115fc0b84e3b57028b493fbec63a9b1bb0219d223bfa4"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:04:28.665Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-15T23:04:28.665Z","author":"codex-agent","text":"PM-linked verification passed: pm test pm-rl-bi4v --run --progress executed the unchanged 300000 ms inner test successfully in 156944.936 ms, 1/1 with zero failures/skips/cancellations/todo. The outer harness permits 360 seconds for startup/reporting; the test deadline remains 300 seconds. All eight real crash scenarios retain their original assertions. Full release checks will verify the final schedule with all source coverage thresholds unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:04:28.665Z"}],"before_hash":"1caa01894e8645f601361f226772e4fb3de95e35745868f6da43622581911d64","after_hash":"ec49e2ffe998b68a0cbd5fd622a000bf846e9db2a297a95e60a3d97a8031281a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"294924389ff174b73e93bd642a95bfcae96634ff54f60d6ae0e501c20300aa8e"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:02:53.011Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-16T00:02:53.011Z","author":"codex-agent","text":"Both full covered runs on 4676320 still hit the original 300-second HTTP crash-test deadline under concurrent validation. Inspection found that fork inherited the parent coverage execArgv and raw V8 output directory and ran from the code checkout. SIGKILL-able built-code children now receive empty execArgv, no raw coverage directory and the actual scratch project cwd. Each child reports a boolean bootstrap observation, and the existing crash test requires it to be uninstrumented. The parent still executes and covers the authored source APIs; no source exclusion, threshold or deadline changes. All eight real crash/accounting scenarios remain."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:02:53.011Z"}],"before_hash":"ec49e2ffe998b68a0cbd5fd622a000bf846e9db2a297a95e60a3d97a8031281a","after_hash":"d278acac438d84ecd7172a7ac1090a5df76a68a3ed2ee189287a4f05046028a5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e327ab792afe5118c9eda16207b10238ce1c072ad5cef8707b49b5a32cce146f"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:02:58.598Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"test/fixtures/decision-controller.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:02:58.598Z"}],"before_hash":"d278acac438d84ecd7172a7ac1090a5df76a68a3ed2ee189287a4f05046028a5","after_hash":"bc01b4f9189d6d2502b260a145a1b99b7764467d1429373081e8e71b573f8af0","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"9e2b87dcb3b22d74312b76ee3dee9cac615357ff75ac84bf576da27d4b688b56"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:09:31.940Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-16T00:09:31.940Z","author":"codex-agent","text":"Focused verification with an explicit raw V8 parent directory passed all eight boundaries in 137862.796 ms (1/1, zero skips). Behavioural guard-removal proof in an isolated source snapshot: remove only empty execArgv and NODE_V8_COVERAGE suppression from launch, retain the new child observation and all assertions, then run the covered case; it fails 0/1 in 8975.060 ms with SIGKILL controller inherited V8 coverage. Restoring isolation passes 1/1. This changes neither source coverage scope nor test deadlines; built child dist code is not one of the gated TypeScript sources, and parent source APIs remain exercised."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:09:31.940Z"}],"before_hash":"bc01b4f9189d6d2502b260a145a1b99b7764467d1429373081e8e71b573f8af0","after_hash":"694cba1e5c37e89605d4d259ab2b760253d76548efb2db6c736a6b161188f7b5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"1616e8596d24753182e18bcef0c06a99f3f858345d782f779c1e3c4aad52c0cf"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:15:13.401Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-16T00:15:13.400Z","author":"codex-agent","text":"Latest PM-linked verification passes 1/1 with all eight boundaries, zero skips: case 206859.047 ms inside the unchanged 300000 ms test deadline. The full-gate child inheritance regression is explicitly checked at bootstrap; guard removal fails and restored isolation passes. The child also runs with the actual scratch project cwd. No authored source is excluded from coverage, and no test assertion or deadline was removed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:15:13.401Z"}],"before_hash":"694cba1e5c37e89605d4d259ab2b760253d76548efb2db6c736a6b161188f7b5","after_hash":"2a5eca0ae67cd0eaf23ac76787cb7a6fd0e4f5468be2b6e45454929058837f35","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"f56328025f62c844eecd2af08b115afa0604f94ad4ca210b11afbffb79ce0f6c"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:19.355Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-10-16T03:47:19.355Z","author":"codex-agent","text":"Final verification: both complete npm and Bun release gates pass 590/590, zero skips, all four coverage dimensions 100%. All eight real HTTP crash boundaries pass within the unchanged 300-second test limit. Independent datasets execute in bounded pairs; SIGKILL children report and enforce coverage isolation; the parent still covers the complete source inventory. Removing child coverage isolation fails the runtime regression, restored execution passes. No assertion, deadline or threshold was weakened. Leave open for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:19.355Z"}],"before_hash":"2a5eca0ae67cd0eaf23ac76787cb7a6fd0e4f5468be2b6e45454929058837f35","after_hash":"01c27524acd16e3960b1117152121f4ddc00978405aa462e73a2a4710a8c2f94","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0c5500dafadf4a049cdc37b5a539eaef1d1cc3a6a2dcd887ce952352548bed6b"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:23.897Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:23.897Z"}],"before_hash":"01c27524acd16e3960b1117152121f4ddc00978405aa462e73a2a4710a8c2f94","after_hash":"53e880efbdb7414937f3b8e90ea3ef86463ae65f5db3cf7614b59e97abfd85c8","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"40fef12c25c8645a9cc4bdfdc786be8ebfcb19df73ab053af60a53538e26ed37"} diff --git a/.agents/pm/history/pm-rl-whrd.jsonl b/.agents/pm/history/pm-rl-whrd.jsonl new file mode 100644 index 00000000..8ee6baf5 --- /dev/null +++ b/.agents/pm/history/pm-rl-whrd.jsonl @@ -0,0 +1,41 @@ +{"hash_algorithm":"sha256","ts":"2026-10-15T22:07:13.392Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-rl-whrd"},{"op":"add","path":"/metadata/title","value":"Train the bounded LM in external Compose jobs across recursive generations"},{"op":"add","path":"/metadata/description","value":"Reuse durable process ownership, reservations, receipts and recovery for bounded causal LM fits in isolated resource-limited containers; retain held-out evaluation and promotion in the controller."},{"op":"add","path":"/metadata/type","value":"Feature"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["execution","llm-adapter","recursive-training"]},{"op":"add","path":"/metadata/created_at","value":"2026-10-15T22:07:13.392Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-15T22:07:13.392Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"Two real generations with successor collection; SIGKILL recovery, cancellation, corrupt checkpoint and limit refusals, one-launch contention; behavioural revert proofs and unchanged npm and Bun release gates."},{"op":"add","path":"/metadata/parent","value":"pm-rl-apvf"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"9de6670e88ce231ea0eaa8e7f51b6f28c4c397f2e5e795031ee3e15d967568ec","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"9eb59ee43201da914f98cf0666b858ec6732fcb289f2a87555a41d2fb0dd445c"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:07:39.840Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:07:39.840Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"9de6670e88ce231ea0eaa8e7f51b6f28c4c397f2e5e795031ee3e15d967568ec","after_hash":"42e779505e7b6b7c5b50bc4f68c5ced4a021ad57cad204c75ca33bcc44603f0f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"9e1b22b8f2ee51ed2026170b1c7c1d8edef8e3145a37ba58bafe9b17c767c8f9"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:07:41.666Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:07:41.666Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"42e779505e7b6b7c5b50bc4f68c5ced4a021ad57cad204c75ca33bcc44603f0f","after_hash":"663022dfdcb0058a85a52ade8f9e2a2ffaae036b2a0d0aac2b7c5549cd538525","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b1c6f009ce0ccbf5fc2af3c827c18b34ab8ef4bf76e433e1a112975308f79f04"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:07:43.111Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:07:43.111Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-15T22:07:43.111Z","author":"codex-agent","text":"Design: extend the existing process contract with a typed LM payload and checkpoint validator; share owner fences, atomic reservation, staged adoption, cancellation and settlement. Keep held-out examples in the controller. Bind immutable image, memory and CPU ceilings into programme and job identity; send only frozen weights, source adapter, training strings and collected rewards to a networkless non-root container. Add real recursive generations and crash/cancellation/limit/contention drills, then behavioural revert proofs and unchanged release gates."}]}],"before_hash":"663022dfdcb0058a85a52ade8f9e2a2ffaae036b2a0d0aac2b7c5549cd538525","after_hash":"cdb29ad0c40c010b8b7049df7234f3a028a91546207c83e100e66865628ca17d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"2153d21ec1c3d3694cbc45775512984706a1c127db57fa3ca426315b27b2a74a"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:13:01.784Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:13:01.784Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/lm-compose.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-15T22:13:01.506Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}]}],"before_hash":"cdb29ad0c40c010b8b7049df7234f3a028a91546207c83e100e66865628ca17d","after_hash":"09bd6303dbc79f6997f289e52ed9c98a1d38356a3c67d3191666a7962af2e6eb","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ba5755d4cfeadc0bf42109ab2879c52d7768ed2b47b709df91ee334105044d52"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:15:55.164Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:15:55.164Z"},{"op":"add","path":"/metadata/files","value":[{"path":".dockerignore","scope":"project"},{"path":"compose.ts","scope":"project"},{"path":"index.ts","scope":"project"},{"path":"lm-trainer.Dockerfile","scope":"project"},{"path":"lm.ts","scope":"project"},{"path":"loop.ts","scope":"project"},{"path":"process-bandit.ts","scope":"project"},{"path":"process-lm-trainer.ts","scope":"project"},{"path":"process-lm.ts","scope":"project"},{"path":"process.ts","scope":"project"},{"path":"test/fixtures/lm-compose-trainer.ts","scope":"project"},{"path":"test/fixtures/lm-compose.Dockerfile","scope":"project"},{"path":"test/lm-compose.test.ts","scope":"project"}]}],"before_hash":"09bd6303dbc79f6997f289e52ed9c98a1d38356a3c67d3191666a7962af2e6eb","after_hash":"797f1f7177ed7a19c2ad1a68e3b1ea67031d28562e2e736c2840a7ca342ce42d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"552edb733b916100a559fe78b7405cab411f8766ce05297f5159262e895b3079"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:16:01.297Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:16:01.297Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/lm-adapter.md","scope":"project"},{"path":"README.md","scope":"project"},{"path":"RECURSIVE_TRAINING.md","scope":"project"}]}],"before_hash":"797f1f7177ed7a19c2ad1a68e3b1ea67031d28562e2e736c2840a7ca342ce42d","after_hash":"392dadc19f1a8057350e56a8da3fc83959ad3dc41e867573ca87469716116224","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"9fa7cb0b03d9fc6a982fbecee058802366c12236b970226f0efcf7676e2634f4"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:16:06.750Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-15T22:16:06.750Z","author":"codex-agent","text":"First real Compose evidence: generation 1 checkpoint 058aa3d2c08438abd18cf0e9b455aca99ddb95492de852201c55c79a0672130d -> 37e503748a170be32e1d506997b6c0cc5c9ee256b24ab01fb49cf0564da07b4c; held-out sampled reward 0.183854, six steps, 1152 target tokens, observed wall 5567 ms, fit CPU 820 ms, peak RSS 138629120 bytes, retained staging 88930 bytes. Generation 2 collected with that promoted digest and trained -> 086f78992283cba37929321abe3961ca6132a52c332bf8a8387a52f22141cbdb; reward 0.292396, six steps, 1152 tokens, wall 5654 ms, CPU 803 ms, peak RSS 143323136 bytes, staging 92364 bytes. Both promoted through the existing gate. Real controller SIGKILL after completion before receipt commit recovered the same checkpoint and original reservation with one launch. Mid-fit cancellation retained input/events and process_cancelled. Full drills and coverage verification continue. The first linked run reached its default 120-second timeout; rerun will explicitly allow the complete integration duration."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:16:06.750Z"}],"before_hash":"392dadc19f1a8057350e56a8da3fc83959ad3dc41e867573ca87469716116224","after_hash":"1646e0544aed5b727f9820daf08a8541159d4e86214be278c6c2579e72e78aaa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"eb82658f99fed5bcbc3ca253ad9cf619e3a3be6ab15a20505fad5be0fb584818"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:24:09.789Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-15T22:24:09.789Z","author":"codex-agent","text":"Validated linked run: pm test pm-rl-whrd --run --progress --timeout 900 passed 8/8, zero failures/skips/cancellations/todo. Real kernel memory drill retained partial allocation evidence and daemon oom_killed=true at 201326592 bytes; the time drill began the real 1000-step fit before exceeding its 15000 ms allowance. Both refused promotion and successor collection. Two-generation execution took 54134.161 ms; independent verification/replay/status took 4470.981 ms. Canonical hashes and held-out objectives match the first measurement. Final fit resources: generation 1 wall 4297 ms, CPU 713 ms, RSS 138207232 bytes, staging 89057 bytes; generation 2 wall 4266 ms, CPU 759 ms, RSS 144183296 bytes, staging 92509 bytes; both six steps and 1152 target tokens. Daemon receipts verify memory=268435456, CPU=1, no network and non-root user. Existing Compose barrier variability is tracked in pm-rl-8nev. Revert proofs and complete release gates remain in progress."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:24:09.789Z"}],"before_hash":"1646e0544aed5b727f9820daf08a8541159d4e86214be278c6c2579e72e78aaa","after_hash":"cc62e5d70247f6c0e59a86dcd27b46f7c644e1ee22a71278e0c59b9e7c2e1282","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e2258ffd539f4586e8e38a1cc6fbf9c83f6da6385d20c748af7611f4f7f9891a"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:24:16.348Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/12/path","value":"test/fixtures/compose.Dockerfile"},{"op":"replace","path":"/metadata/files/11/path","value":"test/compose.test.ts"},{"op":"replace","path":"/metadata/files/10/path","value":"process.ts"},{"op":"replace","path":"/metadata/files/9/path","value":"process-lm.ts"},{"op":"replace","path":"/metadata/files/8/path","value":"process-lm-trainer.ts"},{"op":"replace","path":"/metadata/files/7/path","value":"process-bandit.ts"},{"op":"replace","path":"/metadata/files/6/path","value":"package.json"},{"op":"add","path":"/metadata/files/13","value":{"path":"test/fixtures/lm-compose-trainer.ts","scope":"project"}},{"op":"add","path":"/metadata/files/14","value":{"path":"test/fixtures/lm-compose.Dockerfile","scope":"project"}},{"op":"add","path":"/metadata/files/15","value":{"path":"test/lm-compose.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:24:16.348Z"}],"before_hash":"cc62e5d70247f6c0e59a86dcd27b46f7c644e1ee22a71278e0c59b9e7c2e1282","after_hash":"499ba40fa3cf8514c713fa601015bda5b732e55291ae601f002c2ec351d832c3","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"28872f2f37c9c602a609901f5ea4912095c9375770b9a4ccca2c24a8dfb5c6ef"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:24:20.594Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/docs/2/path","value":"README.md"},{"op":"replace","path":"/metadata/docs/1/path","value":"docs/process-trainer.md"},{"op":"add","path":"/metadata/docs/3","value":{"path":"RECURSIVE_TRAINING.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:24:20.594Z"}],"before_hash":"499ba40fa3cf8514c713fa601015bda5b732e55291ae601f002c2ec351d832c3","after_hash":"57373846ac2efee51253d215b0c7fb98fa0bc844b2b12a02f9fbf73162ef5193","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"b3ec7f3dba0415d85b291ca06b2852e05693668e800f21da855377164f1c1fd6"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:29:39.962Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-15T22:29:39.962Z","author":"codex-agent","text":"Behavioural revert proofs in isolated source snapshots, unchanged tests: (1) restore only executePendingGeneration from origin/main, then node --test --test-name-pattern=\"real Compose LM promotes two\" test/lm-compose.test.ts: 0/1, no external process jobs. (2) Change only executeReservedProcessJob planned-branch guard to relaunch compose-lm running jobs, then node --test --test-name-pattern=\"real Compose LM staged completion\" test/lm-compose.test.ts: 0/1, exclusive staged input already exists instead of recovery. (3) Remove only the measured memory/token refusal in validateLmProcessCheckpoint, then node --test --test-name-pattern=\"LM real fit publishes\" test/lm-compose.test.ts: 0/1, missing expected exception for invalid measured use. Restore those functions, then node --test --test-name-pattern=\"real Compose LM promotes two|real Compose LM staged completion|LM real fit publishes\" test/lm-compose.test.ts: 3/3, zero failures/skips/cancellations/todo. No live source, tests, thresholds or container memory ceilings were changed by the proofs."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:29:39.962Z"}],"before_hash":"57373846ac2efee51253d215b0c7fb98fa0bc844b2b12a02f9fbf73162ef5193","after_hash":"a2ca3e36182d166eddcd20d343210acc22e4f21d6fb8c9b19b598ba9903d7c80","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"41549e68c85bec086027bda66da453c57d5e4d7ff38b32f58019008c1215f95e"} +{"hash_algorithm":"sha256","ts":"2026-10-15T22:41:43.334Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-15T22:41:43.334Z","author":"codex-agent","text":"The first full npm release attempt reached the existing decision-recovery SIGKILL-at-every-HTTP-to-PM-boundary test and hit its unchanged 300000 ms timeout. The case is outside the authored LM path; baseline attribution and a complete passing retry are still required. No deadline or coverage threshold has been raised. Focused LM 8/8 and restored behavioural proofs 3/3 remain passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T22:41:43.334Z"}],"before_hash":"a2ca3e36182d166eddcd20d343210acc22e4f21d6fb8c9b19b598ba9903d7c80","after_hash":"2d7606f0e8dd601d980c1d2fd5f62d24786b3f7e9f84af80d95a8d2c1b7468c0","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e6939cf7159490eae0d2698c059ef9b234b53d13d5bc9af4d81d00fe934c6a0c"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:04:31.782Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/15/path","value":"test/fixtures/lm-compose.Dockerfile"},{"op":"replace","path":"/metadata/files/14/path","value":"test/fixtures/lm-compose-trainer.ts"},{"op":"replace","path":"/metadata/files/13/path","value":"test/fixtures/compose.Dockerfile"},{"op":"replace","path":"/metadata/files/12/path","value":"test/decision-recovery.test.ts"},{"op":"add","path":"/metadata/files/16","value":{"path":"test/lm-compose.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:04:31.782Z"}],"before_hash":"2d7606f0e8dd601d980c1d2fd5f62d24786b3f7e9f84af80d95a8d2c1b7468c0","after_hash":"73b072f4689a471c8eb63403b7d52a4ba257a6a6a4b1163083a7289c037aaa3d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"0fa1afc3ec233684b849b5c1f13eca1ead5e8b1f345b4f54b5217091480a30ce"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:04:35.050Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-15T23:04:35.050Z","author":"codex-agent","text":"Gate follow-up pm-rl-bi4v now runs the two independent HTTP receipt datasets concurrently at each crash boundary, at most two scenarios active. No boundary, assertion, resource bound or test/coverage threshold changed. The paired covered case passed directly and through its PM-linked run (156944.936 ms, 1/1). This addresses the narrow serial timing margin observed in the first full release attempt. Complete fresh npm/Bun gates remain required."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:04:35.050Z"}],"before_hash":"73b072f4689a471c8eb63403b7d52a4ba257a6a6a4b1163083a7289c037aaa3d","after_hash":"1b8129181446b4a820edc41d1d79b23a74826e6c935d247205942d4ecac0845b","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"24b34183b1ce2c15e30136c173a13ddf7e768bf3941769b6c24fe3da89583595"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:09:56.615Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-15T23:09:56.615Z","author":"codex-agent","text":"Stopped the first, already-failed ordinary-temp release attempt after its recorded HTTP receipt timeout. Fresh full gates now use an isolated executable RAM-backed temporary tracker directory, with isolated HOME and disabled global/system Git configuration. Tests, all four 100% thresholds, source inventory, deadlines and container limits are unchanged. The separate ordinary-filesystem real Compose LM run and all eight real-container drills passed before this environment change; their measured evidence remains in docs/lm-adapter.md. Do not interpret the full RAM-temp gates as power-loss or physical-disk durability evidence."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:09:56.615Z"}],"before_hash":"1b8129181446b4a820edc41d1d79b23a74826e6c935d247205942d4ecac0845b","after_hash":"e5afb35e2856eba7520d93342916b2e079ca24004a921e8add437c6915777056","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"fb3bb14bfe83f154a79188d262276fe54a8854db0794991e643f63ff33f2959c"} +{"hash_algorithm":"sha256","ts":"2026-10-15T23:27:03.863Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-10-15T23:27:03.863Z","author":"codex-agent","text":"The second full attempt exposed the existing orphan test startup barrier (pm-rl-8nev). Its final fix waits for the real running-job write and then starts the existing 15-second partial-artifact interval; the 30-second trainer allowance and recovery/accounting assertions remain. Direct covered and PM-linked checks pass. Image names and labels are unique per validation worker, allowing isolated npm/Bun gates without cross-worker image cleanup. The production trainer contract remains the implementation in ed4069c."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-15T23:27:03.863Z"}],"before_hash":"e5afb35e2856eba7520d93342916b2e079ca24004a921e8add437c6915777056","after_hash":"77e25257e8bb99e12937a5b08bc758c5e82162f5897541774a8366b2e25b9549","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"57136b6c48d19a76676adbf725df22df337b9c8dbd77ff36b9b2e222140699b9"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:15:16.855Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/8","value":{"created_at":"2026-10-16T00:15:16.855Z","author":"codex-agent","text":"The remaining HTTP full-gate timeout was traced to SIGKILL fixture children inheriting coverage flags/raw-counter output plus the code checkout cwd. The built child now reports and enforces that it is uninstrumented and runs in its scratch project; parent source coverage stays unchanged. Focused explicit-raw-directory run passes all eight boundaries, PM-linked run passes inside 300 seconds, and removing only child coverage isolation fails on the runtime observation. Production LM code remains unchanged. Full gate restarts will be staggered so their long HTTP crash drills do not contend."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:15:16.855Z"}],"before_hash":"77e25257e8bb99e12937a5b08bc758c5e82162f5897541774a8366b2e25b9549","after_hash":"d427ded10a6e8d5b800225f5c293501c1d49aabac0606b81ef0240f745b123f0","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"fa53d620e217c766b1fdfe9e5060b60eaeb2fc3bf94078fb1a1c15c67ae81de8"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:28:06.054Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-10-16T00:28:06.054Z","author":"codex-agent","text":"Additional receipt consistency regression: the pre-fix processLmTraining accepted a negative fit wall time, which participates in the combined generation deadline. An isolated pre-fix snapshot with the new assertions fails 0/1 (missing expected exception). Require fit wall >=0 and fit wall <= the completion process-wall measurement before evaluation. Both negative and larger-than-process durations are now refused as process_receipt_invalid. The fixed covered regression passes 1/1; normal measured fits remain valid. No resource allowance, deadline or threshold changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:28:06.054Z"}],"before_hash":"d427ded10a6e8d5b800225f5c293501c1d49aabac0606b81ef0240f745b123f0","after_hash":"e0c58c6b231941b0c332fe880b702a9b2bae720e6c5c56f0c21f9c737bc66c8e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"5ce52ad998a857b6cb40619ec1e221d8473e7858a38ba6a9048a6612ca171206"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:28:10.281Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"node --test --experimental-test-coverage --test-name-pattern=\"LM real fit publishes\" test/lm-compose.test.ts","scope":"project","timeout_seconds":120,"provenance":{"author":"codex-agent","created_at":"2026-10-16T00:28:10.150Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:28:10.281Z"}],"before_hash":"e0c58c6b231941b0c332fe880b702a9b2bae720e6c5c56f0c21f9c737bc66c8e","after_hash":"56396d6022f0c01eafd19e5ca3977007bdccd05f1282097152bab7c767171c7c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"eff650d6a78be8628d2cf4357626c8703909b3edbd18acafb0758f72f51c35b3"} +{"hash_algorithm":"sha256","ts":"2026-10-16T00:31:03.675Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-10-16T00:31:03.675Z","author":"codex-agent","text":"Fit-clock guard validation: covered direct regression and PM-linked --only-last run both pass 1/1 with zero skips. Explicit behavioural revert in an isolated source snapshot removes only the processLmTraining clock guard; the unchanged regression fails 0/1 with missing expected exception. Restore the guard and it passes 1/1. Valid canonical tensors and original usage remain unchanged; both negative and greater-than-reported fit durations refuse before evaluation."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T00:31:03.675Z"}],"before_hash":"56396d6022f0c01eafd19e5ca3977007bdccd05f1282097152bab7c767171c7c","after_hash":"05506e9648c4b4a6d1dd2a3a990f7c3414972824195d2bac402afe1263dd6e8f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"530179b30ae092d092515604a9ca0611ee2621c4a4b47e87cfe9b78199933a4a"} +{"hash_algorithm":"sha256","ts":"2026-10-16T01:39:54.120Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-10-16T01:39:54.120Z","author":"codex-agent","text":"Complete npm validation at df70cb0: all 590 tests pass, zero failed/cancelled/skipped. Line, branch and function coverage are 100%, but the unchanged statement gate correctly refuses 3977/3978 V8 blocks (99.97%). Raw counters identify the System One promotion-refusal null argument in executePendingGeneration. Extending the existing real-tracker terminal-refusal acceptance case across bandit and System One preserves every assertion and exercises compatibility with the shared LM lifecycle helper. The older Bun attempt was stopped before completion; it is not passing evidence. Both literal release gates will rerun after this coverage addition."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T01:39:54.120Z"}],"before_hash":"05506e9648c4b4a6d1dd2a3a990f7c3414972824195d2bac402afe1263dd6e8f","after_hash":"70642c7d0ffb7ede89156f5fe5f74efca0408375db622ddd8c0925e0a65d5d55","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"bc036ed90902a1c063764918a6643db0634f0d62ba9b17012a7f3afed0e39b2b"} +{"hash_algorithm":"sha256","ts":"2026-10-16T01:41:31.778Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/16/path","value":"test/fixtures/lm-compose.Dockerfile"},{"op":"replace","path":"/metadata/files/15/path","value":"test/fixtures/lm-compose-trainer.ts"},{"op":"replace","path":"/metadata/files/14/path","value":"test/fixtures/compose.Dockerfile"},{"op":"replace","path":"/metadata/files/13/path","value":"test/durable-loop.test.ts"},{"op":"add","path":"/metadata/files/17","value":{"path":"test/lm-compose.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T01:41:31.778Z"}],"before_hash":"70642c7d0ffb7ede89156f5fe5f74efca0408375db622ddd8c0925e0a65d5d55","after_hash":"9ae324575ff594910083d0950fd841459744b438dd4513ec42039438592f08d9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"d2c5ac704bc92781d33fefed2d3997060ec37a07711b28adf0a783d36a79c256"} +{"hash_algorithm":"sha256","ts":"2026-10-16T01:41:33.681Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/2","value":{"command":"node --test --experimental-test-coverage --test-coverage-include=index.ts --test-name-pattern='recorded promotion refusal stays terminal' test/durable-loop.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-16T01:41:33.592Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T01:41:33.681Z"}],"before_hash":"9ae324575ff594910083d0950fd841459744b438dd4513ec42039438592f08d9","after_hash":"a26da1357d847414a574f08789c7ef87b5ef9df40aa9de8468c8c0bd21cb4501","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"47bc04e6958f1630f8a325bd259b0a6446966945fcb70a1e3288a4a451d5e0e6"} +{"hash_algorithm":"sha256","ts":"2026-10-16T01:42:49.046Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-10-16T01:42:49.046Z","author":"codex-agent","text":"Coverage acceptance addition passes directly 1/1 in 18.475 seconds and through PM-linked execution 1/1 in 39.849 seconds, zero skips. Both real bandit and HTTP System One programmes exhaust one permitted promotion, keep terminal refusal stable, create no external job, make no replay query and refuse approval switching. Raw counters explicitly enter the missing null-argument block. Diagnostic union with the completed 590/590 run is 3979/3979 blocks across all 30 authored sources; this union is not a completed release gate. Production source is byte-identical to df70cb0. Typecheck, lint and changelog checks pass. Restarting both literal release gates at the new committed test tree."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T01:42:49.046Z"}],"before_hash":"a26da1357d847414a574f08789c7ef87b5ef9df40aa9de8468c8c0bd21cb4501","after_hash":"57a688f2b26354617cb37f9ce173bae9aff06592863783302fc9ed8a1499495a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e185679f704536eaa96d52967a234082ed2446e702caf67483878eb95f49f09d"} +{"hash_algorithm":"sha256","ts":"2026-10-16T02:52:09.986Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/13","value":{"created_at":"2026-10-16T02:52:09.986Z","author":"codex-agent","text":"At f405171 the completed npm attempt confirms all 8 new LM cases and the expanded System One compatibility case pass. Raw counters separately confirm 3979/3979 blocks across all 30 authored sources, with 100% line/branch/function coverage. The complete gate is rejected by the pre-existing packed CLI performance bound: 589/590 pass, measured 23890 ms versus 20000 ms. Reusing pm-rl-7c7m for exact evidence. An independent same-source Bun gate continues; a clean same-source npm validation checkout is prepared. This failed attempt and focused diagnostics are not gate approval."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T02:52:09.986Z"}],"before_hash":"57a688f2b26354617cb37f9ce173bae9aff06592863783302fc9ed8a1499495a","after_hash":"501258f8bde95e4373ff6bfb7b75650dcc2ed6874fa268ed8a8f51a305ac6a23","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"74f02bfa9e650cdb2b2ba0f2d00c120c68712a71609a78feae4e07d92e6c7890"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:30:45.008Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-10-16T03:30:45.008Z","author":"codex-agent","text":"Completed literal bun run release:check at f405171 in an isolated regular checkout: exit 0, 590/590 tests passed, zero failed/cancelled/skipped. Exact source inventory 30 files and unchanged statements/lines/branches/functions thresholds all 100.00%; 272 documented declarations; zero duplicate clones; approved identity audit; zero production vulnerabilities; packed contents 105 files; generated changelog current. Node 24.19.0, Bun 1.3.5. Full gate scratch trackers used isolated executable RAM storage and HOME/Git configuration; the separate measured PM-linked 8/8 Compose suite used ordinary filesystem storage. The fresh literal npm gate at the identical committed source/test tree continues. Earlier failed attempts are excluded from this completed gate result."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:30:45.008Z"}],"before_hash":"501258f8bde95e4373ff6bfb7b75650dcc2ed6874fa268ed8a8f51a305ac6a23","after_hash":"51061236c30ca0a015ec68294e4c83e34196415285c6819006a43f53b8c864c1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"9dfcf716f7258059bd5df02e382a1e6d3b7174f65599a8c4ac27f2a722a4b3a7"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:15.084Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/17/path","value":"test/fixtures/lm-compose.Dockerfile"},{"op":"replace","path":"/metadata/files/16/path","value":"test/fixtures/lm-compose-trainer.ts"},{"op":"replace","path":"/metadata/files/15/path","value":"test/fixtures/decision-controller.ts"},{"op":"add","path":"/metadata/files/18","value":{"path":"test/lm-compose.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:15.084Z"}],"before_hash":"51061236c30ca0a015ec68294e4c83e34196415285c6819006a43f53b8c864c1","after_hash":"fcbac7269fddb2cefe0be93a85f367894b4c3988917760b8bfbd8c239aa4ad23","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"261c68bc4e5eaf654d3e78628a1af51f1c81d98ece755a37aab900f599141297"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:16.337Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/15","value":{"created_at":"2026-10-16T03:47:16.337Z","author":"codex-agent","text":"Final literal gates at source/test commit f405171 both exit 0: npm run release:check and bun run release:check each pass 590/590 tests with zero failed/cancelled/skipped. All 30 authored sources meet unchanged statements/lines/branches/functions 100.00%; 272 declarations documented; zero duplicate clones and production vulnerabilities; identity, publish attestation, 105-file package and generated changelog pass. Node 24.19.0, npm 11.17.0, Bun 1.3.5. Separate regular checkouts use isolated HOME/Git configuration and executable RAM-backed scratch trackers. The ordinary-filesystem PM-linked 8/8 real-container evidence and resource table remain separately identified. Primary behavioural reverts fail 0/1 each and restored controls pass 3/3; fit-clock and child-coverage guards have additional 0/1 to 1/1 proofs. Cleanup verified zero owned containers and retained one task-built trainer image. Documentation/tracker-only receipts follow; source and tests remain byte-identical to this gated commit. Release configuration and dependency pins remain unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:16.337Z"}],"before_hash":"fcbac7269fddb2cefe0be93a85f367894b4c3988917760b8bfbd8c239aa4ad23","after_hash":"63bc94ba19a1c524c3014448865da69714db7fb6115d4c7cf07833d5ed096e0c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"7284a117320c4f0c44c644d70dc13bab49d88eb209911ee4bf50774e6cb72bb7"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:47:22.191Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:47:22.191Z"}],"before_hash":"63bc94ba19a1c524c3014448865da69714db7fb6115d4c7cf07833d5ed096e0c","after_hash":"dba4b765da99ec3f25cc88cc39c1e2566aa2bf5f05fa448e1f9aded44f625f46","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"570cfacc2920b606a6c4c9c75aba51c9db27c48849ddc096cdeaef4cc40e0023"} +{"hash_algorithm":"sha256","ts":"2026-10-16T03:50:51.809Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0b7326fe7529e37d974d8e2f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"xhigh","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/16","value":{"created_at":"2026-10-16T03:50:51.809Z","author":"codex-agent","text":"Final metadata checks: strict PM health with required merge drivers exits 0 under CI-like isolated HOME; integrity and history drift checks are clean. Existing advisory storage findings remain preserved. The default user environment separately reports unrelated global extension/host skew, so no broad default-environment health claim is made. No dependency, global extension, telemetry setting or historical provenance was changed. Updated documentation packages successfully as the same 105-file distribution. Claims remain released and all items unclosed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T03:50:51.809Z"}],"before_hash":"dba4b765da99ec3f25cc88cc39c1e2566aa2bf5f05fa448e1f9aded44f625f46","after_hash":"120c703998f54d717078842f6d5cbbbc8962a3b9ad3442f020d00da5477029c2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"fee45a67a13a7d2b73ffc2195e328155cbb2451a4ab37b3fa9d159d22b34d508"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:19:34.099Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:19:34.099Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"120c703998f54d717078842f6d5cbbbc8962a3b9ad3442f020d00da5477029c2","after_hash":"04f9d813c02889416dc41915bcc9ad0de8823337207eab2bc11a22a555b19f4c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"4c1288bb0c51a1d5891e9582f6fa749a74e7145c62767ba75537ba4610745d82"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:19:43.232Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/17","value":{"created_at":"2026-10-16T04:19:43.232Z","author":"codex-agent","text":"Review round 1: fetched origin; main is already an ancestor of the existing PR head, with no merge needed. Read the complete failed Node 26 job log. Investigating the bandit Compose controller winner exit after 31 seconds, including IPC continuation and process deadlines, and comparing shared contention/accounting changes against main. Will preserve exactly one launch/reservation assertions, run 20 repetitions per available Node 22/26 runtime, and run both unchanged release gates."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:19:43.232Z"}],"before_hash":"04f9d813c02889416dc41915bcc9ad0de8823337207eab2bc11a22a555b19f4c","after_hash":"f13e68d6e32d8443bee5a9c707b1bd468ffbad79a5cce25ab9f22cb54bee2ae2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"493a21c03766685d8698fc32eae827fa632447de31304755f82a330ce7a992b6"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:22:48.812Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/18/path","value":"test/fixtures/process-controller.ts"},{"op":"add","path":"/metadata/files/19","value":{"path":"test/lm-compose.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:22:48.812Z"}],"before_hash":"f13e68d6e32d8443bee5a9c707b1bd468ffbad79a5cce25ab9f22cb54bee2ae2","after_hash":"700bc95ed3260fc1e6598ffc22f9838b04ee9cdd67593d053f6f4031e2b9c7ec","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"a7475f902447261d3c64e225adfc283fcf13043d1250b67f8f99156ebce116b9"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:27:13.125Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/18","value":{"created_at":"2026-10-16T04:27:13.125Z","author":"codex-agent","text":"Investigation: full Node 26 CI log fails on winner exit code 1 after 31157 ms, not a duplicate-launch assertion. Main-to-PR comparison leaves controller lease acquisition/renewal, reservation accounting and the bandit launch path unchanged. Twenty diagnostic Node 26.7.0 repetitions passed before synchronization changes; original CI refusal is absent from that log, so its exact cause is not established. The test previously paused at collect before a job existed. It now pauses after the committed running reservation, verifies exactly one unsettled one-step reservation before trainer startup, and retains winner exit 0, one launch, one job, one history create and idempotent resume. Added refusal message diagnostics and exact-job cleanup on failure. No production code, resource allowance, lease duration, timeout or coverage threshold changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:27:13.125Z"}],"before_hash":"700bc95ed3260fc1e6598ffc22f9838b04ee9cdd67593d053f6f4031e2b9c7ec","after_hash":"87c48c3ccef0d77cf7f978126adbdc592090e2e86563ff9e590eb5928a029e86","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"1017b7f4e5fac1c21159052c34d9bf6470f06f77a259f4bb53ab87eab35210f6"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:27:17.535Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/19","value":{"created_at":"2026-10-16T04:27:17.535Z","author":"codex-agent","text":"Behavioural revert proof: restoring only the original process-controller fixture with the new contention assertions retained fails deterministically at the reservation barrier (actual 0 jobs, expected 1; exit 1). Restored the fixture afterward. The new reservation state is therefore observable evidence rather than a scheduling delay. This proves the added synchronization invariant, not the historical CI timeout root cause."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:27:17.535Z"}],"before_hash":"87c48c3ccef0d77cf7f978126adbdc592090e2e86563ff9e590eb5928a029e86","after_hash":"37f85a3a1c8d52015d596e36e8c0929abc2cdf3f07943b34ec6be746c530a2b7","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"54c37ab780a7b8d8184a337b31a4ab4e068972d8ddd63efe78276f3dd4537a77"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:27:20.194Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/3","value":{"command":"node --test --test-name-pattern=\"two real Compose controllers\" test/compose.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-16T04:27:20.119Z","source_kind":"local_mutation","source_ref":"feat/lm-compose-trainer"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:27:20.194Z"}],"before_hash":"37f85a3a1c8d52015d596e36e8c0929abc2cdf3f07943b34ec6be746c530a2b7","after_hash":"c728f2874f88af891533919654712db5988559eec934fb67ed10b474ffd7b3f6","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"b6d0edba2fc3b2d05968202329ae9777877594587b06e2b8f670925e4eab6607"} +{"hash_algorithm":"sha256","ts":"2026-10-16T04:36:54.188Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/20","value":{"created_at":"2026-10-16T04:36:54.188Z","author":"codex-agent","text":"Final unchanged-allowance contention loops: Node 22.23.1 passes 20/20 and Node 26.7.0 passes 20/20, zero failures or skips; each invocation executes the real Compose contention test. Independently checked all 40 reporter logs. The new linked contention command also passes through pinned pm test --run --only-last --progress in the default schema context (1/1, exit 0). CI-style isolated HOME strict PM health with required merge drivers passes; default HOME separately reports the existing global pm-github version-skew gate failure and retained advisory findings. No global extension or history repair was performed. Next: full literal npm and Bun gates with isolated HOME/Git and executable RAM scratch trackers."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T04:36:54.188Z"}],"before_hash":"c728f2874f88af891533919654712db5988559eec934fb67ed10b474ffd7b3f6","after_hash":"fa732fb5df48b0210804228c782079ab94e8fbd6bc68cf9b2fca143d06c8b64e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"650af899a2169189b9a6103ff148030931e770e84a89dcba0d9cca210e8b8be2"} +{"hash_algorithm":"sha256","ts":"2026-10-16T05:51:23.733Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/21","value":{"created_at":"2026-10-16T05:51:23.733Z","author":"codex-agent","text":"Extended CI parity: the failed GitHub job used Node 26.12.0, while initially installed Node 26 was 26.7.0. Installed the exact 26.12.0 runtime with verified download checksum. Three diagnostic original-source contention probes also passed on 26.12.0, so the historical refusal remains unconfirmed. The final synchronized test now independently passes 20/20 on each of Node 22.23.1, 26.7.0 and 26.12.0 (60/60, zero failures/skips). Re-fetched main; it remains an ancestor, so no merge is needed. Literal Bun release gate at source 91d40fc passes 590/590, zero failures/skips/cancellations, all four coverage dimensions 100% across 30 sources, 272 documented declarations, and all audit/package/changelog checks. First npm attempt retains 589/590 with only the packed CLI child reaching its unchanged 25000 ms timeout; concurrent diagnostic probes added load, so that attempt is not passing gate evidence. Serial npm retry is now running alone on exact CI Node 26.12.0."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T05:51:23.733Z"}],"before_hash":"fa732fb5df48b0210804228c782079ab94e8fbd6bc68cf9b2fca143d06c8b64e","after_hash":"0e7089c38bb07105a8ec9efbcde9f9ff485d04b5664c6741d4d8aa37de0bd69f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"22f79158dbf99f358539e70ff661c48ba00510935b327a94f16d9d4bd585cc51"} +{"hash_algorithm":"sha256","ts":"2026-10-16T06:12:44.947Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/22","value":{"created_at":"2026-10-16T06:12:44.947Z","author":"codex-agent","text":"Final local delivery at source/test commit 91d40fc: literal npm run release:check on Node 26.12.0 exits 0 with 590/590 passing, zero failed/skipped/cancelled, all four coverage dimensions 100% over all 30 sources, 272 documented declarations, and identity/audit/package/changelog checks passed. Literal bun run release:check on Node 26.7.0 and Bun 1.3.5 also exits 0 with 590/590 and the same unchanged four 100% thresholds. Packed CLI measured 10996.891 ms (npm retry) and 11086.851 ms (Bun). Preserve the first npm attempt: 589/590, sole unchanged 25000 ms packed CLI timeout; it is not passing gate evidence. Final contention loops are 20/20 each on Node 22.23.1, 26.7.0 and exact CI 26.12.0. The original CI winner refusal cannot be recovered from its log and did not reproduce; the reservation barrier invariant is proved, but the historical timeout root cause remains unconfirmed. Cleanup independently verifies zero repository Compose containers, and three owned validation images were removed after checking their exact owner label. Original history byte prefixes remain unchanged for both touched items. Main is already an ancestor; ready to push and wait for GitHub checks, without merging or resolving threads."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T06:12:44.947Z"}],"before_hash":"0e7089c38bb07105a8ec9efbcde9f9ff485d04b5664c6741d4d8aa37de0bd69f","after_hash":"4acea874a351b48c7cb28f3c7ea84d12235391797229b0ec84674d10f099771e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"61d2c2568eabd57ce902efbb95fd1b205184d527c0af9f33dd3cecb9c80665a0"} +{"hash_algorithm":"sha256","ts":"2026-10-16T06:12:51.664Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"7a6c53759a996b88aafc5a5f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-16T06:12:51.664Z"}],"before_hash":"4acea874a351b48c7cb28f3c7ea84d12235391797229b0ec84674d10f099771e","after_hash":"30b0dd19789308a01bfab726a63188834f14bd2c6a5887b5c650f91cf2e08e33","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"8584656a2f2418f56c9d502dfe10ec4ea5163feb16a93aedb7bfbcc6e6b29860"} diff --git a/.agents/pm/tasks/pm-rl-7c7m.toon b/.agents/pm/tasks/pm-rl-7c7m.toon index 90e3251e..4079181c 100644 --- a/.agents/pm/tasks/pm-rl-7c7m.toon +++ b/.agents/pm/tasks/pm-rl-7c7m.toon @@ -6,12 +6,26 @@ status: open priority: 3 tags: [] created_at: "2026-10-15T18:17:46.948Z" -updated_at: "2026-10-15T18:19:13.038Z" +updated_at: "2026-10-16T06:12:50.428Z" author: codex-agent acceptance_criteria: Repeated full npm and Bun gates satisfy the existing 20-second packed LM CLI acceptance on declared runner resources; coverage scope and all four thresholds remain unchanged; measurements distinguish CLI time from total test setup time. parent: pm-rl-mjo5 +comments[11]{created_at,author,text}: + "2026-10-16T02:20:26.445Z",codex-agent,"Reused for the existing packed LM CLI timing failure during the Compose LM release gate. At f405171 the unchanged test reports failure after 28607.147 ms total test time; exact failure detail awaits the completed Node reporter footer, so this is not asserted to be the measured CLI duration. The previous complete df70cb0 test run passed this unchanged case at measured CLI 14697.724 ms and all 590 tests passed (that gate separately refused one statement block, now covered). No deadline, allowance or threshold was changed. The current attempt continues only to retain complete diagnostics; it is not gate approval. New real-container LM execution, staged recovery and cancellation already pass in this attempt." + "2026-10-16T02:52:07.374Z",codex-agent,"Completed f405171 full npm diagnostics: 590 total, 589 pass, 1 fail, zero cancelled/skipped. The sole failure is measured real CLI 23890 ms versus unchanged 20000 ms. All new Compose LM cases pass; L/B/F are 100%; separate raw-counter diagnostic is 3979/3979 blocks across 30 authored sources. PM-linked direct command first fails before training because default schema PM_PATH redirects implicit SDK init to existing sandbox settings. Supported none context correctly requires snapshot/isolated workspace. With none plus snapshot the command reaches the real CLI and fails its unchanged 25000 ms child timeout. No timeout or threshold was weakened, no settings were forced, and no upstream PM changes or issues were filed. The context interaction and timing are separate observations." + "2026-10-16T03:03:22.522Z",codex-agent,"Node 26.7 focused packed acceptance also reaches the unchanged 25000 ms child timeout, so changing to the existing CI runtime does not resolve the observed timing. Inspecting a bounded numerical optimization in an isolated source snapshot: memoize at most 128 next-token logit rows within one fixed-policy held-out evaluation, with a fresh cache for each policy/evaluation. Keep all episodes, softmax calculations, LCG draws, rewards and gate inputs identical. Plan a deterministic real-weight access-count regression against the uncached sampler plus exact seeded reward equality, including a behavioural revert. No main worktree source or threshold has changed yet." + "2026-10-16T03:04:23.609Z",codex-agent,"The independent f405171 Bun gate now passes the unchanged packed CLI case at measured 9620.706 ms (17643.969 ms total test time), with the same canonical checkpoints and held-out rewards. This follows the 23890 ms full npm failure and the focused timeout observations, so uncontrolled timing variation remains evidenced. The bounded cache idea was inspected but no sampling source or tests were changed; the prepared prototype contains no implementation changes and is discarded. Keep the product source/test tree frozen while completing both literal gates in isolated validation checkouts. This does not claim the timing follow-up is fixed." + "2026-10-16T03:30:46.801Z",codex-agent,"The full literal Bun release gate at f405171 completes exit 0: all 590 tests pass, zero skips, every coverage dimension 100% over 30 sources, plus audit/pack/changelog. Its unchanged packed LM CLI measured 9620.706 ms. This successful run does not remove the retained 23890 ms npm failure or focused timeouts; timing variability remains an open follow-up. No sampling optimization or threshold change was applied." + "2026-10-16T03:47:20.390Z",codex-agent,"Final same-source isolated literal npm release gate passes 590/590 with zero skips and all four coverage dimensions 100%; packed LM CLI measured 11716.141 ms. Literal Bun also passes 590/590 and measured 9620.706 ms. Retain the observed 23890 ms failed attempt and focused timeouts as evidence of unresolved timing variability; no sampling optimization or threshold change was applied. PM test UX observations are separate: default schema PM_PATH collides with this fixture implicit init; none requires snapshot/isolated workspace, and none plus snapshot reaches the benchmark. Automatic result tracking is disabled, so results are recorded in comments. No upstream PM repository changes or issues were filed. Return this existing follow-up to open, without claiming its timing stability criterion solved." + "2026-10-16T04:19:45.627Z",codex-agent,"Review round 1: addressing the linked command failure under the normal PM test context. Will preserve append-only history and use pinned pm test remove/add, then run the linked acceptance through pm test without weakening its CLI timing assertion." + "2026-10-16T04:22:42.537Z",codex-agent,"Revert proof: the original linked command under default schema context fails before training with init_existing_settings_requires_force. With only fixture init changed to pass its own explicit temporary pmRoot, the same normal pm test invocation reaches the unchanged CLI timing assertion: observed 22003 ms exceeds 20000 ms in this attempt. Pinned pm test remove/add replaced the link without editing existing history. Context collision is fixed; timing variability remains separate and the item stays active." + "2026-10-16T04:36:55.887Z",codex-agent,"Further normal-schema linked runs reach training: observed CLI 23191 ms against the unchanged 20000 ms assertion, and a separate Node 26 attempt reaches the existing 25000 ms child timeout. Isolated HOME does not remove that timing variability on this host. These are benchmark failures, not fixture-init collisions; retain them without calling timing stability fixed. Automatic PM test-result recording remains disabled, so explicit comments retain the results." + "2026-10-16T05:51:25.100Z",codex-agent,"The linked command now passes normally through pinned pm test pm-rl-7c7m --run --progress on Node 26.12.0, default schema context and source workspace, exit 0 (1/1). It retains the 20000 ms CLI assertion and 25000 ms child timeout. Its total PM-run time is 28377 ms, which includes package setup and must not be called measured CLI time. The literal Bun gate separately measures CLI 11086.851 ms and passes all 590 tests. Retain prior assertion failures and timeout observations as timing-variability evidence; fixture initialization is fixed by explicit temporary pmRoot, without forced settings or PM context overrides. Old history is unchanged and the test link was replaced through pinned remove/add." + "2026-10-16T06:12:46.388Z",codex-agent,"Final linked acceptance on exact CI Node 26.12.0 passes 1/1 through the normal pinned pm test --run --progress workflow: measured CLI 14597.809 ms, total linked-command time 28377 ms. Fixture initialization explicitly targets its own temporary pmRoot and does not require a PM context override or forced settings. Both final full literal gates pass 590/590 at unchanged thresholds; npm packed CLI measures 10996.891 ms and Bun 11086.851 ms. Retain earlier 22003/23191 ms assertion failures and 25000 ms child timeouts as unresolved timing variability rather than claiming stable performance. Return this timing follow-up to open; the linked-command context finding is fixed. Existing history is append-only and the original byte prefix was verified unchanged." files[1]{path,scope}: test/lm-cli.test.ts,project +tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test test/lm-cli.test.ts,project,codex-agent,"2026-10-16T04:21:16.089Z",local_mutation,feat/lm-compose-trainer docs[1]{path,scope}: docs/lm-adapter.md,project body: "" diff --git a/.agents/pm/tasks/pm-rl-8nev.toon b/.agents/pm/tasks/pm-rl-8nev.toon new file mode 100644 index 00000000..f3a8d3b6 --- /dev/null +++ b/.agents/pm/tasks/pm-rl-8nev.toon @@ -0,0 +1,22 @@ +id: pm-rl-8nev +title: Stabilize Compose cancellation barrier diagnostics under shared host load +description: "The existing cancellation-plus-cleanup-failure drill missed its partial-artifact barrier during concurrent package validation, then reported the wall watchdog refusal. Preserve current bounds while making trainer startup, timeout attribution and cleanup deterministic under shared host load. This predates the LM integration; its full release gate will independently verify the unchanged case." +type: Task +status: in_progress +priority: 3 +tags: [] +created_at: "2026-10-15T22:23:36.056Z" +updated_at: "2026-10-16T03:47:22.975Z" +author: codex-agent +acceptance_criteria: "Reproduce the startup/barrier timing condition with real containers, retain cleanup evidence and distinguish startup delay from a trainer cancellation failure without weakening resource or coverage limits." +parent: pm-rl-mjo5 +comments[3]{created_at,author,text}: + "2026-10-15T23:18:24.790Z",codex-agent,"The failed full Bun attempt reported Container did not publish its partial artifact in the existing orphan-recovery drill. It began the 15-second partial-artifact polling interval after first seeing a planned reservation, while the controller still committed its running transition. The revised drill waits on the existing real running-write IPC barrier, reads the job once, then starts the unchanged trainer startup barrier. Tighten fixture matching to a full process job ID so the loop collection step cannot satisfy the running barrier. Keep the 15-second barrier, 30-second trainer allowance and every orphan/recovery/accounting assertion; always kill the owned controller and clean its job container on assertion failure." + "2026-10-15T23:26:52.822Z",codex-agent,"The lifecycle-barrier drill passed under coverage directly (1/1) and through pm test pm-rl-8nev --run --progress (1/1, case 46658.868 ms, zero skips). An isolated check restoring the old broad fixture-ID predicate also passed (1/1, 51304.099 ms), so the extra predicate change was dropped; it is not claimed as a behavioural revert proof. The final test waits on the existing running-job barrier before the unchanged 15-second trainer-startup interval, reads the reservation once, and always cleans its owned controller/container. The observed failure was the startup-artifact assertion; no production recovery defect was demonstrated. Test image builds now use unique worker names/labels so cleanup cannot remove a peer validation image." + "2026-10-16T03:47:17.620Z",codex-agent,"Final verification: both complete npm and Bun release gates pass 590/590, zero skips, 100% across all four coverage dimensions. The real orphan drill passes in both. The fixture observes the persisted running state before the unchanged partial-artifact interval and preserves all SIGKILL, no-relaunch, retained-evidence and settlement assertions. Owned cleanup is verified; leave open for orchestrator verification." +files[2]{path,scope}: + test/compose.test.ts,project + test/fixtures/process-controller.ts,project +tests[1]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: + "node --test --experimental-test-coverage --test-name-pattern=\"real Compose recovery terminates an orphan\" test/compose.test.ts",project,180,codex-agent,"2026-10-15T23:18:36.042Z",local_mutation,feat/lm-compose-trainer +body: "" diff --git a/.agents/pm/tasks/pm-rl-bi4v.toon b/.agents/pm/tasks/pm-rl-bi4v.toon new file mode 100644 index 00000000..e1da281c --- /dev/null +++ b/.agents/pm/tasks/pm-rl-bi4v.toon @@ -0,0 +1,27 @@ +id: pm-rl-bi4v +title: Keep the HTTP receipt crash drill within its unchanged timeout +description: "The existing SIGKILL-at-every-HTTP-to-PM-boundary integration case exceeded its unchanged 300-second timeout during full instrumented release validation. Compare the same case against the main baseline, identify startup, I/O or ownership waits, and preserve every crash boundary and assertion while bringing execution inside the bound." +type: Task +status: in_progress +priority: 2 +tags: [] +created_at: "2026-10-15T22:41:40.256Z" +updated_at: "2026-10-16T03:47:23.897Z" +author: codex-agent +acceptance_criteria: A baseline/candidate timing reproduction identifies the cause; the real eight-boundary drill and complete release gates pass with the existing 300-second timeout and unchanged coverage thresholds. +parent: pm-rl-apvf +comments[8]{created_at,author,text}: + "2026-10-15T22:50:30.933Z",codex-agent,"Diagnostic baseline: an isolated origin/main source archive, using the same pinned dependencies, passed node --test --test-concurrency=1 --experimental-test-coverage --test-coverage-include=*.ts --test-coverage-include=scripts/*.ts --test-name-pattern=\"SIGKILL at every HTTP-to-PM\" test/decision-recovery.test.ts in 276105.853 ms (1/1). The candidate full release run reached the unchanged 300000 ms timeout. This establishes a narrow baseline timing margin; it does not by itself attribute the full-run failure. Keep all eight boundaries and the original timeout. A complete passing candidate gate is still required." + "2026-10-15T22:55:58.563Z",codex-agent,"The eight crash scenarios use independent trackers and synthetic servers. Run the training/held-out pair concurrently for each of the four boundaries, with at most two scenarios active. Keep every real SIGKILL, replay, physical-request, token, identity and idempotency assertion and the original 300000 ms timeout. This changes scheduling only and avoids eight serial setup/replay intervals consuming the whole wall allowance. Verify the focused case under coverage, then require complete npm and Bun release gates." + "2026-10-15T23:01:06.478Z",codex-agent,"The bounded pair schedule passed all eight real boundary scenarios under coverage in 254328.583 ms (1/1 test, zero skips). The isolated serial main-source diagnostic measured 276105.853 ms; the earlier serial full candidate hit 300000 ms. These are shared-host observations, not a controlled speedup claim. The diff changes only outer loop order and Promise.all of two independent metric cases; every assertion and the 300000 ms timeout remain." + "2026-10-15T23:04:28.665Z",codex-agent,"PM-linked verification passed: pm test pm-rl-bi4v --run --progress executed the unchanged 300000 ms inner test successfully in 156944.936 ms, 1/1 with zero failures/skips/cancellations/todo. The outer harness permits 360 seconds for startup/reporting; the test deadline remains 300 seconds. All eight real crash scenarios retain their original assertions. Full release checks will verify the final schedule with all source coverage thresholds unchanged." + "2026-10-16T00:02:53.011Z",codex-agent,"Both full covered runs on 4676320 still hit the original 300-second HTTP crash-test deadline under concurrent validation. Inspection found that fork inherited the parent coverage execArgv and raw V8 output directory and ran from the code checkout. SIGKILL-able built-code children now receive empty execArgv, no raw coverage directory and the actual scratch project cwd. Each child reports a boolean bootstrap observation, and the existing crash test requires it to be uninstrumented. The parent still executes and covers the authored source APIs; no source exclusion, threshold or deadline changes. All eight real crash/accounting scenarios remain." + "2026-10-16T00:09:31.940Z",codex-agent,"Focused verification with an explicit raw V8 parent directory passed all eight boundaries in 137862.796 ms (1/1, zero skips). Behavioural guard-removal proof in an isolated source snapshot: remove only empty execArgv and NODE_V8_COVERAGE suppression from launch, retain the new child observation and all assertions, then run the covered case; it fails 0/1 in 8975.060 ms with SIGKILL controller inherited V8 coverage. Restoring isolation passes 1/1. This changes neither source coverage scope nor test deadlines; built child dist code is not one of the gated TypeScript sources, and parent source APIs remain exercised." + "2026-10-16T00:15:13.400Z",codex-agent,"Latest PM-linked verification passes 1/1 with all eight boundaries, zero skips: case 206859.047 ms inside the unchanged 300000 ms test deadline. The full-gate child inheritance regression is explicitly checked at bootstrap; guard removal fails and restored isolation passes. The child also runs with the actual scratch project cwd. No authored source is excluded from coverage, and no test assertion or deadline was removed." + "2026-10-16T03:47:19.355Z",codex-agent,"Final verification: both complete npm and Bun release gates pass 590/590, zero skips, all four coverage dimensions 100%. All eight real HTTP crash boundaries pass within the unchanged 300-second test limit. Independent datasets execute in bounded pairs; SIGKILL children report and enforce coverage isolation; the parent still covers the complete source inventory. Removing child coverage isolation fails the runtime regression, restored execution passes. No assertion, deadline or threshold was weakened. Leave open for orchestrator verification." +files[2]{path,scope}: + test/decision-recovery.test.ts,project + test/fixtures/decision-controller.ts,project +tests[1]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: + "node --test --test-concurrency=1 --experimental-test-coverage --test-name-pattern=\"SIGKILL at every HTTP-to-PM\" test/decision-recovery.test.ts",project,360,codex-agent,"2026-10-15T22:56:05.659Z",local_mutation,feat/lm-compose-trainer +body: "" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..5e470153 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +** +!*.ts +!package.json +!package-lock.json +!scripts/ +!scripts/script-launcher.ts +!test/ +!test/fixtures/ +!test/fixtures/*.ts +!test/fixtures/*.Dockerfile +!lm-trainer.Dockerfile diff --git a/README.md b/README.md index d94218ec..b83ac8a9 100644 --- a/README.md +++ b/README.md @@ -351,6 +351,7 @@ under [`pm-rl-yi7j`](.agents/pm/epics/pm-rl-yi7j.toon). | `trainer: "systemone"` | Calibration head over a frozen decision model | Explicitly selected labelled items | | `trainer: "lm"` | Bounded CPU LoRA update | Programme-declared sequences | | Bandit with `adapter.kind: "process-bandit"` | Packaged local child process | Collected training batch | +| LM with `adapter.kind: "compose-lm"`, immutable `image`, `memory_bytes` and `cpus` | Networkless non-root Compose fit with kernel memory/CPU ceilings | Frozen weights, source tensors and collected rewards; held-out examples stay in the controller ([evidence](docs/lm-adapter.md#external-compose-trainer-2026-10-16)) | | Bandit with `adapter.kind: "compose-bandit"` and immutable `adapter.image` | Isolated Docker Compose trainer | Same batch and receipt contract; only job staging is mounted | diff --git a/RECURSIVE_TRAINING.md b/RECURSIVE_TRAINING.md index b76bea03..3ef35f26 100644 --- a/RECURSIVE_TRAINING.md +++ b/RECURSIVE_TRAINING.md @@ -58,7 +58,7 @@ Adapters report structured progress and completion evidence over a documented st `pm rl loop run` is already registered and tested for bounded in-process bandit training. The following criteria apply to the remaining durable process adapters and LLM training work. 1. **Met for local process and Compose adapters:** two real external bandit generations and successor collection are recorded in [process validation](docs/process-trainer.md). A real small local policy is trained for at least two generations. Generation two collects with generation one's newly trained checkpoint. The experiment records before/after weights or checkpoint hashes and a held-out objective; a simulated process that emits a prewritten metric does not satisfy this criterion. -2. **Met for the in-process CPU adapter:** a bounded LLM adapter demonstrates collection, a real parameter update, held-out evaluation and successor use. Model and dataset licenses, checkpoint size and resource limits are declared in the programme before execution. See [the measured LM adapter](docs/lm-adapter.md) and [pm-rl-upe0](.agents/pm/features/pm-rl-upe0.toon). The external Docker trainer remains open. +2. **Met:** a bounded LLM adapter demonstrates collection, a real parameter update, held-out evaluation and successor use. Model and dataset licenses, checkpoint size and resource limits are declared in the programme before execution. See [the measured LM adapter](docs/lm-adapter.md) and [pm-rl-upe0](.agents/pm/features/pm-rl-upe0.toon). The external Docker Compose LM trainer now executes two real recursive generations with successor collection, crash recovery, cancellation, resource refusal and contention evidence in [External Compose trainer, 2026-10-16](docs/lm-adapter.md#external-compose-trainer-2026-10-16), tracked by [pm-rl-whrd](.agents/pm/features/pm-rl-whrd.toon). 3. **Met for local process and Compose execution:** real controller contention proves one launch and one reservation; independently approved worktree jobs preserve their metrics and lineage on merge. Two controllers contend for one job: exactly one process launches and exactly one budget reservation is charged. Concurrent unrelated jobs keep their metrics and lineage when branches merge in either direction. 4. **Met for local process and Compose adapters:** real SIGKILL after staged completion and before its receipt commit recovers without retraining or double charge; planning/evaluation/promotion crashes and mid-fit SIGTERM retain evidence. Crash and cancellation drills cover every transition, including a complete checkpoint without a committed receipt. Restart is idempotent and does not overwrite prior artifacts. 5. **Met for local process and Compose adapters:** dataset overlap, reward/environment drift, excessive gap, missing/corrupt receipts, invalid checkpoints and exceeded resource allowances refuse progression; failed candidates never collect the next generation. Contamination, a training-to-evaluation gap above `maximum_gap`, stale reward or environment identity, corrupt checkpoints, missing metrics and exceeded resource limits prevent promotion. A failed candidate cannot become the next collection policy. diff --git a/compose.ts b/compose.ts index 39d2ecd4..f6fcd03f 100644 --- a/compose.ts +++ b/compose.ts @@ -5,7 +5,7 @@ import { existsSync, readFileSync } from "node:fs"; import { devNull } from "node:os"; import { join, resolve } from "node:path"; import { promisify } from "node:util"; -import { launchTrainer, type ProcessInput, type Resources } from "./process.ts"; +import { launchTrainer, type TrainerInput, type ProcessAdapter, type Resources } from "./process.ts"; import { writeTrainerArtifact } from "./process-bandit.ts"; import { expectedFail } from "./refuse.ts"; @@ -16,13 +16,16 @@ export function composeProject(id: string, directory: string): string { } /** Generate only one networkless trainer with job-local storage and no host service inputs. */ -export function composeConfiguration(image: string): unknown { +export function composeConfiguration(image: string, limits?: Extract): unknown { return { services: { trainer: { + ...(limits === undefined ? {} : { mem_limit: limits.memory_bytes, memswap_limit: limits.memory_bytes, cpus: limits.cpus, pids_limit: 64 }), + labels: { "pm-rl-trainer": "pm-rl-compose" }, image, pull_policy: "never", network_mode: "none", read_only: true, - user: `${process.getuid!()}:${process.getgid!()}`, + user: `${Math.max(1, process.getuid!())}:${Math.max(1, process.getgid!())}`, cap_drop: ["ALL"], security_opt: ["no-new-privileges:true"], working_dir: "/job", stdin_open: true, restart: "no", volumes: [ + ...(limits === undefined ? [] : [{ type: "bind", source: "./input.json", target: "/job/input.json", read_only: true, bind: { create_host_path: false } }]), { type: "bind", source: ".", target: "/job", bind: { create_host_path: false } }, { type: "bind", source: "./compose.json", target: "/job/compose.json", read_only: true, bind: { create_host_path: false } }, ], @@ -35,32 +38,38 @@ function composeArgs(directory: string, id: string): string[] { } /** Stop only this job's Compose project, preserving every host artifact and recording stop evidence. */ -export async function stopComposeTrainer(directory: string, id: string, image: string, reason: string): Promise { +export async function stopComposeTrainer(directory: string, id: string, image: string, reason: string, limits?: Extract): Promise { const file = join(directory, "compose.json"); // A crash before config publication cannot have launched a container. if (!existsSync(file)) return; - if (readFileSync(file, "utf8") !== JSON.stringify(composeConfiguration(image))) expectedFail("Compose configuration drifted; retained for inspection.", "process_job_drift"); + if (readFileSync(file, "utf8") !== JSON.stringify(composeConfiguration(image, limits))) expectedFail("Compose configuration drifted; retained for inspection.", "process_job_drift"); const project = composeProject(id, directory); const options = { timeout: 15_000, maxBuffer: 65_536 }; // Compose down/rm omit one-off run containers. Resolve only this exact named, labelled container, // then remove its immutable daemon ID so name reuse cannot grant stop authority. const listed = await promisify(execFile)("docker", ["container", "ls", "--all", "--no-trunc", "--filter", `name=^/${project}-trainer$`, "--filter", `label=com.docker.compose.project=${project}`, "--filter", "label=com.docker.compose.service=trainer", "--format", "{{.ID}}"], options); const container = listed.stdout.trim(); + let resources: unknown = null; + if (limits !== undefined && container.length > 0) { + const inspected = await promisify(execFile)("docker", ["container", "inspect", container, "--format", '{"memory_bytes":{{.HostConfig.Memory}},"nano_cpus":{{.HostConfig.NanoCpus}},"oom_killed":{{.State.OOMKilled}},"exit_code":{{.State.ExitCode}},"network":{{json .HostConfig.NetworkMode}},"user":{{json .Config.User}}}'], options); + resources = JSON.parse(inspected.stdout) as unknown; + } if (container.length > 0) await promisify(execFile)("docker", ["container", "rm", "--force", container], options); await promisify(execFile)("docker", [...composeArgs(directory, id), "down", "--timeout", "0"], options); const evidence = join(directory, "compose-stop.json"); - if (!existsSync(evidence)) writeTrainerArtifact(evidence, { format: "pm-rl/compose-stop/1", job: id, project, reason }); + if (!existsSync(evidence)) writeTrainerArtifact(evidence, { format: "pm-rl/compose-stop/1", job: id, project, reason, ...(limits === undefined ? {} : { resources }) }); } /** Run the container as the trainer; the controller owns its reservation, watchdog and cleanup. */ -export async function launchComposeTrainer(directory: string, id: string, input: ProcessInput, allowance: Resources, image: string, onCleanupFailure: (error: unknown) => void, signal?: AbortSignal): Promise { - writeTrainerArtifact(join(directory, "compose.json"), composeConfiguration(image)); +export async function launchComposeTrainer(directory: string, id: string, input: TrainerInput, allowance: Resources, image: string, onCleanupFailure: (error: unknown) => void, signal?: AbortSignal, limits?: Extract): Promise { + if (limits !== undefined) writeTrainerArtifact(join(directory, "input.json"), { job: id, input }); + writeTrainerArtifact(join(directory, "compose.json"), composeConfiguration(image, limits)); let reason = "failed"; try { await launchTrainer(directory, id, input, allowance, ["docker", ...composeArgs(directory, id), "run", "--no-deps", "--pull", "never", "-T", "--name", `${composeProject(id, directory)}-trainer`, "trainer"], signal); reason = "exited"; } finally { - try { await stopComposeTrainer(directory, id, image, signal?.aborted === true ? "cancelled" : reason); } + try { await stopComposeTrainer(directory, id, image, signal?.aborted === true ? "cancelled" : reason, limits); } catch (error) { onCleanupFailure(error); } } } diff --git a/docs/lm-adapter.md b/docs/lm-adapter.md index 60a2c5b9..30344904 100644 --- a/docs/lm-adapter.md +++ b/docs/lm-adapter.md @@ -4,7 +4,7 @@ [`pm-rl-apvf`](../.agents/pm/epics/pm-rl-apvf.toon), implements criterion 2 in [RECURSIVE_TRAINING.md](../RECURSIVE_TRAINING.md). It runs through the existing controller, controller lease, replay, Hoeffding gate and transactional promotion -path. External Docker trainers remain open. +path. The external Compose adapter shares that controller and the durable process contract described below. ## Model and learning contract @@ -164,3 +164,161 @@ thresholds of 100/100/100/100, docstrings, lint, zero jscpd duplication, identit and production dependency audits, packed contents and generated changelog. `bun run release:check` runs the repository's existing Node-based quality gates; the direct Bun tests separately exercise the adapter and packed CLI with Bun. + + +## External Compose trainer, 2026-10-16 + +[pm-rl-whrd](../.agents/pm/features/pm-rl-whrd.toon) extends the existing +[process contract](process-trainer.md) with `adapter.kind: "compose-lm"`. +It uses the same job identity, SDK owner fences, atomic reservation, staged +completion validation, cancellation, settlement and recovery. The container +performs the real TypeScript REINFORCE update; the controller evaluates its +candidate on held-out data and uses the existing promotion gate. + +Build from the source checkout with: + +```sh +docker build --tag pm-rl-lm-trainer --file lm-trainer.Dockerfile . +docker image inspect pm-rl-lm-trainer --format '{{.Id}}' +``` + +Put that immutable image ID in the programme's adapter before execution: + +```json +{ + "kind": "compose-lm", + "image": "sha256:", + "memory_bytes": 268435456, + "cpus": 1, + "allowance": { + "training_steps": 6, + "wall_ms": 60000, + "storage_bytes": 2097152, + "compute_units": 60000, + "tokens": 100000 + } +} +``` + +The programme retains the existing explicit MIT model/data licences, 4,096 +parameter cap, 65,536-byte checkpoint cap and 256-step total optimizer bound. +The measured programme uses two generations, 64 collected completions each, +1,600 held-out episodes per policy and the unchanged confidence/improvement/gap +gates. An independently created approval Decision permits two promotions and +supplies `resource_budget` covering both complete per-job allowances. The +controller never expands that Decision. Adapter step and wall allowances must +agree with the LM programme; image, CPU and memory changes alter its identity. + +The Dockerfile pins Node's base by SHA-256 and executes a TypeScript entrypoint +as a non-root user. Compose also sets a non-root numeric UID/GID, read-only root, +dropped capabilities, no privilege escalation, no network, no ports, no host +environment forwarding and a PID cap. `mem_limit` and `memswap_limit` enforce +the declared memory ceiling with no extra swap; `cpus` limits CPU scheduling. +The image installs only locked package dependencies during build. Training uses +`--pull never` and has no network. It mounts only job staging; its serialized +training request and Compose configuration are additionally mounted read-only. +The request contains frozen tensors, source adapter, training strings and the +actual collected completions/rewards. Held-out examples and tracker access are +absent. Frozen weights are restored without repeating pretraining. + +The completion receipt binds canonical checkpoint bytes, source policy, +training/dataset identities, losses, parameter delta and real tensors. It reports +actual optimizer steps, fit CPU milliseconds, target tokens optimized across +those steps, peak resident memory, wall milliseconds and checkpoint bytes. +The controller additionally measures startup-through-completion wall time and +all retained staging bytes. Surrogate diagnostic passes are included in CPU and +wall use; `tokens` counts optimized collected target tokens. Bootstrap and +protocol processing are outside the fit CPU interval. Cleanup retains a compact +`compose-stop.json` containing daemon-observed memory/CPU configuration, +network/user isolation, exit code and OOM status. Unknown failed-job consumption +settles conservatively against the whole reservation and retained evidence. + +The validated PM-linked real run produced the following canonical checkpoint hashes. +Each source hash equals the prior promoted candidate, and the second collection +Run's component was independently checked against generation one's digest. + +| Generation | Collecting checkpoint SHA-256 | Candidate checkpoint SHA-256 | Held-out sampled reward | Fit wall ms, including startup | Fit CPU ms | Peak RSS bytes | Optimized tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| 1 | `058aa3d2c08438abd18cf0e9b455aca99ddb95492de852201c55c79a0672130d` | `37e503748a170be32e1d506997b6c0cc5c9ee256b24ab01fb49cf0564da07b4c` | 0.183854 | 4297 | 713 | 138207232 | 1152 | +| 2 | `37e503748a170be32e1d506997b6c0cc5c9ee256b24ab01fb49cf0564da07b4c` | `086f78992283cba37929321abe3961ca6132a52c332bf8a8387a52f22141cbdb` | 0.292396 | 4266 | 759 | 144183296 | 1152 | + +Both candidates promoted. The execution took 54.134 seconds on a shared host; independent reconstruction, +replay and status checks took another 4.471 seconds. Each fit charged six steps; +retained staging occupied 89,057 / 92,509 bytes. These are synthetic rotate-task +results over an adaptive validation set, with the same numerical model as the +in-process adapter, and do not establish broad language capability. + +`test/lm-compose.test.ts` exercises the same trainer through real stdin/stdout +and Docker Compose. Its Docker availability gate is identical to the existing +Compose suite: skip only when `docker compose version` fails; a failed daemon, +build or drill with an available CLI is a failure. All authored sources retain +the existing exact 100/100/100/100 coverage thresholds. + + +Real-container drills retained attributable evidence: + +- A controller SIGKILL after checkpoint/completion fsync and before the PM receipt + commit recovered byte-identical artifacts, promoted once and settled the + original six-step charge, with exactly one launch marker. +- Cancellation after the real fit's first progress event killed only the owned + container, retained its read-only input and events and settled `process_cancelled`. +- A corrupted real fit checkpoint failed digest validation; a real allocation + beyond a 192 MiB container ceiling recorded Docker `oom_killed: true`; a + 1,000-step real LM fit crossed its 15-second wall allowance after its progress + event. All three refused promotion. + Each failure retained staging, settled its reservation and collected no successor. +- Two real controllers contended for one programme: one received + `loop_controller_active`; the winner launched one container and created one + settled reservation. Resume adopted the same completed generation. + +Behavioural revert proofs used isolated source snapshots with unchanged tests: +restoring only `executePendingGeneration` from the pre-feature implementation +failed the two-generation test because external job receipts were absent (0/1); +forcing LM jobs through the launch branch of `executeReservedProcessJob` instead +of staged recovery failed the SIGKILL test on exclusive existing input evidence +(0/1); removing only the measured memory/token refusal in +`validateLmProcessCheckpoint` failed its invalid-use regression (0/1). +The restored snapshot passed all three cases, with zero skips. These proofs +neither removed the live container memory ceiling nor altered the package worktree. + +A further receipt guard requires nonnegative fit wall time no greater than its +reported process wall time before held-out evaluation. Removing only that guard +fails the unchanged real-fit invalid-time regression (0/1); restoring it passes +1/1, including through PM-linked execution. This closes inconsistent staged +clock evidence without expanding any allowance. + + +The completed release gates ran at source/test commit `f405171` with unchanged +thresholds and the complete authored-source inventory: + +| Command | Passed | Failed / skipped | Statements / lines / branches / functions | +| --- | ---: | ---: | --- | +| `npm run release:check` | 590 / 590 | 0 / 0 | 100 / 100 / 100 / 100 | +| `bun run release:check` | 590 / 590 | 0 / 0 | 100 / 100 / 100 / 100 | + +Both runs covered all 30 authored sources, documented all 272 declarations, +found zero duplicate clones and zero production vulnerabilities, approved the +identity and publish-attestation audits, verified the 105-file package and +confirmed the generated changelog. Node was 24.19.0; npm was 11.17.0 and Bun +was 1.3.5. Each gate used a separate regular checkout, isolated HOME/Git +configuration and executable RAM-backed temporary trackers. The measured +PM-linked 8/8 real-container suite above used ordinary filesystem storage. +The RAM-backed gates establish logical recovery and coverage; the separate +drills exercise process-death recovery with fsynced files, without a physical +power-loss claim. Final documentation and tracker receipts preserve the gated +source and test bytes. + +The existing Compose orphan fixture now observes the persisted running-job +barrier before its unchanged startup interval. The eight HTTP receipt crash +scenarios retain every assertion and their 300-second deadline, execute in +bounded pairs and explicitly prevent killed children from inheriting coverage. +Validation workers own unique image names and labels. The System One and bandit +terminal-refusal acceptance case checks that the shared lifecycle helper creates +no external job for either in-process adapter. + +Earlier failed attempts are retained as diagnostic PM comments and excluded +from the completed gate results. The unchanged 20-second packed LM CLI bound +passed at 11.716 seconds in the completed npm run and 9.621 seconds in Bun; +a separate same-source attempt measured 23.890 seconds and failed. This timing +variability remains open in [pm-rl-7c7m](../.agents/pm/tasks/pm-rl-7c7m.toon). +Storage retains the shared watchdog rather than a hard filesystem quota. diff --git a/docs/process-trainer.md b/docs/process-trainer.md index 56e0dce5..a3e34e1b 100644 --- a/docs/process-trainer.md +++ b/docs/process-trainer.md @@ -78,7 +78,7 @@ never extracts executable commands from PM item text. The SDK adapter entry poin can receive argv selected by trusted application code; it binds the launcher selection digest into job identity. Those argv values are not persisted in the tracker. Custom launchers must implement this same bandit checkpoint contract; -additional model formats require a separately validated adapter. +the LM adapter supplies its model-specific validator through this same execution contract (see [LM Compose validation](lm-adapter.md#external-compose-trainer-2026-10-16)). The child receives one JSON request on stdin, then EOF. It contains `job` and `input`: the programme/generation/input-policy identities, training examples, @@ -228,7 +228,7 @@ available for inspection when cleanup could not finish. ### Real container drills Run `node --test test/compose.test.ts` from a source checkout. Its tiny local -`test/fixtures/compose.Dockerfile` uses `node:22-alpine`, copies only the actual +`test/fixtures/compose.Dockerfile` pins `node:24-alpine` by digest, copies only the actual TypeScript numerical trainer and fixture entrypoint, and installs no dependencies. Only the base image may need a pull; trainer execution has no network and never pulls. The fixture performs the real REINFORCE update rather than emitting a diff --git a/index.ts b/index.ts index 4ad1fe67..fce4e5b7 100644 --- a/index.ts +++ b/index.ts @@ -324,7 +324,8 @@ import { type SystemOneObservation, } from "./systemone.ts"; -import { executeProcessTrainer, listProcessJobs, readProcessJob, processJobId, verifiedProcessPolicy, transitionProcessJob, verifyProcessJobBudget, type ProcessJob, type ProcessJobState } from "./process.ts"; +import { lmProcessInput, processLmTraining, verifyLmProcessJob } from "./process-lm.ts"; +import { executeProcessTrainer, listProcessJobs, readProcessJob, processJobId, verifiedProcessPolicy, transitionProcessJob, verifyProcessJobBudget, type ProcessJob, type ProcessJobState, type TrainerPolicy } from "./process.ts"; export { executeProcessTrainer, reserveProcessJob, readProcessJob, processJobId, processStaging, parseResources, parseProcessAdapter, validateProcessCompletion, referenceTrainerArgv, type ProcessJob, type ProcessCompletion, type ProcessAdapter, type ProcessInput, type Resources } from "./process.ts"; import { banditCheckpoint, type BanditCheckpoint, type BanditGeneration } from "./bandit.ts"; @@ -2513,7 +2514,7 @@ export interface RlLoopGenerationReport { /** The bounded terminal report of one executed recursive loop. */ export interface RlLoopReport { /** Optional local process jobs with settled resource and durable completion evidence. */ - readonly process_jobs?: readonly ProcessJob[]; + readonly process_jobs?: readonly ProcessJob[]; /** The exact condition that terminated the loop. */ readonly stop_reason: "generation_limit" | "budget_exhausted" | "unchanged_checkpoint" | "gap_rejected" | "evaluation_rejected" | "promotion_refused" | "checkpoint_limit_exceeded" | "wall_limit_exceeded" | "process_failed" | "process_cancelled" | "process_invalidated"; /** The registered environment item id every run and generation references. */ @@ -2581,7 +2582,7 @@ export interface RlLoopGenerationStatus { /** The read-only reconstruction `pm rl loop status` returns for one loop. */ export interface RlLoopStatusReport { /** Retained local process lifecycle and resource outcomes, including failed jobs. */ - readonly process_jobs?: readonly ProcessJob[]; + readonly process_jobs?: readonly ProcessJob[]; /** The loop id prefix the report was reconstructed for. */ readonly id: string; /** The trainer adapter the persisted programme executes. */ @@ -3287,8 +3288,8 @@ async function appendCommentOnce(client: PmClient, id: string, text: string): Pr } /** Read complete process outcomes belonging to this programme instance without exposing executable paths. */ -async function loopProcessJobs(client: PmClient, id: string, programme: LoopProgramme): Promise { - if (programme.trainer !== "bandit" || programme.config.process === undefined) return []; +async function loopProcessJobs(client: PmClient, id: string, programme: LoopProgramme): Promise[]> { + if (programme.trainer === "systemone" || programme.config.process === undefined) return []; const jobs = (await listProcessJobs(client)).filter((job) => jsonObject(job.binding, "Process binding")["programme_id"] === id); return jobs.sort((left, right) => Number(jsonObject(left.binding, "Process binding")["generation"]) - Number(jsonObject(right.binding, "Process binding")["generation"])); } @@ -3421,6 +3422,12 @@ async function inspectLmGeneration(pmRoot: string, client: PmClient, programme: fail(`Loop ${request.id} generation ${generation} registered a candidate whose collection evidence is incomplete; the persisted lineage is inconsistent.`, "loop_generation_drift", EXIT_CODE.CONFLICT); } await verifyLmCheckpointArtifact(pmRoot, source, config); + if (config.process !== undefined) { + const externalInput = lmProcessInput(config, chain.step, generation, source, observations, request.id); + const job = await readProcessJob(client, processJobId(externalInput, request.processTrainerArgv, config.process)); + verifyLmProcessJob(job, pmRoot, externalInput, { digest: stored.candidateCheckpoint }); + await verifyProcessJobBudget(client, job!); + } const trainingStep = await readLoopStep(client, `${request.id}-g${generation}`, "train", input); const evaluationStep = await readLoopStep(client, `${request.id}-g${generation}`, "evaluate", input); const verified = trainingStep === null || evaluationStep === null ? verifyStoredLmGeneration(config, chain.step, source, stored, observations) @@ -3643,6 +3650,10 @@ async function executeLoopProgramme(client: PmClient, coordinates: WorkspaceCoor const receipt = runLoopGeneration(programme.config, chain.step, generation, chain.current as BanditCheckpoint, inspection.advance.checkpoint as BanditCheckpoint); await finishProcessGeneration(client, coordinates, prepared.lease, programme.config, chain, generation, receipt, "promoted", "promotion_verified", request); } + if (programme.trainer === "lm" && programme.config.process !== undefined) { + const job = (await loopProcessJobs(client, request.id, programme)).find((entry) => jsonObject(entry.binding, "Process binding")["generation"] === generation)!; + await prepared.lease.guard(() => transitionProcessJob(client, coordinates, job, "promoted", "promotion_verified")); + } await durableLoopStep(client, prepared.lease, `${request.id}-g${generation}`, "promote", { input: loopStepInput(programme, chain, generation), approval: request.approval }, async () => ({ item: inspection.report.item })); chain.reports.push(inspection.report); @@ -3750,8 +3761,16 @@ async function executePendingGeneration(client: PmClient, coordinates: Workspace } else if (programme.trainer === "lm") { const observations = (await readRunMetricEvents(client, runId!)).map((event, index) => parseLmCollectionEvent(event, programme.config, `Loop collection event ${index}`)); if (observations.length !== programme.config.samplesPerGeneration) fail("LM collection evidence is incomplete.", "loop_generation_drift", EXIT_CODE.CONFLICT); - const trained = await durableLoopStep(client, prepared.lease, stepId, "train", input, async () => - lmTrainingJson(trainLmStep(programme.config, chain.step, generation, chain.current as LmCheckpoint, observations, null))); + const externalInput = programme.config.process === undefined ? null : lmProcessInput(programme.config, chain.step, generation, chain.current as LmCheckpoint, observations, request.id); + const trained = await durableLoopStep(client, prepared.lease, stepId, "train", input, async () => { + if (externalInput === null) return lmTrainingJson(trainLmStep(programme.config, chain.step, generation, chain.current as LmCheckpoint, observations, null)); + const job = await executeProcessTrainer(client, coordinates, externalInput, request.approval, programme.config.process!, request.signal); + return lmTrainingJson(processLmTraining(externalInput, job.completion!)); + }); + if (externalInput !== null) { + const job = (await readProcessJob(client, processJobId(externalInput, undefined, programme.config.process)))!; + verifyLmProcessJob(job, coordinates.pmRoot, externalInput, { digest: String(jsonObject(trained.value, "LM fit")["candidate_checkpoint"]), training: trained.value }); + } const training = restoreLmTraining(trained.value, programme.config, generation, chain.current as LmCheckpoint, observations); if (serializeLmCheckpoint(training.candidate, programme.config).bytes <= programme.config.limits.maxCheckpointBytes) { await prepared.lease.guard(() => persistLmCheckpoint(coordinates.pmRoot, training.candidate, programme.config)); @@ -3761,6 +3780,7 @@ async function executePendingGeneration(client: PmClient, coordinates: Workspace const evaluated = await durableLoopStep(client, prepared.lease, stepId, "evaluate", input, async () => lmGenerationTrainingConfig(programme.config, chain.step, evaluateLmStep(programme.config, chain.step, training, null))); receipt = restoreLmGeneration(evaluated.value, programme.config, chain.step, training); + await finishProcessGeneration(client, coordinates, prepared.lease, programme.config, chain, generation, receipt, "evaluated", "held_out_evaluated", request); evaluationExecuted = evaluated.executed; } else { if (programme.trainer === "systemone" && programme.config.endpoint.receiptProtocol !== undefined @@ -3812,7 +3832,7 @@ async function executePendingGeneration(client: PmClient, coordinates: Workspace await prepared.lease.guard(() => appendCommentOnce(client, generationId, message)); chain.reports.push({ generation, run: runId!, item: generationId, promoted: false, held_out_mean: receipt.candidateHeldOutMean, candidate_checkpoint: receipt.candidate.digest, refusal_reason: receipt.refusalReason }); - await finishProcessGeneration(client, coordinates, prepared.lease, programme.trainer === "bandit" ? programme.config : null, chain, generation, receipt as BanditGeneration, "failed", receipt.refusalReason!, request); + await finishProcessGeneration(client, coordinates, prepared.lease, programme.trainer !== "systemone" ? programme.config : null, chain, generation, receipt as BanditGeneration | LmGeneration, "failed", receipt.refusalReason!, request); return { terminal: { stopReason: receipt.stopReason!, refusalReason: receipt.refusalReason! } }; } // Promote through the existing transactional, budget-checked flow. An @@ -3849,11 +3869,11 @@ async function executePendingGeneration(client: PmClient, coordinates: Workspace await prepared.lease.guard(() => appendCommentOnce(client, generationId, message)); chain.reports.push({ generation, run: runId!, item: generationId, promoted: false, held_out_mean: receipt.candidateHeldOutMean, candidate_checkpoint: receipt.candidate.digest, refusal_reason: error.message }); - await finishProcessGeneration(client, coordinates, prepared.lease, programme.trainer === "bandit" ? programme.config : null, chain, generation, receipt as BanditGeneration, "invalidated", error.message, request); + await finishProcessGeneration(client, coordinates, prepared.lease, programme.trainer !== "systemone" ? programme.config : null, chain, generation, receipt as BanditGeneration | LmGeneration, "invalidated", error.message, request); return { terminal: { stopReason: "promotion_refused", refusalReason: error.message } }; } if (!promotionExecuted) fail("Completed loop promotion disagrees with an unpromoted generation", "loop_step_drift", EXIT_CODE.CONFLICT); - await finishProcessGeneration(client, coordinates, prepared.lease, programme.trainer === "bandit" ? programme.config : null, chain, generation, receipt as BanditGeneration, "promoted", "promotion_verified", request); + await finishProcessGeneration(client, coordinates, prepared.lease, programme.trainer !== "systemone" ? programme.config : null, chain, generation, receipt as BanditGeneration | LmGeneration, "promoted", "promotion_verified", request); await request.onPhase?.("promote", generation); failIfLoopCancelled(signal, request.id, generation, "successor collection"); chain.reports.push({ generation, run: runId!, item: generationId, promoted: true, @@ -3866,9 +3886,10 @@ async function executePendingGeneration(client: PmClient, coordinates: Workspace } /** Advance the process lifecycle alongside the existing held-out and promotion gates. */ -async function finishProcessGeneration(client: PmClient, coordinates: WorkspaceCoordinates, lease: ControllerLease, config: LoopConfig | null, chain: LoopChain, generation: number, receipt: BanditGeneration, state: ProcessJobState, outcome: string, request: RlLoopRequest): Promise { +async function finishProcessGeneration(client: PmClient, coordinates: WorkspaceCoordinates, lease: ControllerLease, config: LoopConfig | LmLoopConfig | null, chain: LoopChain, generation: number, receipt: BanditGeneration | LmGeneration, state: ProcessJobState, outcome: string, request: RlLoopRequest): Promise { if (config?.process === undefined) return; - const input = banditProcessInput(config, chain.step, generation, receipt, request.id); + const input = "baseDigest" in config ? lmProcessInput(config, chain.step, generation, (receipt as LmGeneration).source, (receipt as LmGeneration).observations, request.id) + : banditProcessInput(config, chain.step, generation, receipt as BanditGeneration, request.id); await lease.guard(async () => { const job = (await readProcessJob(client, processJobId(input, request.processTrainerArgv, config.process)))!; await verifyProcessJobBudget(client, job); diff --git a/lm-trainer.Dockerfile b/lm-trainer.Dockerfile new file mode 100644 index 00000000..70a3c063 --- /dev/null +++ b/lm-trainer.Dockerfile @@ -0,0 +1,9 @@ +FROM node:24-alpine@sha256:44fce696f38ba8c987c4db89954051000bbd05ffd8d8659ba15ed4a8602e937f +LABEL pm-rl-trainer="pm-rl-lm" +WORKDIR /trainer +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts --no-audit --no-fund +COPY *.ts ./ +COPY scripts/script-launcher.ts ./scripts/ +USER node +ENTRYPOINT ["node", "/trainer/process-lm-trainer.ts"] diff --git a/lm.ts b/lm.ts index 1b8e4fb8..1f681692 100644 --- a/lm.ts +++ b/lm.ts @@ -31,6 +31,7 @@ import { createHash, randomUUID } from "node:crypto"; import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; +import { parseProcessAdapter, type ProcessAdapter } from "./process.ts"; import { EXIT_CODE } from "@unbrained/pm-cli/sdk/runtime"; import { canonicalJson, type EnvironmentSpec, type JsonValue } from "./index.ts"; @@ -402,8 +403,13 @@ export interface LmLimits { readonly datasetLicense: string; } +/** Training-only numerical inputs, excluding held-out examples and evaluation configuration. */ +export type LmFitConfig = Pick; + /** A complete validated language-model loop programme. */ export interface LmLoopConfig { + /** Optional external Compose LM execution, declared before collection. */ + readonly process?: ProcessAdapter; /** Human-readable environment family name for the registered environment. */ readonly environmentName: string; /** Environment version; changed content must change this value. */ @@ -603,7 +609,7 @@ function jsonLmWeights(shape: LmModelShape, weights: LmWeights): JsonValue { } /** Restore declared frozen weights with exact Float32 shapes and their content identity. */ -function restoreLmBase(value: JsonValue, shape: LmModelShape): LmWeights { +export function restoreLmBase(value: JsonValue, shape: LmModelShape): LmWeights { const envelope = asJsonObject(value, "LM frozen base", "lm_invalid_checkpoint"); const record = asJsonObject(envelope["weights"], "LM frozen weights", "lm_invalid_checkpoint"); const rawLayers = record["layers"]; @@ -1787,7 +1793,7 @@ function sampledHeldOutMean(config: LmLoopConfig, generation: number, adapter: L * @param wallMs - The persisted wall milliseconds to replay, or null to measure this execution. * @returns The generation's complete receipt, including its terminal condition. */ -export function trainLmStep(config: LmLoopConfig, step: LoopStepConfig, generation: number, source: LmCheckpoint, observations: readonly LmObservation[], wallMs: number | null): LmTraining { +export function trainLmStep(config: LmFitConfig, step: LoopStepConfig, generation: number, source: LmCheckpoint, observations: readonly LmObservation[], wallMs: number | null): LmTraining { const startedAt = wallMs === null ? performance.now() : 0; const samples: LmFitSample[] = observations.map((observation) => { const example = config.training.find((candidate) => candidate.id === observation.example); @@ -1881,13 +1887,13 @@ export function lmTrainingJson(training: LmTraining): JsonValue { } /** Bind the collected completions to the policy and declared training dataset. */ -function lmCollectionDigest(config: LmLoopConfig, source: LmCheckpoint, observations: readonly LmObservation[]): string { +function lmCollectionDigest(config: Pick, source: LmCheckpoint, observations: readonly LmObservation[]): string { return lmDigest({ source: source.digest, trainingDigest: config.trainingDigest, collection: observations.map((observation) => ({ example: observation.example, tokens: [...observation.tokens], reward: observation.reward })) }); } /** Restore a durable fit, verifying checkpoint, collection and parameter-delta identities without fitting. */ -export function restoreLmTraining(value: JsonValue, config: LmLoopConfig, generation: number, source: LmCheckpoint, observations: readonly LmObservation[]): LmTraining { +export function restoreLmTraining(value: JsonValue, config: LmFitConfig, generation: number, source: LmCheckpoint, observations: readonly LmObservation[]): LmTraining { const record = asJsonObject(value, "LM training step", "lm_invalid_training_config"); const candidate = lmCheckpoint(parseLmAdapter(record["candidate_adapter"], config.shape, "LM training step"), config); const collectionDigest = lmCollectionDigest(config, source, observations); @@ -2153,7 +2159,10 @@ export function parseLmLoopConfig(raw: JsonValue): LmLoopConfig { if (serializeLmCheckpoint(initial, { baseDigest, shape }).bytes > maxCheckpointBytes) { expectedFail(`LM loop configuration limits max_checkpoint_bytes ${maxCheckpointBytes} cannot hold the serialized seed checkpoint; the programme is unbounded as declared.`, "lm_limit_checkpoint_bytes"); } + const external = record["adapter"] === undefined ? undefined : parseProcessAdapter(record["adapter"]); + if (external !== undefined && (external.kind !== "compose-lm" || external.allowance.training_steps !== fitSteps || external.allowance.wall_ms > maxWallSeconds * 1000)) expectedFail("LM requires a Compose LM adapter matching its fit and wall limits.", "process_invalid_adapter"); const config: LmLoopConfig = { + ...(external === undefined ? {} : { process: external }), environmentName, environmentVersion, alphabet, @@ -2182,6 +2191,7 @@ export function parseLmLoopConfig(raw: JsonValue): LmLoopConfig { digest: "", }; return { ...config, digest: lmDigest({ + ...(external === undefined ? {} : { adapter: external as unknown as JsonValue }), format: LM_PROGRAMME_FORMAT, environment: { name: environmentName, version: environmentVersion }, task: "rotate", @@ -2201,6 +2211,7 @@ export function parseLmLoopConfig(raw: JsonValue): LmLoopConfig { export function lmConfigurationJson(config: LmLoopConfig): JsonValue { return { trainer: "lm", + ...(config.process === undefined ? {} : { adapter: config.process as unknown as JsonValue }), frozen_base: { weights: jsonLmWeights(config.shape, config.base), digest: config.baseDigest }, environment: { name: config.environmentName, version: config.environmentVersion }, task: "rotate", diff --git a/loop.ts b/loop.ts index dd4d055c..390208eb 100644 --- a/loop.ts +++ b/loop.ts @@ -213,6 +213,7 @@ export function parseLoopConfig(raw: JsonValue): LoopConfig { const processAdapter = record["adapter"] === undefined ? undefined : parseProcessAdapter(record["adapter"]); if (processAdapter !== undefined) { const heldOut = new Set(datasets.evaluation.map((example) => canonicalJson({ feature: example.feature, rewards: [...example.rewards] }))); + if (processAdapter.kind === "compose-lm") expectedFail("Bandit requires a bandit process adapter.", "process_invalid_adapter"); if (datasets.training.some((example) => heldOut.has(canonicalJson({ feature: example.feature, rewards: [...example.rewards] })))) expectedFail("Process training and held-out content overlap.", "process_dataset_overlap"); } const config: LoopConfig = { @@ -549,7 +550,7 @@ export function parseLoopTrainer(raw: JsonValue): LoopTrainer { */ export function parseLoopProgramme(raw: JsonValue): LoopProgramme { const trainer = parseLoopTrainer(raw); - if (trainer !== "bandit" && asJsonObject(raw, "Loop configuration")["adapter"] !== undefined) expectedFail("Process adapter requires the bandit trainer.", "process_invalid_adapter"); + if (trainer === "systemone" && asJsonObject(raw, "Loop configuration")["adapter"] !== undefined) expectedFail("Process adapter requires the bandit trainer.", "process_invalid_adapter"); if (trainer === "systemone") { return { trainer, config: parseSystemOneLoopConfig(raw) }; } diff --git a/package.json b/package.json index df72c576..de04b1f7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,10 @@ "dist/process-bandit.*", "dist/scripts/script-launcher.*", "docs/process-trainer.md", - "dist/compose.*" + "dist/compose.*", + "dist/process-lm.*", + "dist/process-lm-trainer.*", + "lm-trainer.Dockerfile" ], "engines": { "node": ">=22.18.0" @@ -96,7 +99,8 @@ "test/process.test.ts", "test/process-loop.test.ts", "test/process-cli.test.ts", - "test/compose.test.ts" + "test/compose.test.ts", + "test/lm-compose.test.ts" ], "thresholds": { "statements": 100, diff --git a/process-lm-trainer.ts b/process-lm-trainer.ts new file mode 100644 index 00000000..1001e948 --- /dev/null +++ b/process-lm-trainer.ts @@ -0,0 +1,42 @@ +/** Packaged TypeScript LM entrypoint implementing the shared durable process protocol. */ +import { createHash } from "node:crypto"; +import { closeSync, fsyncSync, openSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { Readable, Writable } from "node:stream"; +import { trainLmStep, lmTrainingJson, serializeLmCheckpoint } from "./lm.ts"; +import { restoreLmProcessInput, lmFitTokens } from "./process-lm.ts"; +import type { ProcessCompletion, ProcessInput } from "./process.ts"; +import { writeTrainerArtifact } from "./process-bandit.ts"; +import { isMainInvocation } from "./scripts/script-launcher.ts"; + +/** Fit actual collected rewards, fsync the canonical checkpoint and then publish completion. */ +export async function runLmTrainer(stdin: Readable = process.stdin, stdout: Writable = process.stdout, directory = "."): Promise { + let text = ""; + for await (const chunk of stdin) text += String(chunk); + const { job, input } = JSON.parse(text) as { job: string; input: ProcessInput<{ readonly digest: string }> }; + const start = performance.now(); const cpu = process.cpuUsage(); + const { config, source, payload } = restoreLmProcessInput(input); + stdout.write(`${JSON.stringify({ type: "progress", job, usage: { training_steps: 0, wall_ms: 0, storage_bytes: 0, compute_units: 0, tokens: 0 } })}\n`); + const training = trainLmStep(config, payload.step, payload.generation, source, payload.observations, null); + const checkpoint = serializeLmCheckpoint(training.candidate, config); + const handle = openSync(join(directory, "checkpoint.json"), "wx"); + try { writeFileSync(handle, checkpoint.text); fsyncSync(handle); } finally { closeSync(handle); } + const consumed = process.cpuUsage(cpu); + const completion: ProcessCompletion<{ readonly digest: string }> = { + format: "pm-rl/process-completion/1", job, checkpoint: "checkpoint.json", + digest: `sha256:${createHash("sha256").update(checkpoint.text).digest("hex")}`, policy: { digest: training.candidate.digest }, + source_policy: input.source.digest, training_config: input.training_config, lineage: input.lineage, training: lmTrainingJson(training), + peak_memory_bytes: process.resourceUsage().maxRSS * 1024, + usage: { training_steps: config.fitSteps, wall_ms: Math.ceil(performance.now() - start), storage_bytes: checkpoint.bytes, + compute_units: Math.ceil((consumed.user + consumed.system) / 1000), tokens: lmFitTokens(payload) }, + }; + writeTrainerArtifact(join(directory, "completion.json"), completion); + stdout.write(`${JSON.stringify({ type: "complete", job, receipt: completion })}\n`); +} + +/** Dispatch the same trainer through a real process or direct IO under coverage. */ +export async function lmTrainerMain(argv = process.argv, moduleUrl = import.meta.url, stdin: Readable = process.stdin, stdout: Writable = process.stdout, directory = "."): Promise { + if (isMainInvocation(argv, moduleUrl)) await runLmTrainer(stdin, stdout, directory); +} + +await lmTrainerMain(); diff --git a/process-lm.ts b/process-lm.ts new file mode 100644 index 00000000..929b37fe --- /dev/null +++ b/process-lm.ts @@ -0,0 +1,81 @@ +/** Training-only LM payload and model-specific validation for the shared process contract. */ +import type { JsonValue } from "./index.ts"; +import type { LoopStepConfig } from "./loop.ts"; +import { lmConfigurationJson, jsonLmAdapter, parseLmAdapter, lmCheckpoint, restoreLmBase, restoreLmTraining, serializeLmCheckpoint, type LmFitConfig, type LmLoopConfig, type LmCheckpoint, type LmObservation, type LmTraining } from "./lm.ts"; +import { processDigest, verifiedProcessPolicy, type ProcessJob, type TrainerPolicy, type ProcessInput, type ProcessCompletion } from "./process.ts"; +import { expectedFail } from "./refuse.ts"; + +/** Serializable numerical inputs; excludes held-out examples and tracker access. */ +export interface LmProcessPayload { + /** Frozen tensors are JSON arrays, restored without pretraining. */ + readonly config: Omit & { readonly frozen_base: JsonValue }; + /** Source adapter tensors, bound by the input checkpoint digest. */ + readonly source_adapter: JsonValue; + /** Real collected completions and rewards. */ + readonly observations: readonly LmObservation[]; + /** One-based generation and derived fit configuration. */ + readonly generation: number; + /** Derived learning rate and evaluation sample identity; contains no examples. */ + readonly step: LoopStepConfig; + /** Declared kernel memory ceiling. */ + readonly memory_bytes: number; +} + +/** Construct one content-bound request containing only the inputs needed for fitting. */ +export function lmProcessInput(config: LmLoopConfig, step: LoopStepConfig, generation: number, source: LmCheckpoint, observations: readonly LmObservation[], programmeId: string): ProcessInput<{ readonly digest: string }> { + const adapter = config.process; + if (adapter?.kind !== "compose-lm") expectedFail("LM process input requires its declared Compose adapter.", "process_invalid_adapter"); + const serialized = lmConfigurationJson(config) as Record; + return { + identity: { programme_id: programmeId, programme: config.digest, generation, source: source.digest, + environment: processDigest({ name: config.environmentName, version: config.environmentVersion }), reward: "pm-rl/lm/1" }, + source: { digest: source.digest }, training: [], observations: [], + training_config: { learning_rate: step.learningRate, steps: config.fitSteps }, + lineage: { training: config.trainingDigest, held_out: config.evaluationDigest, collection: processDigest(observations) }, + lm: { config: { shape: config.shape, baseDigest: config.baseDigest, frozen_base: serialized["frozen_base"], alphabet: config.alphabet, + training: config.training, trainingDigest: config.trainingDigest, fitSteps: config.fitSteps, klWeight: config.klWeight, clipNorm: config.clipNorm, limits: config.limits }, + source_adapter: jsonLmAdapter(source.adapter), observations, generation, step, memory_bytes: adapter.memory_bytes }, + }; +} + +/** Restore exact frozen and source tensors without fitting or accessing held-out data. */ +export function restoreLmProcessInput(input: ProcessInput<{ readonly digest: string }>): { config: LmFitConfig; source: LmCheckpoint; payload: LmProcessPayload } { + const payload = input.lm!; + const config = { ...payload.config, base: restoreLmBase(payload.config.frozen_base, payload.config.shape) }; + const source = lmCheckpoint(parseLmAdapter(payload.source_adapter, config.shape, "LM source"), config); + if (source.digest !== input.source.digest) expectedFail("LM source checkpoint drifted.", "process_checkpoint_invalid"); + return { config, source, payload }; +} + +/** Restore staged numerical evidence and bind its canonical checkpoint and measured limits. */ +export function processLmTraining(input: ProcessInput<{ readonly digest: string }>, completion: ProcessCompletion<{ readonly digest: string }>): LmTraining { + const { config, source, payload } = restoreLmProcessInput(input); + const training = restoreLmTraining(completion.training!, config, payload.generation, source, payload.observations); + if (training.wallMs < 0 || training.wallMs > completion.usage.wall_ms) expectedFail("LM fit wall use is inconsistent with measured process wall.", "process_receipt_invalid"); + return training; +} + +/** Verify durable external evidence against the controller's fit or evaluated candidate without retraining. */ +export function verifyLmProcessJob(job: ProcessJob | null, root: string, input: ProcessInput<{ readonly digest: string }>, expected: { readonly digest: string; readonly training?: JsonValue }): void { + if (job === null || verifiedProcessPolicy(job, root, input) === null) expectedFail("LM candidate has no external trainer receipt.", "process_receipt_required"); + if (job.completion!.policy.digest !== expected.digest || (expected.training !== undefined && processDigest(expected.training) !== processDigest(job.completion!.training))) { + expectedFail("LM candidate differs from its external trainer receipt.", "process_job_drift"); + } +} + +/** Validate LM bytes, tensor identities, fit evidence, memory and actual optimized target tokens. */ +export function validateLmProcessCheckpoint(content: Buffer, completion: ProcessCompletion<{ readonly digest: string }>, input: ProcessInput<{ readonly digest: string }>): void { + const training = processLmTraining(input, completion); + const payload = input.lm!; + const serialized = serializeLmCheckpoint(training.candidate, payload.config); + if (content.toString("utf8") !== serialized.text || completion.policy.digest !== training.candidate.digest + || content.length > payload.config.limits.maxCheckpointBytes) expectedFail("LM checkpoint bytes or tensors are invalid.", "process_checkpoint_invalid"); + const memory = completion.peak_memory_bytes; + if (!Number.isSafeInteger(memory) || Number(memory) < 1 || Number(memory) > payload.memory_bytes + || completion.usage.tokens !== lmFitTokens(payload)) expectedFail("LM memory or token use exceeds its declaration.", "process_limit_exceeded"); +} + +/** Count the real collected target tokens optimized across the declared gradient steps. */ +export function lmFitTokens(payload: LmProcessPayload): number { + return payload.observations.reduce((total, observation) => total + observation.tokens.length, 0) * payload.config.fitSteps; +} diff --git a/process.ts b/process.ts index a4817537..5acf26ad 100644 --- a/process.ts +++ b/process.ts @@ -12,6 +12,7 @@ import { EXIT_CODE, isPmCliExpectedError, certifyCompleteListResult } from "@unb import { banditCheckpoint, type BanditCheckpoint, type BanditExample, type BanditObservation } from "./bandit.ts"; import { canonicalJson, commitProcessTransaction, loopProcessStartTime, withProcessJobFence, readCompleteLoopItem, type JsonValue } from "./index.ts"; import { expectedFail } from "./refuse.ts"; +import { validateLmProcessCheckpoint, type LmProcessPayload } from "./process-lm.ts"; import { launchComposeTrainer, stopComposeTrainer } from "./compose.ts"; /** Independently accounted resource dimensions; all values are nonnegative safe integers. */ @@ -31,26 +32,43 @@ export type ProcessAdapter = { readonly image: string; /** Maximum allowance atomically reserved before this generation launches. */ readonly allowance: Resources; +} | { + /** Bounded causal LM using the same external execution contract. */ + readonly kind: "compose-lm"; + /** Locally built immutable trainer image. */ + readonly image: string; + /** Kernel-enforced memory ceiling in bytes. */ + readonly memory_bytes: number; + /** Kernel-enforced CPU scheduling ceiling. */ + readonly cpus: number; + /** Maximum resources reserved before launch. */ + readonly allowance: Resources; }; +/** Common policy identity used by the shared execution machinery. */ +export type TrainerPolicy = BanditCheckpoint | { readonly digest: string }; +/** Bandit or LM request accepted by the shared execution machinery. */ +export type TrainerInput = ProcessInput; /** Domain lifecycle, independent of the tracker item's planning status. */ export type ProcessJobState = "planned" | "running" | "materialized" | "evaluated" | "promoted" | "failed" | "cancelled" | "invalidated"; /** Immutable inputs delivered on stdin; held-out examples are deliberately absent. */ -export interface ProcessInput { +export interface ProcessInput

{ /** Programme, generation, source and dataset identities, environment, reward, collection and fit configuration. */ readonly identity: JsonValue; /** The collecting policy's actual parameters. */ - readonly source: BanditCheckpoint; + readonly source: P; /** Collection dataset only. */ - readonly training: readonly BanditExample[]; + readonly training: readonly (P extends BanditCheckpoint ? BanditExample : never)[]; /** Actual source-policy samples; reward values are the training signal. */ - readonly observations: readonly BanditObservation[]; + readonly observations: readonly (P extends BanditCheckpoint ? BanditObservation : never)[]; /** One bounded REINFORCE update configuration. */ - readonly training_config: { readonly learning_rate: number; readonly steps: 1 }; + readonly training_config: { readonly learning_rate: number; readonly steps: number }; /** Source and dataset digests bind the receipt to this collected batch. */ readonly lineage: JsonValue; + /** Training-only LM payload; held-out examples never cross this boundary. */ + readonly lm?: LmProcessPayload; } /** Required durable completion evidence; successful process exit alone is never sufficient. */ -export interface ProcessCompletion { +export interface ProcessCompletion

{ /** Versioned stream and disk schema. */ readonly format: "pm-rl/process-completion/1"; /** Content-bound job identity. */ @@ -60,7 +78,7 @@ export interface ProcessCompletion { /** SHA-256 over the exact readable checkpoint bytes. */ readonly digest: string; /** Actual parameters whose policy identity must match the checkpoint content. */ - readonly policy: BanditCheckpoint; + readonly policy: P; /** Immutable fit configuration, source policy and dataset lineage. */ readonly training_config: ProcessInput["training_config"]; /** Digest of the policy that collected the training batch. */ @@ -69,9 +87,13 @@ export interface ProcessCompletion { readonly lineage: JsonValue; /** Adapter-measured steps, elapsed milliseconds, checkpoint bytes, CPU milliseconds and tokens. */ readonly usage: Resources; + /** LM fit evidence with tensors and losses, independently of held-out evaluation. */ + readonly training?: JsonValue; + /** Measured peak resident memory in bytes for the LM trainer. */ + readonly peak_memory_bytes?: number; } /** One reservation and job outcome, committed together as a real Task body. */ -export interface ProcessJob { +export interface ProcessJob

{ /** Distinguishes execution Tasks from unrelated workspace work. */ readonly format: "pm-rl/process-job/1"; /** Stable requested tracker key derived from the immutable inputs and trusted launcher identity. */ @@ -93,7 +115,7 @@ export interface ProcessJob { /** Whether unused allowance has been released. */ readonly settled: boolean; /** Staging receipt adopted after validation, or null before materialization. */ - readonly completion: ProcessCompletion | null; + readonly completion: ProcessCompletion

| null; /** Recorded terminal outcome or phase description. */ readonly outcome: string; /** Cleanup diagnostics retained independently of the trainer outcome and validated completion. */ @@ -125,11 +147,17 @@ export function parseResources(value: unknown): Resources { export function parseProcessAdapter(value: unknown): ProcessAdapter { if (value === null || typeof value !== "object" || Array.isArray(value)) expectedFail("Invalid process adapter.", "process_invalid_adapter"); const record = value as Record; - const compose = record["kind"] === "compose-bandit"; - if ((!compose && record["kind"] !== "process-bandit") || Object.keys(record).some((key) => key !== "kind" && key !== "allowance" && !(compose && key === "image"))) expectedFail("Select process-bandit or compose-bandit.", "process_invalid_adapter"); + const lm = record["kind"] === "compose-lm"; + const compose = record["kind"] === "compose-bandit" || lm; + if ((!compose && record["kind"] !== "process-bandit") || Object.keys(record).some((key) => key !== "kind" && key !== "allowance" && !(compose && key === "image") && !(lm && ["memory_bytes", "cpus"].includes(key)))) expectedFail("Select process-bandit or compose-bandit.", "process_invalid_adapter"); if (compose && (typeof record["image"] !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(record["image"]))) expectedFail("Compose requires a local immutable image ID.", "process_invalid_adapter"); const allowance = parseResources(record["allowance"]); - if (allowance.training_steps !== 1 || allowance.wall_ms < 1 || allowance.wall_ms > 3_600_000 || allowance.storage_bytes < 1 || allowance.storage_bytes > 1_073_741_824) expectedFail("Process training requires one step, bounded wall time and staging bytes.", "process_invalid_allowance"); + if ((!lm && allowance.training_steps !== 1) || (lm && (allowance.training_steps < 1 || allowance.training_steps > 10_000)) || allowance.wall_ms < 1 || allowance.wall_ms > 3_600_000 || allowance.storage_bytes < 1 || allowance.storage_bytes > 1_073_741_824) expectedFail("Process training requires one step, bounded wall time and staging bytes.", "process_invalid_allowance"); + if (lm) { + if (!Number.isSafeInteger(record["memory_bytes"]) || Number(record["memory_bytes"]) < 16_777_216 || Number(record["memory_bytes"]) > 1_073_741_824 + || typeof record["cpus"] !== "number" || record["cpus"] < 0.1 || record["cpus"] > 4 || !Number.isFinite(record["cpus"])) expectedFail("LM requires bounded memory and CPU ceilings.", "process_invalid_allowance"); + return { kind: "compose-lm", image: record["image"] as string, memory_bytes: Number(record["memory_bytes"]), cpus: record["cpus"], allowance }; + } return compose ? { kind: "compose-bandit", image: record["image"] as string, allowance } : { kind: "process-bandit", allowance }; } /** Resolve the packaged reference script for either source execution or the compiled package. */ @@ -138,8 +166,8 @@ export function referenceTrainerArgv(moduleUrl = import.meta.url): readonly stri return [process.execPath, fileURLToPath(new URL(`./process-bandit.${suffix}`, moduleUrl))]; } /** Derive replay identity including the trusted executable selection without persisting its paths. */ -export function processJobId(input: ProcessInput, argv: readonly string[] = referenceTrainerArgv(), adapter?: ProcessAdapter): string { - const launcher = adapter?.kind === "compose-bandit" ? ["pm-rl/compose-bandit/1", adapter.image] : argv; +export function processJobId(input: TrainerInput, argv: readonly string[] = referenceTrainerArgv(), adapter?: ProcessAdapter): string { + const launcher = adapter?.kind === "compose-lm" ? ["pm-rl/compose-lm/1", adapter] : adapter?.kind === "compose-bandit" ? ["pm-rl/compose-bandit/1", adapter.image] : argv; return `process-${processDigest({ input, launcher: processDigest(launcher) }).slice(7)}`; } /** Resolve this job's retained evidence directory under the tracker. */ @@ -147,13 +175,13 @@ export function processStaging(root: string, id: string): string { return join(root, "runtime", "pm-rl", "jobs", id); } /** Read a complete real item and reject omitted output or damaged job bodies. */ -export async function readProcessJob(client: PmClient, id: string): Promise { +export async function readProcessJob

(client: PmClient, id: string): Promise | null> { const item = await readCompleteLoopItem(client, id).catch((error: unknown) => { if (isPmCliExpectedError(error) && error.exitCode === EXIT_CODE.NOT_FOUND) return null; throw error; }); if (item === null) return null; - const job = JSON.parse(String(item.item.body)) as ProcessJob; + const job = JSON.parse(String(item.item.body)) as ProcessJob

; if (item.item.type !== "Task" || job.format !== "pm-rl/process-job/1" || job.id !== id || !["planned", "running", "materialized", "evaluated", "promoted", "failed", "cancelled", "invalidated"].includes(job.state)) expectedFail("Job identity or state drifted.", "process_job_drift"); parseResources(job.allowance); parseResources(job.charge); if (typeof job.settled !== "boolean" || (!job.settled && processDigest(job.charge) !== processDigest(job.allowance)) @@ -169,7 +197,7 @@ async function approvedResources(client: PmClient, approval: string): Promise<{ return { budget: parseResources(spec["resource_budget"]), digest: processDigest(spec), id: String(item.item.id) }; } /** Refuse a changed budget identity before adopting, evaluating or promoting a job. */ -export async function verifyProcessJobBudget(client: PmClient, job: ProcessJob): Promise { +export async function verifyProcessJobBudget(client: PmClient, job: ProcessJob): Promise { const approved = await approvedResources(client, job.approval); if (job.budget_digest !== approved.digest) expectedFail("Approved budget identity changed.", "process_budget_drift"); } @@ -193,9 +221,9 @@ export function processJobBodyId(body: string): string | null { * A job body counts only on the item that is that job (item ids carry the tracker prefix), so a job body * copied into another item neither double-charges a reservation nor names a job that does not exist. */ -export async function listProcessJobs(client: PmClient): Promise { +export async function listProcessJobs(client: PmClient): Promise[]> { const listed = certifyCompleteListResult(await client.listAllComplete({ includeBody: true })); - const jobs: ProcessJob[] = []; + const jobs: ProcessJob[] = []; for (const item of listed.items) { const jobId = processJobBodyId(String(item.body)); if (jobId === null || (!item.id.endsWith(`-${jobId}`) && item.id !== jobId)) continue; @@ -206,7 +234,7 @@ export async function listProcessJobs(client: PmClient): Promise { } /** Reserve once under the workspace writer lock, counting every held or settled job on this approval. */ -export async function reserveProcessJob(client: PmClient, coordinates: ProcessCoordinates, id: string, input: ProcessInput, approval: string, allowance: Resources): Promise { +export async function reserveProcessJob(client: PmClient, coordinates: ProcessCoordinates, id: string, input: TrainerInput, approval: string, allowance: Resources): Promise> { const step: WorkspaceTransactionStep = { id: "reserve", async inspect() { return { state: await readProcessJob(client, id) === null ? "pending" : "applied" }; }, @@ -217,7 +245,7 @@ export async function reserveProcessJob(client: PmClient, coordinates: ProcessCo const total = charges.reduce((sum, charge) => sum + charge[key], allowance[key]); if (!Number.isSafeInteger(total) || total > approved.budget[key]) expectedFail(`Approved ${key} budget exhausted.`, "process_budget_exhausted"); } - const job: ProcessJob = { format: "pm-rl/process-job/1", id, input: processDigest(input), binding: input.identity, approval: approved.id, budget_digest: approved.digest, + const job: ProcessJob = { format: "pm-rl/process-job/1", id, input: processDigest(input), binding: input.identity, approval: approved.id, budget_digest: approved.digest, state: "planned", allowance, charge: allowance, settled: false, completion: null, outcome: "reserved" }; await client.create({ id, type: "Task", title: `Process trainer ${id.slice(-12)}`, body: JSON.stringify(job) }); }, @@ -238,7 +266,7 @@ const JOB_TRANSITIONS: Readonly { +export async function transitionProcessJob

(client: PmClient, coordinates: ProcessCoordinates, job: ProcessJob

, state: ProcessJobState, outcome: string, completion = job.completion, charge = job.charge): Promise> { if (job.state !== state && !JOB_TRANSITIONS[job.state].includes(state)) expectedFail("Illegal process job transition.", "process_job_drift"); const target = job.state === state ? job : { ...job, state, outcome, completion, charge, settled: state !== "running" && state !== "planned" }; await commitProcessTransaction({ ...coordinates, transactionId: `${job.id}-${state}`, steps: [{ id: "outcome", @@ -246,7 +274,7 @@ export async function transitionProcessJob(client: PmClient, coordinates: Proces async apply() { await client.update(job.id, { body: JSON.stringify(target) }); }, async compensate() { await client.update(job.id, { body: JSON.stringify(job) }); }, }] }); - return (await readProcessJob(client, job.id))!; + return (await readProcessJob

(client, job.id))!; } /** Measure all retained flat staging files, rejecting links and directories rather than following them. */ export function stagingBytes(directory: string): number { @@ -259,19 +287,23 @@ export function stagingBytes(directory: string): number { return bytes; } /** Validate the staged completion against immutable input, readable bytes and every resource dimension. */ -export function validateProcessCompletion(directory: string, id: string, input: ProcessInput, allowance: Resources): ProcessCompletion { +export function validateProcessCompletion

(directory: string, id: string, input: ProcessInput

, allowance: Resources): ProcessCompletion

{ if (!existsSync(join(directory, "completion.json"))) expectedFail("Trainer supplied no completion receipt.", "process_receipt_required"); const bytes = stagingBytes(directory); if (bytes > allowance.storage_bytes || lstatSync(join(directory, "completion.json")).size > 65_536) expectedFail("Trainer exceeded staging storage allowance.", "process_limit_exceeded"); - const value = JSON.parse(readFileSync(join(directory, "completion.json"), "utf8")) as ProcessCompletion; + const value = JSON.parse(readFileSync(join(directory, "completion.json"), "utf8")) as ProcessCompletion

; if (value.format !== "pm-rl/process-completion/1" || value.job !== id || value.checkpoint !== "checkpoint.json" || processDigest(value.training_config) !== processDigest(input.training_config) || value.source_policy !== input.source.digest || processDigest(value.lineage) !== processDigest(input.lineage)) expectedFail("Completion lineage or configuration drifted.", "process_receipt_invalid"); - if (lstatSync(join(directory, value.checkpoint)).size > 4096) expectedFail("Bandit checkpoint exceeds its schema bound.", "process_checkpoint_invalid"); + if (lstatSync(join(directory, value.checkpoint)).size > (input.lm?.config.limits.maxCheckpointBytes ?? 4096)) expectedFail("Checkpoint exceeds its schema bound.", "process_checkpoint_invalid"); const content = readFileSync(join(directory, value.checkpoint)); const digest = `sha256:${createHash("sha256").update(content).digest("hex")}`; - const policy = JSON.parse(content.toString("utf8")) as BanditCheckpoint; - if (digest !== value.digest || processDigest(policy) !== processDigest(value.policy) || !Number.isFinite(policy.weight) || Math.abs(policy.weight) > 20 || policy.digest !== banditCheckpoint(policy.weight).digest) expectedFail("Checkpoint content digest or parameters are invalid.", "process_checkpoint_invalid"); + if (digest !== value.digest) expectedFail("Checkpoint content digest or parameters are invalid.", "process_checkpoint_invalid"); + if (input.lm !== undefined) validateLmProcessCheckpoint(content, value, input); + else { + const policy = JSON.parse(content.toString("utf8")) as BanditCheckpoint; + if (processDigest(policy) !== processDigest(value.policy) || !Number.isFinite(policy.weight) || Math.abs(policy.weight) > 20 || policy.digest !== banditCheckpoint(policy.weight).digest) expectedFail("Checkpoint content digest or parameters are invalid.", "process_checkpoint_invalid"); + } const usage = parseResources(value.usage); if (usage.training_steps !== input.training_config.steps || usage.storage_bytes !== content.length) expectedFail("Completion lacks measured training or checkpoint use.", "process_receipt_invalid"); for (const key of RESOURCE_KEYS) if (usage[key] > allowance[key]) expectedFail(`Trainer exceeded reserved ${key}.`, "process_limit_exceeded"); @@ -283,7 +315,7 @@ export function validateProcessCompletion(directory: string, id: string, input: return { ...value, usage: { ...usage, wall_ms: wall, storage_bytes: bytes } }; } /** Refuse terminal job reuse and verify receipts before they may supply a collection policy. */ -export function verifiedProcessPolicy(job: ProcessJob, root: string, input: ProcessInput, inspectTerminal = false): BanditCheckpoint | null { +export function verifiedProcessPolicy

(job: ProcessJob, root: string, input: ProcessInput

, inspectTerminal = false): P | null { if (job.input !== processDigest(input)) expectedFail("Job input identity drifted.", "process_job_drift"); if (!inspectTerminal && ["failed", "cancelled", "invalidated"].includes(job.state)) expectedFail(`Process job is ${job.state}: ${job.outcome}.`, "process_job_terminal"); if (job.completion === null) return null; @@ -296,7 +328,7 @@ export function stopTrainer(pid: number, start: string): void { if (loopProcessStartTime(pid) === start) process.kill(pid, "SIGKILL"); } /** Launch a real child with bounded NDJSON, wall/storage polling and owner-checked cancellation. */ -export async function launchTrainer(directory: string, id: string, input: ProcessInput, allowance: Resources, argv: readonly string[], signal?: AbortSignal): Promise { +export async function launchTrainer(directory: string, id: string, input: TrainerInput, allowance: Resources, argv: readonly string[], signal?: AbortSignal): Promise { const startedAt = Date.now(); const started = performance.now(); const child = spawn(argv[0], argv.slice(1), { cwd: directory, shell: false, stdio: ["pipe", "pipe", "pipe"] }); @@ -389,13 +421,14 @@ function failedProcessCharge(directory: string, allowance: Resources): Resources } /** Recover staged work or execute exactly one reserved phase; failures settle conservatively and retain evidence. */ -export async function executeProcessTrainer(client: PmClient, coordinates: ProcessCoordinates, input: ProcessInput, approval: string, adapter: ProcessAdapter, signal?: AbortSignal, argv: readonly string[] = referenceTrainerArgv()): Promise { +export async function executeProcessTrainer

(client: PmClient, coordinates: ProcessCoordinates, input: ProcessInput

, approval: string, adapter: ProcessAdapter, signal?: AbortSignal, argv: readonly string[] = referenceTrainerArgv()): Promise> { + if ((input.lm !== undefined) !== (adapter.kind === "compose-lm") || (input.lm !== undefined && adapter.kind === "compose-lm" && (input.lm.memory_bytes !== adapter.memory_bytes || input.training_config.steps !== adapter.allowance.training_steps))) expectedFail("Trainer payload differs from the declared adapter limits.", "process_invalid_adapter"); const id = processJobId(input, argv, adapter); - return withProcessJobFence(coordinates, id, () => executeReservedProcessJob(client, coordinates, input, approval, adapter, id, signal, argv)); + return withProcessJobFence(coordinates, id, async () => await executeReservedProcessJob(client, coordinates, input, approval, adapter, id, signal, argv) as ProcessJob

); } /** Execute inside the verified owner fence; this function never re-launches a previously running job. */ -async function executeReservedProcessJob(client: PmClient, coordinates: ProcessCoordinates, input: ProcessInput, approval: string, adapter: ProcessAdapter, id: string, signal: AbortSignal | undefined, argv: readonly string[]): Promise { +async function executeReservedProcessJob(client: PmClient, coordinates: ProcessCoordinates, input: TrainerInput, approval: string, adapter: ProcessAdapter, id: string, signal: AbortSignal | undefined, argv: readonly string[]): Promise> { let job = await reserveProcessJob(client, coordinates, id, input, approval, adapter.allowance); const policy = verifiedProcessPolicy(job, coordinates.pmRoot, input); if (policy !== null) return transitionProcessJob(client, coordinates, job, job.state, job.outcome); @@ -407,11 +440,11 @@ async function executeReservedProcessJob(client: PmClient, coordinates: ProcessC mkdirSync(directory, { recursive: true }); if (job.state === "planned") { job = await transitionProcessJob(client, coordinates, job, "running", "launched"); - if (adapter.kind === "compose-bandit") await launchComposeTrainer(directory, id, input, job.allowance, adapter.image, cleanupFailed, signal); + if (adapter.kind !== "process-bandit") await launchComposeTrainer(directory, id, input, job.allowance, adapter.image, cleanupFailed, signal, adapter.kind === "compose-lm" ? adapter : undefined); else await launchTrainer(directory, id, input, job.allowance, argv, signal); } else { - if (adapter.kind === "compose-bandit") { - try { await stopComposeTrainer(directory, id, adapter.image, "recovery"); } + if (adapter.kind !== "process-bandit") { + try { await stopComposeTrainer(directory, id, adapter.image, "recovery", adapter.kind === "compose-lm" ? adapter : undefined); } catch (error) { cleanupFailed(error); } } else if (existsSync(join(directory, "runner.json"))) { diff --git a/test/compose.test.ts b/test/compose.test.ts index 2347dee2..029705c0 100644 --- a/test/compose.test.ts +++ b/test/compose.test.ts @@ -1,10 +1,11 @@ /** Real Docker Compose contract, container death, cancellation, controller races and successor collection. */ import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; import { execFileSync, fork, spawnSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { delimiter, join } from "node:path"; import { setTimeout } from "node:timers/promises"; -import { test } from "node:test"; +import { after, test } from "node:test"; import { composeConfiguration, composeProject, launchComposeTrainer, stopComposeTrainer } from "../compose.ts"; import { resumeRlLoop, rlLoopStatus } from "../index.ts"; import { executeProcessTrainer, listProcessJobs, parseProcessAdapter, processJobId, processStaging, readProcessJob, reserveProcessJob, transitionProcessJob, type ProcessAdapter } from "../process.ts"; @@ -17,12 +18,15 @@ const skip = version.status === 0 ? false : `SKIPPED: docker compose version fai const allowance = { training_steps: 1, wall_ms: 30_000, storage_bytes: 65_536, compute_units: 1000, tokens: 0 }; const budget = { ...allowance, training_steps: 2, wall_ms: 60_000, storage_bytes: 131_072, compute_units: 2000 }; const images = new Map(); +const imageRun = randomUUID(); +const imageOwner = process.env["PM_RL_VALIDATION_OWNER"] ?? `pm-rl-${imageRun}`; +after(() => { for (const image of new Set(images.values())) execFileSync("docker", ["image", "rm", "--force", image], { stdio: "ignore" }); }); /** Build only a local fixture plus the Node base; a build or daemon failure is a hard test failure. */ function adapter(mode = "fit"): ProcessAdapter { let image = images.get(mode); if (image === undefined) { - image = execFileSync("docker", ["build", "--quiet", "--file", "test/fixtures/compose.Dockerfile", "--build-arg", `MODE=${mode}`, "."], { encoding: "utf8", timeout: 120_000 }).trim(); + image = execFileSync("docker", ["build", "--quiet", "--tag", `pm-rl-bandit-${mode}-${imageRun}`, "--label", `pm-rl-validation-run=pm-rl-${imageRun}`, "--label", `pm-rl-validation-owner=${imageOwner}`, "--file", "test/fixtures/compose.Dockerfile", "--build-arg", `MODE=${mode}`, "."], { encoding: "utf8", timeout: 120_000 }).trim(); assert.match(image, /^sha256:[a-f0-9]{64}$/u); images.set(mode, image); } return { kind: "compose-bandit", image, allowance }; @@ -113,16 +117,26 @@ test("real Compose cancellation stops the container and retains attributable can assert.ok(existsSync(join(directory, "partial.json"))); removed(id, directory); }); -test("two real Compose controllers produce exactly one launch and one reservation", { skip }, async () => { +test("two real Compose controllers produce exactly one launch and one reservation", { skip }, async (t) => { const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); const selected = adapter(); const contenders = [controller(pmRoot, approval, "race", selected), controller(pmRoot, approval, "race", selected)]; + t.after(async () => { + for (const entry of contenders) if (entry.child.exitCode === null) entry.child.kill("SIGKILL"); + await Promise.all(contenders.map((entry) => entry.result)); + for (const job of await listProcessJobs(client)) if (selected.kind === "compose-bandit") await stopComposeTrainer(processStaging(pmRoot, job.id), job.id, selected.image, "test-cleanup"); + }); await Promise.all(contenders.map((entry) => entry.ready("starting"))); for (const entry of contenders) entry.child.send("start"); const winner = await Promise.any(contenders.map(async (entry) => { await entry.ready(); return entry; })); const loser = contenders.find((entry) => entry !== winner)!; assert.equal(await loser.result, 1); assert.ok(JSON.stringify(loser.messages).includes("loop_controller_active")); - winner.child.send("continue"); assert.equal(await winner.result, 0); + // The winner holds a durable reservation before the parent permits trainer startup. + const reserved = await listProcessJobs(client); assert.equal(reserved.length, 1); assert.equal(reserved[0].state, "running"); + assert.equal(reserved[0].settled, false); assert.equal(reserved[0].charge.training_steps, 1); + winner.child.send("continue"); assert.equal(await winner.result, 0, JSON.stringify(winner.messages)); const jobs = await listProcessJobs(client); assert.equal(jobs.length, 1); assert.equal(jobs[0].charge.training_steps, 1); + assert.equal(jobs[0].id, reserved[0].id); assert.equal(jobs[0].settled, true); + assert.equal(readFileSync(join(pmRoot, "history", `rl-${jobs[0].id}.jsonl`), "utf8").split("\n").filter((line) => line.includes('"op":"create"')).length, 1); const directory = processStaging(pmRoot, jobs[0].id); assert.equal(readFileSync(join(directory, "launches.ndjson"), "utf8").trim().split("\n").length, 1); assert.equal((await resumeRlLoop(client, coordinates, { id: "process-drill", approval })).resumed_generations, 1); removed(jobs[0].id, directory); @@ -142,14 +156,21 @@ test("real Compose completed staging survives controller SIGKILL before receipt test("real Compose recovery terminates an orphan trainer without relaunching a running job", { skip }, async () => { const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); const selected = adapter("hang"); - const running = controller(pmRoot, approval, "cancel", selected); + const running = controller(pmRoot, approval, "running", selected); let id = ""; - for (let attempt = 0; attempt < 600; attempt += 1) { const jobs = await listProcessJobs(client); if (jobs.length === 1) { id = jobs[0].id; break; } await setTimeout(25); } - assert.ok(id); const directory = processStaging(pmRoot, id); await partial(directory); - running.child.kill("SIGKILL"); await running.result; - await assert.rejects(resumeRlLoop(client, coordinates, { id: "process-drill", approval }), /no completion receipt/); - const job = (await readProcessJob(client, id))!; assert.equal(job.state, "failed"); assert.equal(job.settled, true); - assert.ok(existsSync(join(directory, "partial.json"))); assert.equal(readFileSync(join(directory, "launches.ndjson"), "utf8").trim().split("\n").length, 1); removed(id, directory); + try { + // Observe the real running-job write before timing trainer startup; no repeated tracker reads compete with reservation. + await running.ready(); + const jobs = await listProcessJobs(client); assert.equal(jobs.length, 1); assert.equal(jobs[0].state, "running"); id = jobs[0].id; + const directory = processStaging(pmRoot, id); running.child.send("continue"); await partial(directory); + running.child.kill("SIGKILL"); await running.result; + await assert.rejects(resumeRlLoop(client, coordinates, { id: "process-drill", approval }), /no completion receipt/); + const job = (await readProcessJob(client, id))!; assert.equal(job.state, "failed"); assert.equal(job.settled, true); + assert.ok(existsSync(join(directory, "partial.json"))); assert.equal(readFileSync(join(directory, "launches.ndjson"), "utf8").trim().split("\n").length, 1); removed(id, directory); + } finally { + running.child.kill("SIGKILL"); await running.result; + if (id.length > 0 && selected.kind === "compose-bandit") await stopComposeTrainer(processStaging(pmRoot, id), id, selected.image, "test-cleanup"); + } }); test("real Compose two-generation run changes checkpoints and collects with the promoted successor", { skip }, async (t) => { diff --git a/test/decision-recovery.test.ts b/test/decision-recovery.test.ts index b8495a6a..1166eebf 100644 --- a/test/decision-recovery.test.ts +++ b/test/decision-recovery.test.ts @@ -83,27 +83,36 @@ test("built receipt recovery reconciles a lost HTTP response without a second in /** Fork the built controller and fail promptly if it exits before the requested crash barrier. */ function launch(pmRoot: string, config: Record, stage: string, metric: string, step: number) { - const child = fork(new URL("./fixtures/decision-controller.ts", import.meta.url), [pmRoot, JSON.stringify(config), stage, metric, String(step)], { stdio: ["ignore", "pipe", "pipe", "ipc"] }); + const child = fork(new URL("./fixtures/decision-controller.ts", import.meta.url), [pmRoot, JSON.stringify(config), stage, metric, String(step)], { cwd: join(pmRoot, "..", ".."), stdio: ["ignore", "pipe", "pipe", "ipc"], execArgv: [], env: { ...process.env, NODE_V8_COVERAGE: undefined } }); let output = ""; child.stderr!.on("data", (data: Buffer) => { output += data.toString(); }); const result = new Promise((resolve) => { child.once("exit", () => resolve()); }); + // Killed built-code children cannot flush useful source counters; the parent exercises every authored source. + const uninstrumented = new Promise((resolve, reject) => { + child.on("message", (value: unknown) => { + if (value !== null && typeof value === "object" && "coverage" in value) { + if (value.coverage === false) resolve(); else reject(new Error("SIGKILL controller inherited V8 coverage")); + } + }); + child.once("exit", () => reject(new Error(`controller ended before bootstrap: ${output}`))); + }); const ready = stage === "remote" ? Promise.resolve() : new Promise((resolve, reject) => { - child.on("message", (value: unknown) => { if (value === "ready") resolve(); else reject(new Error(JSON.stringify(value))); }); + child.on("message", (value: unknown) => { if (value !== null && typeof value === "object" && "coverage" in value) return; if (value === "ready") resolve(); else reject(new Error(JSON.stringify(value))); }); child.once("exit", () => reject(new Error(`controller ended before barrier: ${output}`))); }); - return { child, ready, result }; + return { child, ready, result, uninstrumented }; } test("SIGKILL at every HTTP-to-PM receipt boundary replays one decision per query", { timeout: 300000 }, async () => { - for (const metric of ["systemone_decision", "systemone_held_out_decision"]) { - for (const stage of ["remote", "response", "commit", "request"]) { + for (const stage of ["remote", "response", "commit", "request"]) { + await Promise.all(["systemone_decision", "systemone_held_out_decision"].map(async (metric) => { const endpoint = await receiptServer(false, stage === "remote" ? (metric === "systemone_decision" ? 1 : 3) : 0); const { root, pmRoot, client } = await workspace(); const config = configValue(endpoint.baseURL); config.decision_model = { base_url: endpoint.baseURL, model: "tev1:4b", timeout_ms: 10000, receipt_protocol: "idempotency-v1" }; const process = launch(pmRoot, config, stage, metric, 0); try { - await Promise.race([stage === "remote" ? endpoint.remote : process.ready, - process.result.then(() => { throw new Error("controller exited before crash injection"); })]); + await Promise.all([process.uninstrumented, Promise.race([stage === "remote" ? endpoint.remote : process.ready, + process.result.then(() => { throw new Error("controller exited before crash injection"); })])]); process.child.kill("SIGKILL"); await process.result; const before = await rlLoopStatus(client, "recovery"); assert.equal(before.samples_consumed, (metric === "systemone_decision" ? 0 : 2) + (stage === "commit" ? 1 : 0)); @@ -128,7 +137,7 @@ test("SIGKILL at every HTTP-to-PM receipt boundary replays one decision per quer await resumeSource(client, { pmRoot, author: "receipt-test" }, { id: "recovery", approval: "approval" }); assert.equal(endpoint.attempts(), attempts); } finally { process.child.kill("SIGKILL"); await process.result; endpoint.server.closeAllConnections(); endpoint.server.close(); rmSync(root, { recursive: true, force: true }); } - } + })); } }); diff --git a/test/durable-loop.test.ts b/test/durable-loop.test.ts index b437d5c4..852510bb 100644 --- a/test/durable-loop.test.ts +++ b/test/durable-loop.test.ts @@ -194,13 +194,22 @@ test("decision-model crash replay never re-queries persisted decisions and statu }); test("recorded promotion refusal stays terminal on replay and governing approval cannot switch", async () => { - const { client, pmRoot, approval } = await workspace(); - const limited = await client.create({ id: "limited", type: "Decision", title: "One promotion", body: '```json\n{"permitted_promotions":1}\n```' }); - const request = { id: "limited-loop", config, approval: String(limited.item.id) }; - const first = await runRlLoop(client, { pmRoot, author: "rl-test" }, request); assert.equal(first.stop_reason, "promotion_refused"); - assert.deepEqual(stable(await runRlLoop(client, { pmRoot, author: "rl-test" }, request)), stable(first)); - assert.equal((await rlLoopStatus(client, "limited-loop")).samples_consumed, first.samples_consumed); - await assert.rejects(resumeRlLoop(client, { pmRoot, author: "rl-test" }, { id: "limited-loop", approval }), (error: unknown) => isPmCliExpectedError(error) && error.context.code === "loop_approval_mismatch"); + const endpoint = await decisionServer(); + try { + for (const programme of [config, configValue(endpoint.baseURL)]) { + const { client, pmRoot, approval } = await workspace(); + const limited = await client.create({ id: "limited", type: "Decision", title: "One promotion", body: '```json\n{"permitted_promotions":1}\n```' }); + const request = { id: "limited-loop", config: programme, approval: String(limited.item.id) }; + const first = await runRlLoop(client, { pmRoot, author: "rl-test" }, request); assert.equal(first.stop_reason, "promotion_refused"); + assert.equal(first.promoted, 1); assert.equal(first.generations.at(-1)!.promoted, false); + assert.equal(first.process_jobs, undefined, "in-process trainers create no external job or reservation"); + const attempts = endpoint.calls(); + assert.deepEqual(stable(await runRlLoop(client, { pmRoot, author: "rl-test" }, request)), stable(first)); + assert.equal(endpoint.calls(), attempts, "terminal refusal replay cannot query again"); + assert.equal((await rlLoopStatus(client, "limited-loop")).samples_consumed, first.samples_consumed); + await assert.rejects(resumeRlLoop(client, { pmRoot, author: "rl-test" }, { id: "limited-loop", approval }), (error: unknown) => isPmCliExpectedError(error) && error.context.code === "loop_approval_mismatch"); + } + } finally { endpoint.server.closeAllConnections(); endpoint.server.close(); } }); test("step-machinery conflicts during promotion propagate instead of becoming a permanent promotion refusal", async () => { diff --git a/test/fixtures/compose.Dockerfile b/test/fixtures/compose.Dockerfile index 79189842..0cc0111c 100644 --- a/test/fixtures/compose.Dockerfile +++ b/test/fixtures/compose.Dockerfile @@ -1,4 +1,5 @@ -FROM node:22-alpine +FROM node:24-alpine@sha256:44fce696f38ba8c987c4db89954051000bbd05ffd8d8659ba15ed4a8602e937f +LABEL pm-rl-trainer="pm-rl-bandit" WORKDIR /trainer COPY package.json bandit.ts promotion.ts process-bandit.ts ./ COPY scripts/script-launcher.ts ./scripts/ diff --git a/test/fixtures/decision-controller.ts b/test/fixtures/decision-controller.ts index 57341cf6..7239bbbc 100644 --- a/test/fixtures/decision-controller.ts +++ b/test/fixtures/decision-controller.ts @@ -3,6 +3,7 @@ import { PmClient } from "@unbrained/pm-cli/sdk/core"; import { runRlLoop, type JsonValue } from "../../dist/index.js"; const [pmRoot, encoded, targetStage, targetMetric, targetStep] = process.argv.slice(2); +process.send!({ coverage: process.env["NODE_V8_COVERAGE"] !== undefined || process.execArgv.includes("--experimental-test-coverage") }); const client = new PmClient({ pmRoot, author: "receipt-process" }); try { await runRlLoop(client, { pmRoot, author: "receipt-process" }, { diff --git a/test/fixtures/lm-compose-trainer.ts b/test/fixtures/lm-compose-trainer.ts new file mode 100644 index 00000000..cab9dc4f --- /dev/null +++ b/test/fixtures/lm-compose-trainer.ts @@ -0,0 +1,15 @@ +/** Real LM fits plus deterministic corruption, memory exhaustion and mid-fit barriers. */ +import { appendFileSync, writeFileSync } from "node:fs"; +import { runLmTrainer } from "../../process-lm-trainer.ts"; +import { writeTrainerArtifact } from "../../process-bandit.ts"; +appendFileSync("launches.ndjson", `${JSON.stringify({ started: true })}\n`); +const mode = process.env["TRAINER_MODE"]; +if (mode === "memory") { + process.stdin.resume(); + writeTrainerArtifact("partial.json", { allocation_started: true }); + const blocks: Buffer[] = []; + for (;;) blocks.push(Buffer.alloc(8 * 1024 * 1024, 1)); +} else { + await runLmTrainer(); + if (mode === "corrupt") writeFileSync("checkpoint.json", "corrupt"); +} diff --git a/test/fixtures/lm-compose.Dockerfile b/test/fixtures/lm-compose.Dockerfile new file mode 100644 index 00000000..c2039d84 --- /dev/null +++ b/test/fixtures/lm-compose.Dockerfile @@ -0,0 +1,12 @@ +FROM node:24-alpine@sha256:44fce696f38ba8c987c4db89954051000bbd05ffd8d8659ba15ed4a8602e937f +LABEL pm-rl-trainer="pm-rl-lm-fixture" +WORKDIR /trainer +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts --no-audit --no-fund +COPY *.ts ./ +COPY scripts/script-launcher.ts ./scripts/ +COPY test/fixtures/lm-compose-trainer.ts ./test/fixtures/ +ARG MODE=fit +ENV TRAINER_MODE=$MODE +USER node +ENTRYPOINT ["node", "/trainer/test/fixtures/lm-compose-trainer.ts"] diff --git a/test/fixtures/process-controller.ts b/test/fixtures/process-controller.ts index 75ee4eec..540e2570 100644 --- a/test/fixtures/process-controller.ts +++ b/test/fixtures/process-controller.ts @@ -14,7 +14,7 @@ client.create = async (options) => { const result = await create(options); if (! const update = client.update.bind(client); client.update = async (id, options) => { const body = options?.body; - const matches = typeof body === "string" && id.includes("process-") && body.includes(`"state":"${mode === "committed-materialized" ? "materialized" : mode}"`); + const matches = typeof body === "string" && id.includes("process-") && body.includes(`"state":"${mode === "race" ? "running" : mode === "committed-materialized" ? "materialized" : mode}"`); if (!held && mode === "materialized" && matches) { held = true; await barrier(); } const result = await update(id, options); if (!held && matches) { held = true; await barrier(); } @@ -23,9 +23,8 @@ client.update = async (id, options) => { try { if (mode === "race") { process.send!("starting"); await new Promise((resolve) => { process.once("message", () => resolve()); }); } const report = await runRlLoop(client, { pmRoot, author: "process-test" }, { id: "process-drill", approval, config: JSON.parse(encoded) as JsonValue, signal: abort.signal, - async onPhase(phase) { if (!held && mode === "race" && phase === "collect") { held = true; await barrier(); } }, ...(mode === "cancel" ? { processTrainerArgv: [process.execPath, new URL("./process-trainer.ts", import.meta.url).pathname, "hang"] } : {}), }); process.send!({ report }); -} catch (error) { if (!isPmCliExpectedError(error)) throw error; process.send!({ refusal: error.context.code }); process.exitCode = 1; } +} catch (error) { if (!isPmCliExpectedError(error)) throw error; process.send!({ refusal: error.context.code, message: error.message }); process.exitCode = 1; } finally { process.disconnect!(); } diff --git a/test/lm-cli.test.ts b/test/lm-cli.test.ts index 81f880c5..2114f201 100644 --- a/test/lm-cli.test.ts +++ b/test/lm-cli.test.ts @@ -14,7 +14,7 @@ import { parseGenerationSpec } from "../lineage.ts"; test("packed pm CLI performs three real LM generations and records contamination refusal under 20 seconds", async () => { const root = mkdtempSync(join(tmpdir(), "pm-rl-lm-cli-")); try { - const tracker = await init("rl", { defaults: true, agentGuidance: "skip", author: "rl-acceptance" }, { cwd: root }); + const tracker = await init("rl", { defaults: true, agentGuidance: "skip", author: "rl-acceptance" }, { cwd: root, pmRoot: join(root, ".agents/pm") }); const client = new PmClient({ pmRoot: tracker.path, cwd: root, author: "rl-acceptance" }); const approval = await client.create({ id: "approval", type: "Decision", title: "Bounded synthetic LM approval", body: '```json\n{"permitted_promotions":4}\n```' }); const cli = resolve("node_modules/@unbrained/pm-cli/dist/cli.js"); diff --git a/test/lm-compose.test.ts b/test/lm-compose.test.ts new file mode 100644 index 00000000..e748d305 --- /dev/null +++ b/test/lm-compose.test.ts @@ -0,0 +1,215 @@ +/** Shared-contract LM unit, real-process and real-container recursive execution drills. */ +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; +import { execFileSync, fork, spawnSync } from "node:child_process"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { Readable, Writable } from "node:stream"; +import { setTimeout } from "node:timers/promises"; +import { after, test } from "node:test"; +import { composeConfiguration, composeProject } from "../compose.ts"; +import { runRlLoop, resumeRlLoop, rlLoopStatus, type JsonValue } from "../index.ts"; +import { lmCollectBatch, lmConfigurationJson, parseLmLoopConfig, type LmLoopConfig } from "../lm.ts"; +import { parseLoopProgramme, parseLoopConfig } from "../loop.ts"; +import { lmProcessInput, processLmTraining, restoreLmProcessInput, validateLmProcessCheckpoint, verifyLmProcessJob } from "../process-lm.ts"; +import { lmTrainerMain } from "../process-lm-trainer.ts"; +import { executeProcessTrainer, launchTrainer, listProcessJobs, parseProcessAdapter, processJobId, processStaging, readProcessJob, validateProcessCompletion, verifiedProcessPolicy, type ProcessAdapter, type ProcessCompletion, type Resources } from "../process.ts"; +import { configuration, processWorkspace } from "./fixtures/process-workspace.ts"; + +const allowance = { training_steps: 6, wall_ms: 60_000, storage_bytes: 2_097_152, compute_units: 60_000, tokens: 100_000 }; +const budget: Resources = { training_steps: 20_000, wall_ms: 120_000, storage_bytes: 4_194_304, compute_units: 120_000, tokens: 2_000_000 }; +const imagePlaceholder = `sha256:${"a".repeat(64)}`; +const example = JSON.parse(readFileSync(new URL("../examples/loop-lm.json", import.meta.url), "utf8")) as Record; +/** Programme declares all limits before parsing or execution. */ +function rawConfig(selected: ProcessAdapter = { kind: "compose-lm", image: imagePlaceholder, memory_bytes: 268_435_456, cpus: 1, allowance }): JsonValue { + return { ...example, max_generations: 2, budget: 128, adapter: selected as unknown as JsonValue }; +} +let cached: LmLoopConfig | undefined; +/** Reuse only the deterministic frozen base across independent scratch trackers. */ +function config(): LmLoopConfig { cached ??= parseLmLoopConfig(rawConfig()); return cached; } +/** Build a real collected request with held-out examples excluded. */ +function inputFor(selected?: ProcessAdapter) { + const cfg = selected === undefined ? config() : parseLmLoopConfig({ ...lmConfigurationJson(config()) as Record, adapter: selected as unknown as JsonValue }); + const step = { learningRate: cfg.learningRate, evaluationSamples: cfg.evaluationSamples }; + return lmProcessInput(cfg, step, 1, cfg.initial, lmCollectBatch(cfg, 1, cfg.initial), "process-drill"); +} +/** Capture real stdin/stdout IO without substituting numerical results. */ +async function staged() { + const { root } = await processWorkspace(budget); const directory = join(root, "staged"); mkdirSync(directory); + const input = inputFor(); const id = processJobId(input, undefined, config().process); + let output = ""; + const sink = new Writable({ write(chunk, _encoding, callback) { output += String(chunk); callback(); } }); + await lmTrainerMain([process.execPath, new URL("../process-lm-trainer.ts", import.meta.url).pathname], new URL("../process-lm-trainer.ts", import.meta.url).href, + Readable.from([JSON.stringify({ job: id, input })]), sink, directory); + writeFileSync(join(directory, "runner.json"), JSON.stringify({ started_at: Date.now() - 5000 })); + return { directory, input, id, output, receipt: JSON.parse(readFileSync(join(directory, "completion.json"), "utf8")) as ProcessCompletion<{ digest: string }> }; +} + +test("LM Compose parser declares image, licences, fit, memory, CPU and resource bounds", () => { + const cfg = config(); assert.equal(parseLoopProgramme(lmConfigurationJson(cfg)).trainer, "lm"); + assert.equal(parseLmLoopConfig(lmConfigurationJson(cfg)).digest, cfg.digest); + assert.throws(() => lmProcessInput({ ...cfg, process: undefined }, { learningRate: 1, evaluationSamples: 1 }, 1, cfg.initial, [], "x")); + const selected = cfg.process!; + for (const extra of [{ memory_bytes: undefined }, { memory_bytes: 1 }, { memory_bytes: 2 ** 31 }, { cpus: undefined }, { cpus: 0 }, { cpus: 5 }, { cpus: Infinity }, { cpus: NaN }, { image: "mutable" }, { command: "untrusted" }]) assert.throws(() => parseProcessAdapter({ ...selected, ...extra })); + for (const steps of [0, 10001]) assert.throws(() => parseProcessAdapter({ ...selected, allowance: { ...allowance, training_steps: steps } })); + for (const extra of [{ kind: "compose-bandit", image: imagePlaceholder, allowance: { ...allowance, training_steps: 1 } }, { ...selected, allowance: { ...allowance, training_steps: 5 } }, { ...selected, allowance: { ...allowance, wall_ms: 60_001 } }]) assert.throws(() => parseLmLoopConfig(rawConfig(extra as ProcessAdapter))); + assert.throws(() => parseLoopConfig({ ...configuration, adapter: selected as unknown as JsonValue })); + const generated = composeConfiguration(imagePlaceholder, selected as Extract) as { services: { trainer: Record } }; + assert.equal(generated.services.trainer["mem_limit"], 268_435_456); assert.equal(generated.services.trainer["memswap_limit"], 268_435_456); + assert.equal(generated.services.trainer["cpus"], 1); assert.equal(generated.services.trainer["network_mode"], "none"); + assert.notEqual(String(generated.services.trainer["user"]).split(":")[0], "0"); + const input = inputFor(); const text = JSON.stringify(input); + for (const item of cfg.evaluation) assert.ok(!text.includes(`"string":"${item.string}"`)); + assert.equal(processJobId(input, ["ignored"], selected), processJobId(input, undefined, selected)); + assert.notEqual(processJobId(input, undefined, selected), processJobId(input, undefined, { ...selected, memory_bytes: 134_217_728 } as ProcessAdapter)); + assert.notEqual(processJobId(input, undefined, selected), processJobId(input, undefined, { ...selected, cpus: 0.5 } as ProcessAdapter)); +}); + +test("LM real fit publishes canonical tensors and rejects corrupt bytes, memory and token evidence", async () => { + const { directory, input, id, receipt, output } = await staged(); + assert.equal(JSON.parse(output.trim().split("\n")[1]).type, "complete"); + assert.notEqual(receipt.policy.digest, input.source.digest); + assert.ok(processLmTraining(input, receipt).parameterDeltaL2 > 0); + assert.equal(validateProcessCompletion(directory, id, input, allowance).policy.digest, receipt.policy.digest); + for (const wallMs of [-1, receipt.usage.wall_ms + 1]) { + const invalid = { ...receipt, training: { ...receipt.training as Record, wall_ms: wallMs } }; + assert.throws(() => processLmTraining(input, invalid), /fit wall/); + } + const bytes = readFileSync(join(directory, "checkpoint.json")); + for (const altered of [{ ...receipt, policy: { digest: input.source.digest } }, { ...receipt, peak_memory_bytes: 0 }, { ...receipt, peak_memory_bytes: 2 ** 30 }, { ...receipt, peak_memory_bytes: undefined }, { ...receipt, usage: { ...receipt.usage, tokens: 0 } }]) assert.throws(() => validateLmProcessCheckpoint(bytes, altered, input)); + assert.throws(() => validateLmProcessCheckpoint(Buffer.from("corrupt"), receipt, input)); + assert.throws(() => validateLmProcessCheckpoint(bytes, receipt, { ...input, lm: { ...input.lm!, config: { ...input.lm!.config, limits: { ...input.lm!.config.limits, maxCheckpointBytes: 1 } } } })); + assert.throws(() => restoreLmProcessInput({ ...input, source: { digest: imagePlaceholder } })); + writeFileSync(join(directory, "checkpoint.json"), "corrupt"); assert.throws(() => validateProcessCompletion(directory, id, input, allowance)); +}); + +test("LM real child process uses the shared stream and refuses mismatched adapter limits before reserving", async () => { + const { root, client, coordinates, approval } = await processWorkspace(budget); const input = inputFor(); const directory = join(root, "process"); mkdirSync(directory); + const id = processJobId(input, undefined, config().process); + await launchTrainer(directory, id, input, allowance, [process.execPath, new URL("../process-lm-trainer.ts", import.meta.url).pathname]); + assert.notEqual(validateProcessCompletion(directory, id, input, allowance).policy.digest, input.source.digest); + for (const selected of [{ kind: "process-bandit", allowance: { ...allowance, training_steps: 1 } }, { ...config().process!, memory_bytes: 134_217_728 }, { ...config().process!, allowance: { ...allowance, training_steps: 1 } }]) await assert.rejects(executeProcessTrainer(client, coordinates, input, approval, selected as ProcessAdapter)); + assert.equal((await listProcessJobs(client)).length, 0); +}); + +const version = spawnSync("docker", ["compose", "version"], { encoding: "utf8" }); +const skip = version.status === 0 ? false : `SKIPPED: docker compose version failed: ${version.error?.message ?? version.stderr.trim()}`; +const images = new Map(); +const imageRun = randomUUID(); +const imageOwner = process.env["PM_RL_VALIDATION_OWNER"] ?? `pm-rl-${imageRun}`; +after(() => { for (const [mode, image] of images) if (mode !== "production") execFileSync("docker", ["image", "rm", "--force", image], { stdio: "ignore" }); }); +/** Build only named and labelled repository trainer images; retain the production image. */ +function adapter(mode = "fit"): Extract { + let image = images.get(mode); + if (image === undefined) { + image = execFileSync("docker", ["build", "--quiet", "--tag", `pm-rl-lm-${mode}-${imageRun}`, "--label", `pm-rl-validation-run=pm-rl-${imageRun}`, "--label", `pm-rl-validation-owner=${imageOwner}`, "--file", mode === "production" ? "lm-trainer.Dockerfile" : "test/fixtures/lm-compose.Dockerfile", "--build-arg", `MODE=${mode}`, "."], { encoding: "utf8", timeout: 180_000 }).trim(); + assert.match(image, /^sha256:[a-f0-9]{64}$/u); images.set(mode, image); + } + return { kind: "compose-lm", image, memory_bytes: 268_435_456, cpus: 1, allowance }; +} +/** Wait for durable evidence without depending on scheduler timing. */ +async function until(predicate: () => boolean): Promise { + for (let count = 0; count < 1200; count += 1) { if (predicate()) return; await setTimeout(25); } + assert.fail("LM container barrier was not reached"); +} +/** Spawn the real controller with the existing transaction-boundary barriers. */ +function controller(pmRoot: string, approval: string, cfg: JsonValue, mode: string) { + const child = fork(new URL("./fixtures/process-controller.ts", import.meta.url), [pmRoot, approval, JSON.stringify(cfg), mode], { silent: true, execArgv: [], env: { ...process.env, NODE_V8_COVERAGE: undefined } }); + const messages: unknown[] = []; let diagnostics = ""; + child.stderr!.on("data", (chunk: Buffer) => { diagnostics += String(chunk); }); + const result = new Promise((resolve, reject) => { child.once("error", reject); child.on("message", (message) => messages.push(message)); child.once("exit", (code) => { if (messages.length === 0 && code !== null && code !== 0) reject(new Error(diagnostics)); else resolve(code); }); }); + /** Observe a boundary already reached or a future IPC notification. */ + const ready = (name = "ready"): Promise => messages.includes(name) ? Promise.resolve() : new Promise((resolve, reject) => { child.on("message", (message) => { if (message === name) resolve(); }); child.once("exit", () => reject(new Error(`Controller exited before barrier: ${diagnostics} ${JSON.stringify(messages)}`))); }); + return { child, messages, result, ready }; +} +/** Assert only this job's exact container was removed. */ +function removed(id: string, directory: string): void { + const result = spawnSync("docker", ["container", "inspect", `${composeProject(id, directory)}-trainer`], { encoding: "utf8" }); + assert.notEqual(result.status, 0); assert.match(result.stderr, /No such (object|container)/u); +} + +test("real Compose LM promotes two generations and collects with the promoted checkpoint", { skip }, async (t) => { + const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); const selected = adapter("production"); + const start = performance.now(); const report = await runRlLoop(client, coordinates, { id: "process-drill", approval, config: rawConfig(selected) }); + const executionMs = performance.now() - start; + assert.equal(report.promoted, 2); assert.equal(report.process_jobs!.length, 2); + const rows = []; + for (const [index, job] of report.process_jobs!.entries()) { + assert.equal(job.state, "promoted"); assert.equal(job.charge.training_steps, 6); assert.ok(job.charge.tokens > 0); + const cfg = parseLmLoopConfig(rawConfig(selected)); + const run = await client.get(report.generations[index].run!); assert.equal(run.item.component, job.completion!.source_policy); + assert.equal(job.completion!.digest, job.completion!.policy.digest); assert.notEqual(job.completion!.source_policy, job.completion!.digest); + if (index === 1) assert.equal(job.completion!.source_policy, report.process_jobs![0].completion!.digest); + const directory = processStaging(pmRoot, job.id); const stagedInput = JSON.parse(readFileSync(join(directory, "input.json"), "utf8")).input; + for (const example of cfg.evaluation) assert.ok(!JSON.stringify(stagedInput).includes(`"string":"${example.string}"`)); + verifiedProcessPolicy(job, pmRoot, stagedInput); + verifyLmProcessJob(job, pmRoot, stagedInput, { digest: job.completion!.digest, training: job.completion!.training }); + for (const missing of [null, { ...job, completion: null }]) assert.throws(() => verifyLmProcessJob(missing, pmRoot, stagedInput, { digest: job.completion!.digest })); + assert.throws(() => verifyLmProcessJob(job, pmRoot, stagedInput, { digest: job.completion!.source_policy })); + assert.throws(() => verifyLmProcessJob(job, pmRoot, stagedInput, { digest: job.completion!.digest, training: null })); + const stop = JSON.parse(readFileSync(join(directory, "compose-stop.json"), "utf8")); + assert.equal(stop.resources.memory_bytes, selected.memory_bytes); assert.equal(stop.resources.nano_cpus, 1_000_000_000); + assert.equal(stop.resources.network, "none"); assert.equal(stop.resources.oom_killed, false); assert.notEqual(stop.resources.user.split(":")[0], "0"); + removed(job.id, directory); + rows.push({ generation: index + 1, before: job.completion!.source_policy, after: job.completion!.digest, held_out: report.generations[index].held_out_mean, + usage: job.charge, peak_memory_bytes: job.completion!.peak_memory_bytes }); + } + assert.equal((await resumeRlLoop(client, coordinates, { id: "process-drill", approval })).resumed_generations, 2); + assert.equal((await rlLoopStatus(client, "process-drill", pmRoot)).promoted, 2); + t.diagnostic(JSON.stringify({ execution_ms: executionMs, verification_ms: performance.now() - start - executionMs, image: selected.image, rows })); +}); + +test("real Compose LM staged completion survives SIGKILL without retraining or double charge", { skip }, async () => { + const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); const selected = adapter(); + const running = controller(pmRoot, approval, { ...rawConfig(selected) as Record, max_generations: 1 }, "materialized"); await running.ready(); + const [job] = await listProcessJobs(client); assert.equal(job.state, "running"); const directory = processStaging(pmRoot, job.id); + const bytes = readFileSync(join(directory, "checkpoint.json")); const completion = readFileSync(join(directory, "completion.json")); + running.child.kill("SIGKILL"); await running.result; + assert.equal((await resumeRlLoop(client, coordinates, { id: "process-drill", approval })).promoted, 1); + assert.deepEqual(readFileSync(join(directory, "checkpoint.json")), bytes); assert.deepEqual(readFileSync(join(directory, "completion.json")), completion); + assert.equal(readFileSync(join(directory, "launches.ndjson"), "utf8").trim().split("\n").length, 1); + const jobs = await listProcessJobs(client); assert.equal(jobs.length, 1); assert.equal(jobs[0].charge.training_steps, 6); assert.equal(jobs[0].settled, true); removed(job.id, directory); +}); + +test("real Compose LM mid-fit cancellation retains evidence and process_cancelled", { skip }, async () => { + const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); + const selected = { ...adapter(), allowance: { ...allowance, training_steps: 1000, tokens: 1_000_000 } }; + const raw = { ...rawConfig(selected) as Record, max_generations: 1, fit_steps: 1000, limits: { ...example["limits"] as Record, max_steps: 2000 } }; + const cfg = parseLmLoopConfig(raw); const input = lmProcessInput(cfg, { learningRate: 1, evaluationSamples: 1600 }, 1, cfg.initial, lmCollectBatch(cfg, 1, cfg.initial), "process-drill"); + const id = processJobId(input, undefined, selected); const directory = processStaging(pmRoot, id); const abort = new AbortController(); + const running = assert.rejects(executeProcessTrainer(client, coordinates, input, approval, selected, abort.signal), /cancelled/); + await until(() => existsSync(join(directory, "events.ndjson"))); abort.abort(); await running; + const job = (await readProcessJob(client, id))!; assert.equal(job.state, "cancelled"); assert.equal(job.outcome, "process_cancelled"); assert.equal(job.settled, true); + assert.ok(existsSync(join(directory, "input.json"))); assert.ok(existsSync(join(directory, "events.ndjson"))); assert.equal(job.completion, null); removed(id, directory); +}); + +test("real Compose LM corrupt checkpoints and exceeded memory/time refuse promotion", { skip }, async () => { + for (const mode of ["corrupt", "memory", "time"]) { + const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); + const selected = mode === "time" ? { ...adapter(), allowance: { ...allowance, training_steps: 1000, wall_ms: 15_000, tokens: 1_000_000 } } : mode === "memory" ? { ...adapter(mode), memory_bytes: 201_326_592 } : adapter(mode); + const cfg = mode === "time" ? { ...rawConfig(selected) as Record, fit_steps: 1000, limits: { ...example["limits"] as Record, max_steps: 2060 } } : rawConfig(selected); + await assert.rejects(runRlLoop(client, coordinates, { id: "process-drill", approval, config: cfg })); + const [job] = await listProcessJobs(client); assert.equal(job.state, "failed"); assert.equal(job.settled, true); assert.equal(job.completion, null); + assert.equal((await client.listAllComplete({})).items.filter((item) => item.type === "Run").length, 1); + assert.equal((await client.listAllComplete({})).items.filter((item) => item.type === "Generation" && item.status === "closed").length, 0); + assert.equal(job.outcome, mode === "corrupt" ? "process_checkpoint_invalid" : mode === "time" ? "process_limit_exceeded" : "trainer_failure"); + const directory = processStaging(pmRoot, job.id); assert.ok(existsSync(join(directory, "input.json"))); + if (mode === "memory") { assert.ok(existsSync(join(directory, "partial.json"))); assert.equal(JSON.parse(readFileSync(join(directory, "compose-stop.json"), "utf8")).resources.oom_killed, true); } + if (mode === "time") assert.ok(existsSync(join(directory, "events.ndjson"))); + removed(job.id, directory); + await assert.rejects(resumeRlLoop(client, coordinates, { id: "process-drill", approval })); + } +}); + +test("two real Compose LM controllers launch once and reserve once", { skip }, async () => { + const { client, coordinates, approval, pmRoot } = await processWorkspace(budget); const selected = adapter(); + const raw = { ...rawConfig(selected) as Record, max_generations: 1 }; + const contenders = [controller(pmRoot, approval, raw, "race"), controller(pmRoot, approval, raw, "race")]; + await Promise.all(contenders.map((entry) => entry.ready("starting"))); for (const entry of contenders) entry.child.send("start"); + const winner = await Promise.any(contenders.map(async (entry) => { await entry.ready(); return entry; })); + const loser = contenders.find((entry) => entry !== winner)!; assert.equal(await loser.result, 1); assert.ok(JSON.stringify(loser.messages).includes("loop_controller_active")); + winner.child.send("continue"); assert.equal(await winner.result, 0); + const jobs = await listProcessJobs(client); assert.equal(jobs.length, 1); assert.equal(jobs[0].charge.training_steps, 6); + const directory = processStaging(pmRoot, jobs[0].id); assert.equal(readFileSync(join(directory, "launches.ndjson"), "utf8").trim().split("\n").length, 1); + assert.equal((await resumeRlLoop(client, coordinates, { id: "process-drill", approval })).resumed_generations, 1); removed(jobs[0].id, directory); +});