Repository navigation
preserve accepted harness memories across concurrent saves - #2133
kevinjosethomas wants to merge 21 commits into
Conversation
Refs: ENG-6007; symptom report in discussion #2009.
| return state; | ||
| } | ||
|
|
||
| function readHarnessState(statePath: string, scope: HarnessScope): HarnessState { |
There was a problem hiding this comment.
The TS side still wipes a legacy NaN-poisoned harness file: readHarnessState JSON.parse-fails, degrades to empty state, and the next refinement save silently overwrites the whole store with that empty state. The recovery added here lives only in the Python writer, so whichever writer touches the file first decides whether the user keeps their harness — the exact legacy scenario this PR fixes on the other writer. Wiring the same recovery into the TS reader — with a cross-writer round-trip test (poisoned file → TS load/save → Python load, and the reverse) so the two recoveries can't drift — would make the outcome writer-independent.
[written by prime-agent, checked by snimu]
| } | ||
| merged.refinements = structuredClone(proposed.refinements); | ||
| } | ||
| if (proposed.schema !== baseline.schema) { |
There was a problem hiding this comment.
Both readers accept any finite schema number, so a future-schema file loads fine — and an old Python load->save then strips its unknown entry fields and kinds while keeping the new schema number, leaving the file silently corrupted yet still stamped with that newer number. A schema > known guard that preserves the file untouched or refuses the save would close this. The round-trips are also asymmetric — TS preserves unknown entry fields while Python strips them — worth aligning while the schema handling is open.
[written by prime-agent, checked by snimu]
Prime Agent performance — completedPR Overall: 0 regressed · 0 improved · 17 no clear change.
Python runtime
Sandbox cost: ~$0.0851 — no inference calls. Methodology and samplesMain resolved at 2026-09-11T20:08:48.296453+00:00. Harness
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 96f2fef. Configure here.

Concurrent kernel memory writes and host
/refinesaves could both report success, then the later save replaced the entire file from an older snapshot and silently discarded the earlier accepted memory. Fixes ENG-6007, independently reproduced from the symptoms reported in discussion #2009.Both writers now acquire the same adjacent file lock, reread the latest state, and merge only changes since their load snapshot before replacing the file atomically. Unrelated memories and refinement events survive; conflicting edits to an entry, schema, or replaced history fail with a reload-and-retry error. Python also preserves the stored schema, generates distinct default refinement IDs, rejects new non-finite JSON values, and normalizes legacy invalid numbers while retaining valid data.
Validation on commit
32bf15dcb885310d6e7eec0bc04342e8eb254896:npm run checkpass locally and in a Linux Prime sandbox. The TypeScript set includes 30 concurrency regressions and existing refinement/literal-prompt controls.0.9.4-eng6007.32bf15dcb8; the loaded Python module matches the installed artifact and pinned source by SHA-256.CI setup now scopes apt dependency installation to Ubuntu sources after repeated unrelated Chrome repository hash failures. The shared literal-prompt test correction checks the final provider user message, allowing the persistent-memory context added on main while preserving the exact prompt and transcript assertions.
Limits: both writers must be updated. A killed writer can leave a lock that must be removed only after all writers stop. Controlled interleavings establish the tested persistence behavior, not production failure frequency. Automatic stale-lock recovery, power-loss durability, and separate audit-log transactions are outside this change. Live inference is not part of this validation.
Linear: ENG-6007
Note
Medium Risk
Changes shared continual-harness persistence used by refinement and the Python RLM kernel; incorrect merge/validation logic could still lose or block memories, though conflicts now fail closed and coverage is extensive.
Overview
Fixes ENG-6007, where concurrent host
/refinesaves and Python kernel writes could both succeed but the last full-file write dropped the other writer’s accepted memories.TypeScript and Python now share an adjacent
harness_state.json.lock, re-read disk under the lock, and merge only deltas from each writer’s load snapshot (mergeHarnessStateChanges/_merge_harness_changes). Unrelated entries and compatible refinement appends survive; conflicts on the same entry, schema, or rewritten refinement history fail with reload-and-retry and persist nothing from that save. Python also serializes mutations on a sharedHarnessStateinstance with anRLock.Save behavior change: corrupt, malformed schema-1, unsupported schema, or lossy/non-finite JSON files stay read-only—loads still degrade for session use, but saves refuse to overwrite instead of self-healing. Writes must match schema 1; default Python refinement IDs are UUID-based to avoid duplicates under concurrency. Protocol and ops notes are in
harness-persistence.md, with a large 6007 regression suite covering host/Python interleavings.Reviewed by Cursor Bugbot for commit d06ef04. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Preserve harness memories across concurrent TypeScript and Python saves
invalidHarnessStateError/_invalid_harness_state_errorrather than silently rewriting it; saves now block on the shared lock and may fail with a timeout error after 10 seconds if another writer holds it.Changes since #2133 opened
rlm.harness._contains_unsafe_json_numberutility to detect integer-valued floats exceeding the maximum safe JSON integer [23bb98d]HarnessStatein Python to serialize concurrent method calls [0bb2d6b]readHarnessStateResultfunction to preserve harness state entries and refinements when unsafe JSON numbers are detected [1c114e0]normalizeJsonNumberTokenutility to preserve the sign prefix when normalized significant digits collapse to empty after trimming leading zeros [96f2fef]saveHarnessStatewithin therefinementmodule to select merge baseline based on whether the snapshot's recordedstatePathmatches the current target path, using the prior snapshot state only when paths match and falling back to an empty harness state otherwise [d06ef04]harnessSnapshotsmodule-level WeakMap value type to include astatePathproperty, introducedresolveHarnessStateTargetutility function for computing canonical target paths by realpath-resolving parent directories, and updatedloadHarnessStateandsaveHarnessStateto record resolved state paths in snapshots [d06ef04]Macroscope summarized 4744ae2.