Skip to content

Add subagent activity visibility#3176

Open
Quicksaver wants to merge 12 commits into
pingdotgg:mainfrom
Quicksaver:split/subagent-threading-work
Open

Add subagent activity visibility#3176
Quicksaver wants to merge 12 commits into
pingdotgg:mainfrom
Quicksaver:split/subagent-threading-work

Conversation

@Quicksaver

@Quicksaver Quicksaver commented Jun 19, 2026

Copy link
Copy Markdown

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.

What Changed

  • Added persisted projection-thread parent relations with root thread ids, direct parent thread ids, parent activity metadata, provider child thread ids, depth, start/completion timestamps, and subagent status.
  • Added migrations and backfill coverage for projection-thread parent/root relation fields and lookup indexes.
  • Extended Codex adapter and provider ingestion to map Codex child sessions into deterministic local child thread ids, preserve raw child prompts separately from title seeds, append launch prompts into child timelines, and update resumed child work as new parent activity blocks.
  • Added orchestration safeguards so normal root/default projection upserts do not overwrite existing subagent relations.
  • Added subagent lifecycle handling for terminal status updates, stop/interrupt targeting, and root archive/delete cascades through active descendant subagent threads.
  • Updated parent timeline rendering so child output/actions no longer jumble into the parent view; parent rows render compact subagent blocks with status and duration.
  • Updated child thread routing so hidden terminal subagent threads remain openable from parent blocks and show a subagent control bar instead of the normal composer.
  • Updated sidebar rendering so running subagents nest under their direct parent, terminal subagents normally hide from the sidebar, and the active terminal-child path remains visible while selected.
  • Added shared subagent display helpers for status tones, duration labels, and active/terminal wording.
  • Added web/client-runtime tests for subagent timeline rendering, duplicate parent rows, child composer suppression, duration labels, and retained stream-backed thread state.
  • Added SUBAGENTS.md to 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 check passed. It reported 20 existing lint warnings in unrelated mobile/markdown/command-palette files and 0 errors.
  • pnpm exec vp run typecheck passed across all 15 packages.
  • pnpm --filter @t3tools/web test -- src/session-logic.test.ts src/subagentDisplay.test.tsx src/components/chat/MessagesTimeline.test.tsx passed: 122 test files, 1113 tests.
  • pnpm --filter @t3tools/client-runtime test -- src/state/threadReducer.test.ts passed: 34 test files, 229 tests.
  • Attempted focused server test slices, but both server vp test run commands 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.ts
    • pnpm --filter t3 test -- src/persistence/Migrations/034_BackfillEmptyProjectionThreadRootIds.test.ts src/orchestration/Layers/ProjectionSnapshotQuery.test.ts

Proof

Screenshot 2026-06-13 at 18 48 53
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 parentRelation metadata, 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. ProviderRuntimeIngestion creates or updates child threads from Codex collab lifecycle items, generates titles from title seeds, appends raw launch prompts via thread.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 subagentChildren on 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

  • Introduces OrchestrationThreadParentRelation to 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.
  • The Codex provider adapter buffers child subagent message deltas keyed to their parent collab_agent_tool_call item, emitting item.updated (in-progress) and attaching aggregated output to item.completed; session runtime routes collab notifications under deterministic subagent child thread IDs.
  • ProviderRuntimeIngestion detects subagent children in item events, creates synthetic thread shells, dispatches thread.create/thread.meta.update, and optionally generates titles via TextGeneration.
  • ProviderCommandReactor routes interrupt requests for subagent turns through the provider-bound root thread and marks the child thread stopped.
  • Sidebar renders subagent threads indented under their parent using a flattened DFS tree; archive/delete actions are suppressed for child threads, and bulk delete blocks when any selection is a subagent.
  • Chat view replaces the composer with a SubagentControlBar for active subagent threads, showing a live/terminal duration and a Stop button; the header gains an 'Open parent conversation' button.
  • MessagesTimeline renders SubagentWorkEntryButton rows per child with live status/duration labels that navigate to the child thread on click.
  • Decider cascades thread.delete and thread.archive to subagent descendants in depth-first order before acting on the parent; useThreadActions applies the same cascade on the client.
  • Risk: migration 033 adds twelve columns and two indexes to projection_threads; migration 034 backfills root_thread_id for 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.

- 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
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e83f3142-8b11-4c09-a13a-c13c39bf2b65

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jun 19, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 6 potential issues.

Fix All in Cursor

❌ 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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f744946. Configure here.

completedAt: event.payload.createdAt,
status: "stopped",
},
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f744946. Configure here.

projectThreads.filter((thread) => thread.archivedAt === null),
threadSortOrder,
);
const visibleRootProjectThreads = rootSidebarThreads(visibleProjectThreads);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f744946. Configure here.

const eventTurnId = toTurnId(event.turnId);
const activeTurnId = thread.session?.activeTurnId ?? null;

const subagentChildren = readRuntimeSubagentChildren(event);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f744946. Configure here.


function subagentOutputBufferKey(threadId: ThreadId, itemId: string): string {
return `${threadId}\0${itemId}`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f744946. Configure here.

parentThreadId,
providerThreadId: receiverThreadId,
}),
rawPrompt: startsNewParentActivity ? rawPrompt : (rawPrompt ?? existing?.rawPrompt),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
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).

@macroscopeapp

macroscopeapp Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant