refactor(daemon): consolidate rlm subagent metadata onto the spawn ledger - #1390
refactor(daemon): consolidate rlm subagent metadata onto the spawn ledger#1390snimu wants to merge 7 commits into
Conversation
5f1ad74 to
6f3b2f7
Compare
7b8dcc5 to
769ec5d
Compare
Pull Request is not mergeable
Pull Request is not mergeable
Pull Request is not mergeable
769ec5d to
ee8d43f
Compare
| // 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({ |
There was a problem hiding this comment.
🟠 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
There was a problem hiding this comment.
🟠 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
There was a problem hiding this comment.
🟠 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
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 ee8d43f. Configure here.
| const siblings = childrenByParent.get(parentPath) ?? []; | ||
| siblings.push(edge); | ||
| childrenByParent.set(parentPath, siblings); | ||
| } |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit ee8d43f. Configure here.


What this is
PR 2 of the spawn-ledger stack (base: #1387). With the ledger as the topology authority, the per-parent
rlm-subagents.jsonlregistries 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
rlm-subagent-display.ts(88 lines): onerlm-subagent.jsonper 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.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.siblingscommand, its client method, and the registry-walkinglistSavedSessionSiblings(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 checkclean, reviewed twice (all findings addressed or explicitly accepted as nits: display-file trust is display-grade only;createdAtepoch 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.jsonlregistries. New children get spawn edges on the spawn ledger and hydration fields in a per-childrlm-subagent.json(atomic writes viarlm-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 userecordRlmSubagentState/recordRlmSubagentDeletionwith display tombstone first, then an awaited ledger delete.RlmSpawnLedger.edgesgains optionalincludeDeletedfor retry/cleanup paths.Removed: catalog
siblingscommand,DaemonCatalogClient.siblings, andlistSavedSessionSiblings(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
rlm-subagents.jsonlregistries as the source of truth.listPassiveRlmSubagents,recordRlmSubagentState, andrecordRlmSubagentDeletionin 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.catalog.siblings()to ledger-backedrlmLedgerSiblings(); removes thesiblingscommand from the catalog process entirely.rlmSpawnLedger.flush()before returning the runtime; deletion durability order is display tombstone then ledger tombstone with an explicit reason (userorrevoked).📊 Macroscope summarized ee8d43f. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.