Skip to content

Rebase fork onto upstream 0.9.1 with durable task coordination - #24

Merged
William Zhang (phytal) merged 128 commits into
mainfrom
feat/upstream-0.9.1-durable-coordination
Sep 4, 2026
Merged

William Zhang (phytal) merged 128 commits into
mainfrom
feat/upstream-0.9.1-durable-coordination

Conversation

@phytal

@phytal William Zhang (phytal) commented Sep 4, 2026

Copy link
Copy Markdown

Summary

Updates the Zero-owned Prime Agent fork to the current external-upstream 0.9.1 baseline and reapplies the durable shared task-coordination runtime on top.

  • preserves upstream's newer child startup, deletion, settlement, and terminal-notice lifecycle
  • persists scoped task plans, attempts, handoffs, gaps, evidence, and usage in a shared task graph
  • injects predecessor handoffs into replacement agents and prevents historical claim/question overlap
  • preserves recursive child delegation while returning durable findings to ancestors
  • uses trusted inspection evidence and graph-wide token accounting for convergence
  • supports durable task-owner resume across inline and daemon-hosted runtimes

This is a semantic rebase for zeroset-inc/prime-agent only. It replaces the fork's obsolete 0.7.12 lifecycle portions with the current upstream implementations while retaining the Zero-specific coordination layer.

Validation

  • npm run check
  • focused coding-agent suites: 263 passed
  • upstream recursion suite: 113 passed (included above)
  • full Prime Python runtime suite: 281 passed, 1 skipped
  • git diff --check

Supersedes the 0.7.12-targeted coordination PR #23.

No-Ticket: internal fork synchronization and runtime hardening follow-up.

Seth Karten (sethkarten) and others added 30 commits August 14, 2026 14:03
…rimeIntellect-ai#1387)

* feat(daemon): add rlm spawn ledger module

* feat(daemon): record rlm spawns, renames, and deletions in the spawn ledger

* test(daemon): cover rlm spawn ledger semantics, seeding, and wiring

* fix(daemon): harden rlm spawn ledger reads, writes, and depth checks

- enforce spawn invariants at append (never write what the reader rejects)
- verify depth monotonicity only between ledger-known depths; drop+log a
  contradictory edge instead of failing the whole family
- tolerate one torn final line (no trailing newline) on read; truncate it
  before the next append; interior malformed lines stay fail-closed
- skip v:1 records with unknown ops (forward compat); v!==1 fails loudly
- shared registry seed source (header-id read, tolerant LWW registry parse)
- canonicalSessionPath for all path keys; realpath the sessions dir
- flush() to await durable appends; document multi-writer O_APPEND reality

* feat(daemon): serve supervisor siblings from the spawn ledger

- supervisor holds its own ledger instance; the three catalog.siblings
  call sites (named create, saved rename reservation, saved-name check)
  now read ledger-backed siblings
- offline rename_saved_session at the supervisor appends a ledger rename
- worker spawn appends are awaited before admission returns (no self-heal
  exists for a lost spawn record)
- ledger delete is appended after the registry tombstone succeeds

* test(daemon): cover ledger hardening and supervisor siblings wiring

* fix(daemon): await and self-heal the rlm ledger delete

- recordRlmSubagentDeletion awaits the ledger delete (tombstone-first
  ordering kept)
- a retried deletion over an existing tombstone finishes a ledger delete
  lost to a crash instead of leaving a permanent ghost live edge
- TODO at the spawn-append failure log: revisit failing admission once
  the ledger is the messaging authority

* fix(daemon): address PR PrimeIntellect-ai#1387 bot findings on the rlm spawn ledger

- await the ledger rename at the active-session rename write point so a
  rename is durable before its name reservation is released
- sessionRow strips header-claimed parentSessionPath/rlmDepth: topology
  in ledger rows is exclusively ledger-sourced (fork headers no longer
  leak a parent onto root rows)
- seeding is atomic: collect all records, publish via temp file + rename;
  an interrupted seed leaves no ledger file and re-seeds next time; a
  racing live append wins and suppresses the seed
- siblings() falls back to a lone root-shaped row when the target's edge
  was reconciliation-dropped (parent file gone) but the child file exists
- drop the unused public rlmLedgerFamily/rlmLedgerSiblings wrappers on
  AgentDaemon (tests use the ledger via daemon internals)

* fix(daemon): byte-exact torn-tail repair and no-clobber seed publish

- truncateTornTailSync works on raw buffers with byte offsets: string
  indices diverge from byte offsets on multi-byte UTF-8 names, so the
  old truncate could cut into a preceding valid record and poison the
  ledger; also hardened cross-process with a byte-stable double read
  and same-fd fstat/ftruncate (residual race stays documented)
- seed publish uses linkSync (EEXIST => live append wins, seed dropped)
  instead of existsSync+renameSync, whose clobbering rename could lose
  a racing append with no self-heal

* fix(daemon): bound the repair-path read and add a link-less seed publish fallback

- readAllSync and the torn-tail repair enforce RLM_LEDGER_MAX_BYTES
  before any file-sized allocation, throwing the same loud bounded-read
  error as replaySync (outside the swallowing repair try-block)
- seed publish falls back to check-then-rename when linkSync fails with
  anything but EEXIST (filesystems without hard links); EEXIST still
  means the racing live append wins; fallback path is logged

* fix(daemon): never publish a seed that exceeds the ledger read bounds

