Skip to content

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

Open
snimu wants to merge 3 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 3 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

Src: semantic-edges.ts +16/−61, event-log.ts +17/−21 (net src −49). 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 crash-interference ledger replay for semantic edges (dropping unterminated final records) and centralizes durability in EventLog, which affects session resume and downstream edge derivation in those edge cases.

Overview
Moves ACP semantic-edge ledger I/O onto the shared EventLog substrate, deleting SemanticEdgeRecorder's bespoke load/append/repair path (_loadExisting, parseLedgerContent, _pendingRepair, direct appendFileSync). Construction and writes now go through EventLog.replaySync / appendSync; readSemanticEdgeLedger still fails loud on a missing file via missingFileThrows.

Unifies torn-tail behavior with the RLM spawn ledger: an unterminated final line is treated as an uncommitted append—omitted during replay (even if JSON-parseable) and truncated before the next append, instead of the semantic-edge path newline-completing a valid partial record. EventLog.replaySync implements that skip up front rather than only when parse throws.

Tests pin the new read/append semantics for parseable unterminated tails in both event-log and semantic-edges.

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

Note

Move semantic-edge ledger onto EventLog substrate and unify unterminated-tail handling

  • Replaces the bespoke ledger load/append/repair logic in SemanticEdgeRecorder with calls to the shared EventLog for both replay (replaySync) and writes (appendSync).
  • EventLog.replaySync now skips any non-empty unterminated final line (including valid JSON) instead of parsing it, and gains an optional missingFileThrows flag to control ENOENT behavior.
  • Removes SemanticEdgeRecorder._loadExisting and the manual readFileSync/appendFileSync path; parseSemanticEdgeLine becomes a per-line parser supplied as the replay callback.
  • Risk: EventLog.replaySync no longer swallows parser exceptions for a skipped unterminated tail, and readSemanticEdgeLedger now silently skips an unterminated final line rather than completing it — callers relying on newline-completion of partial tails will see those records dropped.

Macroscope summarized 9e6f959.

…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.
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