Skip to content

feat(coding-agent): event-driven supervisor agent roster serving list from one ledger - #1897

Merged
xeophon merged 48 commits into
mainfrom
feat/agent-roster-ledger
Sep 1, 2026
Merged

feat(coding-agent): event-driven supervisor agent roster serving list from one ledger#1897
xeophon merged 48 commits into
mainfrom
feat/agent-roster-ledger

Conversation

@snimu

@snimu snimu commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Part 2 of 3 for ENG-5794 (https://linear.app/primeintellect/issue/ENG-5794). Stacked on #1895; base is feat/agent-roster-classifier, so this diff is PR2-only. Part 3 (subscription push + TUI consumption) follows.

Summary

  • workers now push their agent roster to the supervisor as events (roster_delta/roster_heartbeat private worker frames): send-only-if-changed deltas from every session/child lifecycle point, queued RLM children included before any session exists, plus a complete replacing snapshot on every (re)auth that carries pending removals — deletions survive supervisor disconnects
  • the supervisor owns one AgentRosterLedger: status classified once at write via the shared classifier (feat(coding-agent): shared agent-status classifier, honest worker visibility, subagents bar rename #1895), seeded at startup from the catalog and the RLM spawn ledger (artifact-dir subagents included, tombstones excluded), then kept current purely by events
  • list is served synchronously from the ledger with zero worker round-trips; the per-worker fan-out with its 5s timeout, the silent stale-summaries path, and mergeSessionLists are deleted; the public list population is byte-compatible with pre-PR2 behavior (pinned by the restored process test)
  • worker death marks family rows recovering natively via socket close; recovery exhaustion marks failed; one 15s unref'd watchdog stamps lastHeardFromAt after 45s of silence (honest staleness instead of silently stale data)
  • legacy workers without the roster capability keep the old refresh path via a capability-gated shim
  • worker.summaries remains only for adoption/recovery/eviction; wake fallback, create readiness, and reuse lookups read the ledger; sessionDir filtering matches artifact-dir subagents by owning-root topology (no sibling-dir cross-contamination)

Validation

  • three adversarial review rounds; final verdict APPROVE with all mutants killed: write-time classification skip, legacy-shim widening, removal-handling drop, worker-close marking skip, queued-supersession lifecycle delete, late-queued-update guard — each by named tests; composition insertion-order reversal proven semantics-free (21/21 green)
  • disconnect semantics pinned: delete-while-disconnected cannot resurrect (removals ride the snapshot, applied after replacement); queue+bind+close while disconnected survives as its durable passivated row
  • sandbox: 19-suite battery 526 passed / 9 skipped; full coding-agent suite zero regressions vs base (identical environmental failure set); root npm run check green

Note

High Risk
Core daemon supervision and session listing now depend on a new worker–supervisor roster protocol and ledger consistency; legacy workers are rejected until restarted, and delete/list/ownership paths were rewired around tombstones and roster state.

Overview
Moves daemon list off per-worker pulls onto a supervisor-owned AgentRoster ledger: workers advertise agent_roster on auth and push roster_delta (diff or full snapshot on reconnect) plus roster_heartbeat over private worker frames; the supervisor applies frames with epoch/chaining, seeds offline rows from the session catalog and RLM spawn ledger, and serves list synchronously from that store.

Workers compose and flush roster rows from session lifecycle (including queued subagents before a session exists, passivated/evicted rows, and explicit removals on archive/delete/swap). Worker disconnect marks rows recovering; prolonged silence sets lastHeardFromAt via a watchdog. Client-owned workers drop roster rows when unregistered; shared tombstoneSavedSessionDelete runs before saved-session deletes. pendingRlmSpawnAppends keys are scoped by parentActiveSessionId#childId. Legacy workers without the capability are rejected (PreRosterWorkerError) and non–client-owned ones may be restarted on adoption.

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

Note

Add event-driven AgentRoster to daemon supervisor and worker protocol

  • Introduces AgentRoster in agent-roster.ts as the single source of truth for session listing, root selection, and ownership checks in the supervisor, replacing direct reads of worker.summaries
  • Workers now compose roster entries from session state and publish roster_delta (snapshot or diff) and roster_heartbeat frames to authenticated supervisors; the protocol is extended in daemon-worker-protocol.ts with DAEMON_WORKER_ROSTER_CAPABILITY and DaemonWorkerRosterOutbound
  • On connect, the supervisor registers a frame listener before auth, requires the agent_roster capability, and throws PreRosterWorkerError for legacy workers; non-client-owned legacy workers are auto-restarted during recovery
  • Session deletion now writes a spawn-ledger tombstone via tombstoneSavedSessionDelete before file removal; roster entries for deleted/closed sessions are removed in the next delta
  • UI label in subagent-summary-line.ts changes from "agents" to "subagents", and agents-view-state.ts now shows live sessions even when worker state is not ready, displaying the worker state as the status label
  • Risk: legacy workers without agent_roster capability will be rejected and restarted; pendingRlmSpawnAppends keys change from childId to ${parentActiveSessionId}#${childId} in daemon-mode.ts — any out-of-tree consumers of that map will break

Changes since #1897 opened

  • Modified DaemonSupervisor roster reseed logic to filter spawn-ledger edges by session family root [385f566]
  • Refactored RlmSpawnLedger.familyUnlocked method to emit orphaned children as root rows and modified delete-session tombstoning to cover all duplicate edges [385f566]
  • Added test coverage for session family filtering during reseed, duplicate edge tombstoning, and orphaned child behavior [385f566]
  • Updated changelog to clarify event-driven agent roster freshness semantics [385f566]

Macroscope summarized 1639737.

Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/.changes/eng-5794-agent-roster-ledger.md Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
@snimu
snimu force-pushed the feat/agent-roster-ledger branch from 79444c5 to 194c5b7 Compare August 31, 2026 14:41
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
@snimu
snimu force-pushed the feat/agent-roster-classifier branch from 05960ed to 8dd6901 Compare August 31, 2026 14:56
@snimu
snimu force-pushed the feat/agent-roster-ledger branch from 194c5b7 to bc71397 Compare August 31, 2026 15:31
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
@snimu
snimu force-pushed the feat/agent-roster-ledger branch from e59b37a to f5e960d Compare August 31, 2026 18:22
Comment thread packages/coding-agent/.changes/eng-5794-agent-roster-ledger.md Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/agent-roster.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts

@sethkarten sethkarten left a comment

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.

The supervisor-owned, event-driven roster is directionally the right design. Before approving, I need the state-authority model clarified.

ResidentWorker now contains both summaries and roster-related state such as rosterEpoch, rosterApplyChain, rosterRepairPull, lastFrameAt, and rosterStale, while AgentRoster is described as the supervisor-owned projection.

Please document:

  1. Which structure is authoritative for listing, routing, recovery, eviction, and reconnect reconciliation.
  2. Why worker.summaries must remain after list moves to AgentRoster.
  3. What prevents worker.summaries and AgentRoster from diverging.
  4. Why each roster ordering/repair field is needed and which race it prevents.

If worker.summaries is only a legacy cache, please remove it or restrict it to a transient pull result. If it is required, add a concise invariant comment near ResidentWorker so future lifecycle changes do not accidentally treat both representations as authoritative.

I am not asking for a broad redesign. I want the lifecycle ownership and consistency model to be explicit before this becomes the basis for listing and routing.

snimu added 13 commits September 1, 2026 09:18
…ration

Passivating an owned worker's rows strips the workerId and turns private
rows into public inactive rows (path/cwd/name/message metadata) served to
every client through offline list paths and roster reads. Client-owned
workers are ephemeral, so their rows die with the registration; the public
disk scan still lists whatever files actually persist.
…rite-through pulls

Adapts the empty-session last-detach eviction (from the idle-eviction fix
round on main) to the roster world with one decision source:

- isEmptyDetachEvictionCandidate reads the worker's non-queued roster rows
  instead of the worker.summaries pull cache.
- The hook's two pulls stay as responsiveness gates and now write through:
  syncRosterFromWorkerSummaries (formerly the gap fill) lets a worker's own
  rows take the pull's fields, so the post-drain re-read deterministically
  sees a schedule registered by a mutation admitted mid-refresh. The
  pull-epoch guard keeps every write-through at least as fresh as the row it
  replaces, and rows claimed by another worker are never stolen.
- The detach-eviction tests seed the supervisor roster like the other
  adapted suites (matchWorkers is roster-backed).

Semantics of the empty-detach eviction are unchanged: empty + unnamed + not
busy + no registrations + no attached clients, last detach only, client-owned
workers excluded, fence coordination intact.
cronStore.onHeartbeatChange only fires on the heartbeat catalog signature,
and cron_add/cron_cancel emit no session event, so hasRegisteredCronJob on
the roster row went stale: the idle sweep could evict a worker whose only
reason to stay resident was a fresh cron job, or keep a cancelled one pinned
forever. The handlers flush explicitly, like set_model does for events that
have no session-event carrier.
…a claimed child

The absentee reseed wrote a synthetic ledger seed (no lastActivityAt,
messageCount 0, artifact-dir cwd) over a previously hydrated claimed row.
Every worker snapshot goes through this for passive registry children, and
Date.parse(undefined) = NaN made canEvictWorker permanently false while the
degraded row persisted; plain list served the degraded fields too. The
reseed now rewrites the previous entry's summary (claim and data both
survive); only rows with no prior entry get the synthetic workerless seed.
- flushRoster's queuedChildren loop var is an agentId (parent-qualified),
  not a bare childId; name it so.
- set/cycle_thinking_level drop their explicit roster flushes: an actual
  change emits thinking_level_changed, which is a trigger already (the
  set_model flushes stay - model changes emit no session event).
- Non-worker daemons no longer accumulate removedAgentIds that no flush
  ever drains.
- The changelog stops presenting recovering/last-heard-from as user-visible
  in this PR; the surfaces that display them ship in the follow-up.
…nd fence applies on socket close

- A pending removal now records the sessionId it removes. A row composed
  again under the same agentId with a different sessionId (or a re-admitted
  queued run) is a new incarnation and cancels the stale removal instead of
  being suppressed from every flush including the reconnect snapshot; the
  removed incarnation itself stays suppressed mid-teardown so a deleted
  child cannot ghost back as a passivated row.
- isWorkerRosterApplyCurrent also requires a live (or authenticating)
  connection: an apply left in flight by a closed socket can no longer
  rewrite rows and drop the recovering label handleWorkerClose just set.
  Reconnection resumes applies through the pending client.
Comments: 153 -> 33 added src comment lines. Kept only notes resolving real
ambiguity (pull-epoch guard, close fence, reseed/NaN rationale, incarnation
suppression, privacy rules, backpressure delivery assumption, pid-recycle and
SIGKILL-wait justifications, wire-schema notes); deleted all narration.

Tests: one behavior test per contract. Merged into their parent behavior
test: bind-window compose-wins, trigger republish, queued rows ledger-internal,
queued-ghost flip, offline rename + failed-disk delete, client-owned inactive
list row, unchained-delta currency, set_model no-carrier flush, reseed data
quality, qualified removal ids. Deleted pins whose behavior another test or
the process-suite E2E already proves: undelivered-change escalation, real-
socket backpressured snapshot, recovering-on-close (asserted in the close-
fence test), frame-source trust, seeded selector resolution, root-pointer
epoch persist, miss-path refresh routing, hydrate race, sessions-dir topology
scoping, delivery-semantics mock twin, descriptor-path delete-routing variant.
… review round

- The offline delete's roster cleanup deletes only the row object it observed:
  a write during the tombstone/unlink awaits replaces the row, and deleting by
  agentId alone would kill the replacement.
- Subagent roster ids fall back to the live parent id when the parent has no
  session path (--no-session parents never write ledger edges), so children of
  two such parents cannot collide on the per-parent 32-bit child id.
- An archived top-level close (killed/completed/replaced; not shutdown/update)
  publishes a roster removal instead of leaving a passivated "live" ghost: the
  worker's list no longer carries the session and the disk scan serves the
  archived file honestly. Subagent rows keep passivating, mirroring the
  registry's completed children.
- Roster applies are fenced by their own source connection: an apply parked on
  the spawn-ledger read by a dead connection can no longer resume during a
  reconnect's pre-auth window and clear the recovering labels, while the
  authenticating connection's own post-auth snapshot still applies immediately.
- One shared roster-seeding fixture (test/fixtures/roster-seed.ts) replaces
  the five per-suite copies.
- Deleted mechanism pins with accepted residual risk: flush change-dedup and
  trigger-set micro-pins (the lifecycle test still pins the closed-session
  flip), the single-flight repair pull, the mid-pull epoch skip, the crafted
  late-update guard phase, and the second pre-roster identity scenario.
- Another comment pass: dropped notes that restate the guard beside them.
…reconciled seeds, one file-ownership source

- pendingRlmSpawnAppends is keyed by parent + childId at every site: child ids
  are only unique per parent, and a cross-parent collision made one admission
  await the wrong ledger append while the other proceeded without awaiting its
  own durable spawn record.
- The roster's ledger seeding and snapshot reseeds read liveEdges(), the
  ledger's own stat-reconciled view (the rule family() already owned): rows
  whose transcript was removed out-of-band never serve in list --all.
  Tombstone-first covers in-band deletes; this covers external removal.
- findWorkerBySessionFile no longer consults the stale pull cache: the roster
  claim and the durable descriptor paths are the ownership sources, so a
  removed row cannot route a create back to a worker that would answer with
  its root session.
- classifyWorkerRosterEntry is module-private (no consumer outside the module).
- The changelog notes the client-owned exception to inactive-row retention.
…ssion swaps

new_session/switch_session/fork swap the runtime under the same state: the
activeSessionId survives while the sessionId (and so the top-level agentId)
changes. The old agentId vanished from composition without a close, so the
passivation-retention loop kept serving it as a stale claimed row that plain
list never carried before the roster. The flush loop now treats a vanished
row whose activeSessionId still composes under a different agentId as a
removal — one owner for every swap origin, no per-command bookkeeping — and
the pending-removal cancel rule also revives resident top-level rows
(switch-back, resume-after-archive) while the resident-subagent teardown
race stays suppressed.
@xeophon
xeophon force-pushed the feat/agent-roster-ledger branch from 1639737 to 0acfeb2 Compare September 1, 2026 07:18

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

Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
Comment thread packages/coding-agent/src/modes/daemon/rlm-ledger.ts
Comment thread packages/coding-agent/.changes/eng-5794-agent-roster-ledger.md Outdated
Comment thread packages/coding-agent/src/modes/daemon/rlm-ledger.ts
…coped reseeds, filter-all tombstones

- family() builds its child suppression from the same single replay + stat
  snapshot that emits child rows: a sessions-dir child whose parent transcript
  vanished degrades to a root row instead of disappearing, and a concurrent
  cross-process append can no longer make the two views disagree.
- Snapshot reseeds are scoped to the snapshotting worker's own family: the
  reseed exists to restore that worker's absentee-swept registry children, and
  resurrecting other families' unclaimed rows leaked a client-owned worker's
  just-dropped children back into list --all as public rows (the ownership
  record is already gone by then, so this scoping IS the privacy rule).
- Transcript deletes tombstone every edge matching the path: appendSpawn's
  per-process uniqueness check leaves a cross-process TOCTOU window, and a
  raced duplicate left live would resurrect a later recreation as a subagent.
- The changelog states the staleness behavior honestly: rows are as fresh as
  the worker's last delta, silence is annotated rather than hidden.
@snimu
snimu dismissed sethkarten’s stale review September 1, 2026 08:36

Addressed, and we're close to a release and for timezone reasons I'll get a different reviewer and merge asap.

@xeophon
xeophon merged commit 8d5722e into main Sep 1, 2026
23 checks passed
@xeophon
xeophon deleted the feat/agent-roster-ledger branch September 1, 2026 09:02
PR9000 pushed a commit to PR9000/prime-agent that referenced this pull request Sep 1, 2026
… from one ledger (PrimeIntellect-ai#1897)

* feat(coding-agent): event-driven supervisor agent roster; serve list from the ledger

Workers now push roster deltas to the supervisor on session events
(roster_delta/roster_heartbeat worker frames, compute-on-event and
send-only-if-changed, plus a 15s unref'd heartbeat tick). The supervisor
keeps one roster ledger seeded at startup from the session catalog and the
RLM spawn ledger (tombstones excluded), classifies status exactly once at
write via classifyAgentStatus, and serves list, selector matching, family
catalogs, and peer rosters from it.

Deletions this enables:
- handleList per-worker fan-out with its 5s timeout and silent stale
  summaries; list now does zero worker round-trips.
- Event-triggered blanket refreshWorkerSummaries (kept only as a per-worker
  shim for legacy workers that do not advertise the roster capability in
  their worker_auth response).
- mergeSessionLists; 'list all' is served from the already-merged ledger.
- streamingMessage off the list wire (recovery/adoption refresh still seeds
  the stream reconstructor).

Visibility and liveness:
- Admitted child runs appear as queued roster rows before their session
  exists and merge into the session row when it binds.
- Close, passivation, and eviction flip rows to inactive; rows are removed
  only for discarded drafts and spawn-ledger delete records.
- A dead worker's rows are marked recovering natively on socket close and
  failed when recovery gives up; one 15s unref'd watchdog stamps
  lastHeardFromAt on rows of workers silent for more than 45s.

busyClientOwnedSessionCount and daemon-launch busy checks are pinned by
tests; roster frames live in the worker protocol, not the client schema,
so no client protocol change ships in this part.

Part 2 of 3 for the event-driven daemon-owned agent roster.

ENG-5794

* fix(coding-agent): review fixes for the supervisor agent roster

- list keeps its resident-only contract: non-all list emits only sessions
  with an activeSessionId; queued child runs and passivated rows stay
  ledger-internal, and list all carries the non-resident rows (owned rows
  keep their workerState/workerPid). sessionDir on list all now filters
  rows by sessions dir (including its sibling session-artifacts tree)
  instead of being ignored.
- Offline saved-session renames and deletes, and worker-side saved-session
  deletes, now write the roster ledger.
- Supervisor (re)authentication makes the worker send a replacing roster
  snapshot; rows absent from the snapshot passivate when a transcript
  exists and are removed otherwise. Pending state commits only after a
  frame reaches an authenticated supervisor, and the supervisor registers
  its frame listener before authenticating so the snapshot cannot race.
- Remaining worker.summaries read paths (wake fallback, create reuse and
  readiness) moved to the ledger; create-forward and rename refreshes are
  gated to legacy workers, with the returned summary written to the ledger.
- The roster wire summary keeps modelFallbackMessage for the active-open
  path.
- Queued-run supersession has one mechanism (session rows overwrite queued
  rows at flush); the run-lifecycle cleanup in observeRosterChildUpdate is
  pinned by a bind-then-close test, and the saved-delete test proves the
  supervisor removes the ledger row end-to-end.
- Worker roster reporter state is created lazily so prototype-based
  fixtures exercising worker_auth cannot crash the flush path.

ENG-5794

* fix(coding-agent): roster review fixes round two

- Non-all list restores the pre-roster population exactly: worker-owned
  rows (materialized and passivated) stay listed; sessionless queued-child
  rows are served by no list form; seeded/offline rows remain all-only.
- The reauth snapshot is the worker's complete roster: composition always
  runs (delivery-gated separately), passivated rows persist in a
  lastComposed map independent of delivery, and pending removedAgentIds
  ride the snapshot frame so deletions survive a disconnect; the
  supervisor applies removals after replacement.
- Queued-run supersession has one mechanism at the queued-entry lifecycle:
  observeRosterChildUpdate deletes the queued row when the child's session
  is bound and its write guard rejects late queued updates for bound
  children; roster composition order carries no semantics (verified by
  insertion-order reversal).
- list-all sessionDir scoping matches artifact-dir children through their
  owning root's sessions dir instead of the shared sibling artifacts tree,
  so sibling session dirs no longer leak each other's subagents.
- Worker roster reporter state is a plain field initializer again; the
  prototype-based worker_auth fixture constructs the state it needs.

ENG-5794

* fix(coding-agent): roster bot-review fixes

- A child run that terminates before binding is a roster removal, never a
  passivated phantom row.
- list all rescans the disk per call (supervisor-local catalog subprocess,
  no worker round-trips) and merges with the ledger, which wins for rows it
  knows; sessionDir defaults to the configured sessions dir, the seed scan
  passes it too, and name validation reads the same per-call catalog path.
  Seeding now exists for selectors, name checks, and liveness only.
- Saved-session deletes publish removals only when the file was actually
  deleted, resolve the roster agent id through the ledger entry or spawn
  edge (childId for subagents), append the spawn-ledger tombstone so
  deleted subagents never reseed, and offline deletes of worker-owned
  passivated files forward to the owning worker instead of being rejected
  as active.
- Roster frames respect backpressure: a non-drained socket gets no writes,
  delivery requires an accepted write, undelivered state stays uncommitted,
  and a drain re-flushes it.
- Roster agent ids qualify child ids by parent path: child ids are 32-bit
  and uniqueness-checked only per parent (agent-session mkdir loop), so
  bare ids collide across parents at scale.
- findWorker's miss path refreshes all workers once, closing the
  just-bound-but-unflushed routing window without reviving the hot-path
  fan-out; a summaries refresh no longer overwrites roster deltas that
  landed while its list request was in flight.
- Seeded artifact-dir rows hydrate their real cwd lazily from the
  transcript header on first list-all use, keeping startup free of
  per-child file reads.

ENG-5794

* fix(coding-agent): roster bot-review fixes round two

- Both remaining removal producers (rlm subagent deletion and discarded
  bound-child drafts) publish parent-qualified agent ids through one shared
  resolution (rosterAgentIdForRlmChild), matching the qualified row keys.
- Delivery authority is the live supervisor claim: hasAuthenticated-
  SupervisorClient and broadcastRosterFrame require supervisorClaims
  membership, so a revoked socket can never satisfy delivery.
- Generation-acked tombstone retention closes the kernel-write-vs-consumed
  gap: every roster frame carries a monotonic generation, delivered
  removals are retained as tombstones, the supervisor acks its last
  consumed generation in worker_auth, the reauth snapshot replays newer
  tombstones, and the worker prunes acked ones (recreated agents drop
  their stale tombstones at composition).
- A refresh response staler than a mid-flight delta is discarded entirely
  (one bounded retry) instead of partially applied, and the eviction
  snapshot reads the roster so a busy delta always outranks a stale list.
- Saved-child deletes append the spawn-ledger tombstone FIRST and abort on
  append failure; a tombstoned-but-undeleted file is the accepted orphan of
  a failed delete and keeps its roster row for retry.

ENG-5794

* fix(coding-agent): child deletes never proceed past an unreadable spawn ledger

Child-ness of a saved-session delete target now comes from worker-held
state (the file-indexed composed roster entry or the transcript's parent
metadata), never from a ledger read that can fail. For a child target an
edges() rejection or a failed tombstone append aborts before file deletion
with the error surfaced and no removal published; top-level targets never
touch the spawn ledger.

ENG-5794

* fix(coding-agent): classify unreadable delete targets through the spawn ledger

Saved-session delete targets discriminate three ways: a readable no-parent
transcript (or composed top-level row) is positively top-level and skips
the spawn ledger; a positively-child target keeps the unguarded
tombstone-first path; an UNKNOWN target (no composed row, unreadable or
corrupt header — readSessionInfo's null is normalized so it cannot pass as
readable) classifies via the ledger, where an edge means child, no edge
means top-level, and a failed read aborts before deletion with no removal
published.

ENG-5794

* fix(coding-agent): roster bot-review fixes round three

- Offline deletes honor descriptor-based ownership: a worker owning the
  file without a claimed roster row forwards when reachable and rejects
  with a retryable error when its socket is down, so a transcript is never
  deleted underneath a live owner.
- The supervisor offline delete uses the worker path's three-way
  discrimination (positively top-level, positively child, unknown-via-
  ledger with abort on an unreadable read), so catalog-seeded children
  without rlmChildId and unreadable targets still tombstone first.
- The list session-dir parent walk uses a visited-set cycle guard instead
  of a hop cap, staying correct at any depth.
- Seeded artifact rows derive their session id from the transcript
  filename so persisted-session-id selectors resolve before any worker
  delta; edge.childId remains the child identifier.
- Worker frames carry their source connection and are dropped when a
  superseded connection's buffer flushes after a reconnect.
- list all treats the disk as authoritative for non-resident rows,
  propagates scan failures instead of shrinking the list, and preserves
  the newest-first catalog order with worker rows replacing their scanned
  files in place.

ENG-5794

* fix(coding-agent): roster bot-review fixes round four

- Worker frames accept exactly the current client and the in-flight
  replacement (worker.pendingClient, set before authentication and cleared
  in a finally on success or rollback), so a replacing connection's
  immediate snapshot is never discarded while the old client is still
  installed.
- Offline deletes reclaim a dead failed registration through the existing
  reclaim machinery before proceeding; live or recovering owners keep the
  retryable rejection.
- Depth-33 parent chains and self-cycles are pinned for session-dir
  scoping, and seeded artifact rows are pinned to resolve by their
  persisted transcript id when the filename differs from the child id.

ENG-5794

* refactor(coding-agent): drop the lossless roster channel; disk is durable truth

- Deltas become best-effort freshness hints: any undelivered, refused, or
  backpressured write just marks a pending snapshot, and one full
  replacing snapshot flows on (re)connect or drain. Generation counters,
  delivered-commit bookkeeping, tombstone retention with ack and prune,
  and the worker_auth rosterGeneration ack all go away.
- The supervisor applies a snapshot atomically: it replaces the worker's
  rows, deletes absent rows outright, then reseeds subagent families from
  the spawn ledger with tombstoned edges filtered out. Tombstone-first
  delete classification stays on both delete paths.
- Undelivered removal ids stay pending and ride the first delivered
  frame, so removals of unattributed rows survive backpressure.
- The startup catalog seed goes away; list all, name checks, and worker
  matching already read disk per call, so only the spawn-ledger seed
  remains.
- The roster test suite consolidates into lifecycle, delivery, delete-
  path, and regression groups: one queued-child lifecycle scenario, one
  snapshot escalation pin, one snapshot-replace-and-reseed pin, and an
  ownership-routing table replace the per-round accretions; the depth-33
  walk pin drops with the machinery it guarded.

ENG-5794

* perf(coding-agent): cache roster row serializations across flushes

Change detection reuses the previous flush's JSON strings, so a churny
flush stringifies each current row once instead of twice.

ENG-5794

* refactor(coding-agent): restart pre-roster workers on adoption; drop the legacy shim

- connectWorker rejects a worker_auth response without the roster
  capability, so adoption of a pre-roster worker routes through the
  existing recoverWorker machinery and respawns it from the current
  binary; sessions reload idle and resume on the next prompt.
- The rosterCapable flag, the legacy event-refresh branch, both
  conditional refresh call sites, and the refresh-vs-delta race guard go
  away; deltas own the roster and pulled summaries only feed recovery
  stream seeding, eviction checks, and descriptor pointers.
- syncWorkerSummariesIntoRoster shrinks to a gap filler: launch and
  recovery pulls fill missing rows and claim workerless seeded rows
  (registry children no delta composes) without ever overwriting
  delta-fed rows, so no ordering guard is needed.
- Tests seed rosters via writeRosterEntry, eviction fixtures seed the
  delta-fed rows they previously got from refresh syncs, and a new pin
  covers the adoption restart routing.

ENG-5794

* fix(coding-agent): roster rework review fixes, supervisor and worker halves

- Worker frames adopt real socket semantics: a write queued under
  backpressure IS delivered, so pending state clears on it; only an
  absent, destroyed, or unauthenticated claim socket is a loss gap, and
  one replacing snapshot closes it. Drains never resend queued frames.
- Gap fills are epoch-guarded: every applied roster frame bumps a
  supervisor-local per-worker counter, a pull that straddled a frame
  re-pulls once, and a still-moving epoch skips the fill entirely so a
  stale list can never resurrect a just-removed row.
- Snapshot applies pre-read the spawn ledger and queue later frames
  behind them per worker, so replacement, absentee deletion, and the
  tombstone-filtered reseed land atomically with no transient removal.
- The startup catalog seed returns: a push-only view needs saved
  top-level rows in the ledger itself. Rows stay slim and list-all keeps
  its per-call disk rescan.
- Pre-roster adoption performs a real bare restart: the durable
  descriptor is the whole respawn context, the old process is killed
  only under its observed identity, and launchWorker respawns from the
  current binary. Pinned end-to-end against a real supervisor with a
  capability-less fake worker, no recovery mocks.
- Model, thinking-level, and rename changes reach subscribers: the
  thinking_level_changed trigger joins the roster event set and the four
  model/thinking handlers schedule a flush.

ENG-5794

* test(coding-agent): await owned process exits before teardown rmSync

The shared afterEach now awaits every tracked child and worker pid
before deleting temp directories, and rmSync retries transient failures,
so a dying worker's log writer cannot race the cleanup into ENOTEMPTY.
Hardened in the shared helper because every test in this file spawns
supervisors and workers through the same teardown.

ENG-5794

* fix(coding-agent): serialize roster pulls with frame applies and harden owner resolution

- bump the roster epoch at frame receipt and route pull gap-fills through
  the one per-worker apply chain (chainWorkerRosterApply)
- resolve delete owners through findWorkerBySessionFile, which now also
  consults pulled worker summaries for unflushed child rows
- restartPreRosterWorker launches a replacement only against a
  confirmed-stopped predecessor; unverifiable live processes keep the
  worker failed
- canonicalize session paths in findActiveSessionByFile so the active
  guard matches the tombstone/removal side across symlinks
- flush the roster projection after execute_bash_and_wait

* test(coding-agent): pin snapshot/pull serialization, pre-roster restart guard, symlink delete guard

- a pull fill queued behind an in-flight snapshot re-claims reseeded rows
- an unverifiable live pre-roster worker stays failed with no replacement
- delete_saved_session through a symlink hits the active-session guard

* fix(coding-agent): abort queued roster applies for unregistered workers; launch only on a confirmed-stopped predecessor

- chained frame applies and pull fills re-check the worker registration
  before running and after the snapshot's ledger pre-read, so a stop can
  never be overwritten by a resumed apply
- a failed partial apply schedules one gap-fill pull as repair
- restartPreRosterWorker launches only when the final identity verdict is
  gone or replaced; a current-to-unknown flip keeps the worker failed

* test(coding-agent): let the stop land mid pre-read in the snapshot-abort pin

* fix(coding-agent): single-flight roster repair pull with a logged failure

- a per-worker marker caps repair pulls at one in flight; repeated apply
  failures reuse it and a failing repair cannot respawn itself
- a failed repair logs one warning naming the worker

* test(coding-agent): reduce the roster suite to distinct behavior pins

- drop the delete round-trip, supervisor unknown-target classification,
  modelFallbackMessage projection, discarded-draft removal ids, and the
  duplicated ledger-read-abort scenario; each surviving pin is named in
  the review ledger
- one makeOfflineSupervisor helper replaces four hand-rolled real
  supervisor constructions; the queued-child test now also pins delta
  removals

* test(coding-agent): drop an unused import after the projection pin removal

* test(coding-agent): fix formatting after the removal-id pin cut

* test(coding-agent): final reviewer-directed roster suite cuts

- collision qualification folds into the queued-child lifecycle pin
- one population matrix covers seeding, resident worker rows, and
  eviction; the standalone passivated-children test is absorbed
- the supervisor staleness sweep pin moves to the push-layer test only
- the two pre-roster restart scenarios become one named table
- the real-socket test drops its fixed sleep; the top-level delete pin
  asserts the exact removed session id

* test(coding-agent): biome format for the population matrix

* test(coding-agent): pin resident and seeded rows side by side in one live list-all

* chore(coding-agent): comment sweep — one-line present-tense rationale, drop dead fixture fields

- condense the moved two-line busy-projection comment and the test
  section banners to one line each
- present-tense fixes in two test comments
- delete the dead rosterCapable/lastFrameAt/rosterStale fixture fields

* fix(coding-agent): republish retry/tool transitions and guard pulled root pointers

- auto_retry_* and tool_execution_* events join the roster flush triggers:
  they flip isSessionActive/activity and isRunningTools; the flush already
  coalesces per tick and sends only changed rows
- the pulled root descriptor persists through the per-worker apply chain
  under the epoch guard, so a stale list can never clobber pointers a
  frame updated mid-pull

* refactor(coding-agent): rename AgentRosterLedger to AgentRoster

* refactor(coding-agent): one owner each for busy/status adapters, registration flags, delete tombstone policy, and the roster heartbeat contract

- isSessionSummaryBusy and classifySessionRosterStatus move into agent-roster.ts
  (re-exported from daemon-session-list.ts for existing importers);
  classifyWorkerRosterEntry now delegates instead of re-inlining the busy predicate.
- The user-delete classification + tombstone-first policy lives once in
  rlm-ledger.ts (tombstoneSavedSessionDelete); the worker and supervisor
  delete_saved_session routes both call it.
- passivatedWorkerRosterEntry never freezes hasRegisteredHeartbeat/hasRegisteredCronJob:
  the worker flush recomputes them from the cron store via the extracted
  scheduledJobRegistrations index (the one registration truth); callers without
  a cron store strip them.
- ROSTER_HEARTBEAT_INTERVAL_MS moves next to the roster capability in
  daemon-worker-protocol.ts; the supervisor staleness threshold derives from it
  (three missed heartbeats) instead of restating 45s.

* fix(coding-agent): supervisor roster correctness batch

- Offline delete_saved_session asserts client access to the owning worker
  before forwarding or reclaiming: a foreign client's delete of a client-owned
  worker's passivated session is an unknown target again.
- Adopted pre-roster workers with an owner are parked through recoverWorker
  (their launch env lives only with the owning client) instead of a bare
  descriptor respawn that would drop it.
- A worker's queued-child rows are removed, not passivated, when its
  registration goes away: a terminal unbound run owns no transcript, and the
  fileless ghost row nothing could list or delete is gone.
- Snapshot reseeds keep a passive registry child's previous worker claim, and
  gap fills also replace synthetic ledger seeds, so passive children stop
  flapping out of the non-all list and stale frozen rows stop feeding eviction.
- hydrateSeededEntry re-checks the row after its header read; a frame that
  rebinds the agentId mid-read is never clobbered with the stale seed.
- matchWorkers and findSummaryInWorker skip queued-child rows: there is no
  session to route to, and a queued name must not create false ambiguity.
- familyCatalogEntries is fail-closed again: a failed catalog scan propagates
  instead of silently shrinking name-uniqueness checks.
- The idle-eviction pull is documented as a responsiveness gate; the decision
  data comes from the delta-fed roster.
- handleList list-all merge drops the O(n^2) includes() and overlaps seeded-row
  header reads.

* test(coding-agent): pin the roster correctness batch

- foreign client delete of a client-owned worker's passivated session rejects as unknown
- worker unregistration removes queued rows instead of passivating unlistable ghosts
- hydrateSeededEntry never clobbers a row rebound during its header read
- passive registry children keep their worker claim across snapshots that omit
  them and stay in the non-all list; the queued gap fill also replaces the
  synthetic ledger seed with the pulled summary
- the worker reporter fixture carries the real lastComposedJson field

* fix(coding-agent): let composed session rows beat lingering queued markers

addRuntime registers a child session before the bind-reporting
rlm_child_update arrives; a roster flush in that window replaced the
resident row with its sessionless queued stub. Session rows now win at
compose time and clear the stale queued marker.

* fix(coding-agent): keep unserved worker files listed as inactive rows in list all

A client-owned worker's row sits in activeByFile even when the client is
not served it; the list-all merge then dropped both the live row and the
catalog row, hiding the session entirely. The on-disk scan is public (no
list surface filters it by ownership), so the file lists as a plain
inactive row again, exactly like before the roster ledger.

* fix(coding-agent): guard roster applies against dead registrations and unreadable ledgers

- Unchained (fast-path) deltas now re-check registration currency exactly
  like chained applies: a late frame from an unregistered or replaced worker
  registration cannot resurrect its rows with a stale claim.
- A snapshot whose spawn-ledger pre-read fails skips the absentee sweep and
  reseed (it cannot tell registry children from stale rows without edges),
  keeps applying the snapshot's own entries, and schedules the single-flight
  repair pull instead of silently deleting passive children.

* fix(coding-agent): drop client-owned workers' roster rows on unregistration

Passivating an owned worker's rows strips the workerId and turns private
rows into public inactive rows (path/cwd/name/message metadata) served to
every client through offline list paths and roster reads. Client-owned
workers are ephemeral, so their rows die with the registration; the public
disk scan still lists whatever files actually persist.

* fix(coding-agent): re-verify pid identity at the last moment before the recovery SIGKILL

* fix(coding-agent): import the moved busy predicate for the empty-session evictability rule

* fix(coding-agent): serve empty-detach eviction from the roster with write-through pulls

Adapts the empty-session last-detach eviction (from the idle-eviction fix
round on main) to the roster world with one decision source:

- isEmptyDetachEvictionCandidate reads the worker's non-queued roster rows
  instead of the worker.summaries pull cache.
- The hook's two pulls stay as responsiveness gates and now write through:
  syncRosterFromWorkerSummaries (formerly the gap fill) lets a worker's own
  rows take the pull's fields, so the post-drain re-read deterministically
  sees a schedule registered by a mutation admitted mid-refresh. The
  pull-epoch guard keeps every write-through at least as fresh as the row it
  replaces, and rows claimed by another worker are never stolen.
- The detach-eviction tests seed the supervisor roster like the other
  adapted suites (matchWorkers is roster-backed).

Semantics of the empty-detach eviction are unchanged: empty + unnamed + not
busy + no registrations + no attached clients, last detach only, client-owned
workers excluded, fence coordination intact.

* fix(coding-agent): flush the roster on plain cron job add and cancel

cronStore.onHeartbeatChange only fires on the heartbeat catalog signature,
and cron_add/cron_cancel emit no session event, so hasRegisteredCronJob on
the roster row went stale: the idle sweep could evict a worker whose only
reason to stay resident was a fresh cron job, or keep a cancelled one pinned
forever. The handlers flush explicitly, like set_model does for events that
have no session-event carrier.

* fix(coding-agent): keep the hydrated summary when a snapshot reseeds a claimed child

The absentee reseed wrote a synthetic ledger seed (no lastActivityAt,
messageCount 0, artifact-dir cwd) over a previously hydrated claimed row.
Every worker snapshot goes through this for passive registry children, and
Date.parse(undefined) = NaN made canEvictWorker permanently false while the
degraded row persisted; plain list served the degraded fields too. The
reseed now rewrites the previous entry's summary (claim and data both
survive); only rows with no prior entry get the synthetic workerless seed.

* chore(coding-agent): roster review nits

- flushRoster's queuedChildren loop var is an agentId (parent-qualified),
  not a bare childId; name it so.
- set/cycle_thinking_level drop their explicit roster flushes: an actual
  change emits thinking_level_changed, which is a trigger already (the
  set_model flushes stay - model changes emit no session event).
- Non-worker daemons no longer accumulate removedAgentIds that no flush
  ever drains.
- The changelog stops presenting recovering/last-heard-from as user-visible
  in this PR; the surfaces that display them ship in the follow-up.

* fix(coding-agent): scope pending roster removals to one incarnation and fence applies on socket close

- A pending removal now records the sessionId it removes. A row composed
  again under the same agentId with a different sessionId (or a re-admitted
  queued run) is a new incarnation and cancels the stale removal instead of
  being suppressed from every flush including the reconnect snapshot; the
  removed incarnation itself stays suppressed mid-teardown so a deleted
  child cannot ghost back as a passivated row.
- isWorkerRosterApplyCurrent also requires a live (or authenticating)
  connection: an apply left in flight by a closed socket can no longer
  rewrite rows and drop the recovering label handleWorkerClose just set.
  Reconnection resumes applies through the pending client.

* chore(coding-agent): slim roster comments and consolidate roster tests

Comments: 153 -> 33 added src comment lines. Kept only notes resolving real
ambiguity (pull-epoch guard, close fence, reseed/NaN rationale, incarnation
suppression, privacy rules, backpressure delivery assumption, pid-recycle and
SIGKILL-wait justifications, wire-schema notes); deleted all narration.

Tests: one behavior test per contract. Merged into their parent behavior
test: bind-window compose-wins, trigger republish, queued rows ledger-internal,
queued-ghost flip, offline rename + failed-disk delete, client-owned inactive
list row, unchained-delta currency, set_model no-carrier flush, reseed data
quality, qualified removal ids. Deleted pins whose behavior another test or
the process-suite E2E already proves: undelivered-change escalation, real-
socket backpressured snapshot, recovering-on-close (asserted in the close-
fence test), frame-source trust, seeded selector resolution, root-pointer
epoch persist, miss-path refresh routing, hydrate race, sessions-dir topology
scoping, delivery-semantics mock twin, descriptor-path delete-routing variant.

* fix(coding-agent): roster identity and staleness fixes from the sixth review round

- The offline delete's roster cleanup deletes only the row object it observed:
  a write during the tombstone/unlink awaits replaces the row, and deleting by
  agentId alone would kill the replacement.
- Subagent roster ids fall back to the live parent id when the parent has no
  session path (--no-session parents never write ledger edges), so children of
  two such parents cannot collide on the per-parent 32-bit child id.
- An archived top-level close (killed/completed/replaced; not shutdown/update)
  publishes a roster removal instead of leaving a passivated "live" ghost: the
  worker's list no longer carries the session and the disk scan serves the
  archived file honestly. Subagent rows keep passivating, mirroring the
  registry's completed children.
- Roster applies are fenced by their own source connection: an apply parked on
  the spawn-ledger read by a dead connection can no longer resume during a
  reconnect's pre-auth window and clear the recovering labels, while the
  authenticating connection's own post-auth snapshot still applies immediately.

* chore(coding-agent): second slim pass on roster tests and comments

- One shared roster-seeding fixture (test/fixtures/roster-seed.ts) replaces
  the five per-suite copies.
- Deleted mechanism pins with accepted residual risk: flush change-dedup and
  trigger-set micro-pins (the lifecycle test still pins the closed-session
  flip), the single-flight repair pull, the mid-pull epoch skip, the crafted
  late-update guard phase, and the second pre-roster identity scenario.
- Another comment pass: dropped notes that restate the guard beside them.

* fix(coding-agent): seventh review round — spawn-append scoping, stat-reconciled seeds, one file-ownership source

- pendingRlmSpawnAppends is keyed by parent + childId at every site: child ids
  are only unique per parent, and a cross-parent collision made one admission
  await the wrong ledger append while the other proceeded without awaiting its
  own durable spawn record.
- The roster's ledger seeding and snapshot reseeds read liveEdges(), the
  ledger's own stat-reconciled view (the rule family() already owned): rows
  whose transcript was removed out-of-band never serve in list --all.
  Tombstone-first covers in-band deletes; this covers external removal.
- findWorkerBySessionFile no longer consults the stale pull cache: the roster
  claim and the durable descriptor paths are the ownership sources, so a
  removed row cannot route a create back to a worker that would answer with
  its root session.
- classifyWorkerRosterEntry is module-private (no consumer outside the module).
- The changelog notes the client-owned exception to inactive-row retention.

* fix(coding-agent): remove, not passivate, rows renamed by in-place session swaps

new_session/switch_session/fork swap the runtime under the same state: the
activeSessionId survives while the sessionId (and so the top-level agentId)
changes. The old agentId vanished from composition without a close, so the
passivation-retention loop kept serving it as a stale claimed row that plain
list never carried before the roster. The flush loop now treats a vanished
row whose activeSessionId still composes under a different agentId as a
removal — one owner for every swap origin, no per-command bookkeeping — and
the pending-removal cancel rule also revives resident top-level rows
(switch-back, resume-after-archive) while the resident-subagent teardown
race stays suppressed.

* fix(coding-agent): ninth review round — one family snapshot, family-scoped reseeds, filter-all tombstones

- family() builds its child suppression from the same single replay + stat
  snapshot that emits child rows: a sessions-dir child whose parent transcript
  vanished degrades to a root row instead of disappearing, and a concurrent
  cross-process append can no longer make the two views disagree.
- Snapshot reseeds are scoped to the snapshotting worker's own family: the
  reseed exists to restore that worker's absentee-swept registry children, and
  resurrecting other families' unclaimed rows leaked a client-owned worker's
  just-dropped children back into list --all as public rows (the ownership
  record is already gone by then, so this scoping IS the privacy rule).
- Transcript deletes tombstone every edge matching the path: appendSpawn's
  per-process uniqueness check leaves a cross-process TOCTOU window, and a
  raced duplicate left live would resurrect a later recreation as a subagent.
- The changelog states the staleness behavior honestly: rows are as fresh as
  the worker's last delta, silence is annotated rather than hidden.
olety added a commit to oneiron-dev/prime-agent that referenced this pull request Sep 1, 2026
Takes upstream's event-driven supervisor roster ledger + push (PrimeIntellect-ai#1897, PrimeIntellect-ai#1900),
direct TUI<->worker transport (ENG-5817), daemon startup/recovery hardening
(PrimeIntellect-ai#1929, PrimeIntellect-ai#1909), single-dump kernel snapshots (PrimeIntellect-ai#1945), empty-draft eviction
(PrimeIntellect-ai#1946), rlm_child_update suppression (PrimeIntellect-ai#1944), bash-skill preview (PrimeIntellect-ai#1911).

Fork laws re-expressed on the roster architecture:
- stable-target follow-up honesty kept (capability proof via worker hello,
  target_unavailable never not_found when ownership unproven)
- schema revision 26 (union of fork rev-24 stable-target + upstream
  rev-24/25 roster+transport); digest minted by the repo's own algorithm
- summary freshness reuse + single-flight + staleness + root-omission
  rejection restored on upstream's refresh pull
- adoption/recovery never fails a live worker on a slow or root-omitting
  catalog: get_state root seed + stale mark + bounded background rehydration
- repl.py keeps fork prune-on-aggregate-overflow
- delete handlers keep fork persistence reporting; eviction fence test keeps
  the stronger two-worker contention variant

Known test debt (deferred to post-Wave cleanup per owner): roster-era fixture
migrations in daemon-supervisor-monitor (2), plus un-triaged failures in
package-command-paths, agent-session-recursion, daemon-runtime-stress,
4600-supervisor-singleton, 4603-worker-recovery, 4606-update-restart-
coordinator, agents-view-roster. Production laws preserved; failures are
fixture-era artifacts or mechanism assertions to rewrite.
ketema added a commit to ketema/prime-agent that referenced this pull request Sep 1, 2026
- Direct session transport between TUI and worker (ENG-5817, PrimeIntellect-ai#1926)
- Event-driven supervisor agent roster with push subscriptions (PrimeIntellect-ai#1897, PrimeIntellect-ai#1900, PrimeIntellect-ai#1895)
- Hardened daemon startup, recovery ownership, and worker launch diagnostics (PrimeIntellect-ai#1929, PrimeIntellect-ai#1918)
- Python REPL runtime single-dump snapshots and bash preview tool (PrimeIntellect-ai#1945, PrimeIntellect-ai#1911)
- Non-blocking RLM subagent deletion and snapshot update suppression (PrimeIntellect-ai#1954, PrimeIntellect-ai#1944)
- Saved catalog loading on Agents View open (PrimeIntellect-ai#1960)
- Advanced Anthropic prompt caching marker across tool results (PrimeIntellect-ai#1927)
- TUI process replacement on update and empty draft eviction (PrimeIntellect-ai#1631, PrimeIntellect-ai#1946, PrimeIntellect-ai#1920)
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.

3 participants