Skip to content

refactor(coding-agent): move the semantic-edge ledger onto the event-log substrate - #2028

Open
snimu wants to merge 8 commits into
mainfrom
refactor/semantic-edges-ledger-on-event-log
Open

refactor(coding-agent): move the semantic-edge ledger onto the event-log substrate#2028
snimu wants to merge 8 commits into
mainfrom
refactor/semantic-edges-ledger-on-event-log

Conversation

@snimu

@snimu snimu commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Purpose

The ACP semantic-edge ledger (#1885) and the RLM spawn ledger implement the same append-only JSONL crash-safety independently. #1987 extracted that mechanics into event-log.ts and moved the spawn ledger onto it; this PR completes the dedup by moving the semantic-edge ledger's private append/replay/repair IO onto the same substrate. EventLog is now the single owner of torn-tail handling, repair-on-append, and line-level replay for both ledgers.

What changed

  • SemanticEdgeRecorder keeps its semantics (degrade-to-disabled on the first ledger failure, write-before-action ordering, replay-on-resume, retry-identity parking) and deletes its IO: _loadExisting, parseLedgerContent, _pendingRepair (truncate/terminate memo), and the mkdir/appendFileSync plumbing are gone; construction replays through EventLog and appends go through EventLog.appendSync
  • readSemanticEdgeLedger keeps its contract: reads never mutate or create the file, a missing ledger throws (statSync probe), interior corruption stays loud with the same corrupt semantic-edge ledger line N message

One durability rule unified (union, not intersection)

The two IO layers disagreed on a parseable-but-unterminated final line: the edges ledger newline-completed it (keeping the record), the substrate truncates it (#1987's review verdict: completion turns a line a strict parser rejects into permanent fail-closed interior poison). Worse, the substrate's replay still surfaced such a line while its next append destroyed it — reading data the log then disowns. This PR unifies on one coherent rule in the substrate, for both ledgers: an unterminated final line is an uncommitted append — skipped on read (with the torn-final-line log) and truncated before the next append. This state is unreachable from either ledger's own single-write appends (record and newline are one write); it matters only for crash/interference windows, where "uncommitted" is the only safe reading.

Test surface

  • rlm-ledger.test.ts: passes unchanged
  • semantic-edges.test.ts: one test edited — the pin of the old newline-completion behavior now pins the unified truncation semantics (skipped on read, truncated on append); flagged for exactly this migration in the refactor(coding-agent): extract the append-only event-log substrate from the RLM spawn ledger #1987 review. Everything else (torn-tail tolerance, repair-once, read-never-mutates, read-never-creates, corruption messages, disable-on-failure) passes unchanged
  • event-log.test.ts: the JSON-parseable-tail pin gains the read-side assertion; both behavior pins verified fail-unfixed against the pre-change substrate

Validation

LOC

Total src: +59/−117 (net -58); tests: +79/−5 (net +74).
Tests +11/−4, changelog +1. Classification: deletion — duplicated IO mechanics removed; the only mechanism change is the read-side half of the unified unterminated-tail rule, which removes an inconsistency rather than adding a branch.

Linear: RES-1260 https://linear.app/primeintellect/issue/RES-1260


Note

Medium Risk
Changes durable ledger I/O and tail semantics for semantic-edge sessions (parseable torn tails are dropped on resume), though normal single-write appends are unaffected; repair failures now block appends instead of being swallowed.

Overview
Routes ACP semantic-edge ledger append/replay through shared EventLog, removing bespoke _loadExisting, parseLedgerContent, and deferred truncate/newline repair in SemanticEdgeRecorder. readSemanticEdgeLedger replays via EventLog with missingFileThrows so a missing file still errors for explicit readers.

EventLog tightens crash semantics for both ledgers: unterminated final lines are always treated as uncommitted (skipped on replay even when JSON-valid, truncated on next append—never newline-completed). appendSync fails on short writes instead of silently partial appends; repairTailSync surfaces repair failures so appends do not write through an unrepaired torn tail.

Tests add fault-injection coverage for short writes and failed truncation; semantic-edge expectations shift from newline-completion to the unified tail rule.

Reviewed by Cursor Bugbot for commit d749cf9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Move semantic-edge ledger onto EventLog substrate

  • Replaces the recorder-local file I/O in semantic-edges.ts with shared EventLog.replaySync and EventLog.appendSync calls in event-log.ts, consolidating tail repair, write verification, and missing-file handling into EventLog.
  • EventLog.replaySync now skips any unterminated final line (even valid JSON) and can optionally throw on a missing file.
  • EventLog.appendSync writes via a UTF-8 buffer and throws on short writes instead of silently treating the partial append as complete.
  • EventLog.repairTailSync propagates open/read/truncate errors instead of swallowing them, and truncates a stable unterminated tail at the last newline.
  • Adds fault-injection tests in event-log-faults.test.ts covering short writes and unrepairable tails, and updates existing semantic-edge and event-log tests for the new tail-skip behavior.
  • Risk: EventLog.replaySync no longer surfaces parser errors from an unterminated final line; callers that relied on such errors will see that line skipped instead.

Macroscope summarized d749cf9.

…log substrate

The recorder's private append/replay/repair IO is deleted; EventLog owns it, the same move #1987 made for the RLM spawn ledger. One durability rule is unified in the substrate rather than dropped: an unterminated final line is an uncommitted append, skipped on read and truncated before the next append — never newline-completed and never surfaced to a consumer whose next append destroys it.
Comment thread packages/coding-agent/src/core/semantic-edges.ts Outdated
…atomic

readSemanticEdgeLedger probed with statSync before reading through EventLog, which swallows ENOENT; a ledger deleted between the two returned [] instead of throwing. The missing-file decision now lives at the single open (replaySync missingFileThrows), so no check-then-read window exists.
The unterminated-tail contract was restated four times (module doc, replaySync doc, two test comments). It now lives once in the module doc; the method doc keeps only its own parse/missing-file semantics and the test comments reference the contract.
…tail repair

writeSync may write short (ENOSPC after a prefix); appendSync now loops until the payload is fully on disk so write-before-action callers never act on a torn record reported as success. A tail-repair failure (e.g. append-only ACL permitting O_APPEND but not r+) now propagates instead of being swallowed: writing through an unrepaired torn tail would weld it to the new record as permanent interior corruption. ENOENT and the concurrent-writer instability path keep their existing semantics.
Comment thread packages/coding-agent/src/core/event-log.ts Outdated
Comment thread packages/coding-agent/src/core/event-log.ts
…ng them

The rlm spawn ledger is multi-writer by documented design (supervisor plus each worker over one file), so completing a short O_APPEND write with a second write could interleave with a rival append and weld two records. A short write now truncates its own torn prefix back off (only while this writer still owns the tail) and fails the append; a torn tail is read-tolerated, a weld is permanent corruption. The append fd opens a+ so the ownership check can read the tail.
Comment thread packages/coding-agent/src/core/event-log.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e88fc98. Configure here.

Comment thread packages/coding-agent/src/core/event-log.ts Outdated
…claiming it

The tail-match reclaim could truncate a rival's committed record whose final bytes coincide with our torn prefix - committed-data loss, strictly worse than the torn tail it prevented. A short write now just fails the append: the torn tail is the one tolerated shape, skipped on read and truncated by any writer's next repair (verified for both topologies: a resumed single-writer recorder repairs on its first append; every rlm-ledger writer repairs before each append).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant