Add subagent activity visibility#3176
Conversation
- Track subagent parent activity by child thread plus parent item id - Restart resumed child relations and append new prompt blocks - Update sidebar and work-log dedupe to keep resume blocks distinct
- Treat follow-up completions as terminal subagent snapshots - Keep prior subagent status visible when a new parent item restarts the same thread - Update ingestion tests and timeline rendering to track parent item IDs
- Track subagent child dedupe keys by parent turn when available - Keep resumed child blocks working in the timeline instead of completed - Add regression coverage for duplicate resumed subagent entries
…ading-work # Conflicts: # apps/web/src/components/ChatView.tsx # apps/web/src/components/Sidebar.tsx # apps/web/src/components/chat/ChatHeader.tsx # apps/web/src/components/chat/MessagesTimeline.tsx # apps/web/src/environments/runtime/service.ts # apps/web/src/hooks/useThreadActions.ts # apps/web/src/store.test.ts # apps/web/src/store.ts # apps/web/src/types.ts
|
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 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 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 6 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f744946. Configure here.
| if (!activeThread) return; | ||
| if (activeThreadSubagentRelation?.status === "running") { | ||
| setPendingSubagentStopThreadId(activeThread.id); | ||
| } |
There was a problem hiding this comment.
Subagent stop targets parent turn
High Severity
Stopping a subagent from the child view sends a turn interrupt with only the child thread id and no turn id. When the child shell has not yet recorded a latest turn, the server interrupt path falls back to the root provider session’s active turn, so the stop action can interrupt the parent conversation instead of the running subagent.
Reviewed by Cursor Bugbot for commit f744946. Configure here.
| completedAt: event.payload.createdAt, | ||
| status: "stopped", | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Stop marks subagent stopped always
Medium Severity
After requesting a provider interrupt for a subagent thread, orchestration always dispatches a meta update that sets the subagent relation to stopped, even when the Codex runtime had no effective turn id and the interrupt request was a no-op.
Reviewed by Cursor Bugbot for commit f744946. Configure here.
| projectThreads.filter((thread) => thread.archivedAt === null), | ||
| threadSortOrder, | ||
| ); | ||
| const visibleRootProjectThreads = rootSidebarThreads(visibleProjectThreads); |
There was a problem hiding this comment.
Terminal subagents stay in sidebar
Medium Severity
Sidebar thread lists only filter archived threads; they never hide terminal subagent shells based on parentRelation.status. Completed, errored, interrupted, or stopped subagents keep appearing nested under the parent, contrary to the documented sidebar behavior for terminal child work.
Reviewed by Cursor Bugbot for commit f744946. Configure here.
| const eventTurnId = toTurnId(event.turnId); | ||
| const activeTurnId = thread.session?.activeTurnId ?? null; | ||
|
|
||
| const subagentChildren = readRuntimeSubagentChildren(event); |
There was a problem hiding this comment.
Child events dropped without shell
Medium Severity
Runtime ingestion returns immediately when resolveThreadShell cannot find the event’s thread id, so child-thread provider events are discarded if they arrive before the child shell exists in projection or the in-memory synthetic cache.
Reviewed by Cursor Bugbot for commit f744946. Configure here.
|
|
||
| function subagentOutputBufferKey(threadId: ThreadId, itemId: string): string { | ||
| return `${threadId}\0${itemId}`; | ||
| } |
There was a problem hiding this comment.
Subagent buffer key mismatch
High Severity
Child subagent item/agentMessage/delta events are emitted on the local child thread id, but buffered output is keyed by that thread id and only drained when the parent collab item/completed runs on the root thread id. Buffered deltas never flush, so those events produce no runtime content.delta and child assistant streaming can be missing until a separate completion item arrives.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f744946. Configure here.
| } | ||
| const unseenChildren = entry.subagentChildren.filter((child) => { | ||
| const activityScope = entry.turnId ?? child.parentItemId ?? ""; | ||
| const key = `${child.threadId}:${activityScope}`; |
There was a problem hiding this comment.
Same-turn subagent dedupe
Medium Severity
Parent timeline deduplication keys subagent blocks by child.threadId and entry.turnId when a turn id exists, ignoring parentItemId. Two prompt-bearing collab activities in the same parent turn for the same child collapse to one block, which conflicts with showing a new summary row per resumed subagent activity.
Reviewed by Cursor Bugbot for commit f744946. Configure here.
| parentThreadId, | ||
| providerThreadId: receiverThreadId, | ||
| }), | ||
| rawPrompt: startsNewParentActivity ? rawPrompt : (rawPrompt ?? existing?.rawPrompt), |
There was a problem hiding this comment.
🟢 Low Layers/CodexSessionRuntime.ts:676
When startsNewParentActivity is false, the rawPrompt fallback overwrites the existing value while the detail fallback preserves it. Because both fields come from the same notification item, this inconsistency causes the stored rawPrompt and detail to drift across updates. Consider using the same merge strategy for both, such as existing?.rawPrompt ?? rawPrompt to match the detail pattern.
| rawPrompt: startsNewParentActivity ? rawPrompt : (rawPrompt ?? existing?.rawPrompt), | |
| rawPrompt: startsNewParentActivity ? rawPrompt : (existing?.rawPrompt ?? rawPrompt), |
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CodexSessionRuntime.ts around line 676:
When `startsNewParentActivity` is false, the `rawPrompt` fallback overwrites the existing value while the `detail` fallback preserves it. Because both fields come from the same notification item, this inconsistency causes the stored `rawPrompt` and `detail` to drift across updates. Consider using the same merge strategy for both, such as `existing?.rawPrompt ?? rawPrompt` to match the `detail` pattern.
Evidence trail:
apps/server/src/provider/Layers/CodexSessionRuntime.ts lines 655-678 at REVIEWED_COMMIT. Line 676: `rawPrompt: startsNewParentActivity ? rawPrompt : (rawPrompt ?? existing?.rawPrompt)` — prefers new. Line 677: `detail: startsNewParentActivity ? detail : (existing?.detail ?? detail)` — prefers existing. Lines 663-675: all other fields (`parentTurnId`, `parentItemId`, `childThreadId`) consistently use `existing?.X ?? X` (prefer existing).
ApprovabilityVerdict: Needs human review This PR introduces a major new feature (subagent activity visibility) with database migrations, complex orchestration logic, and new UI components. Multiple high and medium severity bugs remain unresolved in review comments, including issues with stop actions potentially targeting the wrong thread. You can customize Macroscope's approvability policy. Learn more. |


