Skip to content

chore: merge Prime upstream v0.9.4 into pylon - #55

Merged
rynfar merged 82 commits into
pylonfrom
upstream/2026-09-10-v0.9.4
Sep 11, 2026
Merged

rynfar merged 82 commits into
pylonfrom
upstream/2026-09-10-v0.9.4

Conversation

@rynfar

@rynfar rynfar commented Sep 11, 2026

Copy link
Copy Markdown

Integrate Prime upstream 0.9.4 at 1eee2938b4eeb7a4d72e17035adda669a89b63de while preserving Pylon owned-session, bounded-ingress, cleanup, recovery, and publication boundaries. Retain upstream ancestry through merge aeb1c6368194dc6c88d2e64a6d0c1bfca7c2f7bb; follow-up a70feb1a6273e0e74b5c22e8544ad18c8eed65c1 preserves actual worker replay through native synchronization. Final protocol7/schema33, identity protocol-7-schema-33-5924c5b19b8b.

Final head 514e40454e048707a8467940677170c2e15a8a39 also corrects CI-exposed recovery self-parking and shutdown acknowledgment ordering. Recovery-owned attachment/snapshot requests use the existing nonrecoverable inner request mode; external parking remains unchanged. Shutdown checks ownership, journals and writes its reply before cleanup. A failed journal attempt releases only its own pending fence after current ownership and lease proof, allowing safe retry. Direct attachment restores the frozen upstream roster notification. Compaction and cleanup fixtures now assert their actual distinct outcomes; no compaction runtime changes or relaxed timeouts.

CI correction verification: 567 owned/recovery tests passed with 9 skips; the final failed-journal recovery correction passed 36 targeted tests, including two tests that failed on the previous source. Required checks and normal hooks passed. The checkpoint and single finding recheck passed at the final exact tree. Final-head hosted CI is green, including Windows named-pipe, process smoke, all coding-agent shards, reproducibility, and macOS/Linux artifact installs. The final private pack passed exact digest checks, R4 and all 10 release-contract tests.

Adopt upstream retry ownership, model catalog, harness digests, roster/direct-peer transport, structured recovery errors and worker scheduling. Remove superseded fork retry code. Preserve exact caller-owned environment validation; unknown or partial replay never becomes synthetic complete. Inherited release/Linear and unapproved benchmark workflows stay excluded. PR#54 is outside the frozen adoption range.

Validation: the required integration matrix passed82files/1,369tests with16inheritedskips; bounded four-worker execution. Follow-up replay matrix passed673tests; final48protocol/regression tests and a cache mutant confirmed the new guard. npm run check and actual normal hooks passed. Clean pinned Node22.23.2 private pack produced exactly five artifacts, digest checks passed, R4 exported four features with recoverable:true, release-contract tests10/10passed.

Live Pylon0.9.4 contract re-proved at final514e404: native card, both runtime modes, first turn with3tools/ready checkpoint, same-thread denied and approved writes, Stop with interrupted state/ready composer/no toast passed. Restart produced interrupted-with-reason rather than a hang. Manual builds intentionally lack managed adoption authority; postrestart ownership quarantine remains preserved. Managed restart/continuation and full final web/mobile acceptance remain pending safe publication under#53. This PR does not authorize a pre-#53 preview publication.

Two test-fixture corrections were reviewed at checkpoints: private HOME prevents consulting the machine-wide supervisor registry; authoritative zombie-aware liveness replaces a kill(pid,0)-only helper. No ownership guard, assertion, or request budget was relaxed. Full decisions and verification are recorded in .pylon/upstream-review.md.

Per-file conflict decisions (41):

File Decision Resolution
.github/workflows/build-binaries.yml retain Keep the fork deletion; inherited binary publication is not approved.
.github/workflows/linear-ticket.yml retain Keep the fork deletion and remove its now-orphaned script/hook.
.github/workflows/ci.yml hybridize Retain the fork job set; adopt Ubuntu package updates, preserve the quoted fd symlink, omit benchmark automation.
packages/ai/package.json adopt Use upstream deterministic tsgo build; retain build:offline as the existing fork release-recipe alias.
packages/ai/src/providers/openai-completions.ts hybridize Adopt upstream failure/retry handling and preserve volatile-context placement.
packages/ai/src/providers/simple-options.ts adopt Adopt the upstream single retry owner; remove obsolete per-request maxRetries plumbing.
packages/ai/src/types.ts hybridize Adopt upstream provider types and retain appendOnlyHistory/volatile context fields.
packages/ai/src/utils/stream-failure.ts adopt Use upstream error classification and Retry-After parsing.
packages/ai/test/anthropic-sse-parsing.test.ts hybridize Combine upstream stream semantics with retained SSE retry-delay precedence cases.
packages/ai/test/prime-inference-models.test.ts adopt Adopt the current upstream catalog and model assertions.
packages/ai/test/stream-failure.test.ts adopt Use upstream classification and Retry-After parser tests.
packages/coding-agent/docs/settings.md adopt Document upstream retry policy and remove the superseded fork delay-ceiling setting.
packages/coding-agent/src/cli/daemon-launch.ts hybridize Adopt launch recovery changes; retain caller-owned launch and bounded public-client contract checks.
packages/coding-agent/src/core/agent-session.ts hybridize Adopt unified retries, summary-call identities and persisted harness digests; retain correlated action recovery, appendOnlyHistory placement, scoped provider hooks and spawn-only parent identity.
packages/coding-agent/src/core/compaction/branch-summarization.ts hybridize Adopt summary retry policy; retain the owning session payload hook.
packages/coding-agent/src/core/compaction/compaction.ts hybridize Adopt retry/summary-call runners; pass scoped payload hooks to both history and split-turn summaries.
packages/coding-agent/src/core/refinement/refinement.ts hybridize Adopt retry arguments while retaining scoped payload hooks for refinement and review.
packages/coding-agent/src/core/sdk.ts hybridize Adopt current SDK setup, remove obsolete SDK retry override, preserve fork session/extension identity contracts.
packages/coding-agent/src/core/settings-manager.ts adopt Adopt upstream retry settings; remove the obsolete fork max-delay setting.
packages/coding-agent/src/core/side-question.ts hybridize Adopt upstream retry/error semantics; preserve SideQuestionHost scoped hooks and semantic identity.
packages/coding-agent/src/core/system-prompt.ts hybridize Adopt upstream prompt instructions and persisted runtime harness digests. Keep the public volatile-context builder/date placement; legacy explicit harnessState callers remain supported.
packages/coding-agent/src/main.ts hybridize Adopt upstream launch/control paths and retain negotiated owned-session setup.
packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts hybridize Adopt roster/direct transport and terminal fallback behavior; retain serialized attachment/proof epochs, exact launch contract, bounded frames, correlated lifecycle, cleanup/recovery and transfer retry containment.
packages/coding-agent/src/modes/agent-connection/in-process-agent-connection.ts hybridize Adopt upstream connection behavior and retain fork correlation/capability interfaces.
packages/coding-agent/src/modes/daemon/command-recovery-journal.ts hybridize Adopt upstream command bookkeeping and preserve durable correlated request replay.
packages/coding-agent/src/modes/daemon/daemon-client.ts hybridize Adopt roster transport/recoverable options while preserving finite raw-byte ingress, terminal no-replay failures, generation identity and the existing recoverAcrossReconnect option.
packages/coding-agent/src/modes/daemon/daemon-mode.ts hybridize Adopt worker roster/direct-peer/runtime behavior; retain incarnation authentication, fresh transfer IDs, bounded immutable framing, correlated action snapshots, exact bootstrap environment and ownership fences.
packages/coding-agent/src/modes/daemon/daemon-protocol.ts hybridize Combine upstream roster/direct-peer/usage/model/structured recovering fields with all six fork capabilities; control-plane routing keeps fork ownership commands at the supervisor. Revision 32 avoids open PR #54 revision 31; regenerate the real digest.
packages/coding-agent/src/modes/daemon/daemon-supervisor.ts hybridize Adopt roster/catalog/scheduling/peer recovery; retain ownership registry, lifecycle journal, receipt recovery, exact environment, descriptor-last cleanup and stop/epoch/incarnation fences. Internal catch-up reuses the exact retained owner contract through the unchanged validator.
packages/coding-agent/src/modes/daemon/daemon-worker-client.ts hybridize Combine upstream peer/roster authentication with mandatory validated incarnation identity; retain minimum-schema capability checks.
packages/coding-agent/src/modes/daemon/daemon-worker-protocol.ts hybridize Combine peer grants/roster/bootstrap metadata with fork incarnation, recovery and MCP owner-transfer proof shapes.
packages/coding-agent/test/agent-connection-daemon.test.ts hybridize Combine direct-transport fallback fixtures with fork negotiated proof, bounded ingress, generation, lifecycle and cleanup tests; update schema constants and remove duplicate fake members.
packages/coding-agent/test/agent-session-recursion.test.ts hybridize Adopt session-start harness-digest assertions and upstream recursion semantics while preserving child identity/cancellation tests.
packages/coding-agent/test/daemon-mode.test.ts hybridize Combine upstream worker/roster fixtures with fork framing/correlation tests; give delegated-work fixtures real-shaped recovery snapshots.
packages/coding-agent/test/daemon-protocol.test.ts hybridize Preserve capability/digest mutation coverage and adopt upstream command planes; update the mutation sentinel and expected revision to 32.
packages/coding-agent/test/daemon-supervisor-admission.test.ts hybridize Retain fork correlated admission/cleanup fences alongside upstream command handling.
packages/coding-agent/test/daemon-supervisor-monitor.test.ts hybridize Reconcile roster, worker registry/auth and recovery-map fixtures. Add real-validator catch-up regressions for exact context, both purposes, owner/epoch/detach and env rebinding failures.
packages/coding-agent/test/daemon-supervisor-process.test.ts hybridize Retain real owned recovery/proofs and bounded snapshot proxy tests; disable direct transport only for socket-proxy fixtures, authenticate the pre-roster fixture, and exercise actual caller-owned prompt compact-delta catch-up before two-owner recovery. Every supervisor and exact owned recovery/adoption launch uses its private fixture home and registry, with real registry-path and child-HOME assertions.
packages/coding-agent/test/suite/agent-session-retry-events.test.ts adopt Adopt upstream retry events and cancellation semantics.
packages/coding-agent/test/suite/regressions/4602-snapshot-transfer-idempotency.test.ts hybridize Retain fresh generation identity, healthy worker survival and bounded retry/no-self-requeue guarantees alongside upstream runtime changes.
packages/coding-agent/test/system-prompt.test.ts hybridize Authorized stale-mechanical correction: the actual 41-conflict set includes this file although the plan enumeration omitted it. Combine current RLM wording and fork explicit volatile-context placement tests.

The table records the integration schema32; the reviewed replay follow-up advances the final wire shape to33. The caller-owned capability literal is centralized in sdk-features.ts and referenced by the protocol rather than duplicated for a grep count.

Resolves #44. Related:pylon-code/pylon#114; publication remains tracked in#53. Merge with a merge commit to preserve upstream ancestry.

Model:GPT-6 Astra. Harness:Codex in Pylon.

snimu and others added 30 commits August 30, 2026 17:18
…rimeIntellect-ai#1893)

* feat(coding-agent): render Mermaid code blocks as inline Unicode diagrams

Ports pi-mono's Mermaid rendering (grok-mermaid) into prime-agent:
- pi-tui Markdown gains an options.transform hook that rewrites source
  Markdown with the exact content width before parsing (ported from
  upstream pi-tui).
- New mermaid.ts transform replaces top-level mermaid code blocks with
  themed Unicode diagrams, falls back to the raw block when the diagram
  is wider than the message, and appends a themed warning line for
  partially rendered final diagrams.
- AssistantMessageComponent applies the transform to text blocks only
  (never thinking blocks) and tracks streaming state so the
  streaming-to-final transition re-renders.
- New markdown.mermaid setting (off/final/streaming, default streaming)
  with a settings-selector entry.

Linear: ENG-5673

* test(coding-agent): kill mermaid mutation survivors

Adversarial review mutation testing found three surviving mutants:
- width guard flipped to >= (exact-fit diagrams would fall back)
- codeSpan fence reduced to a single backtick (backtick labels break)
- isStreaming inverted at the message_end call site (final mode never renders)

Adds an exact-width boundary test, a backtick-label test, and a
handleEvent-level final-mode transition test that kill them.

* build(coding-agent): pin grok-mermaid exactly for renderer-output stability

* test(coding-agent): collapse redundant mermaid coverage

* docs(coding-agent): tighten mermaid comments

* fix(coding-agent): tolerate non-object markdown settings values

Normalize a malformed markdown settings value at the migration boundary
(mirroring the telemetry handling) so changing the Mermaid mode cannot
throw, and add the missing tui changelog fragment.

* test(coding-agent): drop mermaid.test.ts

* chore(coding-agent): one-line mermaid codeSpan comments
…ns (PrimeIntellect-ai#1920)

* fix(coding-agent): report an empty resident session as idle instead of pinning it at working (ENG-5809)

* feat(coding-agent): evict an empty unnamed session's worker when its last client disconnects (ENG-5809)

* test(coding-agent): kill surviving empty-session eviction mutants (ENG-5809 review)

Adversarial-review killer tests for three mutants that survived the shipped
suite: dropping the heartbeat/cron registration checks from
isEvictableEmptySessionSummary, dropping the client-owned worker exclusion,
and removing the post-refresh identity/stopping re-check in
evictEmptySessionOnLastDetach.

* chore(coding-agent): fold empty-session tests and trim comments

* fix(coding-agent): drain admitted mutations before deciding empty-session eviction (ENG-5809 Macroscope)

* chore(coding-agent): drop comments that restate the code

* fix(coding-agent): share one eviction-fence acquisition between the idle sweep and detach eviction (ENG-5809 review)

* refactor(coding-agent): extract the shared fenced passivation primitive (ENG-5809)
…t launch a worker (PrimeIntellect-ai#1918)

* fix(coding-agent): surface worker spawn failures (EMFILE) as clean create errors

When the daemon hits its fd limit, spawn fails with EMFILE and leaves
child.stdio undefined; launchWorker crashed with a raw TypeError that
masked the real error and reached the client as an unhandled throw with
a stack dump.

- daemon-supervisor: race the child spawn/error events so a failed spawn
  deterministically fails the create with the real spawn error, enriched
  with the resident worker count and a ulimit hint for EMFILE/ENFILE.
- CLI: wrap generic daemon create failures in DaemonSessionCreateError
  and handle it at both createDaemonClientConnection call-site boundaries
  as a one-line error with exit code 1.

* test(coding-agent): assert spawn-failure hint is EMFILE/ENFILE-only (kills errno-unconditional-hint mutant)

* chore(coding-agent): trim comments in the spawn-failure change
…ellect-ai#1927)

* fix(ai): cache Anthropic tool results

* docs(ai): add cache fix changelog fragment

* test(ai): trim Anthropic cache regression

* test(ai): use Haiku cache fixture
…ibility, subagents bar rename (PrimeIntellect-ai#1895)

* feat(coding-agent): shared agent-status classifier, honest worker visibility, subagents bar rename

Add classifyAgentStatus as the one status formula for every agent surface;
classifySessionRosterStatus (agents view) and the new
classifySubagentSnapshotStatus (subagents bar) are thin adapters over it,
so the two surfaces can no longer diverge on what running/idle/inactive mean.

Visibility fixes:
- Sessions of non-ready workers are no longer hidden from the agents view;
  the worker state (starting/recovering/stopping/failed) renders as the
  row's status label instead.
- A resident message-less subagent session is lifecycle "live" (it is a
  spawned worker, not a user draft); top-level drafts stay hidden.
- Subagent summary bar label renamed from "agents" to "subagents".

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

ENG-5794

* fix(coding-agent): drop the redundant roster-status alias and add the changelog fragment

Addresses PR PrimeIntellect-ai#1895 review: SessionRosterStatus was an alias with no
external importers, and the visibility comment restated the code.

ENG-5794

* refactor(coding-agent): derive subagents bar border fill from the label width

* test(coding-agent): fold the classifier pins into one compact contract test
… 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.
…iew and subagents bar (PrimeIntellect-ai#1900)

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

* feat(coding-agent): roster subscription push and agents-view consumption

Adds roster_subscribe/roster_unsubscribe and capability-gated roster_update
pushes (agent_roster, schema revision 24); the agents view holds a shared
DaemonClient and roster store across scope transitions, renders ledger
statuses and labels (queued/recovering/failed, staleness), falls back to
the legacy poll path only against daemons without the capability, and
fetches the saved catalog once per view instance when a search query needs
deep text. The supervisor coalesces pushes per macrotask and resyncs
backpressured subscribers on drain.

ENG-5794

* test(coding-agent): give launch fixtures the roster push buffers

* feat(coding-agent): finalize roster push consumption in the agents view

Navigation issues no daemon requests (pinned), the lazy saved-catalog fetch
happens once per view instance only when a query is typed, and rows
synthesized from the ledger carry rosterStatus so sections, labels, and
staleness render the classify-once verdicts. Adds the changelog fragment.

ENG-5794

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

- The subagents bar follows the roster's terminal rule: a done/error run
  with no session evidence across its history (daemon session id, live
  activity, or session token accounting) is dropped like a cancelled one,
  on both connection kinds; children with transcripts keep their rows.
  The bar/view equality test now drives the real update handler through a
  lifecycle matrix (unbound-error, queued, bound, heartbeat-only,
  passivated, recovering) against roster-derived sections.
- Visibility transitions are roster pushes: a row claimed by a
  client-owned worker reaches subscribers as a removal, and promotion
  re-enqueues the worker's rows.
- A refused drain resync re-arms rosterResyncPending so the next drain
  retries instead of stranding the subscriber; pinned through the real
  connection drain listener.
- The watchdog staleness stamp and clear are pinned end-to-end to a
  subscriber push.
- Saved-sibling name validation prefers the ledger's rosterStatus like the
  other fallbacks.
- Queued and bound child rows share one stable identity (the qualified
  roster agent id) in row identities and reconciliation aliases, so
  selection survives the bind push without duplicate rows.

ENG-5794

* fix(coding-agent): make subagent bound-ness sticky across terminal projections

Terminal merges clear the session-evidence display fields, so a repeated
terminal projection saw an evidence-free snapshot and removed a
transcript-bearing child. Bound-ness is now a sticky everBound snapshot
field set the first time evidence (daemon session id, live activity, or
session token accounting) is observed, and the terminal drop rule reads
it, keeping repeated terminal projections idempotent while a never-bound
run still cannot fabricate evidence. Saved-sibling name validation now
reads the ledger row's status through the session-file index instead of a
rosterStatus field that inactive summaries never carry.

ENG-5794

* refactor(coding-agent): push-only roster consumers and churn hardening

- The agents view drops its poll fallback as dead code: exact-version
  forced restart already ships, so a daemon without the agent_roster
  capability is a hard error naming the stale daemon, refreshes reapply
  the pushed store locally, and reconnects re-attach the subscription.
- The daemon-mode subagents bar consumes the pushed roster through a
  store shared per connection (subscribeAgentRoster on AgentConnection),
  counting direct children with the same ledger statuses the view renders;
  the in-process connection keeps the sanctioned snapshot-to-classifier
  path. The lifecycle equality matrix now pins push-fed bar == view.
- A drain-time roster resync clears its pending flag even when the write
  reports backpressure, since socket.write queues the payload either way:
  one resync per loss gap, never one per drain.
- scripts/roster-soak.ts drives a real supervisor socket with thousands
  of churning sessions, depth-40 chains, and a deliberately slow
  subscriber, asserting convergence, coalesced resyncs, bounded heap, and
  answered commands.

ENG-5794

* test(coding-agent): align view fixtures with the push-only refresh path

Fixture fallout from removing the poll fallback and the legacy refresh
shim: query-changed and reply fixtures stub the saved-catalog fetch, the
handoff-scope pins feed the pushed store instead of a failing list
request, the rename pin asserts one local reapply, and the monitor seed
helper writes delta-shaped rows directly. Biome formatting rides along.

ENG-5794

* fix(coding-agent): push-only view fixes for the rework review round

- The daemon-mode bar fails hard on a stale daemon: subscribing to the
  roster is awaited during session (re)binding, the in-flight forced
  reattach after reconnect throws instead of ignoring a refusal, and
  the snapshot->classifier path survives only on in-process connections.
  A production-path pin (real supervisor socket, real DaemonAgentConnection
  and store) proves the bar counts pushed rows, not stale snapshots.
- The staleness watchdog stamps rows only on the transition into stale;
  repeat sweeps of an already-stale worker emit zero mutations.
- roster_unsubscribe clears any pending resync and drains re-check the
  subscription before resyncing.
- A snapshot apply never surfaces a live spawn-ledger edge as a transient
  removal, pinned over the push surface.
- The soak asserts exact payload equality for both subscribers, requires
  the induced loss gap to resolve through coalesced resyncs, bounds list
  latencies, and names the worker-frame integration pin it leaves to
  vitest. refreshBothCatalogs and the poll-era comments go away; fixtures
  drop the last poll-model stubs.

ENG-5794

* fix(coding-agent): push-only consumer fixes for the roster review round

- buffer roster_update pushes racing the subscribe reply and replay them
  after the snapshot resync (AgentsViewRosterStore.attach)
- await the parsed daemon_hello inside attach so a fresh connection is
  never misread as missing the agent_roster capability
- re-arm the lazy saved-catalog load when its fetch fails, and refresh
  the loaded catalog after renames and deactivations
- classify roster_subscribe/roster_unsubscribe as read-only so command
  journal replays cannot skip re-subscribing a new socket
- roster-soak: try/finally lifecycle; any rejection cleans up and exits
  nonzero instead of hanging

* test(coding-agent): pin subscribe-race replay and subscription journal classification

- pushes racing the roster_subscribe reply replay after the snapshot resync
- roster_subscribe/roster_unsubscribe stay out of the mutation journal

* test(coding-agent): rename pins the loaded-saved-catalog refresh contract

* fix(coding-agent): persistent saved-catalog gate with generation-safe re-arm

- persistentState.savedCatalogLoaded survives view remounts and gates the
  rename/deactivate/delete catalog refreshes
- the per-instance search fetch re-arms only while no catalog exists, so
  a superseded fetch's false return cannot force refetches or clear data
- reconnect-timeout status tells the truth: reconnect stopped

* test(coding-agent): pin the persistent saved-catalog gate and the superseded-fetch race

* fix(coding-agent): re-arm the saved-catalog search latch only from the current fetch

- refreshSavedSessions owns the re-arm: it fires on the current
  generation's failure (or a skipped start) while no catalog exists, so a
  superseded settle can never disarm the latch under a pending fetch
- remount coverage moves to a production-constructor test; the hand-built
  harness variant is deleted

* test(coding-agent): give the 502 refresh harness the real re-arm helper

* test(coding-agent): fold view roster near-duplicates into their surviving pins

- store apply/removal/resync test also pins one listener emission per tick
- one row-label test covers queued, recovering, and stale ledger states
- the zero-request refresh test also drives row navigation

* test(coding-agent): drop a stray blank line from the view roster suite

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

- drop the hand-wired reconcile-batch adapter test and the cross-surface
  bar matrix; the two unique history branches move next to the other
  updateSubagentSummary pins
- the labels test also pins the stable queued-to-bound row identity
- the zero-request refresh pin sheds its navigation half

* test(coding-agent): tsgo and format fixes for the moved bar pins

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

- condense the anchor-identity and soak-header comments to one line each
- present-tense fix in the soak convergence check label
- delete the dead rosterCapable fixture fields

* fix(coding-agent): bot-round view fixes — bar degrade, handshake exit, restored-query fetch, serialized attach

- the subagents bar degrades to child snapshots when the roster subscribe
  fails; a session rebind never hard-fails on it (the agents view keeps
  the hard error)
- the view arms its close-driven reconnect only after a successful roster
  attach, so a handshake failure exits cleanly instead of racing a
  background reconnect against client disposal
- one armSavedSearchFetch latch serves typed and restored queries; run()
  arms it for a restored non-empty query
- AgentsViewRosterStore serializes attaches, so a stale attempt settling
  late can never detach a newer subscription

* fix(coding-agent): loaded-catalog arm gate and capability/transient split on the roster attach seam

- armSavedSearchFetch honors the persistent savedCatalogLoaded gate: a
  remounted view with a restored query never refetches a loaded catalog
- AgentsViewRosterStore.attach returns false only for a missing
  capability; transport and subscribe failures detach their own listener
  and throw, so the connection reconnect loop retries the rebind instead
  of resyncing with a dead subscription; the chat bar keeps catching both

* fix(coding-agent): unparkable roster subscribe and hello-keyed subscription identity

- roster_subscribe opts out of request-recovery parking (new per-request
  recoverable option): a close mid-subscribe rejects into the callers own
  bounded retry loop instead of deadlocking the connection reconnect,
  whose parked request could only be revived by the hello that same loop
  was stuck producing
- the store keys its live subscription to the connection hello: a
  reconnected transport re-subscribes naturally and the force flag is
  deleted from attach and every call site

* fix(coding-agent): restore the AgentRosterStatus type import after the ledger rebase

* docs(coding-agent): correct the roster changelog — the poll path is removed, not kept

* fix(coding-agent): drop never-bound terminal child runs at the producer

AgentSession now owns the roster rule end to end: getRlmChildSnapshots skips
terminal runs that never bound a session (covering seed/replace/state paths),
and a pre-bind failure emits its terminal update as cancelled - the wire's
existing removal signal - so event consumers need no second predicate. Deletes
the everBound sticky marker, its snapshot field, and hasSubagentSessionEvidence.
The failure still reaches the parent as rlm_child_failure and stays listed with
its true error status in listRlmSubagents.

* refactor(coding-agent): funnel roster label/staleness stamps through one store channel

AgentRoster.amend patches statusLabel/lastHeardFromAt in place and notifies, so
markWorkerRosterEntries, the staleness sweep, and the promotion re-publish stop
bypassing the store with manual onRosterMutation calls; onMutation now has one
caller channel.

* test(coding-agent): pin old-client/new-daemon roster compat

An unsubscribed socket on a live supervisor never receives roster_update, and
the pre-roster list validator stays open to the additive rosterStatus/
statusLabel/lastHeardFromAt summary fields; both directions of the schema-24
wire change are now pinned.

* fix(coding-agent): re-arm agents-view reconnect and the saved search fetch after outages

The 15s heartbeat poll now restarts the reconnect loop over a dead socket
instead of leaving the view permanently offline after one 120s window (the 1s
poll used to do this), and a connected poll failure no longer overwrites a
sticky notice. A successful reconnect re-arms the lazy saved-catalog fetch
through the one arm predicate so a query that outlived the outage regains its
deep-search matches.

* fix(coding-agent): push-era agents-view polish — anchor settle, /name disarm, hour ages

Each roster push settles a missing restored selection anchor (rebuilds re-arm
it and no poll clears it anymore), a successful /name disarms the composer like
/kill, the delete-confirm list RPC documents itself as a deliberate
authoritative liveness check, last-heard ages gain an hour unit, and
refreshSessions loses its dead boolean (the 'refresh failed' rename status was
unreachable).

* style(coding-agent): drop rebase-introduced blank lines around the roster imports

* fix(coding-agent): publish roster removals at most once, never for owned-only rows

flushRosterUpdates now gates removed ids on a published-ids set: rows born to
client-owned workers emit nothing (no repeated no-op reconciles in every
subscriber, no leak of private roster ids that embed transcript paths), a
published row claimed by an owned worker leaves the surface exactly once, and
promotion re-publishes through the existing empty amend. Seeds and resyncs
register their ids so later disappearances stay removable.

* fix(coding-agent): roster push repaints the bar; teardown and delete-guard stay push-clean

The subagents-bar roster callback now requests a render, so a push with no
accompanying session event paints immediately. Agents-view exit closes the
socket before store disposal (the supervisor drops the subscription with the
client) and dispose serializes behind attach with a fire-and-forget
unsubscribe, so a wedged daemon cannot block exit and an in-flight attach
cannot leave a dangling listener. The pre-delete liveness probe keeps its
narrower plain-list verdict local instead of overwriting the pushed catalog,
so a failed delete no longer hides queued/passivated rows on the next
heartbeat reconcile.

* chore(coding-agent): sweep poll-era sediment and the duplicated stale-daemon string

liveCatalogReady could never be false once the view runs: the field, its dead
savedCatalogReady initializer, and the liveCatalog* fixture leftovers are gone,
and shouldApplyScopeResolution takes only the saved-catalog readiness. The
stale-daemon capability message now has one owner
(STALE_ROSTER_DAEMON_MESSAGE in roster-store).

* fix(coding-agent): tick stale last-heard ages and share the bar's direct-child linkage

The existing animation tick now rebuilds rows while a stale-age label is on
screen (labels are baked in at row build, so a repaint alone cannot advance
them), and the subagents bar parents children through the same getParentKeys
linkage as the view tree via isDirectAgentChild, so parentSessionId-only
children count in the bar exactly as they render in the view.

* chore(coding-agent): slim roster comments and tests to one pin per behavior

Deletes narration comments (86 -> 20 added lines, keeping only genuine
invariants: subscribe-reply race buffer, attach serialization, unparkable
subscribe, removal-once/privacy set, drain-resync queueing, the cancelled
removal signal, and wire-field docs), merges the roster store's lifecycle/race
micro-pins into single behavior tests, consolidates the saved-search latch
suite into one test, drops the remount-gate near-duplicate, slims the bar and
view-instance suites, and deletes the roster-soak dev harness - its
correctness claims (seed, coalesce, loss-gap resync, owned-row visibility,
store convergence) are all pinned by the real-socket tests; only the
scale/heap-plateau sweep is lost, which CI never ran.

* fix(coding-agent): fall back to snapshot bar counts when the roster has no direct children

A client-owned session's rows are invisible to the public roster by design, so
the push-fed bar showed zero while live subagents existed. The roster wins
whenever it reports at least one direct child for this parent; otherwise the
connection snapshots carry the counts for that render.

* chore(coding-agent): second slim round — merge roster suites onto shared fixtures

One store-lifecycle test now carries capability-miss, raced-push replay,
hello re-keying, attach serialization, and dispose; the wire compat E2E and
the chat-bar E2E share one supervisor boot (bystander, coalescing, repaint,
and the owned-session snapshot fallback ride the same socket). Cut with
accepted residual risk: the mid-flight park test (PR #1909 carries the
recoverable-park suite), the watchdog staleness push test (label rendering
stays pinned in the rows test), the view-instance anchor/failed-delete pins
(one-line UI fixes, self-healing paths), the handshake-exit pin, and the
latch test's stale-success ordering (generation guard predates this PR).
Comment pass two drops restating docs (amend, hello key, two wire-field
docs).

* test(coding-agent): fold the monitor suite's roster seeding into the shared fixture

* fix(coding-agent): isolate roster consumers and restamp last-heard marks after row rebuilds

The store's update dispatch now isolates each listener like the connection's
emit does, so one throwing consumer cannot break the others or the process.
The staleness sweep restamps any still-silent worker's rows that lack the mark
instead of stamping only on the first stale pass - AgentRoster.write rebuilds
rows from the incoming payload and dropped it, leaving 'last heard' labels
missing after any supervisor-side write until the worker recovered and went
stale again; mark equality keeps repeat sweeps push-free and the sweep stays
the mark's single owner.

* test(coding-agent): back the flicker test's live edge with real transcript files

* fix(coding-agent): gate the bar's snapshot fallback on the session's own roster presence

total > 0 conflated a client-owned session with a public parent whose children
all left the roster, reviving stale snapshots (roster deletions emit no
cancelled event to the client) and disagreeing with the agents view. The
discriminator is now the parent session's own row: fall back only while
connectionState.sessionId is absent from the pushed summaries, so a public
parent with zero roster children shows zero.

* test(coding-agent): give the flicker test's worker its family root for the scoped reseed

* fix(coding-agent): never fail a recovered session on the roster bar's subscribe

A transient roster_subscribe failure inside attach() rejected the reconnect
loop's otherwise-complete recovery (and the update-restart reattach), ending in
a closed, unusable session because a bar accessory failed. The seam now
swallows the transient class - the bar degrades and the next reconnect or
rebind re-attaches through the same line - while capability-miss stays the
non-fatal false it already was and the agents view keeps its own hard-require
path.

* fix(coding-agent): hydrate seeded roster cwd

Fixes #1900

* fix(coding-agent): close roster refresh races

Fixes #1900

* fix(coding-agent): finalize roster status handling

Fixes #1900

* fix(coding-agent): limit rendered roster labels

Fixes #1900

* fix(coding-agent): preserve unloaded saved scopes

Fixes #1900

---------

Co-authored-by: Xeophon <46377542+xeophon@users.noreply.github.com>
…tead of parking them (PrimeIntellect-ai#1909)

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

* feat(coding-agent): roster subscription push and agents-view consumption

Adds roster_subscribe/roster_unsubscribe and capability-gated roster_update
pushes (agent_roster, schema revision 24); the agents view holds a shared
DaemonClient and roster store across scope transitions, renders ledger
statuses and labels (queued/recovering/failed, staleness), falls back to
the legacy poll path only against daemons without the capability, and
fetches the saved catalog once per view instance when a search query needs
deep text. The supervisor coalesces pushes per macrotask and resyncs
backpressured subscribers on drain.

ENG-5794

* test(coding-agent): give launch fixtures the roster push buffers

* feat(coding-agent): finalize roster push consumption in the agents view

Navigation issues no daemon requests (pinned), the lazy saved-catalog fetch
happens once per view instance only when a query is typed, and rows
synthesized from the ledger carry rosterStatus so sections, labels, and
staleness render the classify-once verdicts. Adds the changelog fragment.

ENG-5794

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

- The subagents bar follows the roster's terminal rule: a done/error run
  with no session evidence across its history (daemon session id, live
  activity, or session token accounting) is dropped like a cancelled one,
  on both connection kinds; children with transcripts keep their rows.
  The bar/view equality test now drives the real update handler through a
  lifecycle matrix (unbound-error, queued, bound, heartbeat-only,
  passivated, recovering) against roster-derived sections.
- Visibility transitions are roster pushes: a row claimed by a
  client-owned worker reaches subscribers as a removal, and promotion
  re-enqueues the worker's rows.
- A refused drain resync re-arms rosterResyncPending so the next drain
  retries instead of stranding the subscriber; pinned through the real
  connection drain listener.
- The watchdog staleness stamp and clear are pinned end-to-end to a
  subscriber push.
- Saved-sibling name validation prefers the ledger's rosterStatus like the
  other fallbacks.
- Queued and bound child rows share one stable identity (the qualified
  roster agent id) in row identities and reconciliation aliases, so
  selection survives the bind push without duplicate rows.

ENG-5794

* fix(coding-agent): make subagent bound-ness sticky across terminal projections

Terminal merges clear the session-evidence display fields, so a repeated
terminal projection saw an evidence-free snapshot and removed a
transcript-bearing child. Bound-ness is now a sticky everBound snapshot
field set the first time evidence (daemon session id, live activity, or
session token accounting) is observed, and the terminal drop rule reads
it, keeping repeated terminal projections idempotent while a never-bound
run still cannot fabricate evidence. Saved-sibling name validation now
reads the ledger row's status through the session-file index instead of a
rosterStatus field that inactive summaries never carry.

ENG-5794

* refactor(coding-agent): push-only roster consumers and churn hardening

- The agents view drops its poll fallback as dead code: exact-version
  forced restart already ships, so a daemon without the agent_roster
  capability is a hard error naming the stale daemon, refreshes reapply
  the pushed store locally, and reconnects re-attach the subscription.
- The daemon-mode subagents bar consumes the pushed roster through a
  store shared per connection (subscribeAgentRoster on AgentConnection),
  counting direct children with the same ledger statuses the view renders;
  the in-process connection keeps the sanctioned snapshot-to-classifier
  path. The lifecycle equality matrix now pins push-fed bar == view.
- A drain-time roster resync clears its pending flag even when the write
  reports backpressure, since socket.write queues the payload either way:
  one resync per loss gap, never one per drain.
- scripts/roster-soak.ts drives a real supervisor socket with thousands
  of churning sessions, depth-40 chains, and a deliberately slow
  subscriber, asserting convergence, coalesced resyncs, bounded heap, and
  answered commands.

ENG-5794

* test(coding-agent): align view fixtures with the push-only refresh path

Fixture fallout from removing the poll fallback and the legacy refresh
shim: query-changed and reply fixtures stub the saved-catalog fetch, the
handoff-scope pins feed the pushed store instead of a failing list
request, the rename pin asserts one local reapply, and the monitor seed
helper writes delta-shaped rows directly. Biome formatting rides along.

ENG-5794

* fix(coding-agent): push-only view fixes for the rework review round

- The daemon-mode bar fails hard on a stale daemon: subscribing to the
  roster is awaited during session (re)binding, the in-flight forced
  reattach after reconnect throws instead of ignoring a refusal, and
  the snapshot->classifier path survives only on in-process connections.
  A production-path pin (real supervisor socket, real DaemonAgentConnection
  and store) proves the bar counts pushed rows, not stale snapshots.
- The staleness watchdog stamps rows only on the transition into stale;
  repeat sweeps of an already-stale worker emit zero mutations.
- roster_unsubscribe clears any pending resync and drains re-check the
  subscription before resyncing.
- A snapshot apply never surfaces a live spawn-ledger edge as a transient
  removal, pinned over the push surface.
- The soak asserts exact payload equality for both subscribers, requires
  the induced loss gap to resolve through coalesced resyncs, bounds list
  latencies, and names the worker-frame integration pin it leaves to
  vitest. refreshBothCatalogs and the poll-era comments go away; fixtures
  drop the last poll-model stubs.

ENG-5794

* fix(coding-agent): push-only consumer fixes for the roster review round

- buffer roster_update pushes racing the subscribe reply and replay them
  after the snapshot resync (AgentsViewRosterStore.attach)
- await the parsed daemon_hello inside attach so a fresh connection is
  never misread as missing the agent_roster capability
- re-arm the lazy saved-catalog load when its fetch fails, and refresh
  the loaded catalog after renames and deactivations
- classify roster_subscribe/roster_unsubscribe as read-only so command
  journal replays cannot skip re-subscribing a new socket
- roster-soak: try/finally lifecycle; any rejection cleans up and exits
  nonzero instead of hanging

* test(coding-agent): pin subscribe-race replay and subscription journal classification

- pushes racing the roster_subscribe reply replay after the snapshot resync
- roster_subscribe/roster_unsubscribe stay out of the mutation journal

* test(coding-agent): rename pins the loaded-saved-catalog refresh contract

* fix(coding-agent): persistent saved-catalog gate with generation-safe re-arm

- persistentState.savedCatalogLoaded survives view remounts and gates the
  rename/deactivate/delete catalog refreshes
- the per-instance search fetch re-arms only while no catalog exists, so
  a superseded fetch's false return cannot force refetches or clear data
- reconnect-timeout status tells the truth: reconnect stopped

* test(coding-agent): pin the persistent saved-catalog gate and the superseded-fetch race

* fix(coding-agent): re-arm the saved-catalog search latch only from the current fetch

- refreshSavedSessions owns the re-arm: it fires on the current
  generation's failure (or a skipped start) while no catalog exists, so a
  superseded settle can never disarm the latch under a pending fetch
- remount coverage moves to a production-constructor test; the hand-built
  harness variant is deleted

* test(coding-agent): give the 502 refresh harness the real re-arm helper

* test(coding-agent): fold view roster near-duplicates into their surviving pins

- store apply/removal/resync test also pins one listener emission per tick
- one row-label test covers queued, recovering, and stale ledger states
- the zero-request refresh test also drives row navigation

* test(coding-agent): drop a stray blank line from the view roster suite

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

- drop the hand-wired reconcile-batch adapter test and the cross-surface
  bar matrix; the two unique history branches move next to the other
  updateSubagentSummary pins
- the labels test also pins the stable queued-to-bound row identity
- the zero-request refresh pin sheds its navigation half

* test(coding-agent): tsgo and format fixes for the moved bar pins

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

- condense the anchor-identity and soak-header comments to one line each
- present-tense fix in the soak convergence check label
- delete the dead rosterCapable fixture fields

* fix(coding-agent): bot-round view fixes — bar degrade, handshake exit, restored-query fetch, serialized attach

- the subagents bar degrades to child snapshots when the roster subscribe
  fails; a session rebind never hard-fails on it (the agents view keeps
  the hard error)
- the view arms its close-driven reconnect only after a successful roster
  attach, so a handshake failure exits cleanly instead of racing a
  background reconnect against client disposal
- one armSavedSearchFetch latch serves typed and restored queries; run()
  arms it for a restored non-empty query
- AgentsViewRosterStore serializes attaches, so a stale attempt settling
  late can never detach a newer subscription

* fix(coding-agent): loaded-catalog arm gate and capability/transient split on the roster attach seam

- armSavedSearchFetch honors the persistent savedCatalogLoaded gate: a
  remounted view with a restored query never refetches a loaded catalog
- AgentsViewRosterStore.attach returns false only for a missing
  capability; transport and subscribe failures detach their own listener
  and throw, so the connection reconnect loop retries the rebind instead
  of resyncing with a dead subscription; the chat bar keeps catching both

* fix(coding-agent): unparkable roster subscribe and hello-keyed subscription identity

- roster_subscribe opts out of request-recovery parking (new per-request
  recoverable option): a close mid-subscribe rejects into the callers own
  bounded retry loop instead of deadlocking the connection reconnect,
  whose parked request could only be revived by the hello that same loop
  was stuck producing
- the store keys its live subscription to the connection hello: a
  reconnected transport re-subscribes naturally and the force flag is
  deleted from attach and every call site

* fix(coding-agent): restore the AgentRosterStatus type import after the ledger rebase

* docs(coding-agent): correct the roster changelog — the poll path is removed, not kept

* fix(coding-agent): drop never-bound terminal child runs at the producer

AgentSession now owns the roster rule end to end: getRlmChildSnapshots skips
terminal runs that never bound a session (covering seed/replace/state paths),
and a pre-bind failure emits its terminal update as cancelled - the wire's
existing removal signal - so event consumers need no second predicate. Deletes
the everBound sticky marker, its snapshot field, and hasSubagentSessionEvidence.
The failure still reaches the parent as rlm_child_failure and stays listed with
its true error status in listRlmSubagents.

* refactor(coding-agent): funnel roster label/staleness stamps through one store channel

AgentRoster.amend patches statusLabel/lastHeardFromAt in place and notifies, so
markWorkerRosterEntries, the staleness sweep, and the promotion re-publish stop
bypassing the store with manual onRosterMutation calls; onMutation now has one
caller channel.

* test(coding-agent): pin old-client/new-daemon roster compat

An unsubscribed socket on a live supervisor never receives roster_update, and
the pre-roster list validator stays open to the additive rosterStatus/
statusLabel/lastHeardFromAt summary fields; both directions of the schema-24
wire change are now pinned.

* fix(coding-agent): re-arm agents-view reconnect and the saved search fetch after outages

The 15s heartbeat poll now restarts the reconnect loop over a dead socket
instead of leaving the view permanently offline after one 120s window (the 1s
poll used to do this), and a connected poll failure no longer overwrites a
sticky notice. A successful reconnect re-arms the lazy saved-catalog fetch
through the one arm predicate so a query that outlived the outage regains its
deep-search matches.

* fix(coding-agent): push-era agents-view polish — anchor settle, /name disarm, hour ages

Each roster push settles a missing restored selection anchor (rebuilds re-arm
it and no poll clears it anymore), a successful /name disarms the composer like
/kill, the delete-confirm list RPC documents itself as a deliberate
authoritative liveness check, last-heard ages gain an hour unit, and
refreshSessions loses its dead boolean (the 'refresh failed' rename status was
unreachable).

* style(coding-agent): drop rebase-introduced blank lines around the roster imports

* fix(coding-agent): publish roster removals at most once, never for owned-only rows

flushRosterUpdates now gates removed ids on a published-ids set: rows born to
client-owned workers emit nothing (no repeated no-op reconciles in every
subscriber, no leak of private roster ids that embed transcript paths), a
published row claimed by an owned worker leaves the surface exactly once, and
promotion re-publishes through the existing empty amend. Seeds and resyncs
register their ids so later disappearances stay removable.

* fix(coding-agent): roster push repaints the bar; teardown and delete-guard stay push-clean

The subagents-bar roster callback now requests a render, so a push with no
accompanying session event paints immediately. Agents-view exit closes the
socket before store disposal (the supervisor drops the subscription with the
client) and dispose serializes behind attach with a fire-and-forget
unsubscribe, so a wedged daemon cannot block exit and an in-flight attach
cannot leave a dangling listener. The pre-delete liveness probe keeps its
narrower plain-list verdict local instead of overwriting the pushed catalog,
so a failed delete no longer hides queued/passivated rows on the next
heartbeat reconcile.

* chore(coding-agent): sweep poll-era sediment and the duplicated stale-daemon string

liveCatalogReady could never be false once the view runs: the field, its dead
savedCatalogReady initializer, and the liveCatalog* fixture leftovers are gone,
and shouldApplyScopeResolution takes only the saved-catalog readiness. The
stale-daemon capability message now has one owner
(STALE_ROSTER_DAEMON_MESSAGE in roster-store).

* fix(coding-agent): tick stale last-heard ages and share the bar's direct-child linkage

The existing animation tick now rebuilds rows while a stale-age label is on
screen (labels are baked in at row build, so a repaint alone cannot advance
them), and the subagents bar parents children through the same getParentKeys
linkage as the view tree via isDirectAgentChild, so parentSessionId-only
children count in the bar exactly as they render in the view.

* chore(coding-agent): slim roster comments and tests to one pin per behavior

Deletes narration comments (86 -> 20 added lines, keeping only genuine
invariants: subscribe-reply race buffer, attach serialization, unparkable
subscribe, removal-once/privacy set, drain-resync queueing, the cancelled
removal signal, and wire-field docs), merges the roster store's lifecycle/race
micro-pins into single behavior tests, consolidates the saved-search latch
suite into one test, drops the remount-gate near-duplicate, slims the bar and
view-instance suites, and deletes the roster-soak dev harness - its
correctness claims (seed, coalesce, loss-gap resync, owned-row visibility,
store convergence) are all pinned by the real-socket tests; only the
scale/heap-plateau sweep is lost, which CI never ran.

* fix(coding-agent): fall back to snapshot bar counts when the roster has no direct children

A client-owned session's rows are invisible to the public roster by design, so
the push-fed bar showed zero while live subagents existed. The roster wins
whenever it reports at least one direct child for this parent; otherwise the
connection snapshots carry the counts for that render.

* chore(coding-agent): second slim round — merge roster suites onto shared fixtures

One store-lifecycle test now carries capability-miss, raced-push replay,
hello re-keying, attach serialization, and dispose; the wire compat E2E and
the chat-bar E2E share one supervisor boot (bystander, coalescing, repaint,
and the owned-session snapshot fallback ride the same socket). Cut with
accepted residual risk: the mid-flight park test (PR #1909 carries the
recoverable-park suite), the watchdog staleness push test (label rendering
stays pinned in the rows test), the view-instance anchor/failed-delete pins
(one-line UI fixes, self-healing paths), the handshake-exit pin, and the
latch test's stale-success ordering (generation guard predates this PR).
Comment pass two drops restating docs (amend, hello key, two wire-field
docs).

* test(coding-agent): fold the monitor suite's roster seeding into the shared fixture

* fix(coding-agent): isolate roster consumers and restamp last-heard marks after row rebuilds

The store's update dispatch now isolates each listener like the connection's
emit does, so one throwing consumer cannot break the others or the process.
The staleness sweep restamps any still-silent worker's rows that lack the mark
instead of stamping only on the first stale pass - AgentRoster.write rebuilds
rows from the incoming payload and dropped it, leaving 'last heard' labels
missing after any supervisor-side write until the worker recovered and went
stale again; mark equality keeps repeat sweeps push-free and the sweep stays
the mark's single owner.

* test(coding-agent): back the flicker test's live edge with real transcript files

* fix(coding-agent): gate the bar's snapshot fallback on the session's own roster presence

total > 0 conflated a client-owned session with a public parent whose children
all left the roster, reviving stale snapshots (roster deletions emit no
cancelled event to the client) and disagreeing with the agents view. The
discriminator is now the parent session's own row: fall back only while
connectionState.sessionId is absent from the pushed summaries, so a public
parent with zero roster children shows zero.

* test(coding-agent): give the flicker test's worker its family root for the scoped reseed

* fix(coding-agent): never fail a recovered session on the roster bar's subscribe

A transient roster_subscribe failure inside attach() rejected the reconnect
loop's otherwise-complete recovery (and the update-restart reattach), ending in
a closed, unusable session because a bar accessory failed. The seam now
swallows the transient class - the bar degrades and the next reconnect or
rebind re-attaches through the same line - while capability-miss stays the
non-fatal false it already was and the agents view keeps its own hard-require
path.

* fix(coding-agent): reject reconnect-loop requests on socket close instead of parking them

Requests issued from the DaemonAgentConnection reconnect loop and the
update-restart restore loop parked as awaitingReconnect when the socket
closed mid-flight, but the daemon_hello that would replay them can only
be produced by the same stuck loop: the connection deadlocked and never
resynced or emitted closed. Thread the existing recoverable:false request
option through attach()/getInitialSnapshot() and the raw list request so
loop-owned requests reject into the loop's own bounded retry. Non-loop
callers keep the default park-and-replay recovery.

* docs(coding-agent): state the loop-ownership rule on the recoverable request option

* docs(coding-agent): add the reconnect-park changelog fragment

---------

Co-authored-by: Xeophon <46377542+xeophon@users.noreply.github.com>
…cells (PrimeIntellect-ai#1911)

* fix(coding-agent): preview bash-skill calls with literal commands as bash

In the collapsed ipython cell line, python cells like r = await bash('git status')
now render as bash · git status instead of the python wrapper (which redactNoise
erased entirely for commands >=160 chars). Literal-first-arg bash() calls on the
scorer-chosen line are routed through previewBashCommand, matching %%bash cells.
Non-literal arguments keep the python preview.

ENG-5802

* test(coding-agent): kill guard-drop mutant in bash-skill preview extraction

previewIpythonCode must keep the python preview when the quoted literal
is not the whole first argument (string concatenation, escaped quotes).
The existing tests let a mutant that drops the comma/paren-after-close-
quote guard survive; this pins the fallback behavior.

* fix(coding-agent): reject escaped-quote mis-cuts in bash-skill extraction

* fix(coding-agent): evaluate bash-skill literals and gate extraction inside strings

Replace the regex-tail + indexOf extraction with a small python
string-literal scanner: backslash consumes the next char (raw-string
rule included), cooked strings unescape standard sequences, and an
unterminated scan falls back to the python preview. The preview now
shows the evaluated command (real newlines, unescaped quotes) instead
of raw source text, and triple-quoted literals with escaped quotes can
no longer be mis-cut.

Reuse the scanner to detect when the scorer's chosen line sits inside
an unterminated triple-quoted string opened earlier; skip bash-skill
extraction there so string text like a docstring never previews as a
bash command that did not run.

* test(coding-agent): kill scanner and gate mutants in bash-skill extraction

Killer tests for three surviving mutants: raw-string backslash handling
(raw close at backslash-quote), the closed-and-reopened multiline gate
state, and unclosed-literal extraction of a partial value.

* fix(coding-agent): fall back on value-changing escapes in bash-skill literals

* fix(coding-agent): redact authorization headers in previews; tighten tests and comments
…ENG-5817) (PrimeIntellect-ai#1926)

* feat(coding-agent): protocol groundwork for direct worker peer transport

Schema revision 25 adds the capability-gated get_direct_worker_transport
command, the single-use DaemonPeerTransportTicket shape, and the peer grant,
peer_auth, and worker_register_peer_transport wire types. DAEMON_COMMAND_PLANE
classifies every daemon command as session- or control-plane at compile time
so unclassified commands can never route to a direct transport, and the
per-command hello compatibility predicate moves to daemon-protocol.ts so the
routed client and DaemonClient share one check. Worker descriptors gain the
per-incarnation workerInstanceId that peer grants bind to.

* feat(coding-agent): worker-side direct peer transport admission

Workers accept supervisor-registered single-use peer grants
(worker_register_peer_transport; bounded, TTL-capped, burned before their
token is compared with sha256 + timingSafeEqual) and authenticate direct
session clients via peer_auth on their own socket. A pre-auth socket may only
authenticate; a failed peer_auth or any other command ends it. Authenticated
peers are pinned to the session plane of their granted session, so worker
control commands, other sessions, and unknown command types are rejected.
Grants and live peers are fenced during update preparation, archive shutdown,
and worker shutdown; compact assistant deltas stay a supervisor-only encoding.
Session summaries report directAttachedClients so the supervisor roster,
attachment counts, and idle eviction can stay honest.

* feat(coding-agent): supervisor issues direct worker transport tickets

get_direct_worker_transport asks the supervisor for a single-use ticket to a
resident worker's own socket. The supervisor pre-registers a 10s grant in the
worker over its authenticated channel, binds it to the fresh per-incarnation
workerInstanceId (launched via env, echoed in worker_auth, persisted on the
descriptor), verifies the worker process identity is current, and stamps the
socket's dev/ino into the ticket so clients can detect a swapped socket.
Workers advertise peer-transport support in their worker_auth capabilities;
older workers never get grants and client-owned workers are refused at issue
time. Public summaries and idle-eviction snapshots add worker-reported direct
attachments to the supervisor-side count so eviction and the roster stay
honest.

* feat(coding-agent): route session commands over a direct worker transport

DaemonAgentConnection.attach upgrades its supervisor connection with a direct
link to the session's worker when the supervisor advertises
direct_peer_transport: it redeems a single-use ticket, verifies the socket's
dev/ino, and wraps both sockets in a DaemonRoutedClient. Session-plane
commands the worker's own hello serves go direct; control-plane commands, an
old worker, or any direct-path failure fall back silently to supervisor
routing, and a malformed direct frame closes the link instead of throwing
into the client. An established direct link outlives supervisor-socket loss:
the session keeps streaming while the control plane reconnects in the
background. Watchers and owned sessions stay on the shared supervisor socket,
and a successful reattach drops the now-stale direct link.

* docs(coding-agent): add the direct session transport changelog fragment

* fix(coding-agent): schedule supervisor recovery from the socket role, not the live claim

The fence check revokes a stale supervisor claim before ending its socket, so
the disconnect cleanup can no longer consult supervisorClaims to decide
whether to probe supervisor availability; the authentication role survives
revocation and excludes direct session peers. Also drop the redundant
shuttingDown clause from peer_auth (shutdown already fences admissions and
clears grants) and teach the affected daemon fakes the fields the transport
now touches.

* fix(coding-agent): keep reasoned closes authoritative on the direct transport

Review fixes for the direct session transport:
- Peer fencing now writes daemon_closing with the real reason before ending
  direct sockets, and the archive shutdown closes sessions first so direct
  peers read session_closed "killed" instead of a fake daemon shutdown; a
  clean daemon stop therefore stays terminal and never respawns the daemon.
- The connection checks the authoritative shutdown/update close reason before
  the surviving-direct-link reroute, so reasoned closes stay terminal (stop)
  or take the update restoration path (restart) even while the direct link is
  healthy.
- worker_auth enforces the instance binding only when the authenticating
  supervisor presents one, so a downgraded supervisor still adopts live
  workers; the peer-transport fencing between current builds is unchanged.
- One helper owns the direct-plus-supervisor attachment sum; peer_auth drops
  the same-process re-check of the worker's own instance id (registration is
  the single enforcement point; the client-presented field stays checked) and
  the peer command gate drops the updateRestart clause that fencing makes
  unreachable.

* chore(coding-agent): slim direct-transport comments to genuine ambiguity

Drop comments that narrate the adjacent code and squeeze multi-line docs to
one line, keeping the load-bearing notes: grant burn-before-compare, the
archive close-before-fence ordering, the reason-before-FIN contract, the
fail-fast rule on dropped request options, the shutdown-window fence
reachability, and the command-plane classification rule.

* fix(coding-agent): resync only when a reconnect re-established session state

Control-plane recovery with a healthy direct link completed a reconnect cycle
per supervisor-socket flap (attach rides the direct link, so every cycle
succeeded instantly) and each cycle emitted session_resynced, re-rendering an
unchanged transcript up to 15 times during one supervisor replacement. The
reconnect loop now emits session_resynced only when the session transport
itself was re-established through a supervisor attach; a held direct link
streamed state throughout, so recovery emits just the connected status.

* fix(coding-agent): one close-handler owner; direct loss always falls back

A direct-transport loss is never itself a session loss: the connection now
reconnects through a supervisor re-attach even without a recoverDaemon hook
(the supervisor socket is still healthy; a worker that actually died keeps
signalling through the authoritative session_closed path). The transport
close handler is extracted into one method and the control-plane close saved
during an initial direct attach replays through it, so a daemon shutdown
during initial attach stays terminal instead of respawning the daemon.

* fix(coding-agent): flush the roster when direct viewers attach or detach

A direct peer joining or leaving an idle session changes
directAttachedClients without any session event to carry it, so the
supervisor's roster (and roster-based idle eviction) held a stale count until
unrelated activity. The attach handler and the shared detach path now
schedule a roster flush for session_client sockets; supervisor-relayed
viewers keep their existing event-driven cadence.

* test(coding-agent): supervisor-swap E2E expects no resync while the direct link holds

The resident-worker restart test predates the direct transport: its client
now rides the worker's own socket through the supervisor swap, so the
recovery emits connected without a session_resynced. The end-to-end pin now
asserts the new semantics (no resync, no close, state queryable), matching
the unit pins from the resync-emission fix.

* fix(coding-agent): reject the attach when a shutdown lands during initial direct attach

Replaying a saved supervisor-shutdown close through the close handler inside
the static attach emitted the terminal event before any listener could
subscribe, and terminalCloseEmitted then swallowed every later signal: a
daemon stop during first attach produced a zombie connected TUI. No listener
can exist during construction, so a terminal saved close now rejects the
attach with the socket-close error — the pre-transport semantics every call
site already handles; non-terminal saved closes keep replaying through the
single close handler.

* fix(coding-agent): compose pause invalidation with direct-loss fallback; re-acquire direct after reattach

A direct-socket drop while a session input pause was held hit the fail-closed
pause branch before the recoverable-direct-loss ordering, terminalizing a
session the supervisor could still serve and skipping update restoration. The
fence stays fail-closed only for control-plane losses; a lost direct link
clears it (holders still learn through the invalidation generation) and falls
through to the normal close ordering.

A successful in-window reattach dropped the stale direct link but never
acquired one for the target, leaving the session supervisor-routed for good.
The routed client can now upgrade itself with the same silent ticket dance as
the initial attach (one shared acquisition helper), and reattachSession
invokes it on success only — a rejected reattach still keeps the old link.

* fix(coding-agent): revert the reattach transport upgrade; never park the ticket request

The post-reattach upgradeDirectTransport was half a mechanism: its own pin
showed no attach ever crosses the new link, so events stayed on the
supervisor relay while requests silently switched sockets, and supervisor
attachment bookkeeping leaked. Post-reattach supervisor routing is the honest
state until a designed-as-one-piece re-upgrade exists. With its only other
caller gone, the ticket acquisition folds back into
createDaemonSessionTransport, and the ticket request now opts out of
reconnect parking (recoverable:false) so a supervisor drop mid-acquire fails
into supervisor routing instead of pending the attach forever.

* fix(coding-agent): held-direct recovery is control-plane only and unbounded

While the direct link is held, the reconnect loop's success is connect plus
hello: no re-attach and no snapshot cross a socket that never stopped serving
the session, which deletes the snapshot-restream-over-live-socket failure
class and makes the resync skip structural. The bounded reconnect deadline
arms only once the direct link is gone (re-armed at the transition), so a
supervisor staying down never terminally closes a healthy direct stream; a
mid-recovery direct death reruns the loop as a normal bounded session-plane
reconnect.

* docs(coding-agent): state the peer grant's scope and threat model on its type

The session pin is scoped to the active-session slot and guards against
routing accidents, not privilege escalation: ticket holders already hold
supervisor access.

* fix(coding-agent): rebind the roster subscription during held-direct recovery

Control-plane-only recovery skips attach(), which was the roster store's only
rebind seam, so a supervisor blip under a healthy direct link left the agents
view subscribed to the dead hello. The subscription is a control-plane
accessory: held-direct recovery now re-attaches the store non-fatally after
connect + hello, and the session-plane path keeps its single rebind inside
attach().

* chore(coding-agent): slim transport comments and one subsumed test assert

Drop docs that restate their declarations (transport interface, role union),
the close-handler narration the predicate name now carries, and squeeze three
multi-line whys to one line each. The half-open handshake test loses its
zero-resync assert: the deadline-policy pin and the supervisor-swap E2E both
pin held-mode zero-resync across the whole recovery.

* fix(coding-agent): unparkable fallback attach; one liveness check after the held-recovery awaits

The supervisor-side retry of a failed initial direct attach owns its bounded
outcome, so it now opts out of reconnect parking and, on failure, rejects with
the control-plane close saved during the window — a daemon stop racing the
retry reaches the caller as the authoritative reason instead of a pend or a
generic socket error. Held-direct recovery revalidates once after its last
await (the roster re-attach): a terminal close inside the window stops the
loop without emitting connected, and a direct loss inside it falls through to
the bounded session-plane rerun instead of joining the finished recovery.

* fix(coding-agent): held recovery stands down for an in-flight update restoration

The post-await validation only re-derived two of the close handler's three
outcomes, so an update close landing inside the held roster re-attach fell
into the bounded session-plane rerun, whose deadline could close the client
under the running restoration. The check now consults the handler's own
dispatch outputs — terminalCloseEmitted, updateRestartPending, or the
recoverable close that joined this loop — so a window close lands in exactly
the branch the handler chose.
…eIntellect-ai#1929)

* fix(coding-agent): harden daemon recovery and socket ownership

* fix(coding-agent): address daemon recovery review findings

* fix(coding-agent): preserve prompt admission scheduling

* test(coding-agent): consolidate daemon recovery coverage

* fix(coding-agent): preserve safe shutdown outcomes

* fix(coding-agent): close recovery admission gaps

* refactor(coding-agent): one process-identity oracle for recovery verdicts

Adoption and recovery classify liveness through processIdentity() instead of
re-pairing isProcessAlive+getProcessStartId at each site; raw start-id reads
remain only where the value itself is persisted. The sole surviving kill of a
live worker is the identity-verified pre-roster replacement, now pinned:
adoption of a live pre-roster worker still kills-and-relaunches, while an
unverifiable identity still parks failed with no replacement.

* fix(coding-agent): bound live-worker probing at ten defer rounds

A hung-but-alive worker (or a live pid with no verifiable start id) probed
forever: ~11s probe pass, 5s defer, repeat. After MAX_DEFERRED_RECOVERY_ROUNDS
(10 rounds, ~2.5 minutes) the worker parks failed — user-visible through the
existing roster failed status — with its process left alive for a manual
retry_worker, which resets the round count like any successful recovery.

* refactor(coding-agent): type the worker probe timeout

isDaemonWorkerProbeTimeout classified by message prefixes defined in two
other files. The timeout sites in daemon-worker-client.ts (hello, response)
and the supervisor's connect deadline now throw DaemonWorkerProbeTimeoutError,
and the classifier is a plain instanceof — one truth, same shape as
DaemonWorkerAuthenticationError.

* docs(coding-agent): state the leak-over-kill recovery tradeoff

The recovery SIGKILL removal applies to every failure class, not just probe
timeouts. Verified: a live verified-identity failed worker IS reclaimed by the
next fresh create (graceful stop), so the true leak is only the unverifiable
survivor — the deliberate fail-safe. One comment at the decision point and a
changelog line make the tradeoff explicit.

* fix(coding-agent): kill before cleanup in the pre-roster replacement

The carve ran recoverUncertainWorkerOperations (interruption marking, orphan
reaping) before the identity-gated SIGKILL, so destructive cleanup hit a
still-active worker; pre-rebase the kill preceded cleanup inside the old
helper. Reordered: recheck + SIGKILL + bounded teardown wait first, cleanup
only after the predecessor is confirmed gone/replaced, and an unverifiable
survivor parks failed with NO destructive cleanup at all (matching recovery's
rule that a possibly-live worker is never cleaned destructively). The carve
pin asserts the order and the no-cleanup branch.

* fix(coding-agent): never release a registry guard a successor reclaimed

proper-lockfile verified: compromise detection is timer-driven (mtime updates),
and release() removes the lockfile unconditionally when the steal has not been
detected yet — so a caller resuming from a stall past the stale threshold could
delete the successor'"'"'s lock. The guard directory'"'"'s inode is the ownership
identity (a steal is rmdir+mkdir): it is captured at acquisition and checked
synchronously where the timer cannot run — before returning an action'"'"'s result
and before release. A stolen-but-undetected guard is left to its own updater,
which notices the foreign mtime and cleans itself. A truly synchronous stall
still cannot be preempted mid-action; that limitation is stated at the guard.

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
* fix(coding-agent): single-dump kernel snapshots (ENG-5819)

The kernel snapshot pipeline serialized the aggregate payload dict into an
in-memory buffer, copied it out with getvalue(), and only then wrote the
file - on top of the per-variable blob dict, a ~3.9x-payload transient
memory spike on every debounced snapshot (measured 963MB overhead for a
250MB namespace). The aggregate dict now pickles straight into the staged
temp file through a size-capped pass-through writer (measured 253MB
overhead, ~1x payload); the strict max_bytes contract and the
largest-fitting-prefix fallback are preserved by rewinding the temp file
between prefix attempts. _SnapshotBuffer is replaced by the same
_CappedWriter over a plain BytesIO for the per-variable dumps.

* chore(coding-agent): strip narrative comments from the snapshot dump path
…aches (PrimeIntellect-ai#1946)

Direct-transport clients attach and detach on the worker socket, so the
supervisor-socket cleanup that owns empty-session eviction never saw their
last detach and empty drafts lingered until an idle sweep. The worker already
reports the drop: its peer attach/detach roster flush delivers a summary
whose directAttachedClients falls to zero, so the supervisor's roster write
funnel now triggers the existing last-detach eviction on that transition —
covering both clean detach and unclean socket drop, which share the worker's
detach path. The eviction candidate check counts direct viewers through the
shared attachment sum, so a remaining direct client blocks a routed client's
last detach and vice versa. Fixes ENG-5831.
…snapshot (PrimeIntellect-ai#1944)

* fix(coding-agent): suppress rlm_child_update emits with an unchanged snapshot

Child streaming re-emits the parent's child snapshot on every assistant
delta; once the answer preview caps and activity is steady, the repeats
are byte-identical and only add wire and render noise for every attached
client. Emit only when the serialized snapshot actually changed.

* fix(coding-agent): bound the kernel stderr diagnostic buffer to a tail

kernelStderr accumulated for the kernel's lifetime while every reader
takes at most the last 1 KiB; keep only an 8 KiB tail.

* chore(coding-agent): drop narrating comments from the relay-hygiene diff

* revert(coding-agent): move the kernel stderr bound out to its own PR
…i#1631)

* fix(coding-agent): replace TUI process after update

* fix(coding-agent): guard unsafe execve failures

* fix(coding-agent): restore the previous cwd when a thrown execve falls back

The relaunch chdirs before replacing the process; a thrown execve (Node >=26.1)
left the old process on the update cwd while the child fallback ran. The seam
now restores the captured previous cwd on the throw path so a failed attempt
leaves no silent process-state difference.

* docs(coding-agent): qualify the update-relaunch changelog with the platform restriction

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
… lines (PrimeIntellect-ai#1948)

* fix(coding-agent): show the expand hint on sent agent-message summary lines

* test(coding-agent): drop the exact-line sent-message rendering test
…ct-ai#1955)

* revert(coding-agent): restore subagent cleanup guidance

Reverts PrimeIntellect-ai#1952 so agents delete direct subagents when they are no longer needed instead of retaining them until a user explicitly requests deletion. Adds prompt coverage for the intended cleanup policy.

* test(coding-agent): keep prompt rollback free of copy assertions
…ies without changing list --all (PrimeIntellect-ai#1951)

* fix(coding-agent): scope the roster seed to registered workers' families without changing list --all

Reworks the closed PrimeIntellect-ai#1941: the boot seed now covers only registered
workers' descendant families (catalog rows stay catalog-owned), but
subagent rows of families with no registered worker are still served
by list --all, read on demand from the spawn ledger with the same
fields, statuses, and liveEdges ordering as the seeded rows had.
The sessionDir ancestry walk falls back to ledger edges when the
ancestor rows are not roster-resident.

* fix(coding-agent): hydrate on-demand ledger list rows sequentially

The dead-family list --all path fanned out one concurrent transcript
read per unseeded ledger child (Promise.all), a shape the boot seed
deliberately avoids; a large dead-family ledger could exhaust file
descriptors. Hydrate inside the edge loop instead, one read at a
time, matching seedRosterLedger.
…or a doomed kernel snapshot (PrimeIntellect-ai#1954)

* feat(coding-agent): make the final trace flush detachable and the kernel snapshot optional on dispose

AgentSessionRuntime.dispose() and AgentSession.disposeAsync() now take
options: traceFlush "detach" fires the final trace upload without
awaiting it (failures land in agent-traces.log), and kernelSnapshot:false
skips the kernel's final snapshot at the provisioner shutdown boundary.
Defaults keep today's blocking flush and snapshot on every caller.
Session replacement (new/resume/fork teardown) detaches the flush: the
outgoing session file and the process both outlive the swap.

* fix(coding-agent): stop RLM subagent deletion from awaiting the trace upload and writing a doomed kernel snapshot

Deleting a resident child blocked on the full session trace upload
(15s timeout x3 retries, and a flat 60s sleep on HTTP 429 without
retry-after) and wrote a final kernel snapshot that the artifact sweep
removed milliseconds later. The daemon now detaches the flush on every
close that the daemon and the session file outlive, and the delete
paths dispose the kernel with snapshot:false. Daemon shutdown, update
restarts, and worker archive-and-shutdown still await the flush;
passivation still writes the snapshot.

Measured on a resident child with a pending upload: 3s-network flush
3024ms -> 10ms; 429 without retry-after 60120ms -> 9ms.

Linear: ENG-5837

* refactor(coding-agent): replace per-close trace-flush policy with an exit barrier

Review round: disposal policy did not propagate through nested closes
(hosted children), the closingSessions join (delete inheriting a
passivation close's awaited flush; exit joins never upgrading an
in-flight detached close), or startup-abort kernel teardown.

Instead of threading policy through every close, disposal now never
awaits the trace upload; the four exit owners (daemon shutdown, update
restart, worker archive-and-shutdown, in-process connection dispose)
drain all scheduled and in-flight uploads through one barrier
(flushAllPendingAgentTraceUploads). This makes the nested/concurrent
propagation bugs structurally impossible. kernelSnapshot:false stays
threaded from the delete paths and now also covers a dispose that
aborts a kernel startup in flight.

* fix(coding-agent): run the trace-upload exit barrier even when teardown throws

The in-process connection dispose and the daemon shutdown loop exit
regardless of a thrown teardown (print/acp swallow the dispose error and
exit; a rejected shutdown() is an unhandled rejection), so the barrier
moves into a finally. The teardown error still propagates. The other two
owners (update-restart commit, worker archive-and-shutdown) keep the
daemon alive on a throw, so their detached uploads finish on their own.
* chore: prepare v0.9.0 release

* chore: add missing list marker to a 0.9.0 changelog entry
…rimeIntellect-ai#1960)

* fix(coding-agent): load the saved catalog when the agents view opens

v0.9.0 regression: the Inactive section was empty on a fresh agents
view until a search query was typed. The saved catalog was loaded
only for search (a PrimeIntellect-ai#1900 optimization premised on the roster boot
seed carrying the saved corpus as inactive rows); PrimeIntellect-ai#1951 scoped that
seed to live families, so the view must load what it displays.
The load stays progressive and once-per-view.

* docs: tighten the catalog-load comment
…1965)

* chore: require bug report environment details

* ci: prefer Research tickets for Prime Agent

* Revert "ci: prefer Research tickets for Prime Agent"

This reverts commit ed73256.
* ci: prefer Research tickets for Prime Agent

* ci: require RES tickets or explicit opt-out
….x (PrimeIntellect-ai#1993)

* fix(ai): bump impersonated Claude Code version to 2.1.257 for Fable 5.x

The Anthropic API now gates Fable 5.x models on Claude Code >= 2.1.251
and rejects the previously pinned 2.1.75 identity for OAuth requests.

Fixes PrimeIntellect-ai#1962

* chore(ai): drop the identity-header test and rationale comment per review
…ct-ai#1992)

* fix(installer): support npm 12 remote dependency policy

* fix(installer): allow verified npm 12 postinstall

* refactor(installer): simplify npm 12 coverage

* refactor(installer): consolidate installer checks
PrimeIntellect-ai#2002)

* fix: register ACP MCP tools as native callable tools via cpython proxy

* fix: add MCP proxy tool names to allowlist so model can see them

* fix: add missing details field to ACP MCP tool execute results

* fix(coding-agent): harden ACP MCP proxy lifecycle

Fixes PrimeIntellect-ai#2002

* fix(coding-agent): reject MCP without cpython

Fixes PrimeIntellect-ai#2002

* refactor(coding-agent): narrow generic MCP accessor

Fixes PrimeIntellect-ai#2002

* fix(coding-agent): cap ACP MCP server names so composed tool names fit provider limits

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
kevinjosethomas and others added 25 commits September 8, 2026 19:16
* chore: prepare v0.10.0 release

* chore: set release version to v0.9.4

Fixes PrimeIntellect-ai#2118
Fixes ENG-6000 and ENG-6001.

Refs ENG-5999.
…meIntellect-ai#2124)

* fix(ci): accept engineering and research Linear tickets

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124
…xt (RES-1318) (PrimeIntellect-ai#2098)

* feat(coding-agent): keep the system prompt static across refinements and deliver harness state in-context

Fixes RES-1318.

Applying a refinement no longer rebuilds and swaps agent.state.systemPrompt,
so the provider prefix cache survives every refinement. Instead:

- _applyRefine appends one durable refinement_notice custom message per
  applied refinement at the apply seam, labeled [auto-refinement] /
  [user-refinement] / [self-refinement] by source, with the trigger line and
  applied edits in the digest's own notation (rollbacks print as rollbacks).
  Zero-applied-edit refinements emit nothing and the notice never starts a
  turn. convertToLlm passes the new type through as a user message while the
  refinement_outcome audit message stays filtered and untouched.
- The harness digest injection is removed from the system prompt entirely
  (buildSystemPrompt loses the harnessState option). The digest now appears
  at cold context boundaries only, reusing formatHarnessStateForPrompt
  byte-identically: fresh sessions get a digest custom message as the first
  context message; compaction attaches a digest snapshot to the compaction
  entry mechanically (never through the summarizer) so the post-compaction
  head message renders memories-first before the summary on both the initial
  and update-merge paths; resumes append a fresh digest at the tail only when
  the newest digest in live context no longer matches disk state (identity
  dedupe), which is also the migration path for pre-change sessions.

The new compaction entry field and custom message types ride the existing
schema-tolerant JSON entry stream (optional field, generic custom messages):
backward-compatible, no daemon protocol or schema revision change.

* test(coding-agent): adapt message-sequence pins to the session-start harness digest

Every session now opens with a harness_digest custom message, so tests that
pinned exact message sequences, first-message identity, provider-context text
lists, or entry counts see one extra leading custom message. Adds a
conversationMessages helper to the suite harness (filters the digest) and
updates the affected assertions; provider-context probes now select the last
user message instead of the first.

* fix(coding-agent): harden harness digest recency and keep digests out of the summarizer

Review fixes on PrimeIntellect-ai#2098:

- _latestContextHarnessDigest picked the last digest by array position, but
  retained pre-compaction messages are presented after the compaction head
  while being chronologically older, so an old retained digest could defeat
  resume dedupe. Recency is now the greatest message timestamp among all
  in-context digest carriers.
- The session-start harness digest entry was fed to the first compaction's
  summarizer as an ordinary custom message. prepareCompaction now excludes
  harness_digest entries from summarizer input; the digest is only attached
  mechanically at the new compaction head.

* fix(coding-agent): label non-serialized refine.run notices as self-refinement

Review fix on PrimeIntellect-ai#2098: _consumePendingRequestedRefine forwarded agent-callable
refine.run requests through refine() without a source, so the notice carried
the [user-refinement] label in non-serialized sessions. refine() now accepts
an explicit source override and the pending-request consumer passes "self".

* test(coding-agent): pin the self source on the pending refine.run consumer

* fix(coding-agent): treat the digest as boundary-injected mechanical context everywhere

Review round on PrimeIntellect-ai#2098 (three findings):

- Untouched sessions stay empty: the fresh-session digest is now injected
  lazily at the first committed turn instead of at construction, so
  abandoned-draft cleanup and raw message-count emptiness checks
  (daemon-session-list lifecycle/activity, isEmptyDraftContent, branch
  seedability, hasExistingSession) keep seeing an empty session. Non-empty
  contexts (resume) still inject at construction with identity dedupe.
- Tree navigation is a cold boundary: navigateTree rebuilds the context, so it
  now runs the same refresh-iff-stale digest check as resume (fork/clone/switch
  construct a new session and were already covered by the constructor path).
- Headless terminal selection skips harness_digest like other non-output
  customs, so resume-and-print returns the saved final assistant output.

Sweep of other message-kind-sensitive consumers: branch summarization now
excludes harness_digest from summarizer input (same rule as compaction);
estimateContextTokens and side-question seeding intentionally keep the digest
(it is live context); _repliedToParentSinceTask and getUserMessagesForForking
are kind-filtered already.

* chore(coding-agent): trim narrative comments and redundant test asserts on the digest work

Comments cut to one-line invariant guards; duplicate digest-notation and
content asserts removed where a kept unit pin already covers the same
rendering (labels and notation stay pinned in refinement-outcome-message.test,
source plumbing stays pinned per path). No behavioral change.

* fix(coding-agent): scope the first-turn digest to the turn that delivers it

Review fix on PrimeIntellect-ai#2098 (Bugbot thread 2919fcf3): the first-turn digest was
persisted directly at commit, before agent.prompt ran, so cancelling or
clearing that first turn stripped the turn's captured messages but left the
digest in state and in the session file - resurfacing the emptiness-check
class through the cancel path. The digest now rides the turn's next-turn
delivery records: it persists at message_end like the rest of the turn,
first-turn cancellation strips and restores it with the turn, and the lazy
injection re-delivers on the next committed turn.

* test(coding-agent): event-order pins include the digest custom pair on the first turn

* fix(coding-agent): deliver the first-turn digest on skip-policy turns and re-arm on cancel

Two follow-up review findings on the lazy-injection shape:

- Custom-triggered turns use the skip next-turn-context policy, so a fresh
  session whose first turn was sendCustomMessage(triggerTurn) ran without the
  digest. The pending digest now attaches on every committed first turn; skip
  policy still withholds ordinary pending next-turn messages.
- A cancelled first turn whose digest record was already durable dropped the
  digest from live context without restoring it (restorable records skip
  durable ones). Lazy injection now owns digest delivery uniformly: cancelled
  turns never restore the digest message and instead re-arm the pending flag,
  so the next committed turn re-delivers a fresh one.

* fix(coding-agent): never park the harness digest as pending next-turn context

Review fix on PrimeIntellect-ai#2098 (Bugbot skip-turn-digest-lost-on-failure): a failed commit
parked the undelivered digest in _pendingNextTurnMessages, which skip-policy
custom-trigger turns never drain, and a parked copy plus a re-armed injection
could double-deliver. Parking now filters the digest out and re-arms
_harnessDigestPending instead, keeping lazy injection the single owner of
digest delivery.
* fix(ci): accept engineering and research Linear tickets

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(ai): identify OpenCode conversations and application

Fixes ENG-6009.

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124
…-ai#2128)

* fix(ci): accept engineering and research Linear tickets

* fix(coding-agent): preserve literal prompt arguments

Refs: ENG-6014 and discussion PrimeIntellect-ai#2106.

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124
…#2127)

* fix(ci): accept engineering and research Linear tickets

* fix(ai): omit tool results without surviving calls

Fixes PrimeIntellect-ai#984.

Linear: ENG-6012

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124
* fix(ci): accept engineering and research Linear tickets

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(ai): recognize LiteLLM context limit rejections

Refs: ENG-6011. Reported in discussion PrimeIntellect-ai#2088.

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124
…Intellect-ai#2122)

* fix(coding-agent): resume active goals after manual compaction

* fix(coding-agent): retain each compaction abort controller

* refactor(coding-agent): keep compaction fix minimal

* fix(coding-agent): resume goals behind queued session commands
…lect-ai#2160)

* ci: benchmark prime agent pull request updates

Refs ENG-6043

* docs: clarify benchmark installation prerequisites

Refs ENG-6043

* ci: reduce benchmark debounce to two seconds

Refs ENG-6043; fixes PrimeIntellect-ai#2160

* ci: run benchmark harness tests in existing CI

Refs ENG-6043; fixes PrimeIntellect-ai#2160

* ci: benchmark local runtime performance without inference

Refs ENG-6043; fixes PrimeIntellect-ai#2160

* test: calibrate benchmark noise thresholds

Refs ENG-6043; fixes PrimeIntellect-ai#2160
…ntellect-ai#2178)

* fix: simplify benchmark comments and replace old results

Refs ENG-6043; fixes PrimeIntellect-ai#2160

* fix: scale benchmark colours by percentage

Refs ENG-6043; fixes PrimeIntellect-ai#2178.
…lect-ai#2148)

* show the recorded model on inactive agents-view rows

* show the recorded model in the actions panel for inactive rows
…-ai#2134)

* fix(ci): accept engineering and research Linear tickets

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(coding-agent): include the lazy Bedrock provider in CLI bundles

Fixes ENG-6006

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124

* fix(coding-agent): preserve structured Bedrock logs

fixes PrimeIntellect-ai#2134

* test(coding-agent): await Bedrock fixture daemon shutdown

fixes PrimeIntellect-ai#2134

* fix(ci): require complete Linear ticket identifiers

* test(coding-agent): guarantee Bedrock fixture cleanup

* test(coding-agent): allow context before Bedrock prompts

* test(coding-agent): allow context before literal prompts

* fix(ci): isolate Ubuntu package sources
…i#2130)

* fix(ci): accept engineering and research Linear tickets

* fix(ci): clarify the Research ticket project

fixes PrimeIntellect-ai#2124

* fix(coding-agent): preserve reasoning in auxiliary calls

Refs ENG-6010 and discussions PrimeIntellect-ai#1482 and PrimeIntellect-ai#2081.

* fix(ci): clarify Prime Agent board routing

fixes PrimeIntellect-ai#2124

* fix(coding-agent): reserve refinement reasoning output budget

Remove fixed output caps when reasoning is enabled and reserve prompt context within the model limit. Keep truncation failures bounded without retrying.

fixes PrimeIntellect-ai#2130

* fix: preserve refinement context and valid thinking budgets

Estimate refinement tokens and shorten only older trajectory content when space is tight. Enforce valid minimum thinking budgets for budget-based Claude requests.

fixes PrimeIntellect-ai#2130

* fix(ci): isolate Ubuntu package sources

(cherry picked from commit 7656902)

* test(coding-agent): allow context before literal prompts

(cherry picked from commit 59e9714)

* fix(coding-agent): bound dense refinement context (fixes PrimeIntellect-ai#2130)
…ellect-ai#2152)

* rename rlm.run to rlm.spawn and drop the callable shim

The kernel API for spawning a child is now the single explicit call
await rlm.spawn(prompt, ...). Calling rlm(...) raises a TypeError that
names the replacement, so a model imitating an older transcript can
correct itself in one turn. The host request type stays "rlm.run" so
kernels and hosts of different versions keep working.

* teach rlm.spawn in the system prompt and host errors

The system prompt, subagent guidance, refinement guidance, and spawn
error messages now name rlm.spawn instead of the removed callable form.
The kernel readiness check requires rlm.spawn and rejects a runtime that
still exposes rlm.run, so a stale kernel venv is rebuilt instead of
serving the old surface.

* update docs to the rlm.spawn call form

Docs, README, the subagent example, and the Python skill reference now
show await rlm.spawn(...). The runtime doc keeps the "rlm.run" host
request type where it describes the wire protocol.

* keep one comment for the unchanged rlm.run wire type

The runtime comment at the host_request call is the only place the
spawn/wire name difference needs explaining.

* require an explicit child name and finish the rename cleanup

rlm.spawn(prompt, *, name, model=None, thinking=None) now rejects a
nameless spawn at the Python signature, so every child gets a stable
name the parent chose. The host keeps its generated-name fallback for
wire payloads without a name, because older kernels still send those.

Reading the removed rlm.run attribute now raises an AttributeError that
names rlm.spawn, matching the not-callable error, so both stale habits
recover in one turn. Docs no longer describe rlm as callable, and the
bootstrap failure text says an interrupted upgrade rebuild needs network
once more.

* name the child in the runtime sequence diagram

The diagram showed a spawn call that now fails at the signature.
…racket grammar (RES-1321) (PrimeIntellect-ai#2115)

* feat(coding-agent): keep the system prompt static across refinements and deliver harness state in-context

Fixes RES-1318.

Applying a refinement no longer rebuilds and swaps agent.state.systemPrompt,
so the provider prefix cache survives every refinement. Instead:

- _applyRefine appends one durable refinement_notice custom message per
  applied refinement at the apply seam, labeled [auto-refinement] /
  [user-refinement] / [self-refinement] by source, with the trigger line and
  applied edits in the digest's own notation (rollbacks print as rollbacks).
  Zero-applied-edit refinements emit nothing and the notice never starts a
  turn. convertToLlm passes the new type through as a user message while the
  refinement_outcome audit message stays filtered and untouched.
- The harness digest injection is removed from the system prompt entirely
  (buildSystemPrompt loses the harnessState option). The digest now appears
  at cold context boundaries only, reusing formatHarnessStateForPrompt
  byte-identically: fresh sessions get a digest custom message as the first
  context message; compaction attaches a digest snapshot to the compaction
  entry mechanically (never through the summarizer) so the post-compaction
  head message renders memories-first before the summary on both the initial
  and update-merge paths; resumes append a fresh digest at the tail only when
  the newest digest in live context no longer matches disk state (identity
  dedupe), which is also the migration path for pre-change sessions.

The new compaction entry field and custom message types ride the existing
schema-tolerant JSON entry stream (optional field, generic custom messages):
backward-compatible, no daemon protocol or schema revision change.

* test(coding-agent): adapt message-sequence pins to the session-start harness digest

Every session now opens with a harness_digest custom message, so tests that
pinned exact message sequences, first-message identity, provider-context text
lists, or entry counts see one extra leading custom message. Adds a
conversationMessages helper to the suite harness (filters the digest) and
updates the affected assertions; provider-context probes now select the last
user message instead of the first.

* fix(coding-agent): harden harness digest recency and keep digests out of the summarizer

Review fixes on PrimeIntellect-ai#2098:

- _latestContextHarnessDigest picked the last digest by array position, but
  retained pre-compaction messages are presented after the compaction head
  while being chronologically older, so an old retained digest could defeat
  resume dedupe. Recency is now the greatest message timestamp among all
  in-context digest carriers.
- The session-start harness digest entry was fed to the first compaction's
  summarizer as an ordinary custom message. prepareCompaction now excludes
  harness_digest entries from summarizer input; the digest is only attached
  mechanically at the new compaction head.

* fix(coding-agent): label non-serialized refine.run notices as self-refinement

Review fix on PrimeIntellect-ai#2098: _consumePendingRequestedRefine forwarded agent-callable
refine.run requests through refine() without a source, so the notice carried
the [user-refinement] label in non-serialized sessions. refine() now accepts
an explicit source override and the pending-request consumer passes "self".

* test(coding-agent): pin the self source on the pending refine.run consumer

* fix(coding-agent): treat the digest as boundary-injected mechanical context everywhere

Review round on PrimeIntellect-ai#2098 (three findings):

- Untouched sessions stay empty: the fresh-session digest is now injected
  lazily at the first committed turn instead of at construction, so
  abandoned-draft cleanup and raw message-count emptiness checks
  (daemon-session-list lifecycle/activity, isEmptyDraftContent, branch
  seedability, hasExistingSession) keep seeing an empty session. Non-empty
  contexts (resume) still inject at construction with identity dedupe.
- Tree navigation is a cold boundary: navigateTree rebuilds the context, so it
  now runs the same refresh-iff-stale digest check as resume (fork/clone/switch
  construct a new session and were already covered by the constructor path).
- Headless terminal selection skips harness_digest like other non-output
  customs, so resume-and-print returns the saved final assistant output.

Sweep of other message-kind-sensitive consumers: branch summarization now
excludes harness_digest from summarizer input (same rule as compaction);
estimateContextTokens and side-question seeding intentionally keep the digest
(it is live context); _repliedToParentSinceTask and getUserMessagesForForking
are kind-filtered already.

* chore(coding-agent): trim narrative comments and redundant test asserts on the digest work

Comments cut to one-line invariant guards; duplicate digest-notation and
content asserts removed where a kept unit pin already covers the same
rendering (labels and notation stay pinned in refinement-outcome-message.test,
source plumbing stays pinned per path). No behavioral change.

* fix(coding-agent): scope the first-turn digest to the turn that delivers it

Review fix on PrimeIntellect-ai#2098 (Bugbot thread 2919fcf3): the first-turn digest was
persisted directly at commit, before agent.prompt ran, so cancelling or
clearing that first turn stripped the turn's captured messages but left the
digest in state and in the session file - resurfacing the emptiness-check
class through the cancel path. The digest now rides the turn's next-turn
delivery records: it persists at message_end like the rest of the turn,
first-turn cancellation strips and restores it with the turn, and the lazy
injection re-delivers on the next committed turn.

* test(coding-agent): event-order pins include the digest custom pair on the first turn

* fix(coding-agent): deliver the first-turn digest on skip-policy turns and re-arm on cancel

Two follow-up review findings on the lazy-injection shape:

- Custom-triggered turns use the skip next-turn-context policy, so a fresh
  session whose first turn was sendCustomMessage(triggerTurn) ran without the
  digest. The pending digest now attaches on every committed first turn; skip
  policy still withholds ordinary pending next-turn messages.
- A cancelled first turn whose digest record was already durable dropped the
  digest from live context without restoring it (restorable records skip
  durable ones). Lazy injection now owns digest delivery uniformly: cancelled
  turns never restore the digest message and instead re-arm the pending flag,
  so the next committed turn re-delivers a fresh one.

* fix(coding-agent): never park the harness digest as pending next-turn context

Review fix on PrimeIntellect-ai#2098 (Bugbot skip-turn-digest-lost-on-failure): a failed commit
parked the undelivered digest in _pendingNextTurnMessages, which skip-policy
custom-trigger turns never drain, and a parked copy plus a re-armed injection
could double-deliver. Parking now filters the digest out and re-arms
_harnessDigestPending instead, keeping lazy injection the single owner of
digest delivery.

* feat(coding-agent): unify synthetic user-channel messages under one bracket grammar

Every machine-injected user-channel message now opens with one grammar:
`[<kind>(: <qualifier>)( <address>)]`, a blank line, then the payload.

- agent_message: `[agent-message from <relationship>:<name>]`; message id and
  endpoint ids live in details only. Detection keys on customType; the old
  header parser stays for persisted legacy transcripts (accept-both, emit-new).
- heartbeat_prompt: `[heartbeat: <schedule> run#<n>]` instead of the raw
  prompt masquerading as the user.
- async_bash_completion: `[bash-done pid:<pid> exit:<code>]`; the standing
  BashHandle inspection hint is gone (it duplicated the system prompt).
- rlm child notices: `[child-exited: no-reply|cancelled child:<name>]` and
  `[child-failed child:<name>]`.
- goal_context: `[goal: continuation|budget-limit|objective-updated]` replaces
  the <goal_context> XML wrapper.
- ipython_state_restored: `[python-state-restored]` label (customType keeps
  its string); goal prose says "Python REPL" instead of "ipython".
- compactionSummary/branchSummary/harness digest prefixes gain
  `[compaction-summary]`/`[branch-summary]`/`[harness-digest]` first lines.

clearQueuedAgentMessages now matches queued actions by the delivered custom
message's customType, with the legacy text parser as fallback.

Fixes RES-1321

* fix(coding-agent): sanitize names interpolated into bracket headers

One owner (sanitizeMessageHeaderValue) strips brackets, newlines, commas,
and ":" from header-interpolated names: an RLM child name can no longer
close the [child-failed]/[child-exited] header early, and an unlabeled
sender named "parent:root" can no longer render as a relationship label.

Addresses the Bugbot/Macroscope review findings on PR 2115.

* fix(coding-agent): keep the heartbeat header on one line for whitespace-bearing schedules

parseAgentCronSchedule's every/in regexes accept embedded newlines, so the
schedule shown in the [heartbeat: ...] header goes through the shared
header sanitizer; the exact expression stays in details.schedule.

* fix(coding-agent): bring the remaining built-in injectors into the bracket grammar

Review sweep of every built-in customType against convertToLlm found three
model-visible kinds still emitting unmarked content:

- autonomous_status -> `[autonomous-status: on|off]` + usage payload
- autonomous continuations -> `[autonomous-continuation]` + prompt, and
  `[autonomous-continuation: gate-failed]` owned by
  buildAutonomousGateFailureContinuation (covers the headless injector too)
- prime-agent.update_complete -> `[update-complete]` + notice
- ipython_state (kernel persisted through compaction) -> `[python-state]`

All other custom types are either grammar-compliant or filtered out of LLM
context by convertToLlm / entry-only. A table-driven pin now walks every
constructor-backed synthetic kind and asserts a grammar-conforming first line.

* test(coding-agent): adapt daemon-mode and model-extension pins to the bracket grammar

The full CI matrix (first run since the PR retargeted main) caught two
expectations outside test/suite still pinning pre-grammar text: the retained-
subagent send now asserts the exact `[agent-message from ...]` prompt, and the
model_select injected-prompt pin carries the `[heartbeat: ...]` header.
* fold the twin agent rosters into one

Family discovery had two entry points: agent_message.list_agents listed the
nuclear family including members that were only on disk, while
agent_observe.list_agents listed live sessions plus passive subagents. Neither
was a superset, so an agent had to call both to know who it could reach.

agent_observe.list_agents is now the single roster. It is built from the family
catalog that send() already uses to resolve receiver_role and receiver_name, so
membership has one definition. Entries carry the distinction as data: a member
with no live session here has no activeSessionId, keeps status "inactive", and
reports only persisted facts, while live members keep their runtime detail.
Every entry carries its relationship, so a roster row maps directly onto a send.

agent_message.list_agents is gone from the skill module and the docs; its host
request now fails with a message naming agent_observe.list_agents, so an old
kernel or a copied old transcript self-corrects.

Fixes RES-1335

* correct the observe read scope and order the roster snapshot

The observe skill doc claimed transcript reads never work on inactive members;
the daemon hydrates a passive child on demand, so only root siblings in another
worker are unreadable.

Build the resident-session map after the family catalog resolves, so a session
that becomes resident during the catalog's disk and supervisor IO is still
reported with its live fields.

* bound roster previews and keep peer rows honest

A saved root's first user message could be any size, so copying it onto every
sibling row made one roster reply carry the opening prompts of every saved
session. Saved roots no longer carry a first message at all, and the previews a
row can carry share one cap with the live latest-message preview.

A peer working in another worker is live, so its row now keeps the active
session id the peer summary reports instead of claiming an active session with
no id.

* say which rows carry a first-message preview
…us command (PrimeIntellect-ai#2206)

* feat(coding-agent): accept autonomous budget flags from the /autonomous command

/autonomous on now accepts the same budget options as the --autonomous-*
CLI flags, so an interactive run uses a user-defined continuation, turn,
token, or time budget instead of always stopping after the default three
continuations.

- Flags: --max-continuations, --max-turns, --max-tokens, --timeout-ms,
  --gate (repeatable), --gate-retries, --gate-timeout-ms; the full CLI
  spellings (--autonomous-max-continuations, ...) work as aliases.
- Both --flag <value> and --flag=<value> forms are accepted, and quoted
  gate commands keep their spaces.
- Only named fields change; unspecified limits and gates keep the
  session/CLI-configured or default values.
- Invalid values, unknown flags, and flags passed to off/status fail with
  a usage error and do not enable autonomous mode.
- The autonomous status text now also reports the time budget and the
  configured gate commands.

* feat(coding-agent): accept unlimited and separator-formatted autonomous budgets

The four /autonomous budget limits now accept an explicit unlimited value (a JSON-safe no-cap sentinel rendered as unlimited in the status), and numeric values accept comma or underscore digit separators so large budgets like --max-tokens 100,000,000,000 type naturally. Gate flags keep strict positive-integer values.

* feat(coding-agent): bound /autonomous runs only by the named budget flags

When any budget flag is named, the unnamed budget limits become unlimited so only the flags the user passes (plus gates) decide when the run stops; /autonomous on --max-tokens 100,000 no longer stops at the default three continuations. With no budget flags at all, the configured or default limits still apply.
Retain caller-owned catch-up environments, lifecycle proofs and bounded transport while adopting upstream retry, catalog and roster changes. Reconcile schema 32 and isolated process fixtures.

Closes #44.
Fixes #55 integration CI failures while retaining ownership and cleanup checks.
Fixes #55 checkpoint finding: retry after journal recovery without clearing ownership, lease, or active cleanup fences.
@rynfar
rynfar merged commit 8069dbd into pylon Sep 11, 2026
20 checks passed
@rynfar
rynfar deleted the upstream/2026-09-10-v0.9.4 branch September 11, 2026 06:33
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.

Prime upstream sync blocked

5 participants