Skip to content

refactor(daemon): consolidate rlm subagent metadata onto the spawn ledger - #1390

Open
snimu wants to merge 7 commits into
mainfrom
feat/rlm-ledger-consolidation
Open

refactor(daemon): consolidate rlm subagent metadata onto the spawn ledger#1390
snimu wants to merge 7 commits into
mainfrom
feat/rlm-ledger-consolidation

Conversation

@snimu

@snimu snimu commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What this is

PR 2 of the spawn-ledger stack (base: #1387). With the ledger as the topology authority, the per-parent rlm-subagents.jsonl registries stop being written entirely. Hydration metadata (prompt, spawn code, model, names, status) moves to one small JSON display file per child, and every registry consumer now answers topology questions from the ledger and metadata questions from the display file — with a read-only legacy fallback so existing profiles keep working.

What changes

  • New rlm-subagent-display.ts (88 lines): one rlm-subagent.json per child in the artifact dir the child already owns (session-artifacts/<parentId>/<childId>/). Atomic temp+rename writes (0600), tolerant reads (missing/malformed → undefined, unknown fields kept for forward compat). Deliberately contains no topology fields — parent, depth, and current name always come from the ledger; the display file is hydration metadata only. Written at the same three moments the registry used to be: spawn admission, completion, deletion.
  • Registries become read-only legacy data. The writers are deleted. One tolerant reader remains (same validation as before — sub-* ids, absent depths, malformed lines skipped) serving two purposes: the ledger seed source from PR 1, and metadata fallback for children spawned before this PR.
  • Consumers repointed through one helper (display file → legacy registry → edge-only defaults): passive subagent listing, all three hydration sites (a2a wake, cron restore, selector resume), and deletion. A child with a ledger edge but no metadata anywhere still lists and hydrates with defaults rather than becoming unreachable.
  • Deletion is hardened: the display tombstone is written first, then the ledger delete is awaited and load-bearing — if the ledger append fails, the deletion fails, because post-consolidation the ledger is the only thing standing between a deleted child and a permanent ghost edge. A crash between the two writes self-heals on a retried deletion.
  • Dead code deleted: the catalog siblings command, its client method, and the registry-walking listSavedSessionSiblings (unconsumed since feat(daemon): supervisor-owned rlm spawn ledger as family authority #1387 moved the supervisor to ledger-backed siblings), plus all registry-write plumbing.

Compatibility

Numbers

Net src +125 lines (new display module +88, daemon-mode +105 net for the fallback chain and hardened deletion, catalog process −68). The registry merge semantics — last-writer-wins reconstruction at every read — are gone; metadata now has one source order. The large deletion (the catalog family walk and its helper) belongs to the v0.8 stack rebase, not this PR.

Testing

Targeted daemon suites: 458 passed / 8 skipped (env-stripped), npm run check clean, reviewed twice (all findings addressed or explicitly accepted as nits: display-file trust is display-grade only; createdAt epoch default for metadata-less children; the tombstone→ledger crash window keeps its retry self-heal from #1387). The full suite has the same ~93 pre-existing env-dependent failures as the merge base.

Known follow-ups deliberately not in this PR: surfacing the ledger spawn timestamp for metadata-less children's createdAt, and tightening the legacy entry type to mark unvalidated fields as optional.


Note

Medium Risk
Touches daemon session topology, persistence ordering on delete, and migration from registries; failures on ledger append now fail deletion, but behavior is heavily tested and legacy fallbacks remain.

Overview
Stops dual-writing per-parent rlm-subagents.jsonl registries. New children get spawn edges on the spawn ledger and hydration fields in a per-child rlm-subagent.json (atomic writes via rlm-subagent-display.ts). Legacy registries are read-only for seeding and pre-ledger metadata fallback.

Daemon behavior is repointed through passiveRlmSubagentEntryForEdge (display file → legacy registry → ledger defaults): passive listing walks ledger edges instead of registry trees; spawn/completion/deletion use recordRlmSubagentState / recordRlmSubagentDeletion with display tombstone first, then an awaited ledger delete. RlmSpawnLedger.edges gains optional includeDeleted for retry/cleanup paths.

Removed: catalog siblings command, DaemonCatalogClient.siblings, and listSavedSessionSiblings (supervisor already uses ledger-backed siblings).

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

Note

Consolidate RLM subagent metadata onto a durable spawn ledger

  • Introduces a new append-only JSONL spawn ledger (rlm-ledger.ts) that tracks subagent topology (spawn/rename/delete) per sessions directory, replacing per-parent rlm-subagents.jsonl registries as the source of truth.
  • Adds per-child display files (rlm-subagent-display.ts) for storing metadata (prompt, model, status, etc.) atomically via temp-file + fsync + rename.
  • Rewrites listPassiveRlmSubagents, recordRlmSubagentState, and recordRlmSubagentDeletion in daemon-mode.ts to derive topology from the ledger and metadata from display files, with legacy registry as a read-only fallback for pre-ledger children.
  • Migrates sibling lookups in daemon-supervisor.ts from catalog.siblings() to ledger-backed rlmLedgerSiblings(); removes the siblings command from the catalog process entirely.
  • Behavioral Change: subagent admission now waits for rlmSpawnLedger.flush() before returning the runtime; deletion durability order is display tombstone then ledger tombstone with an explicit reason (user or revoked).
📊 Macroscope summarized ee8d43f. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
@snimu
snimu force-pushed the feat/rlm-ledger-consolidation branch from 5f1ad74 to 6f3b2f7 Compare August 14, 2026 15:55
Comment thread packages/coding-agent/src/modes/daemon/rlm-subagent-display.ts
Comment thread packages/coding-agent/src/modes/daemon/rlm-subagent-display.ts Outdated
@snimu
snimu force-pushed the feat/rlm-ledger-consolidation branch 2 times, most recently from 7b8dcc5 to 769ec5d Compare August 14, 2026 16:17
@sethkarten
sethkarten self-requested a review August 14, 2026 18:38
stack merge was automatically disabled August 14, 2026 19:02

Pull Request is not mergeable

stack merge was automatically disabled August 14, 2026 19:08

Pull Request is not mergeable

stack merge was automatically disabled August 14, 2026 21:02

Pull Request is not mergeable

Base automatically changed from feat/rlm-spawn-ledger to main August 14, 2026 21:03
@sethkarten
sethkarten force-pushed the feat/rlm-ledger-consolidation branch from 769ec5d to ee8d43f Compare August 14, 2026 21:03
// written unchanged for their non-topology consumers).
// Spawn admission is the moment the daemon knows the edge firsthand.
if (input.status === "running" && parentSession.sessionFile) {
this.appendRlmLedgerSpawn({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High daemon/daemon-mode.ts:1046

A failed appendRlmLedgerSpawn leaves the child runtime and display file active without a ledger edge, so passive listing and later hydration permanently cannot find that child. recordRlmSubagentState starts the append without awaiting it, and appendRlmLedgerSpawn swallows failures; make spawn admission await and propagate the ledger write before publishing the display entry.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/daemon/daemon-mode.ts around line 1046:

A failed `appendRlmLedgerSpawn` leaves the child runtime and display file active without a ledger edge, so passive listing and later hydration permanently cannot find that child. `recordRlmSubagentState` starts the append without awaiting it, and `appendRlmLedgerSpawn` swallows failures; make spawn admission await and propagate the ledger write before publishing the display entry.

Evidence trail:
packages/coding-agent/src/modes/daemon/daemon-mode.ts:931-946, 1021-1073, 1226-1290, 2603-2641 @ ee8d43fb94e03746e6826f70bc3d69f83bd36aee; packages/coding-agent/src/modes/daemon/rlm-ledger.ts:325-327, 367-370, 436-449 @ ee8d43fb94e03746e6826f70bc3d69f83bd36aee

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High daemon/rlm-ledger.ts:763

truncateTornTailSync can delete a complete record appended by another daemon: after the stability check, ftruncateSync still uses the stale lastNewline + 1 offset and truncates the concurrent append. Because every writer performs this repair before appending, spawn, rename, or delete events can be silently lost; guard repair plus append with a cross-process lock or use a strategy that cannot truncate a file after a concurrent append.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/daemon/rlm-ledger.ts around line 763:

`truncateTornTailSync` can delete a complete record appended by another daemon: after the stability check, `ftruncateSync` still uses the stale `lastNewline + 1` offset and truncates the concurrent append. Because every writer performs this repair before appending, spawn, rename, or delete events can be silently lost; guard repair plus append with a cross-process lock or use a strategy that cannot truncate a file after a concurrent append.

Evidence trail:
packages/coding-agent/src/modes/daemon/rlm-ledger.ts:25-39,705-729,733-770 at 769ec5da9bf6fa9d64115239882be4ab0119d994; https://nodejs.org/api/fs.html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High daemon/rlm-ledger.ts:693

On filesystems where linkSync fails, publishSeedFile can replace a ledger created after its existsSync check, so the stale seed discards newer spawn/delete records and can resurrect deleted edges. The check-then-renameSync sequence is not no-clobber; skip seeding when exclusive publication is unavailable or use an atomic no-replace mechanism.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/daemon/rlm-ledger.ts around line 693:

On filesystems where `linkSync` fails, `publishSeedFile` can replace a ledger created after its `existsSync` check, so the stale seed discards newer spawn/delete records and can resurrect deleted edges. The check-then-`renameSync` sequence is not no-clobber; skip seeding when exclusive publication is unavailable or use an atomic no-replace mechanism.

Evidence trail:
packages/coding-agent/src/modes/daemon/rlm-ledger.ts:18-33, 436-449, 598-611, 680-702, 705-729 at 769ec5da9bf6fa9d64115239882be4ab0119d994

@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 ee8d43f. Configure here.

const siblings = childrenByParent.get(parentPath) ?? [];
siblings.push(edge);
childrenByParent.set(parentPath, siblings);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Failed spawn leaves children unlistable

Medium Severity

Passive listing now walks only ledger edges, and registry writers are gone, so topology has a single store. appendRlmLedgerSpawn still swallows append failures, and flush() cannot see them because the queue catches rejections. A disk or validation failure at spawn still admits the child, then listing, hydration, and a2a wake cannot find it after passivation or restart.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ee8d43f. Configure here.

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.

2 participants