Summary
This branch turns Codex subagent work into first-class, routeable child threads instead of mixing every child output item into the parent timeline. Parent conversations now show compact subagent activity blocks, while each child thread owns its prompt, output, tool activity, file changes, and nested child summaries.
The implementation is Codex-scoped. Providers without durable child-thread lineage continue using the previous fallback behavior.
Fixes #538.
What Changed
SUBAGENTS.mdto document the implemented behavior, provider scope, decisions, verification, and remaining hardening areas.Why
Subagent output currently becomes very jumbled and impossible to follow when child work, tool calls, diffs, and parent activity all render in the same parent conversation stream. This change gives subagent activity proper visibility: the parent remains readable, active child work is discoverable, and detailed child output is still reachable in its own thread.
Validation
pnpm exec vp checkpassed. It reported 20 existing lint warnings in unrelated mobile/markdown/command-palette files and 0 errors.pnpm exec vp run typecheckpassed across all 15 packages.pnpm --filter @t3tools/web test -- src/session-logic.test.ts src/subagentDisplay.test.tsx src/components/chat/MessagesTimeline.test.tsxpassed: 122 test files, 1113 tests.pnpm --filter @t3tools/client-runtime test -- src/state/threadReducer.test.tspassed: 34 test files, 229 tests.vp test runcommands stayed silent for several minutes and were interrupted instead of being counted as passing:pnpm --filter t3 test -- src/orchestration/Layers/ProviderRuntimeIngestion.test.ts src/provider/Layers/CodexAdapter.test.ts src/orchestration/decider.delete.test.ts src/persistence/Migrations/034_BackfillEmptyProjectionThreadRootIds.test.ts src/orchestration/Layers/ProviderCommandReactor.test.ts src/orchestration/Layers/ProjectionSnapshotQuery.test.tspnpm --filter t3 test -- src/persistence/Migrations/034_BackfillEmptyProjectionThreadRootIds.test.ts src/orchestration/Layers/ProjectionSnapshotQuery.test.tsProof
subagents.mp4
Note
High Risk
Large cross-cutting changes to orchestration persistence, Codex event routing, thread lifecycle cascades, and conversation UI; mistakes could mis-route child work or delete/archive the wrong threads.
Overview
Codex subagents become real child threads with persisted
parentRelationmetadata, instead of mixing child output into the parent timeline. Parents show compact Subagent summary blocks; full prompts, tools, diffs, and nested children live only on the child route.Server/orchestration: New projection columns and migrations store root/parent ids, provider child thread ids, depth, status, and timestamps.
ProviderRuntimeIngestioncreates or updates child threads from Codex collab lifecycle items, generates titles from title seeds, appends raw launch prompts viathread.message.user.append, handles resume as new parent blocks, and derives terminal status from child turns. Upserts preserve existing subagent relations. Archive/delete on a root thread cascades to active descendants (deepest first). Subagent stop/interrupt targets the provider-bound root session while updating child status.Codex provider: Session runtime maps receivers to deterministic local child thread ids, attaches
subagentChildrenon collab items, routes child notifications to child threads, and buffers child message deltas until collab completion.Web: Sidebar nests running subagents under parents and hides terminal children unless that path is selected. Child chats replace the composer with a stop-only Subagent control bar and add parent navigation in the header. Subagent threads cannot be archived/deleted independently from the UI.
Reviewed by Cursor Bugbot for commit f744946. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add subagent thread visibility with parent relationship tracking, status display, and cascading lifecycle operations
OrchestrationThreadParentRelationto the contracts layer, propagated through event payloads, the read model, projection DB columns (migrations 33/34), and the client-side thread reducer so every layer knows whether a thread is a root or subagent child.collab_agent_tool_callitem, emittingitem.updated(in-progress) and attaching aggregated output toitem.completed; session runtime routes collab notifications under deterministic subagent child thread IDs.ProviderRuntimeIngestiondetects subagent children in item events, creates synthetic thread shells, dispatchesthread.create/thread.meta.update, and optionally generates titles viaTextGeneration.ProviderCommandReactorroutes interrupt requests for subagent turns through the provider-bound root thread and marks the child thread stopped.SubagentControlBarfor active subagent threads, showing a live/terminal duration and a Stop button; the header gains an 'Open parent conversation' button.MessagesTimelinerendersSubagentWorkEntryButtonrows per child with live status/duration labels that navigate to the child thread on click.thread.deleteandthread.archiveto subagent descendants in depth-first order before acting on the parent;useThreadActionsapplies the same cascade on the client.projection_threads; migration 034 backfillsroot_thread_idfor existing rows, which may be slow on large databases.📊 Macroscope summarized f744946. 24 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.