fix(coding-agent): coalesce child-usage attribution and gate agent-status persistence on real changes - #2050
fix(coding-agent): coalesce child-usage attribution and gate agent-status persistence on real changes#2050snimu wants to merge 7 commits into
Conversation
…dary Every child assistant message_end appended a durable child_usage_attributed entry to the parent journal and re-scanned the child transcript for the origin prompt, so one chatty tool-looping child grew the parent file per model request and kept it permanently changed for every metadata rescan. Completions now accumulate in memory (the parent aggregate still updates per completion) and flush one summed entry per child agent_end, with a settlement backstop for error and cancel paths. All consumers fold attribution entries linearly, so the coalesced entry reloads to the same own-spend. Fixes the defect reported in discussion #1788.
…lassifications The 25s summarizer sweep appended an agent_status entry on every idle pass: a session whose generation could not succeed (no auth, model resolution failure, persistent parse failure) fabricated a needs_input fallback whose empty summary re-armed the retry forever, appending one identical durable entry per sweep per idle session and re-paying the model call for parse failures. Fabricated fallbacks now stay in memory (the roster axis still settles), persistence requires a real generated status that differs from the latest persisted entry, and idle generations stop retrying after three failures on the same settled content until new activity arrives. Fixes the defect reported in discussion #1752.
…iling, key it to content Review fixes for the bookkeeping-append gates: pending child usage now accumulates per origin (steered agent_message usage is no longer mislabeled spawn_task) and a batch older than 60s flushes before it grows or a tool starts, so a process crash loses at most that window instead of a whole turn; the idle-generation retry ceiling keys on settled content (message count plus last timestamp, so a branch/edit back to the same length re-arms) and expires after a 30-minute backoff so transient outages and late credentials recover at one attempt per backoff instead of never; a forget() during an in-flight generation no longer repopulates the failure map for a closed session.
|
Addressed the review in 7bb135f, itemized:
All four new pins verified fail-unfixed against the previous head; suites (recursion 116, summarizer 26 + lifecycle) pass under a sanitized env; |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7bb135f. Configure here.
Message count plus last-message timestamp is not a branch identity: sibling branches can collide on both, so branch navigation could stay blocked by a failure record for different content. The leaf entry id is the branch tip identity - appends, edits, and branch navigation all move it - so the ceiling now keys on it, and navigation to a sibling branch re-arms classification.
|
Resolved the branch-navigation thread in 1566d09 + 601d8d0: the concern was valid. Trace: |
…wall-clock backstop Two review findings on the coalesced attribution flush: the stale-batch flush on message_end ran after attributeChildUsage folded the new completion, so the flushed entry's aggregateUsage included a completion whose childUsage was not yet durable and a replay of that prefix inflated the parent's own spend - the flush now runs before the fold, making every persisted aggregate exact for any prefix. And the staleness bound only fired at event checkpoints, so a crash during one long tool execution could still lose the pending batch - a 60s unref'd timer now flushes the batch when no checkpoint arrives.
|
Both new threads addressed in 1d1e233:
Recursion suite 116/116 under a sanitized env; |
…date its pins Comment blocks collapse to one- or two-line invariants. The tool-loop pins share one session builder, the ceiling re-arm pins become one table (leaf move and backoff expiry) whose warmup also carries the fabricated-fallback and never-persist assertions, and the forget() pin reuses the shared setup. Every fail-unfixed behavior keeps its assertion.

Purpose
Two derived-bookkeeping paths appended durable journal entries at the wrong granularity, growing parent session files without bound and (for one of them) re-paying model calls forever:
message_endsynchronously appended achild_usage_attributedentry to the parent journal and re-scanned the child transcript for the origin prompt — one journal append per model request of every RLM child, keeping parent files permanently "changed" for every metadata rescan (feeds the amplification fixed in fix(coding-agent): incremental single-flight session metadata scans #2043).agent_statusentry on every idle pass: a session whose generation cannot succeed (no prime-inference auth, model resolution failure, persistent parse failure) fabricated aneeds_inputfallback whose empty summary re-armed the retry forever — one identical durable entry per idle session per 25s, a persisted verdict the model never produced, and a paid model call per sweep in the parse-failure case.Mechanism
agent_end, with a run-settlement backstop for error/cancel paths. All consumers fold attribution entries linearly (loader, scanner, context-tree), so the coalesced entry reloads to the same own-spend.getLatestAgentStatus); fabricated fallbacks stay in memory only (the roster activity axis still settles); idle generations stop retrying after three failures on the same settled content until new activity arrives.Measurement
SessionManager+ summarizer driver): main appends 3456agent_statusentries (+591KB journal, unbounded); this PR appends 0 and stops generation after 3 attempts (3456 -> 3 generate calls — for persistent parse failures those are paid calls).Validation
agent-session-recursion(115),daemon-session-summarizer(23),daemon-session-summarizer-lifecycle,session-manager/agent-status— all pass; rootnpm run checkpasses.LOC
Total src: +128/−26 (net +102); tests: +200/−42 (net +158).
Src +64/−18 (net +46): both changes wire persistence to existing boundaries (child
agent_end/ run settlement; the summarizer's existing latest-persisted truth) rather than adding machinery. Tests +79/−9.Squashes discussions #1788 and #1752; with #2043 this also removes the main journal-growth driver behind the #1503 worker OOM profile.
Linear: RES-1272 https://linear.app/primeintellect/issue/RES-1272
Note
Medium Risk
Touches durable session journal semantics for billing attribution and agent-status history; behavior is covered by new tests but incorrect coalescing or persistence gating could skew cost totals or dashboard status replay.
Overview
Stops unbounded parent session journal growth from two bookkeeping paths that were appending on every child model turn and every 25s idle sweep.
RLM child usage: Child assistant completions no longer call
appendChildUsageAttributionon eachmessage_end. Usage is accumulated in memory by origin (spawn_task,agent_message,direct_user) and flushed once per settle boundary (childagent_end, run cleanup, staleness after 60s, or a timer backstop). In-memory parent totals still update per completion; reload semantics stay consistent because consumers fold attribution entries linearly.Daemon idle status: The summarizer caps failed idle model calls (3 attempts per settled content key, 30-minute backoff keyed by leaf id + message count) and only persists
agent_statuswhen the model actually returned a classification that differs fromgetLatestAgentStatus. Fabricatedneeds_inputfallbacks remain in memory for the roster but no longer duplicate into the journal on unchanged idle sweeps.Reviewed by Cursor Bugbot for commit 6b0af5d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Coalesce child-usage attribution in
AgentSessionand gate idle status persistence on real changesAgentSession.runRlmChildnow aggregates multiple per-request child usage records into one attribution entry per origin, flushing at child-turn settlement, cleanup, or after a 60-secondRLM_CHILD_USAGE_FLUSH_MAX_PENDING_MSbackstop. Failed bookkeeping appends no longer interrupt run settlement.DaemonSessionSummarizer.summarizenow caps idle-generation retries at three failed attempts per content key with a 30-minute backoff, and suppresses persisting fallbackneeds_inputverdicts or duplicate settled statuses. Idle statuses are persisted only when the summary, task state, or message count differs from the latest persisted status.runRlmChildis unref'ed, so it will not keep the process alive; pending attribution can be lost on crash beyond the 60s window. TheDaemonSessionSummarizernow relies ongetLeafIdfrom the session manager for content-key identity, so any caller not providing this mock data in tests will break.Macroscope summarized 6b0af5d.