A seed past RLM_LEDGER_MAX_BYTES/RECORDS would publish a ledger every
replaySync refuses to read. Check the single serialized payload against
both bounds before publishing and skip seeding entirely (flat families,
the documented degradation mode) — logged, not thrown, so the guard
cannot recreate the seedAttempted-sticks failure shape.
…dger (PrimeIntellect-ai#1390)

* feat(daemon): add per-child rlm subagent display files

* feat(daemon): serve passive rlm subagents from the ledger and stop writing registries

* refactor(daemon): drop the unconsumed catalog siblings walk

* test(daemon): cover display files and legacy registry metadata fallback

* refactor(daemon): share rlm subagent metadata field spreading

* refactor(daemon): derive the legacy registry entry type from the passive entry

* fix(daemon): resolve deleted-child paths for job cleanup and harden display writes

* fix(daemon): fail admission on lost spawn records and never clobber-publish seeds
…alogs (PrimeIntellect-ai#1445)

Commits the current `npm run generate-models` output. 92 models added,
33 removed (net 1163 -> 1222) across 14 providers. Highlights: DeepSeek
V4 Flash/Pro dated snapshots (workers-ai, fireworks, huggingface),
gemini-3.7-flash (google, copilot), grok-4.5/4.6 and kimi-k3 on copilot,
glm-5.3 on opencode-go, and the GPT-5.x family on cloudflare-ai-gateway;
retired claude-opus-4-1 aliases, gemini-2.0-flash, and ling-3.0-flash
routes.

The build regenerates this file from live catalogs anyway; committing
keeps the checked-in file from drifting further and keeps local keyless
test runs representative.
…tellect-ai#1388)

* feat(coding-agent): add ctrl+j toggle to expand edit diffs independently of tool output

* feat(coding-agent): make ctrl+j sole owner of edit-diff visibility and hint the summary line

* fix(coding-agent): stop duplicating the ctrl+j hint on collapsed edits

Collapsed built-in edits showed the hint twice: on the edit header and on
the summary line. The header hint now renders only when the diff is
expanded (where no summary line exists); collapsed rows keep the single
hint on the summary line.

* fix(coding-agent): keep the ctrl+j hint visible while no summary line renders

Gating the header hint on expansion assumed the collapsed summary line
always carries the cue, but that summary only mounts once a successful
result with a countable diff lands. During the preview-only window and on
error rows the diff was expandable with no visible hint.

The header now keeps the hint whenever the summary line is absent
(mirroring its mount condition) and yields it once the summary renders,
so exactly one hint is visible in every state.

* fix(coding-agent): advertise the collapse key on expanded ipython diff headers

* fix(coding-agent): move the expanded-diff collapse hint to the truncated cell header
…ff (PrimeIntellect-ai#1392)

* feat(coding-agent): add ctrl+j toggle to expand edit diffs independently of tool output

* feat(coding-agent): make ctrl+j sole owner of edit-diff visibility and hint the summary line

* feat(coding-agent): always show the edit summary line and render the diff inline beneath it

* fix(coding-agent): suppress edit summary on failed edits, unify summary path formatting, trim dead exports

* fix(coding-agent): color the edit header as error when execution fails after a successful preview

* fix(coding-agent): always show the ctrl+j hint on edit summary rows

The edit-diff hint was threaded through showExpandHint, the flag that
restricts the ctrl+o hint to the latest tool row. Since the agent almost
always runs more tools after an edit, edit rows stopped being "latest"
immediately and the ctrl+j hint effectively never appeared.

The ctrl+j hint now renders on every edit summary row, matching the
always-visible thinking (ctrl+t) and agent-message (ctrl+p) hints. The
latest-row gating still applies to the ctrl+o hint on the header line.

* fix(coding-agent): stop duplicating the ctrl+j hint on collapsed edits

Collapsed built-in edits showed the hint twice: on the edit header and on
the summary line. The header hint now renders only when the diff is
expanded (where no summary line exists); collapsed rows keep the single
hint on the summary line.

* docs(coding-agent): correct the ctrl+j hint comment to match showHint

The hint renders on every tool row, but within a row only on the last
file's summary line — the comment claimed every summary row.

* fix(coding-agent): keep the ctrl+j hint visible while no summary line renders

Gating the header hint on expansion assumed the collapsed summary line
always carries the cue, but that summary only mounts once a successful
result with a countable diff lands. During the preview-only window and on
error rows the diff was expandable with no visible hint.

The header now keeps the hint whenever the summary line is absent
(mirroring its mount condition) and yields it once the summary renders,
so exactly one hint is visible in every state.

* fix(coding-agent): advertise the collapse key on expanded ipython diff headers

* fix(coding-agent): move the expanded-diff collapse hint to the truncated cell header

* fix(coding-agent): stabilize summary-line truncation across the ctrl+j toggle and reuse countChangedLines
PrimeIntellect-ai#1367)

* fix(coding-agent): say 'type to search sessions' in the agents view splash

* refactor(coding-agent): drop the dead 'type to start' splash fallback

* refactor(coding-agent): change the splash hint fallback in place instead
…ntellect-ai#1449)

* refactor(daemon): extract a safe lease-renew loop for the shutdown admission

RenewableRegistryRecord owns the shutdown-admission lease-renew loop: the
unref()'d interval, a single-flight refresh shared by timer-fired and
direct assertOrRenew calls (whose rejection reaches direct awaiters), a
stopped/lost re-check inside the guarded section so a renew that loses a
race with stop() can never write, and disposal. This replaces
DaemonShutdownAdmission's hand-rolled loop, which had a latent race:
direct assertOrRenew calls bypassed the single-flight slot, so release()
could return while a renew was still queued on the registry guard and the
record could be rewritten after removal. Public semantics (assertOrRenew,
release awaiting in-flight refresh, lease timings) are unchanged.

* fix(daemon): move the supervisor registry out of $TMPDIR

macOS com.apple.bsd.dirhelper deletes files older than three days under
$TMPDIR daily at 03:35. The supervisor ownership registry (owner.json/
scope.json, startup fences, the shutdown admission) lived there only
because it rode along with the socket directory, whose location is forced
by the 104-byte sun_path limit — a constraint JSON records do not share.
Any supervisor alive past three days lost its record and wedged
permanently with supervisor_generation_stale on every command.

The registry now defaults to ~/.prime/supervisor-owners: durable, global
per user (ownerConflicts must see every daemon on the box, so it must not
shard per agent dir), with the PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_
REGISTRY_DIR override unchanged. All registry tenants move together.

No record migration: records are per-process-lifetime. A running old-build
daemon keeps its tmpdir records and works until restarted; its leftover
records self-clean via dirhelper within three days. During the overlap a
new-build daemon sees an empty registry and fails at socket bind instead,
which surfaces like any bind conflict and unwinds acquire cleanly.

* fix(daemon): disambiguate the two ownership-lost error messages

Both daemon-supervisor.ts (this.ownership undefined: never acquired or
already released) and DaemonSupervisorOwnershipLostError (record on disk
missing or replaced) emitted the identical 'no longer owns its registry
entry' string, making the failure mode impossible to tell apart from the
message alone. Each message is now distinct and appends the socket path,
the registry dir where available, and the remedy (restart the daemon;
sessions are preserved). Both keep code: supervisor_generation_stale.

* test(daemon): cover the release-overtakes-renew admission race

Pin the RenewableRegistryRecord hardening at its consumer: a direct
assertOrRenew queued behind a held registry guard while release() runs
must reject with the admission-lost error and must not rewrite
shutdown-admission.json after removal.

* fix(daemon): read the legacy $TMPDIR registry during the move window

A new-build CLI could not see a still-running pre-move daemon's owner
record: persistDaemonStartupFenceFromOwner scanned only the new (empty)
registry and threw AFTER prepare_update_restart had already drained and
fenced the old supervisor, whose updateRestartPhase never leaves
'prepared' without a shutdown — wedging the standard upgrade path.
The worker-auth validation (assertDaemonSupervisorOwnerCurrent) had the
same blind spot for a new-build worker under an old-build supervisor.

Owner-record READS now fall back to the legacy tmpdir location
(read-only, no abandoned-dir reclaim — old-build daemons own that
location's lifecycle; unlocked relative to old-build writers, acceptable
because records are rename-atomic). Writes, including fences, go only to
the new registry. The fallback is resolved structurally where registryDir
is resolved: it exists iff no env override and no explicit registryDir,
so tests with explicit registries can never leak reads to the machine's
real tmpdir. Remove after one release.

acquire's conflict scan keeps no fallback: real socket contention is
still caught at bind by the socket lease.

* test(daemon): cover the worker-auth legacy fallback; filter legacy fence matches

Add the missing test for assertDaemonSupervisorOwnerCurrent's legacy
read (a pre-move owner claim validates through an injected legacy dir and
never falls back for an explicit registry), replace the silent
processStartId early-out in the fence-via-legacy test with a visible
assertion, and filter legacy fence matches by the caller-held token/pid
so stale legacy leftovers cannot produce a spurious multiple-owners
failure.
…fact paths (PrimeIntellect-ai#1450)

* refactor: canonical session artifact path helpers in session-manager

* fix(daemon): drop a deleted rlm child's nested artifact dir, keep the transcript

* feat(daemon): bounded per-parent reaper for tombstoned rlm child artifact dirs

* fix(daemon): re-sweep artifact dirs resurrected by teardown snapshots

* test(daemon): artifact-dir deletion, best-effort rm, reaper bounds, path helper equivalence

* fix(daemon): guard degenerate session-file names in the artifact reaper

Also sweep every tombstoned edge on retry-heal, skip the chmod rm-failure test as root, and pin the depth-2 transcript boundary.

* docs: trim redundant policy comments to repo discipline

* fix(daemon): re-sweep artifact dirs even when child teardown throws

* fix(daemon): never let jobs-store errors mask a child deletion

Pre-round-2 a cancel throw on the healthy path propagated as a deletion
failure. Swallowing it is deliberate: both tombstones are durable by this
point, the reaper and retry-heal converge on leftover state, and a deletion
should not fail over jobs-store bookkeeping.

* revert(daemon): drop the orphan-artifact reaper, keep the deletion hook

New children never orphan artifact dirs once the deletion hook exists; the pre-fix garbage is a one-time mess not worth a permanent mechanism in the deletion path. The degenerate-basename guard keeps direct coverage via deleteSessionArtifacts.

* fix(ai): stabilize Cloudflare gateway model test

---------

Co-authored-by: Seth <seth@primeintellect.ai>
…imeIntellect-ai#1479)

* Show subagent reasoning effort in agents view

* Fix scoped subagent effort labels

* Fix agents view effort regression coverage

* Hide disabled subagent effort labels

* Prioritize subagent model in agents view

* Preserve legacy subagent effort labels
* chore(ai): regenerate the model catalog

fixes ENG-5286

* fix(coding-agent): repoint the Cerebras default after zai-glm-4.7 left the catalog

Also finish the Cloudflare gateway Claude id rename in the handoff test.

fixes ENG-5286

* Keep handoff models aligned with catalog

---------

Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
Co-authored-by: Seth <seth@primeintellect.ai>
…-ai#1279)

* fix(coding-agent): resume interrupted work after auto-compaction

A threshold compaction that intentionally stopped a mid-task tool loop
never resumed the loop when the compaction itself failed or was skipped
(resumeAfterFailure only fired for requested compactions). And with an
active goal, a threshold stop landing after an assistant text turn left
the goal stuck active forever after a successful compaction, because
nothing re-entered the loop and continue() cannot resume from an
assistant-last context.

Widen the failure-resume gate to threshold compactions (overflow stays
excluded on purpose) and queue the goal continuation as a session input
before compaction, mirroring the autonomous-mode compensation, with the
goal taking exclusive priority over autonomous continuation to match
_getContinuationMessages.

* fix(coding-agent): withdraw the queued goal continuation on cancelled compaction

The goal continuation is admitted as a session input before a threshold
compaction runs, so when the user cancelled that compaction the finally
block's session-input pump still delivered it and restarted the agent.
Cancel the queued action on the aborted branch only (skip/fail must keep
delivering it), roll back the queue-time continuationsUsed increment only
when an action was actually cancelled so a stale marker cannot corrupt
completed-goal bookkeeping, and trim the previously added comments down
to the genuinely subtle rationale.

* test(coding-agent): make compaction continuation fixture deterministic

* test(coding-agent): remove ineffective faux usage overrides

* test(ai): use stable Cloudflare Anthropic model id

---------

Co-authored-by: Alex Zhang <alex.lx.zhang@gmail.com>
Co-authored-by: az <altzhang@mit.edu>
…rimeIntellect-ai#1188)

* prompt: disincentivize blocking sleep in kernel, bash cells, and bash tool

Add a control-loop rule to the RLM system prompt forbidding blocking
sleep patterns across all execution modes: time.sleep() loops in Python,
sleep in %%bash cells, and sleep in the bash tool. A blocked cell or
command holds the turn open, wastes wall-clock, and prevents user
interaction. The agent should instead kick off work, record its handle,
end the turn, and check results later.

Expands PR PrimeIntellect-ai#1034 which only covered time.sleep() in Python to also
cover sleep in bash cells and the bash tool.

closes PrimeIntellect-ai#1034

* prompt: bound total wait instead of poll interval

'Short intervals' permitted sleep(5)-loops that block the turn just as
long as one big sleep — the harm is total blocked wall-clock, not
interval size. Allow only a single bounded wait when completion is
imminent; any longer wait or any sleep loop means end the turn.

* prompt: make long-running RLM work nonblocking

* prompt: add proactive progress and clear technical prose

* prompt: limit user progress updates to root agents

* prompt: emphasize parallel work and outcome updates

* prompt: clarify safe async shell usage

---------

Co-authored-by: Seth <seth@primeintellect.ai>
…Intellect-ai#1316)

* feat(coding-agent): add async bash() to IPython kernel

Add an async bash() function to the RLM bootstrap code in ipython.ts
that uses asyncio.create_subprocess_exec to run shell commands without
blocking the kernel event loop. Unlike %%bash cells (which block the
kernel until the command finishes), await bash('...') keeps the kernel
responsive to interrupts and other messages while the process runs.

Supports optional timeout (raises TimeoutError) and cwd parameters.
Returns a _PrimeAgentBashResult with stdout, stderr, and returncode.

Update the RLM system prompt to prefer await bash('...') over %%bash
cells.

* fix(coding-agent): bound async bash subprocesses

* fix(coding-agent): fail closed for bash on Windows

* fix(coding-agent): render async bash output

* fix(coding-agent): clean completed bash sessions

* fix(coding-agent): validate bash output bounds

* fix(coding-agent): bound rendered bash output

* fix(coding-agent): escalate bash group cleanup

* fix(coding-agent): preserve bounded UTF-8 output

* docs(coding-agent): qualify async bash platforms

* fix(coding-agent): clarify bash truncation units

* refactor(coding-agent): keep async bash POSIX-only

* docs(coding-agent): guide background bash tasks

closes PrimeIntellect-ai#1034

* docs(coding-agent): simplify background bash guidance

* fix(coding-agent): preserve goal continuation after compaction

* docs(coding-agent): explain continuation deduplication

---------

Co-authored-by: Seth <seth@primeintellect.ai>
…-ai#1505)

* chore: remove redundant comments

* chore(tui): remove redundant export comments

* chore(coding-agent): remove redundant example comments

* chore(ai): remove redundant comments

* chore(coding-agent): remove obsolete core comments

* chore(coding-agent): remove redundant test comments

* chore(coding-agent): remove redundant test comments

* chore(coding-agent): remove redundant rpc test comments

* chore(coding-agent): remove redundant CLI comments

* chore(agent): remove redundant comments

* chore(tui): remove redundant source comments

* chore(coding-agent): remove redundant mode comments

* chore(coding-agent): remove redundant mode comments

* chore(coding-agent): remove redundant test comments

* chore(ai): remove redundant source comments

* chore(coding-agent): remove redundant session comments

* chore(coding-agent): remove redundant core comments

* chore(coding-agent): remove redundant test comments

* chore(tests): remove redundant test comments

* chore: remove unused internal helpers

* docs: restore public API contracts

* chore(coding-agent): restore invariant documentation

* chore(coding-agent): clarify agent family contracts

* style(coding-agent): format restored contracts

* fix: restore intentional catch explanations
…raw \n (PrimeIntellect-ai#1522)

* fix(coding-agent): let a raw \n insert a newline instead of toggling edit diffs

0.7.3 added app.edits.expand with the default ctrl+j (PrimeIntellect-ai#1388). The custom
editor dispatches app actions before the base editor handling, and a raw
"\n" byte decodes as ctrl+j — but that byte is exactly what Shift+Enter
sends in terminals that map it to a literal newline (the mapping our own
keys.ts comments recommend for Ghostty), and ctrl+j is itself a
traditional newline key. Since 0.7.3, that input toggled edit diffs and
Shift+Enter stopped producing newlines outside kitty-protocol terminals.

Skip app-action dispatch for the raw "\n" byte in the editor and in the
subagent-line key handler so it reaches the newline handling; ctrl+j
still triggers the toggle via the kitty CSI-u encoding, which is
unambiguous.

* Trim the collision comments to one line each
* feat(coding-agent): auto-stash editor draft when opening the agents view and restore it on session reopen

* fix(coding-agent): skip agents-view auto-stash for whitespace-only drafts

* fix(coding-agent): keep init statuses visible when restoring an on-open stash

* test: stub restorePromptStashOnOpen in startup-run fakes and bound the hook spin

run() now calls this.restorePromptStashOnOpen() right after init(). The
Prime CLI onboarding tests drive the real run() on plain fake objects,
so the missing method made run() reject immediately: three tests failed
and two spun forever in `while (!fakeThis.admitPendingStartupPrompts)
await Promise.resolve()`, hanging the CI shard until the job timeout
cancelled it with no output.

Stub the method in createStartupRunHarness and bound both spin loops so
a future regression fails the test instead of hanging the shard.

* test(coding-agent): trim prompt stash coverage

---------

Co-authored-by: Seth <seth@primeintellect.ai>
…ct-ai#1510)

* feat(coding-agent): let subagents be spawned with an explicit reasoning level, fixes ENG-5301

* fix(coding-agent): neutral wording for the subagent thinking option in the rlm prompt

* chore(coding-agent): drop a redundant doc comment

* chore(coding-agent): drop the remaining name-restating doc comments in rlm-runtime

* chore(coding-agent): simplify subagent thinking validation

* fix(coding-agent): preserve early thinking validation

* refactor(coding-agent): centralize thinking levels

* refactor(coding-agent): keep thinking validation local

* test(coding-agent): retain thinking option boundaries

---------

Co-authored-by: Seth <seth@primeintellect.ai>
…t-ai#1520)

* fix(coding-agent): normalize daemon socket paths at every entry point, fixes ENG-5303

* fix(coding-agent): address review findings on socket-path normalization

- Defer --daemon-socket normalization until after --cwd applies so relative
  socket paths resolve against the requested working directory
- Migrate legacy raw-spelling worker-descriptor namespaces to the canonical
  directory on supervisor construction so existing workers stay adoptable
- Consolidate the remaining private normalizers (package-manager-cli,
  daemon-update-restart) onto the shared normalizeSocketPath
- Wait for killed test supervisors to exit before removing their directories
  to fix ENOTEMPTY flakes in CI

* fix(coding-agent): harden descriptor-namespace adoption and test cleanup

- Adopt a raw-spelling descriptor namespace only after the socket-path lease
  and registry ownership are held, so a rejected startup can never move a live
  supervisor's directory
- Skip unreadable JSON entries per file when identifying a namespace instead
  of aborting the whole scan
- Drop migration-era vocabulary from the helper names and test titles
- Disable the Node compile cache for spawned test supervisors and tolerate
  cleanup races so worker processes exiting late cannot fail the suite

* refactor(coding-agent): drop the descriptor-namespace adoption machinery

Normalization at the entry points prevents namespace forks going forward;
descriptors written under an old raw-spelling key are healed per-field on
load only when they share the directory. Cross-directory adoption kept
accreting ordering hazards (live-daemon rename races, persisted-config
staleness) disproportionate to its value, so a daemon that previously ran
on a non-canonical spelling simply starts fresh namespaces; saved sessions
are unaffected (they live in the session catalog, not the descriptor dir).

* docs(coding-agent): align the changelog with the simplified scope

* chore(coding-agent): trim excessive comments

* fix(coding-agent): normalize the early-launch socket key and drop an incidental test

maybeStartDaemonEarly memoized ensure attempts under the raw --daemon-socket
spelling while main uses the normalized one, so equivalent spellings could
run two concurrent spawn attempts for one daemon. The early kick now derives
the same canonical spelling (resolving relative paths against --cwd).

* chore(coding-agent): simplify socket normalization coverage

* Address final review feedback

---------

Co-authored-by: Seth <seth@primeintellect.ai>
* ci: require a linked Linear ticket or an explicit opt-out on pull requests

* ci: drop the No-Ticket opt-out; every PR links a Linear ticket
…1540)

* fix(coding-agent): bound persistent kernel snapshots

* fix(coding-agent): simplify bounded kernel snapshots

* fix(coding-agent): cap individual snapshot variables

* fix(coding-agent): prune oversized state on compaction

* fix(coding-agent): stop snapshots at aggregate limit

* fix(coding-agent): preserve bounded snapshot packing

---------

Co-authored-by: Seth <seth@primeintellect.ai>
* docs: add Trendshift badge

* docs: separate the Trendshift badge

---------

Co-authored-by: Seth <seth@primeintellect.ai>
Fixes ENG-5388

Co-authored-by: Seth <seth@primeintellect.ai>
* feat(acp): harden resident session lifecycle

* test(daemon): align recovery fixtures with durable context

* fix(acp): validate durable host settings

* fix(daemon): clean up failed resident resources

* fix(daemon): recover owned workers from fresh context

* fix(acp): verify authoritative completion state

* fix(daemon): refresh requested child roster

* fix(acp): read child roster without reattaching

* fix(daemon): preserve telemetry opt-out on recovery

* fix(daemon): scope resident path conflicts

* test(acp): remove runtime publish hook

* fix(acp): drain admitted updates on close

* fix(daemon): merge fresh supervisor defaults

* fix(acp): await autonomous lifecycle settlement

* fix(acp): preserve daemon input fences

* fix(acp): serialize terminal lifecycle turns

* fix(acp): reacquire daemon fences after reconnect

* fix(acp): fail closed across abort cuts

* fix(acp): resume queued input after pause

* fix(acp): invalidate pending pumps on pause

* fix(acp): preserve pause ownership through supervisor

* fix(acp): fail closed on pause cleanup loss

* test(daemon): initialize pause ownership harness

* fix(acp): fence detach pause cleanup

* fix(daemon): preserve detach-all cleanup

* test(daemon): initialize worker pause registry

* fix(daemon): reacquire pauses across release

* fix(daemon): clear stable detach selectors

* fix(daemon): retain detach fences on attach failure

* refactor(daemon): keep descriptor validation private

* docs(changelog): describe ACP lifecycle fencing

* fix(acp): settle restart and failed close lifecycle

* fix(acp): serialize cancel after failed close

* fix(acp): preserve lifecycle reconciliation ordering

* fix(acp): settle deleted child runtimes

* test(acp): consolidate child deletion regressions

* fix(acp): rearm settled child deletion

* fix(acp): preserve recursive terminal settlement

* test(acp): update terminal notice regression
…e next turn (PrimeIntellect-ai#800)

* fix(coding-agent): queue ACP prompts behind in-flight work

* fix(acp): keep queued-prompt semantics through the resident lifecycle merge

Renumber owned prompt cancellation to schema revision 20 (17-19 landed on
main), adapt compatibility gating to the requirements-array form, and return
stopReason "cancelled" for a prompt parked behind the terminal lifecycle when
a cancellation drops it before it starts.

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
* feat(coding-agent): add generic MCP runtime

* fix(coding-agent): harden MCP lifecycle and overrides

* fix(runtime): close cancelled host request comms

* fix(runtime): preserve MCP SDK result aliases

* fix(runtime): preserve synchronous kernel shutdown

* fix(runtime): allow MCP close retry after cancellation

* feat(coding-agent): manage MCP servers from CLI and TUI

* fix(coding-agent): preserve inherited stdio env names

* fix(coding-agent): reserve built-in MCP identities

* fix(coding-agent): allow cleanup of reserved MCP entries

* test(coding-agent): update MCP command hint

* fix: address MCP reload and cleanup findings

* fix(coding-agent): advertise generic MCP connections

* fix(coding-agent): keep MCP command results visible

* fix(runtime): surface safe stdio startup diagnostics

* fix(runtime): preserve startup errors through cleanup

* fix(coding-agent): drop stored MCP credentials when a server is removed or replaced

* fix(coding-agent): scope credential drops to generic servers and keep quoted empty argv tokens

* docs(coding-agent): document the removed catalog-name override as a breaking change

* fix: close kernel MCP servers during shutdown

* docs: preserve changelog sections after main merge

* fix: harden MCP shutdown boundaries

* fix: bound graceful kernel shutdown

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
…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>
…ursor outbox (PrimeIntellect-ai#1957)

* refactor(coding-agent): reschedule agent-trace uploads through a disk-cursor outbox

Upload intent and uploaded-content cursors persist per session file in
agent-traces-outbox.json; a once-per-process startup catch-up uploads
whatever a previous process never finished and prunes cursors of deleted
files. Unchanged sessions are never re-uploaded. 429s return immediately
and reschedule via the controller instead of sleeping in-request, and
session disposal no longer knows trace uploads exist.

Linear: ENG-5838

* fix(coding-agent): make the trace outbox per-entry, durable at persist, and process-exit safe

One entry file per session (path-hashed) replaces the single-map file: concurrent writers cannot lose cursors and a bad read costs only its own entry. The pending marker is written synchronously at first persist. A failed cursor write after a successful PUT returns a retryable failure. 429 Retry-After is honored on the next scheduled cycle. All upload timers are unref-ed so telemetry never holds the process open.

* fix(coding-agent): retry failed intent markers and cap Retry-After at the timer maximum

A failed pending-marker write no longer marks the session as locally managed, so the next persist retries it. A Retry-After beyond Node's ~24.8-day setTimeout maximum is capped there instead of overflowing to an immediate retry loop.
…imeIntellect-ai#1985)

A finished top-level agent stayed in the agents view Running section as
"classifying" forever: the roster's activity axis holds an idle session
at "working" until its summarizer verdict is current, but the summarizer
only notified (and thereby scheduled a roster flush) when the verdict
TEXT or task state changed. A settle that produced the same text at a
new message count updated the verdict's currency without ever
republishing the row, freezing rosterStatus at "running" with the
"classifying" label.

The change gate now treats an idle settle that refreshes
basedOnMessageCount as a status change; working refreshes with unchanged
text stay quiet. The dead agentStatusChanged export duplicated the old
two-field predicate (with no production caller) and is deleted.

Diagnosis credit: Vincent Bailly (VincentBailly#8), whose
consumer-side workaround this producer fix supersedes.

RES-1252
…rom the RLM spawn ledger (PrimeIntellect-ai#1987)

* refactor(coding-agent): extract the append-only event-log substrate from the RLM spawn ledger

One EventLog owns the shared crash-safety mechanics: single O_APPEND writes with optional fsync, bounded fail-closed reads, torn-final-line tolerance on replay, and repair-on-append (byte-offset truncate for an unparseable tail, newline completion for a parseable one). RlmSpawnLedger keeps spawn semantics only; its public API and test suite are unchanged.

* fix(coding-agent): keep the union of ledger safety behaviors in the event-log substrate

Reject unserializable events with a TypeError before any byte (including repair) is written; truncate EVERY unterminated tail instead of newline-completing a JSON-parseable one, which would hand a line a strict consumer parser rejects to every later replay as fail-closed interior poison; read through a bounded descriptor so a concurrent grow between size check and read cannot bypass maxBytes.
… residency with durable wake (PrimeIntellect-ai#1967)

* fix: classify sessions with armed heartbeats as idle, not running

An armed heartbeat between firings is residency, not work, so it no
longer counts as a running signal anywhere: the roster classifier, the
unified agents-view classifier, the ancestor-propagation overwrite, and
the subagent count projections all drop the heartbeat clause. A firing
heartbeat still lands in Running through its executing turn, and
hasActiveHeartbeat stays on the wire for badges and labels.

Idle rows with an armed heartbeat now label the next fire time
(heartbeat - next 5m) when the aggregate knows it. Paused-only
heartbeats, previously invisible, render a dimmed heartbeat badge
without affecting the section. The delete confirmation warns when the
targeted session or subagent still has an armed heartbeat; the daemon
already cancels scheduled jobs in both delete paths.

ENG-5847

* fix: passivate heartbeat sessions normally and wake them when jobs come due

An armed heartbeat no longer pins a session resident forever. The
hasRegisteredHeartbeat veto is deleted from the idle-eviction and child
passivation policies (and from the empty-draft detach rule): heartbeat
sessions follow the one existing idle rule (idleEvictionMinutes,
default 90m) like every other session.

The schedule survives passivation through a single wake owner split by
residency: workers keep owning all firing and delivery for resident
trees (their scheduler already revives passive children on due jobs),
and the supervisor owns exactly one new concern - waking a session tree
that is resident nowhere. It recomputes wake timers from durable truth
(the saved-session catalog plus each session's scheduled-jobs artifact)
at boot and on every heartbeat/residency change, and on fire relaunches
the root worker through the same create path clients use; the fresh
worker's scheduler then runs the due job with the existing catch-up
semantics. Workers now also register passive descendants' scheduled-job
artifacts when a root becomes resident, so a relaunched tree schedules
its passivated children's heartbeats without hydrating them.

Passivated sessions keep their heartbeats visible: the supervisor's
heartbeats_list appends passive scheduled jobs, roster rows keep their
registration marks across eviction, and the agents-view badge aggregate
matches jobs by stable session id and file, not just the stale active
id.

ENG-5846

* fix: fence the scheduled-session wake to public sessions and ledger topology

Review round on the residency half:

- Client-owned schedules now die with the registration, exactly like
  their roster rows: stopping an ephemeral worker cancels the scheduled
  jobs of its root and ledger descendants, so a private session is never
  listed in heartbeats_list or relaunched by the wake. Public sessions
  are unaffected because heartbeat_set/cron_add promote owned sessions
  before their jobs land.
- The wake enumeration now reads topology from the RLM spawn ledger's
  family (roots plus ledger descendants, fork headers stripped) instead
  of the saved-session catalog, which missed nested subagent transcripts
  entirely and misassigned passivated forks to their former parent
  through header lineage.
- The wake schedule stays disarmed while an update restart is being
  prepared instead of spinning a zero-delay timer on overdue jobs; the
  failed-preparation transition re-arms it once, and a successful
  restart re-arms at the next boot.
- One corrupt descendant artifact no longer strands the remaining
  descendants' registrations in a fresh worker.
- Managing a passivated heartbeat now works: heartbeat_manage resolves
  the job through the same durable enumeration and mutates its
  scheduled-jobs store directly, without waking the session.

ENG-5846

* chore: slim PR comments and merge overlapping heartbeat pins

Comment pass over both halves: every added comment either deleted or cut
to one load-bearing line. Test pass: the paused/active heartbeat eviction
test duplicated the sweep pin's heartbeat eviction (the distinction has
no code path anymore) and the next-fire label pin merged into the
heartbeat aggregation test it shares fixtures with.

* fix: key the ephemeral cancel by persisted ids and fence it against the wake

The ephemeral-stop cancel derived descendant session ids from filenames,
while scheduled-job artifacts key on the persisted session id: a child
whose id differs from its file name kept armed jobs past its owner's
departure. The cancel now walks the same ledger family() rows the wake
enumeration uses, so both sides share one id resolution.

The cancel also ran after the worker left the worker map, leaving a
window (or, after a swallowed failure, forever) in which the tree looked
non-resident with active jobs and could be woken publicly. It now runs
before the map removal - every enumeration observed during the awaited
cancel still sees the tree covered - and a failed cancel parks the root
in pendingEphemeralCancels: enumeration retries the cancel first and
keeps the tree excluded until it lands.

ENG-5846

* fix: reach passive scheduled jobs from unscoped cron commands; drop stale cancel intents

Unscoped cron_cancel probed only resident workers, so a passivated
session's still-firing job could not be stopped without reopening the
session; it now falls back to the passive enumeration and cancels
through the per-job store API, broadcasting so the wake timer drops it.
Unscoped cron_list merges the same passive jobs after the resident
sweep (worker copies win). With these, no unscoped job surface in the
protocol misses passive sessions.

A parked ephemeral-cancel intent also outlived its premise: reopening
the tree after a failed cancel would let the enumeration retry destroy
the fresh worker's new schedules. The retry now drops the intent when a
worker covers the tree again - the store is worker-owned from that
moment and resident trees are excluded from enumeration anyway.

RES-1250

* fix: read each passive session's scheduled-jobs artifact in isolation

One malformed scheduled-jobs.json aborted collectPassiveScheduledJobs
entirely, so every other passive session's jobs vanished from listings
and their wakes never armed. Ref: RES-1250

* fix: keep the ephemeral stop tombstone as the durable cancel intent

A failed ephemeral cancel was parked only in memory, so a supervisor
restart lost the intent and the next boot recovered the private tree's
jobs wake-eligible. The stop now deletes the persisted tombstone only
after the cancel settles; boot finishes the cancel from the tombstone,
with the same covered-tree exemption that protects a reopened session's
new schedules. Ref: RES-1251

* fix: include passive terminal jobs in an inclusive unscoped cron_list

Resident workers honor includeInactive but the passive merge only
contributed active/paused jobs, so passivation silently shrank an
inclusive listing. The wake path keeps considering active jobs only.
Ref: RES-1250

* fix: revalidate ownership inside the ephemeral cancel's destructive walk

promoteOwnedWorker can clear ownerClientId while the cancel awaits the
ledger family read, so a successful promotion had its now-public
schedules cancelled out from under it. The guard runs in the same
synchronous turn as the store walk; promotion commits are synchronous
single-mutation blocks, so a promote lands either wholly before the
guard (cancel skipped) or wholly after the walk. Ref: RES-1251

* fix: treat a resident worker anywhere on the owning chain as covering its jobs

Passivity was judged by the top ancestor alone, so a job owned by a
mid-tree session with its own resident worker still counted passive and
the wake launched a competing root worker over an active subtree. The
walk now checks findWorkerBySessionFile at every hop; only a fully
uncovered chain stays wake-eligible. Ref: RES-1250

* fix: guard every destructive cancel walk with the same ownership truth

The round-5 stillWanted guard only ran when the family read succeeded:
a promotion landing during a throwing read was still parked, and the
enumeration retry cancelled the now-public schedules unguarded. The
catch now settles instead of parking when ownership is gone, and the
retry passes stillWanted from the parked worker's descriptor - the same
object a late promotion mutates and persists, which the boot gate
already reads. Ref: RES-1251

* refactor: derive ephemeral cancel intents from the persisted descriptors

The in-memory park/retry map duplicated a truth that already lives on
disk: the kept tombstoned client-owned descriptor. Enumeration now
re-derives retry intents (and the still-pending tree exclusion) from
descriptorDir alone, and the covering-worker exemption moves inside the
destructive walk itself, checked per tree member - chain-complete where
the old root-only retry check missed a replacement worker reopened on a
child file - so stop-time, retry, and boot cancels share one guard.
Promotion self-corrects in memory and on disk because promoteOwnedWorker
replaces and persists the descriptor. Ref: RES-1251

* fix: keep wake-blind scheduled workers resident through the idle sweep

The wake scan only enumerates the supervisor's sessions root, so a
public worker whose root file lives in a custom session dir would
passivate and never wake for its schedules. Eviction eligibility now
matches wake enumerability: such a worker with a registered heartbeat
or cron job stays resident, exactly the pre-PR behavior for the blind
set. create.config.sessionDir is client-reachable (daemon CLI
--session-dir). Ref: RES-1250

* fix: own the wake-blind exemption in the eviction eligibility predicates

The gate lived at the sweep's candidate build only, so the fenced
recompute after the mutation drain and the last-detach empty eviction
could still passivate a custom-dir worker into the blind spot. The fact
moves into the WorkerEvictionSnapshot (hasWakeBlindSchedule, refused by
canEvictWorker) so both sweep sites read one policy, and the last-detach
path - a different input shape built from empty-draft summaries, not
eviction snapshots - consults the same isWakeBlindScheduledWorker helper
in its own eligibility predicate. Call-site check deleted. Ref: RES-1250

* test: give the peer-transport eviction fixture a type-complete descriptor

workerEvictionSnapshot now computes the required hasWakeBlindSchedule field, which walks descriptor.sessionFile ?? createCommand.sessionPath. createCommand is required on DaemonWorkerDescriptor and every real descriptor carries it; the fixture impersonated one through an unknown-cast without it. Fix the fixture, not the type contract.

* feat(coding-agent): rank armed heartbeats first within the inactive section
…ntellect-ai#1885)

* feat(coding-agent): ACP lineage-v1 provenance producer

One opaque request ID on the wire (X-ACP-Lineage-Request-ID +
Idempotency-Key, minted before the call and stable across retries of the
same call), a durable append-only lineage.jsonl ledger per agent session
beside its artifacts, context epochs driven by compaction outcomes,
cross-process ancestry for spawned subagents, and deriveLineageManifest
folding a session tree's ledgers into the verifiers lineage-v1 manifest.
Derivation only; nothing publishes or reads the ledger yet.

* fix(coding-agent): harden lineage retry identity, spawn ancestry, and ledger repair

Review round 2 on the lineage-v1 producer: retry Idempotency-Key reuse now
requires a byte-identical body (sha256 of model + system prompt + messages)
instead of a shape proxy; spawn ancestry is snapshotted synchronously at the
spawn entry point and survives resume via ledger replay; child terminal
status is recorded once at actual release (reusable children stay running
across follow-up runs, startup failures record failed); ledger construction
never mutates the file (torn-tail repair deferred to first append) and only
a malformed unterminated final line counts as torn; the completed-compaction
ledger event now precedes the transcript commit. Adds host pass-through,
real-summary-call wire, negative validator calibration, replayed-status,
double-wrap, and failed/cancelled mapping coverage.

* fix(coding-agent): eager lineage body hash and mutation-hardened tests

Round 3 on the lineage-v1 producer. The retry body hash is now computed
eagerly at request creation, before the wire call, so mutating the live
message objects between the failed call and the retry park can no longer
alias two different bodies under one Idempotency-Key (TOCTOU regression
test included; the lazy-thunk indirection is gone). Test hardening from
the mutation campaign: hash keyed on provider/model/systemPrompt each
proven independently; undefined-vs-undefined parked hashes never match;
readLineageLedger proven read-only; torn-tail repair proven single-shot;
spawn-ancestry snapshot proven to precede preflight awaits; the completed
compaction ledger event proven durable before (and exactly once despite
a failing) transcript commit. The invariant port checks compactions
before requests and the negative calibration table asserts the named
error per row, adding duplicate-context, session-owner, and per-predicate
isolation rows.

* test(coding-agent): kill the last two lineage mutants

readLineageLedger is proven to never open the ledger for writing (a
missing path must throw ENOENT and stay missing; a zero-byte append-open
would create it), and the calibration table gains a row where a fully
valid child compaction is named by a root request, so only the
request-side compaction owner check can reject it.

* feat(coding-agent)!: convert the provenance producer to ACP semantic-edges-v1

The ecosystem replaced lineage-v1 (nano-rlm PrimeIntellect-ai#153, verifiers #2449): one
renamed correlation header (X-ACP-Model-Request-ID + Idempotency-Key) and
sparse commit-gated semantic edges instead of a full session manifest.
The durable per-session JSONL ledger keeps its hardened mechanics
(ledger-before-wire, torn-vs-corrupt tails, repair-on-first-append,
read-path purity, replay idempotence, eager body-hash retry identity,
synchronous spawn snapshots) and now records request outcomes: the
streamFn wrapper commits or fails each request when its stream resolves,
compaction summaries commit around the real wire call only, and a parent
claims a child_returned with the child's last committed request at the
success point. deriveSemanticEdges is a pure order-independent fold to
continuation/subagent_call/subagent_return/compaction edges: edges
materialize only when their target commits, failed requests return their
inbound edges to the session's pending set (prime-agent has no prompt
rollback, so they attach to the next commit), spawn edges defer to the
child's first committed request, and a completed compaction suppresses
the summary's own continuation. Deleted: context epochs, session status
recording, depth in ledger events, the manifest deriver, and the
lineage-v1 validator port with its calibration table.

* test(coding-agent): kill the five surviving semantic-edge mutants

Two isolated compaction fold gates (a committed summary followed by a
later commit, and a failed compaction after a committed summary, each
producing no compaction edge), distinct wrapper outcome cases including a
rejected stream promise and an aborted final message (both must fail the
request), and the wire header asserted as the literal string
X-ACP-Model-Request-ID so a renamed production constant cannot pass.

* feat(coding-agent): failed subagent runs also return their last commit

Aligns with nano-rlm a1d7856: a failed child still returns an error
outcome the parent consumes, so the failure settle point now records
child_returned with the child's last committed request. Cancelled runs
and zero-commit children stay silent; the returned-once guard is
unchanged. Tests cover the failed-with-commits return edge, failed
zero-commit silence, and cancelled-with-commits silence.

* fix(coding-agent): harden semantic-edge retry identity and ledger fault paths

Review-bot round: the retry body hash now covers tools and the
request-shaping stream options (reasoning, thinking budgets, temperature,
max tokens, service tier); parking is forfeited entirely when a
before_provider_request hook is registered, since payload hooks rewrite
the wire body after the hash point; ledger write failures in the stream
outcome observer and in compaction error paths are contained with a
one-line warning instead of crashing or masking the original error (the
completed-compaction ID is marked consumed before its write so a failed
write propagates untouched); and side questions now use the unwrapped
inner stream function, keeping their calls out of the session ledger and
the continuation chain.

* fix(coding-agent): distinct request identity per split-turn summary call

Split-turn compactions send two summary calls with different bodies; one
shared Idempotency-Key gets rejected (or replayed) by key-honoring
interceptors. compact() now runs every summary call through a host
runner that mints its own request ID, headers, and finish/fail outcome,
and the edge fold tracks all summary slices of a compaction, keying the
compaction edge off whichever slice is the session's last commit. Earlier
slices contribute ordinary continuation edges.

* fix(coding-agent): degrade the semantic-edge recorder instead of throwing

Review-bot round 2, one mechanism for four findings: the first failed
ledger operation (read at construction or any append) permanently
disables the recorder with a single warning; every write becomes a no-op
and the streamFn wrapper and compaction runner stop emitting request IDs
on the wire, preserving ledger-before-wire instead of weakening it. This
removes the scattered try/catch layers (recordOutcomeSafely and both
compaction-path guards are deleted) and means an unwritable ledger can no
longer break compaction, rewrite a successful child run to error, or fail
a compaction whose summary succeeded. Appends are now durable before
in-memory commit state advances, so a failed write never leaves a
claimable commit. Aborted compaction summaries record request_failed
rather than committing partial text into the chain. Compaction summary
slices claim no pending edges and return none on failure (documented
divergence from nano-rlm): pending defers to the post-compaction turn,
the request that actually consumes subagent results.

* fix(coding-agent): flush pending edges to the last-committed summary slice

A completed compaction as the session's final activity permanently lost
deferred pending edges (subagent_return, reclaimed continuations): the
slices claimed nothing and no post-compaction turn ever arrived. The fold
now flushes still-pending edges to the compaction's last-committed slice
at completion — the same request that sources the compaction edge — so
pending always lands on a committed request whether or not the session
continues, restoring nano-equivalent single-call semantics while staying
deterministic for split turns. Failed or cancelled compactions still
leave pending for the next turn.

* fix(coding-agent): dedupe the terminal flush against generated continuations

The terminal flush appended pending edges after the last summary slice
had already emitted its regenerated continuation, so a reclaimed
continuation from the same source derived twice — duplicate edges the
consumer rejects. The flush now applies nano's source-only suppression:
a pending edge from X removes the slice's generated continuation from X
regardless of the pending edge's type, then pending flushes once.

* fix(coding-agent): commit summary slices only when the compaction commits and gate spawn attribution on an active run

A split-turn summary slice that succeeded on the wire no longer publishes request_finished at stream resolution: slices settle at the compaction outcome, so a failed or cancelled compaction leaves no committed summary request and the next turn's continuation edge stays on the pre-compaction request. Spawn attribution now requires an active agent run; an out-of-band spawn records no spawned_by_request_id instead of a stale one.
…t-traces outbox (PrimeIntellect-ai#1984)

* feat(coding-agent): ACP lineage-v1 provenance producer

One opaque request ID on the wire (X-ACP-Lineage-Request-ID +
Idempotency-Key, minted before the call and stable across retries of the
same call), a durable append-only lineage.jsonl ledger per agent session
beside its artifacts, context epochs driven by compaction outcomes,
cross-process ancestry for spawned subagents, and deriveLineageManifest
folding a session tree's ledgers into the verifiers lineage-v1 manifest.
Derivation only; nothing publishes or reads the ledger yet.

* fix(coding-agent): harden lineage retry identity, spawn ancestry, and ledger repair

Review round 2 on the lineage-v1 producer: retry Idempotency-Key reuse now
requires a byte-identical body (sha256 of model + system prompt + messages)
instead of a shape proxy; spawn ancestry is snapshotted synchronously at the
spawn entry point and survives resume via ledger replay; child terminal
status is recorded once at actual release (reusable children stay running
across follow-up runs, startup failures record failed); ledger construction
never mutates the file (torn-tail repair deferred to first append) and only
a malformed unterminated final line counts as torn; the completed-compaction
ledger event now precedes the transcript commit. Adds host pass-through,
real-summary-call wire, negative validator calibration, replayed-status,
double-wrap, and failed/cancelled mapping coverage.

* fix(coding-agent): eager lineage body hash and mutation-hardened tests

Round 3 on the lineage-v1 producer. The retry body hash is now computed
eagerly at request creation, before the wire call, so mutating the live
message objects between the failed call and the retry park can no longer
alias two different bodies under one Idempotency-Key (TOCTOU regression
test included; the lazy-thunk indirection is gone). Test hardening from
the mutation campaign: hash keyed on provider/model/systemPrompt each
proven independently; undefined-vs-undefined parked hashes never match;
readLineageLedger proven read-only; torn-tail repair proven single-shot;
spawn-ancestry snapshot proven to precede preflight awaits; the completed
compaction ledger event proven durable before (and exactly once despite
a failing) transcript commit. The invariant port checks compactions
before requests and the negative calibration table asserts the named
error per row, adding duplicate-context, session-owner, and per-predicate
isolation rows.

* test(coding-agent): kill the last two lineage mutants

readLineageLedger is proven to never open the ledger for writing (a
missing path must throw ENOENT and stay missing; a zero-byte append-open
would create it), and the calibration table gains a row where a fully
valid child compaction is named by a root request, so only the
request-side compaction owner check can reject it.

* feat(coding-agent)!: convert the provenance producer to ACP semantic-edges-v1

The ecosystem replaced lineage-v1 (nano-rlm PrimeIntellect-ai#153, verifiers #2449): one
renamed correlation header (X-ACP-Model-Request-ID + Idempotency-Key) and
sparse commit-gated semantic edges instead of a full session manifest.
The durable per-session JSONL ledger keeps its hardened mechanics
(ledger-before-wire, torn-vs-corrupt tails, repair-on-first-append,
read-path purity, replay idempotence, eager body-hash retry identity,
synchronous spawn snapshots) and now records request outcomes: the
streamFn wrapper commits or fails each request when its stream resolves,
compaction summaries commit around the real wire call only, and a parent
claims a child_returned with the child's last committed request at the
success point. deriveSemanticEdges is a pure order-independent fold to
continuation/subagent_call/subagent_return/compaction edges: edges
materialize only when their target commits, failed requests return their
inbound edges to the session's pending set (prime-agent has no prompt
rollback, so they attach to the next commit), spawn edges defer to the
child's first committed request, and a completed compaction suppresses
the summary's own continuation. Deleted: context epochs, session status
recording, depth in ledger events, the manifest deriver, and the
lineage-v1 validator port with its calibration table.

* test(coding-agent): kill the five surviving semantic-edge mutants

Two isolated compaction fold gates (a committed summary followed by a
later commit, and a failed compaction after a committed summary, each
producing no compaction edge), distinct wrapper outcome cases including a
rejected stream promise and an aborted final message (both must fail the
request), and the wire header asserted as the literal string
X-ACP-Model-Request-ID so a renamed production constant cannot pass.

* feat(coding-agent): failed subagent runs also return their last commit

Aligns with nano-rlm a1d7856: a failed child still returns an error
outcome the parent consumes, so the failure settle point now records
child_returned with the child's last committed request. Cancelled runs
and zero-commit children stay silent; the returned-once guard is
unchanged. Tests cover the failed-with-commits return edge, failed
zero-commit silence, and cancelled-with-commits silence.

* fix(coding-agent): harden semantic-edge retry identity and ledger fault paths

Review-bot round: the retry body hash now covers tools and the
request-shaping stream options (reasoning, thinking budgets, temperature,
max tokens, service tier); parking is forfeited entirely when a
before_provider_request hook is registered, since payload hooks rewrite
the wire body after the hash point; ledger write failures in the stream
outcome observer and in compaction error paths are contained with a
one-line warning instead of crashing or masking the original error (the
completed-compaction ID is marked consumed before its write so a failed
write propagates untouched); and side questions now use the unwrapped
inner stream function, keeping their calls out of the session ledger and
the continuation chain.

* fix(coding-agent): distinct request identity per split-turn summary call

Split-turn compactions send two summary calls with different bodies; one
shared Idempotency-Key gets rejected (or replayed) by key-honoring
interceptors. compact() now runs every summary call through a host
runner that mints its own request ID, headers, and finish/fail outcome,
and the edge fold tracks all summary slices of a compaction, keying the
compaction edge off whichever slice is the session's last commit. Earlier
slices contribute ordinary continuation edges.

* fix(coding-agent): degrade the semantic-edge recorder instead of throwing

Review-bot round 2, one mechanism for four findings: the first failed
ledger operation (read at construction or any append) permanently
disables the recorder with a single warning; every write becomes a no-op
and the streamFn wrapper and compaction runner stop emitting request IDs
on the wire, preserving ledger-before-wire instead of weakening it. This
removes the scattered try/catch layers (recordOutcomeSafely and both
compaction-path guards are deleted) and means an unwritable ledger can no
longer break compaction, rewrite a successful child run to error, or fail
a compaction whose summary succeeded. Appends are now durable before
in-memory commit state advances, so a failed write never leaves a
claimable commit. Aborted compaction summaries record request_failed
rather than committing partial text into the chain. Compaction summary
slices claim no pending edges and return none on failure (documented
divergence from nano-rlm): pending defers to the post-compaction turn,
the request that actually consumes subagent results.

* fix(coding-agent): flush pending edges to the last-committed summary slice

A completed compaction as the session's final activity permanently lost
deferred pending edges (subagent_return, reclaimed continuations): the
slices claimed nothing and no post-compaction turn ever arrived. The fold
now flushes still-pending edges to the compaction's last-committed slice
at completion — the same request that sources the compaction edge — so
pending always lands on a committed request whether or not the session
continues, restoring nano-equivalent single-call semantics while staying
deterministic for split turns. Failed or cancelled compactions still
leave pending for the next turn.

* fix(coding-agent): dedupe the terminal flush against generated continuations

The terminal flush appended pending edges after the last summary slice
had already emitted its regenerated continuation, so a reclaimed
continuation from the same source derived twice — duplicate edges the
consumer rejects. The flush now applies nano's source-only suppression:
a pending edge from X removes the slice's generated continuation from X
regardless of the pending edge's type, then pending flushes once.

* refactor(coding-agent): reschedule agent-trace uploads through a disk-cursor outbox

Upload intent and uploaded-content cursors persist per session file in
agent-traces-outbox.json; a once-per-process startup catch-up uploads
whatever a previous process never finished and prunes cursors of deleted
files. Unchanged sessions are never re-uploaded. 429s return immediately
and reschedule via the controller instead of sleeping in-request, and
session disposal no longer knows trace uploads exist.

Linear: ENG-5838

* fix(coding-agent): make the trace outbox per-entry, durable at persist, and process-exit safe

One entry file per session (path-hashed) replaces the single-map file: concurrent writers cannot lose cursors and a bad read costs only its own entry. The pending marker is written synchronously at first persist. A failed cursor write after a successful PUT returns a retryable failure. 429 Retry-After is honored on the next scheduled cycle. All upload timers are unref-ed so telemetry never holds the process open.

* fix(coding-agent): retry failed intent markers and cap Retry-After at the timer maximum

A failed pending-marker write no longer marks the session as locally managed, so the next persist retries it. A Retry-After beyond Node's ~24.8-day setTimeout maximum is capped there instead of overflowing to an immediate retry loop.

* fix(coding-agent): commit summary slices only when the compaction commits and gate spawn attribution on an active run

A split-turn summary slice that succeeded on the wire no longer publishes request_finished at stream resolution: slices settle at the compaction outcome, so a failed or cancelled compaction leaves no committed summary request and the next turn's continuation edge stays on the pre-compaction request. Spawn attribution now requires an active agent run; an out-of-band spawn records no spawned_by_request_id instead of a stale one.

* feat(coding-agent): register the semantic-edge ledger with the agent-traces outbox

The per-session semantic-edges.jsonl becomes a second outbox kind: durable kind-tagged intent at the first transcript persist, an append-only byte cursor (uploadedBytes) whose unchanged files are never re-counted, catch-up inclusion after a kill, and pruning when the ledger is deleted with its session. No delivery endpoint exists yet (verifiers#2449 consumes edges in-band over ACP metadata; the trace server has no semantic-edges route), so pending ledgers are counted, never sent, and the cursor stays untouched for the first real sender. Unknown outbox kinds from newer builds are left alone. The ledger path derivation is shared between the recorder and the outbox registration.

* fix(coding-agent): re-register the ledger intent when the outbox ledger path changes

A controller update that changes semanticEdgesLedgerPath resets the intent memo, so a reused SessionManager registers the new ledger at its next persist instead of silently dropping it from catch-up. Also bullet-prefix the changelog fragment.

* fix(coding-agent): consent-gate outbox intent at persist time and drop the ledger intent latch

Outbox entries (transcript and semantic-edges alike) are only created while trace sharing is enabled: an entry recorded during an opted-out era would turn a later enable into retroactive collection. The ledger intent memo is deleted rather than patched — marking re-runs every persist behind an existsSync short-circuit, so an entry pruned by a racing catch-up is re-registered at the next persist, and the path-change reset it needed disappears with it.
…rimeIntellect-ai#1986)

* fix: count busy descendants in the agents-view subagents indicator

The "N subagents running" indicator only tallied direct children whose
row classified as running, so a busy grandchild under an idle
intermediate child left every ancestor reading "N subagents" with a zero
count. The tally is now a bottom-up pass over the built rows - the live
per-descendant truth - replacing the direct-child increment.

Indicator only, per policy: a session is Running when it works itself;
idle ancestors of busy subtrees stay in Idle. Within Idle they now rank
above plain idle rows, and a collapsed group's summary row renders its
running count in the success color instead of dimmed so the busy subtree
is discoverable without expanding.

Diagnosis credit: Vincent Bailly (VincentBailly#9) traced
the stale hasRunningRlmChildren snapshot and the row-walk mechanism;
this adopts his walk for the indicator while rejecting the section
promotion.

RES-1253

* fix: tally busy descendants before heartbeat promotion inflates sections

The descendant tally read row sections after
propagateHeartbeatStateToAncestors had promoted idle ancestors to
running, so a heartbeat-active grandchild counted its promoted parent
too. The tally now runs before the promotion pass and counts
intrinsically busy rows only; once that propagation pass is deleted
(PrimeIntellect-ai#1967) the ordering is a no-op.

RES-1253

* fix: make the descendant tally iterative so deep chains cannot overflow

The recursive tally threw RangeError on a deep child chain just by
opening the agents view. The nesting loop assigns every row at most one
parent, so the tree is a forest: a reverse breadth-first pass over one
work list computes the same bottom-up counts iteratively, with the
pre-promotion ordering kept.

RES-1253

* fix: classify running from the session's own work, not delegated children

Second half of the RES-1253 policy, user-approved: Running means the
session you enter is doing work now. Delegated child work no longer
classifies a session as running - the busy-descendant badge, count, and
idle ranking from the first half carry the delegation signal.

hasRunningRlmChildren loses its section-classification role in both
owners: classifySessionRosterStatus composes busy from the session's own
activity/isSessionActive, and isActiveSessionBusy (the worker's activity
axis) no longer holds a settled parent at "working" for its children.
The "subagents running" status label dies with the section it had to
agree with. The field itself stays on the wire, and isSessionSummaryBusy
keeps it on purpose for its residency and shutdown-safety consumers
(worker eviction snapshots, empty-draft eviction, busy client-owned
session counts, daemon stop confirmation).

RES-1253

* chore: note the running-means-the-session policy in the changelog fragment

* fix: split display activity from live-work safety in the busy predicates

isActiveSessionBusy served two meanings after the classification change:
the display activity axis (session's own work only, correct) and worker
recovery plus draft-discard (where a running RLM child is live work that
dies with the worker). The recovery journal recorded busy:false for a
settled parent with a running child, so a worker death skipped its
interruption record and notice.

The shared predicate is deleted and each meaning gets one named owner:
activeActivityForSession reads the session's own isSessionActive
directly, and hasLiveSessionWork (own turn or running RLM child) backs
recordWorkerRecoveryState and isDiscardableDraft. No caller can grab the
wrong meaning blind.

RES-1253

* fix: gate destructive agent actions on live work, not display section

Since Running means the session's own work, an idle parent of a running
crew was branching into the delete path: killSubagent chose stop-vs-
delete from the display section and stopAgentForDeletion treated
activity==='working' as the only live work. Destructive actions are
safety consumers of the display/safety split: one row-level hasLiveWork
(own section running, busy descendants, or the wire running-children
flag) now drives the stop-first branch, the deletion flow, and the
confirmation verb.

Also trimmed the tally comment to current behavior.

RES-1253

* fix: stop live descendants when cancelling a settled child run

Stopping a subagent row whose own run already settled reported
'Subagent already finished' while a nested descendant kept running:
cancelRlmChildRun only cancelled the targeted run itself, and a fully
released child (run removed, session retained) was never matched by id
at all. Cancellation now descends where the tree lives: a settled target
stops every running or queued run in its retained session's subtree
(cancelRunningRlmDescendants, mirroring hasRunningRlmChildren's walk),
and the returned flag stays truthful so the UI says stopped only when
something stopped.

RES-1253

* chore: trim comments and fold overlapping pins on the indicator branch

Comment pass: multi-line narration cut to one-line invariant guards
(destructive live-work gate, residency-vs-section busy split, worker-
death live work) and code-readable notes deleted. Test pass: the direct-
child indicator pin folded into the grandchild pin (the general case),
which now also carries the idle-label and wire-flag assertions.

* fix: cancel and descend at every node of the rlm cancel walk

Cancelling a live child run aborts it, and abort cascades into the
child's ACTIVE runs - but running work retained under a settled
descendant of that child was the end of the line: neither the cascade
nor the cancel walk visited it. cancelRlmChildRun now descends into the
target's session after cancelling it, and cancelRunningRlmDescendants
descends at every node instead of treating running runs as leaves, so
cancellation is exhaustive over the subtree regardless of each node's
run state. The returned flag stays truthful.

RES-1253
…ubagent lineage (PrimeIntellect-ai#2021)

resolveRuntimeSessionOptions now whitelists semanticParentSessionId and semanticSpawnedByRequestId, so sessions created through the production runtime factory (daemon workers, runtime hosts) register spawn lineage; the factory is extracted as createDefaultRuntimeFactory so the pin exercises the real path instead of a forward-everything test factory. A split-turn summary slice resolving after a sibling already failed the compaction now settles as request_failed instead of pushing into the drained slice list and staying in-flight forever.
)

* fix: count busy descendants in the agents-view subagents indicator

The "N subagents running" indicator only tallied direct children whose
row classified as running, so a busy grandchild under an idle
intermediate child left every ancestor reading "N subagents" with a zero
count. The tally is now a bottom-up pass over the built rows - the live
per-descendant truth - replacing the direct-child increment.

Indicator only, per policy: a session is Running when it works itself;
idle ancestors of busy subtrees stay in Idle. Within Idle they now rank
above plain idle rows, and a collapsed group's summary row renders its
running count in the success color instead of dimmed so the busy subtree
is discoverable without expanding.

Diagnosis credit: Vincent Bailly (VincentBailly#9) traced
the stale hasRunningRlmChildren snapshot and the row-walk mechanism;
this adopts his walk for the indicator while rejecting the section
promotion.

RES-1253

* fix: tally busy descendants before heartbeat promotion inflates sections

The descendant tally read row sections after
propagateHeartbeatStateToAncestors had promoted idle ancestors to
running, so a heartbeat-active grandchild counted its promoted parent
too. The tally now runs before the promotion pass and counts
intrinsically busy rows only; once that propagation pass is deleted
(PrimeIntellect-ai#1967) the ordering is a no-op.

RES-1253

* fix: make the descendant tally iterative so deep chains cannot overflow

The recursive tally threw RangeError on a deep child chain just by
opening the agents view. The nesting loop assigns every row at most one
parent, so the tree is a forest: a reverse breadth-first pass over one
work list computes the same bottom-up counts iteratively, with the
pre-promotion ordering kept.

RES-1253

* fix: classify running from the session's own work, not delegated children

Second half of the RES-1253 policy, user-approved: Running means the
session you enter is doing work now. Delegated child work no longer
classifies a session as running - the busy-descendant badge, count, and
idle ranking from the first half carry the delegation signal.

hasRunningRlmChildren loses its section-classification role in both
owners: classifySessionRosterStatus composes busy from the session's own
activity/isSessionActive, and isActiveSessionBusy (the worker's activity
axis) no longer holds a settled parent at "working" for its children.
The "subagents running" status label dies with the section it had to
agree with. The field itself stays on the wire, and isSessionSummaryBusy
keeps it on purpose for its residency and shutdown-safety consumers
(worker eviction snapshots, empty-draft eviction, busy client-owned
session counts, daemon stop confirmation).

RES-1253

* chore: note the running-means-the-session policy in the changelog fragment

* fix: split display activity from live-work safety in the busy predicates

isActiveSessionBusy served two meanings after the classification change:
the display activity axis (session's own work only, correct) and worker
recovery plus draft-discard (where a running RLM child is live work that
dies with the worker). The recovery journal recorded busy:false for a
settled parent with a running child, so a worker death skipped its
interruption record and notice.

The shared predicate is deleted and each meaning gets one named owner:
activeActivityForSession reads the session's own isSessionActive
directly, and hasLiveSessionWork (own turn or running RLM child) backs
recordWorkerRecoveryState and isDiscardableDraft. No caller can grab the
wrong meaning blind.

RES-1253

* feat: show token and cost totals on agents view rows

Every agents view row with usage data now reads
`<input>/<output> | $<own> ($<recursive>)` in the details cell, replacing
the message-count detail. Own numbers are the session's whole-file spend
(every branch, forks included, attributed child usage subtracted) - the
money answer, deliberately broader than /usage's current-branch context
answer, and identical for resident and on-disk rows so nothing shifts at
passivation or revival. The recursive total rides the same reverse
breadth-first traversal as the running-subagent tally, summing each
descendant row's own cost exactly once.

Producers: resident summaries compute the total from the already-loaded
entries, memoized until entries change (0.27 ms measured cold at 10k
entries); saved rows accumulate it inside the existing mtime/size-cached
catalog scan, so old files get costs with no new IO and no new cache.
Entries over the scan's max line length keep their existing skip, so a
giant assistant message can undercount slightly. Schema revision 26
publishes the new optional summary and saved-row field.

RES-1258

* feat: final row usage format with arrows and conditional recursive cost

Details cell now reads `↑12k ↓1.2k · $0.42 ($1.10 w/ subagents)`; the
parenthesized total renders only when descendants added spend visible at
cent rounding, so leaf rows read `↑500 ↓50 · $0.68`. Arrow glyphs follow
the existing TUI vocabulary (keybinding hints, token status) and the
width pipeline already treats them as single cells.

RES-1258

* fix: one own-spend truth across scan, loader, and filters

Review round on the usage feature:

- The catalog scan subtracted attributed child usage from disk values
  that (in append-only files) still carry the assistant's ORIGINAL
  model-response usage, double-subtracting and shifting numbers at
  passivation. Full-file rewrites (migrations, forks) can also persist
  the already-folded aggregates, so raw summation is wrong for those
  files instead. The scan now mirrors the loader exactly: fold each
  attribution's aggregate onto its target, then subtract the child usage
  - both disk representations cancel to the same own spend the resident
  computation reports. The equality pin now goes through a real v3
  flushed-then-attributed fixture (the old fixture had no attribution,
  which is why it never caught this).
- Recursive cost was computed over search-filtered rows, so a matching
  parent showed an incomplete "w/ subagents" total. The rollup now comes
  from the unfiltered record hierarchy (computeRecursiveCosts over the
  unified index) and filtering can no longer change the number.
- Zero-spend sessions published 0/0/$0.00 live and nothing once
  passivated; sessionUsageSummaryFrom now returns undefined for zero
  totals, shared by both producers, so rows stay age-only until real
  spend exists.

RES-1258

* fix: gate destructive agent actions on live work, not display section

Since Running means the session's own work, an idle parent of a running
crew was branching into the delete path: killSubagent chose stop-vs-
delete from the display section and stopAgentForDeletion treated
activity==='working' as the only live work. Destructive actions are
safety consumers of the display/safety split: one row-level hasLiveWork
(own section running, busy descendants, or the wire running-children
flag) now drives the stop-first branch, the deletion flow, and the
confirmation verb.

Also trimmed the tally comment to current behavior.

RES-1253

* fix: stop live descendants when cancelling a settled child run

Stopping a subagent row whose own run already settled reported
'Subagent already finished' while a nested descendant kept running:
cancelRlmChildRun only cancelled the targeted run itself, and a fully
released child (run removed, session retained) was never matched by id
at all. Cancellation now descends where the tree lives: a settled target
stops every running or queued run in its retained session's subtree
(cancelRunningRlmDescendants, mirroring hasRunningRlmChildren's walk),
and the returned flag stays truthful so the UI says stopped only when
something stopped.

RES-1253

* feat: render the usage cell unconditionally, zeros included

Every agents view row now renders the full details cell
`↑<up> ↓<down> · $<own> ($<total> w/ subagents)` over four defaulted
numbers - no presence branching, no thresholds, no conditional paren. A
session that never spent reads `↑0 ↓0 · $0.00 ($0.00 w/ subagents)`, and
a zero-usage parent with billed subagents shows its subtree spend
instead of hiding it.

RES-1258

* chore: trim comments and fold overlapping pins on the indicator branch

Comment pass: multi-line narration cut to one-line invariant guards
(destructive live-work gate, residency-vs-section busy split, worker-
death live work) and code-readable notes deleted. Test pass: the direct-
child indicator pin folded into the grandchild pin (the general case),
which now also carries the idle-label and wire-flag assertions.

* chore: cut the usage feature's comments and collapse its pins

Comment pass (>80%): 24 added comment lines down to 4 one-line invariant
guards (loader-fold cancellation in the scan, passivation-invariance on
the resident getter, unfiltered rollup, the revision note). Test pass:
the scan-accumulation and resident-equality pins merged onto one forked-
and-attributed fixture; the descendant-rollup and filtered-total pins
merged with a grandchild; the format pin asserts all four row shapes
from one fixture; redundant zero-case and permutation tests deleted.

* fix: cancel and descend at every node of the rlm cancel walk

Cancelling a live child run aborts it, and abort cascades into the
child's ACTIVE runs - but running work retained under a settled
descendant of that child was the end of the line: neither the cascade
nor the cancel walk visited it. cancelRlmChildRun now descends into the
target's session after cancelling it, and cancelRunningRlmDescendants
descends at every node instead of treating running runs as leaves, so
cancellation is exhaustive over the subtree regardless of each node's
run state. The returned flag stays truthful.

RES-1253

* fix: passivated RLM descendants survive restarts as saved-catalog rows

An RLM child's transcript lives in session-artifacts, which the saved-
session scan never visits. While something resident remembered the child
its row survived; after a supervisor restart an inactive parent's
passivated descendants silently disappeared from the agents view, and
their spend vanished from the parent's subtree total.

Both list_saved_sessions composition points now append live spawn-ledger
children the scan missed, through one shared walk: readSessionInfo per
descendant, parentSessionPath/rlmDepth backfilled from the edge, deleted
edges excluded. The view already renders saved-only descendants, merges
resident duplicates by session identity, and rolls saved usage into
recursive totals, so restored rows restore the money with no view change.

Measured at 300 sessions with 36 passivated descendants: scan 33ms,
merge +9ms (cold ledger, ~0.25ms per descendant).

RES-1262
RES-1258

* feat: compaction and branch-summary calls bill the session they serve

Summarization was invisible money: the compaction model call(s) - two on
a split turn - and branch-summary calls returned usage that was dropped
on the floor, so a session that compacted often under-reported what it
actually cost. The summarizer responses' usage is now folded (both
split-turn slices) and persisted on the compaction/branch_summary entry,
and both own-spend producers add the same term: own spend =
fold(assistant usage) + fold(summarization usage) - attributions,
identical resident and scanned. Old entries without the field fold as
zero; a failed compaction persists nothing, so failed-call billing is
not recoverable - there is no committed entry to carry it.

RES-1258

* fix: harden the passive-descendant catalog merge (round 2)

Three review findings on the merge, all real: the transcript header's
parentSession can point at a forked-away ancestor, so the ledger edge is
now the authoritative topology (family() semantics) for parent and
depth; ledgers are per sessions-dir family, so each catalog request now
reads the ledger of the directory it lists instead of always the
default one; and a broken ledger (unreadable, over-quota) no longer
fails the whole list_saved_sessions response - the merge logs and
returns the scanned catalog.

RES-1262

* chore: consolidate the accumulated review-round pins

The worker-daemon catalog pins from three rounds folded onto one
fixture: restart rows, per-family ledger isolation, and broken-ledger
degradation now share the two-family scaffold. Redundant assertions and
narration comments cut; every behavior keeps exactly one pin.

* test(coding-agent): align unified view regression with usage rows

---------

Co-authored-by: Seth <seth@primeintellect.ai>
@phytal William Zhang (phytal) added the no-changelog No new changelog fragment required label Sep 4, 2026
@phytal
William Zhang (phytal) merged commit e29b555 into main Sep 4, 2026
16 checks passed
@phytal
William Zhang (phytal) deleted the feat/upstream-0.9.1-durable-coordination branch September 4, 2026 02:42
@zeroset-agent

zeroset-agent Bot commented Sep 4, 2026

Copy link
Copy Markdown

Zero Review

Summary

  • 0 critical, 0 warnings, 0 nits
  • 15,880,014 tokens used (15,802,661 prompt / 77,353 completion; 11,056,000 served from cache, billed ~10%)
  • Models - prime-root: zero/swe-root (1 agent, 1 task, 78 calls, 6,329,136 tokens), prime-child: zero/balanced (17 agents, 17 tasks, 259 calls, 9,550,878 tokens)
  • Sandbox review mode: full checkout and tool-backed review executed.

Findings

No actionable findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog No new changelog fragment required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants