feat(orchestrator): Surface waiting background work - #4378
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
e46af0a to
f318877
Compare
1e58e65 to
a286c60
Compare
f318877 to
2bf4cdf
Compare
4f72b01 to
dcb197d
Compare
Replays the amended PR pingdotgg#4378 wake-settlement fix onto the v2.1 trial. A terminal task notification no longer opens an opaque-task continuation unless native user, assistant, or result output proves Claude began the wake turn.
ApprovabilityVerdict: Needs human review Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
bd45aae to
29cfba8
Compare
29cfba8 to
123245f
Compare
123245f to
fc4cc2c
Compare
fc4cc2c to
2f07ff6
Compare
e153ab5 to
5572efb
Compare
5572efb to
8024ba3
Compare
…ntime status The CTM merge left two pingdotgg#4415 sidebar-waiting-state assertions that cannot hold on this line, because CTM keys thread status off runtime where main uses session: - apps/mobile threadListV2.test.ts asserted Waiting through session.idle. EnvironmentThreadShell has no session field here, so it did not typecheck. pingdotgg#4378 orchestrator-v2-background-waiting already covers the same behaviour through the presentation bridge, so the duplicate goes. - apps/web Sidebar.logic.test.ts asserted that an idle runtime carrying a stale lastError resolves to ready. On this line runtime.idle is Waiting: the pingdotgg#4378 bridge parks a completed shell at idle only when the post-settlement background roster is nonempty. The sibling completed-status assertion still covers the stale-lastError case.
Carry the append-only review fixes from pingdotgg#4378 orchestrator-v2-background-waiting into pingdotgg#4547 orphaned-output-after-early-settle without rewriting reviewed history.
…ntime status The CTM merge left two pingdotgg#4415 sidebar-waiting-state assertions that cannot hold on this line, because CTM keys thread status off runtime where main uses session: - apps/mobile threadListV2.test.ts asserted Waiting through session.idle. EnvironmentThreadShell has no session field here, so it did not typecheck. pingdotgg#4378 orchestrator-v2-background-waiting already covers the same behaviour through the presentation bridge, so the duplicate goes. - apps/web Sidebar.logic.test.ts asserted that an idle runtime carrying a stale lastError resolves to ready. On this line runtime.idle is Waiting: the pingdotgg#4378 bridge parks a completed shell at idle only when the post-settlement background roster is nonempty. The sibling completed-status assertion still covers the stale-lastError case.
Carry the mobile synchronization repair from pingdotgg#4378 orchestrator-v2-background-waiting while preserving the work owned by pingdotgg#4547 orphaned-output-after-early-settle.
…ntime status The CTM merge left two pingdotgg#4415 sidebar-waiting-state assertions that cannot hold on this line, because CTM keys thread status off runtime where main uses session: - apps/mobile threadListV2.test.ts asserted Waiting through session.idle. EnvironmentThreadShell has no session field here, so it did not typecheck. pingdotgg#4378 orchestrator-v2-background-waiting already covers the same behaviour through the presentation bridge, so the duplicate goes. - apps/web Sidebar.logic.test.ts asserted that an idle runtime carrying a stale lastError resolves to ready. On this line runtime.idle is Waiting: the pingdotgg#4378 bridge parks a completed shell at idle only when the post-settlement background roster is nonempty. The sibling completed-status assertion still covers the stale-lastError case.
…ntime status The CTM merge left two pingdotgg#4415 sidebar-waiting-state assertions that cannot hold on this line, because CTM keys thread status off runtime where main uses session: - apps/mobile threadListV2.test.ts asserted Waiting through session.idle. EnvironmentThreadShell has no session field here, so it did not typecheck. pingdotgg#4378 orchestrator-v2-background-waiting already covers the same behaviour through the presentation bridge, so the duplicate goes. - apps/web Sidebar.logic.test.ts asserted that an idle runtime carrying a stale lastError resolves to ready. On this line runtime.idle is Waiting: the pingdotgg#4378 bridge parks a completed shell at idle only when the post-settlement background roster is nonempty. The sibling completed-status assertion still covers the stale-lastError case.
a543fd4 to
378615b
Compare
70e4972 to
9238867
Compare
c5e53c8 to
922f352
Compare
827e306 to
4d96e2a
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort 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 c2f8710. Configure here.
e24b59c to
4213ac5
Compare
15d803c to
b2c68ba
Compare
f0e1196
into
pingdotgg:t3code/codex-turn-mapping

Summary
remains.
lifecycle into one per-thread roster.
mobile thread feed, and classic and native mobile lists while preserving
Working for an active turn.
clears, then persist the empty roster that returns the thread to Ready.
OrchestratorV2 pipeline with a deterministic frame gate to pin Waiting after
root settlement, late completion, roster clearing, and return to Ready.
background work finishes.
Presentation Scope
#4415 waiting-presentation
closed unmerged, so it is no longer a dependency of this PR. This branch now
contains the orchestrator-v2 roster contract plus the web and mobile
presentation listed above.
The native Thread List v2 is now the default mobile list. It consumes the same
parked-idle Waiting mapping, so its rows stay consistent with Sidebar V2 and
the classic mobile list.
Problem and Fix
pendingBackgroundTasks, after active Working and Connecting precedence.pendingBackgroundTaskswith an empty-array default for backward compatibility.apps/mobilecalledderivePendingBackgroundWork, and the view prefersderiveThreadRuntime(projection), which has no roster parking, soactiveWorkStartedAtwent null at settlement and the working row simply vanished.deriveThreadPendingBackgroundWorkinapps/mobile/src/lib/threadActivity.tsderives the roster from the projection andderiveThreadFeedPresentationappends awaiting-backgroundrow. It is a pure helper rather than inline hook code so the policy is unit tested;apps/mobilehas no hook-rendering test infrastructure.resolveThreadStatushad no Waiting branch.resolveThreadStatusgained a static muted Waiting pill, ranked below approvals, input, active work and failures, and above Plan Ready.runtime.idleshell to Ready and had no Waiting label.Defensive Fixes
shouldPersistThreadpersists only settled ones, so a cached projection is exactly the shape that reproduces a Waiting row for work that already finished. Web cannot hit this.synchronizingis refused alongsidecached, because a subscription passes through it before any fresh projection arrives, so its data can still be the cached one.runsargument is optional, and omitting it leaves the rolled-back-run exclusion set empty, so mobile would have shown Waiting for turn items owned by arolled_backrun where desktop shows nothing.runs: projection.runs, matching the web call site argument for argument. A focused test pins it: deleting that one argument fails exactly that test and nothing else.formatPendingBackgroundWorkLabelinterpolates the raw description, which for asubagentitem is the entire child prompt, wrapping into a wall of text on a phone.waitingwith a nullcompletedAtuntil checkpoint capture lands, soderiveActiveWorkStartedAtkept reporting a start time and the feed's Working branch hid Waiting for that whole window, which is exactly when Waiting should first appear. Found by Bugbot on the mobile commit.deriveThreadWorkingStartedAtsuppresses the start time once the roster has parked the runtime atidle, which is how web sequences Working against Waiting. Awaitingrun with an empty roster still reads Working, since that is checkpoint capture with no background work behind it. A regression test pins the parked case.activeRunIdon a runtime already parked atidlecould leave mobile Stop armed and replace Send.preparingandstartingremain interruptible, while parked Waiting exposes Send.Manual test guide
Browsable scenario guide: https://nam7nt0rbtm6.postplan.dev/
This PR owns the Waiting state, so most background scenarios in the guide assert
it. The ones that matter, across all three providers:
Waiting must appear
5, Background subagent post-settle,
6, Resume completed subagent,
7, App-owned delegated child.
4,
5,
6,
7. Scenarios 5 and 6
are the two-task cases, where Waiting must report both.
and 5, Steer while run is held open.
Waiting must NOT appear. These are the false-positive guards, and they are
the easier half to regress:
3,
4: work that
finishes before the root settles.
same, for a background command.
and 3: persistent Grok
monitors are intentionally excluded from the roster.
Related follow-up
The early-settlement output loss discovered by this scenario is fixed by
#4547 orphaned-output-after-early-settle,
which is stacked on this PR.
Validation
suites pass.
parked-idle Waiting, quiescent Ready, and approval precedence.
claude_background_task_after_rootreplay fixture drives thereal adapter and OrchestratorV2 pipeline. It pins the nonempty roster, root
Waiting state, late empty roster, provider return to idle, and final run
completion without a wall-clock delay. Four replay and contract files pass 88
tests, and the focused fixture passed 10 consecutive stress runs after
cancellation and mismatch cleanup review.
vp checkandgit diff --checkpass. Full typecheck reaches only the knownuntouched CTM baseline error at
apps/server/src/httpCompression/HttpResponseCompression.ts:33.subagents, Codex background exec and subagents, and Grok continuations and
subagents.
the classic sidebar row displayed static, muted Waiting after root settlement,
then returned to its timestamp after the roster cleared.
that ended with notification-only traffic. Waiting cleared after run 2, no
empty run 3 appeared, and the provider stayed idle through a 35-second
residual window.
notification-only path and its regression guards. Two independent scenarios
drove a real nonpersistent Monitor whose terminal notification arrived with no
native wake frames: both cleared Waiting without opening a run, and each held
at two completed runs, idle with an empty roster, through a residual window of
about 176 seconds. The prior defect left an empty run active for 225 seconds.
once across a separate follow-up turn, a resumed subagent still wakes, and Stop
during a blocking TaskOutput still leaves two distinct clean recovery turns.
Every wake log marker in the round was attributable to an expected wake, with
none unattributed.
server: the row rendered a static muted Waiting pill with dot class
bg-sidebar-muted-foregroundand no pulse while the roster was nonempty, andtitle plus timestamp only after it cleared. An adjacent thread rendered Working
in the same view, confirming the states are visually distinct.
Cursor Bugbot, and both Macroscope code checks pass. Check is red only because
Vite+ panicked while printing lint warnings with
Resource temporarily unavailable; localvp checkpasses. Test is red only for the two known CTMProjectScriptsControlw-7assertions. Vercel remains red for forkauthorization.
This branch is based on its approved
t3code/codex-turn-mappingsnapshot.Note
High Risk
Changes orchestration runtime idle/release, ACP post-settle carryover, and MCP status semantics—areas where subtle race or double-projection bugs affect live agent threads and interrupt UX.
Overview
Surfaces Waiting when a thread is parked at
runtime.idlebut still has post-settlement background work, aligned with the web sidebar: mobile Thread List v2 adds a muted Waiting status (mapped from idle + pending roster) and the composer only treats runs as interruptible viathreadRuntimeHasInterruptibleRun, so Stop stays hidden while waiting and Send remains available.Orchestrator MCP thread list/read/filtering now prefer
activityRunStatusover shell status so a still-active run is not masked by a newer cancelled queued run;taskStatusreturns the task’s realproviderInstanceIdinstead of deriving it from the driver kind.ACP adapter gains tighter post-settle continuation and carryover behavior (
hasPendingBackgroundWorkpinning, deferred terminal projection on attach, wake-buffer ownership when user turns race continuations), with a large new effect/live test suite covering those paths plus continuation helper predicates.Reviewed by Cursor Bugbot for commit b2c68ba. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Surface post-settlement background work as a 'Waiting' state in the sidebar and chat timeline
pendingBackgroundTasksroster toOrchestrationV2ProviderThreadandOrchestrationV2ThreadShellcontracts, populated by Claude adapter per-native-thread background task tracking and derived client-side via a newderivePendingBackgroundWorkshared utility.shellRuntimeandderiveThreadRuntimepark the runtime status at'idle'; the sidebar resolves this as'waiting'and the chat timeline emits a newwaiting-backgroundrow with a pulsing indicator.writeIfProviderThreadOwnertoEventSinkV2for ownership-gated provider thread writes, and extendsRunExecutionServiceto route inherited backgroundturn_item.updatedevents from eligible prior interrupted/failed/cancelled runs.ProviderRuntimeRecoveryServicenow cancels stale nonterminal background-capable turn items on restart and clears persistedpendingBackgroundTasksrosters on all provider threads.pendingsubagent status as a settlement blocker,terminalStatusProjectedtracking to prevent double-emission of carryover terminals, andhasPendingBackgroundWorkpinning until all active or carryover subagents with unprojected terminals are resolved.ProviderReplayGatetestkit primitive andrelease_replay_gate/capture_shell_snapshotscenario steps to enable fixture-driven testing of background task lifecycle.threadRuntimeHasInterruptibleRunnow returnsfalsewhen runtime status isidleeven ifactiveRunIdis set, which suppresses the interrupt button for parked-idle threads.Macroscope summarized b2c68ba.