Skip to content

feat(web): hide subagent threads from user-facing thread lists (5/5) - #4664

Open
shivamhwp wants to merge 6 commits into
subagent-obs/04-agents-panelfrom
subagent-obs/05-thread-visibility
Open

feat(web): hide subagent threads from user-facing thread lists (5/5)#4664
shivamhwp wants to merge 6 commits into
subagent-obs/04-agents-panelfrom
subagent-obs/05-thread-visibility

Conversation

@shivamhwp

@shivamhwp shivamhwp commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Last of five stacked PRs replacing #4551. Stacked on #4663 — review that first.

A subagent's child thread is an implementation detail of the agent that owns it, not a conversation the user started. Listing them alongside real threads makes the sidebar grow with every delegation.

Filtered at the source — the shell snapshot the server serves — so every client sees the same set rather than each re-deriving it. The web client filters again for locally-held state, and the thread route redirects to the parent if a subagent thread is reached directly.

Mobile is part of the sweep

Found in review, and the reason this isn't a web-only change. Mobile still offered "Open subagent thread" with no redirect to fall back on, so it would have navigated to a thread the client no longer receives. Its relationships banner also needed the same subagent-edge filter web has — those edges derive from the projection rather than from thread shells, so they survive the shell filtering and would have rendered as "Unavailable", and by sorting first could have taken over the banner headline.

The trade-off, stated plainly

A subagent's transcript becomes unreachable. The Agents panel from #4663 shows what an agent did and what it returned, but not its full conversation. That is the intended shape here, and the "Open subagent thread" affordances are removed on both clients rather than left as dead ends — but it is a real capability loss and worth a deliberate decision rather than being smuggled in with a sidebar tidy-up.

Testing

Typecheck clean on server, web, client-runtime. Server 1,685 passing, web 1,500 passing, client-runtime 485 passing. (apps/mobile has 70 pre-existing typecheck errors on the base branch, unchanged by this PR.)

Known, not fixed here

  • The route's redirect-to-parent is unreachable against a server that also filters, because the shell is already absent and the earlier "missing thread" effect wins — a deep link to a subagent thread lands on / rather than the parent. It matters under version skew, so it is kept.
  • entities.ts swaps memoized ref atoms for per-call filter+map. The cost is negligible but the identity stability is not; filtering inside the client-runtime atoms instead would be the better shape.
  • Each shell event on a subagent thread now emits a spurious thread.removed delta, absorbed harmlessly by the reducer.

Note

Hide subagent threads from all user-facing thread lists and routes

  • Adds isInternalSubagentThread (server) and a matching client-side predicate in threadVisibility.ts that classify threads as internal subagents when their lineage is subagent or they are forked from a node.
  • Filters internal subagent threads from HTTP and WebSocket shell snapshot responses, sidebar hooks, and entity selectors so they never appear in thread lists.
  • Redirects users who navigate directly to a subagent thread URL to the parent thread (or Home), on both web and mobile.
  • Removes UI affordances for opening subagent threads from the item inspector, lifecycle rows, work log, and thread relationships panels.
  • Risk: useThreadShells, useThreadRefs, and related hooks now return a filtered set; any caller that needs the full set must use the new readEnvironmentAllThreadRefs.

Macroscope summarized d5f9b62.


Note

Medium Risk
Changes thread visibility and routing across server snapshots, WebSocket streams, and both clients; worktree deletion logic now depends on the full thread set via readEnvironmentAllThreadRefs. Low security impact but moderate product/behavior risk if predicates or filters miss edge cases.

Overview
Internal subagent threads (lineage subagent or forked from a node) are treated as implementation detail and removed from what users see in lists, snapshots, and navigation—while the server can still hold them for worktree/orchestration bookkeeping.

The server applies userFacingShellSnapshot on HTTP and WebSocket shell loads and drops internal subagent deltas from live shell streams. isInternalSubagentThread / isUserFacingThread centralize the rule on web (threadVisibility.ts) and align list APIs and auto-bootstrap thread selection.

Web filters thread refs and shells in entities.ts, adds readEnvironmentAllThreadRefs for worktree orphan checks that must include hidden subagents, redirects the chat route to the parent thread, strips subagent edges from relationship panels, and removes “open subagent thread” from timeline, inspector, and work-log links. Mobile mirrors redirect, relationship filtering, and work-log link removal.

Trade-off: full subagent transcripts are no longer reachable via thread navigation; activity UI still shows subagent work without opening the child thread.

Reviewed by Cursor Bugbot for commit d5f9b62. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 27, 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 Plus

Run ID: fe2117a6-dab6-4df8-91a3-c44f5ea1f00c

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
  • Commit unit tests in branch subagent-obs/05-thread-visibility

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.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 27, 2026
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 940e8fb to c3a50f8 Compare July 27, 2026 20:20
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from d2ef6c7 to adb1e9b Compare July 27, 2026 20:20
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from c3a50f8 to 8c6a796 Compare July 27, 2026 20:52
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from adb1e9b to 39912b0 Compare July 27, 2026 20:52
Comment thread apps/web/src/components/chat/ThreadRelationshipsControl.tsx
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 8c6a796 to 103b937 Compare July 27, 2026 23:03
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 39912b0 to 6bc3a8d Compare July 27, 2026 23:03
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 103b937 to 31adb36 Compare July 28, 2026 15:31
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 6bc3a8d to 05fe44c Compare July 28, 2026 15:31
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 31adb36 to b270c47 Compare July 28, 2026 19:27
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 05fe44c to 7458298 Compare July 28, 2026 19:27
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from b270c47 to daf1a88 Compare July 28, 2026 19:46
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 7458298 to 4daeac4 Compare July 28, 2026 19:46
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from daf1a88 to 1dce464 Compare July 28, 2026 20:26
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 4daeac4 to 095cab7 Compare July 28, 2026 20:26
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 1dce464 to 4c169ed Compare July 29, 2026 01:19
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 095cab7 to 1d37a6f Compare July 29, 2026 01:19
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 4c169ed to 70d5111 Compare July 29, 2026 01:24
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 1d37a6f to 6cf67a2 Compare July 29, 2026 01:25
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from 70d5111 to c78d0ff Compare July 30, 2026 00:00
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 6cf67a2 to 5e3c535 Compare July 30, 2026 00:00
shivamhwp and others added 4 commits July 30, 2026 06:14
A subagent's child thread is an implementation detail of the agent that
owns it, not a conversation the user started. Listing them alongside real
threads makes the sidebar grow with every delegation.

Filtered at the source — the shell snapshot the server serves — so every
client sees the same set rather than each re-deriving it. The web client
filters again for locally-held state, and the thread route redirects to
the parent if a subagent thread is reached directly.

Mobile is part of the sweep, not left behind. It still offered "Open
subagent thread" and had no redirect to fall back on, so it would have
navigated to a thread the client no longer receives. Its relationships
banner also needed the same subagent-edge filter web has: those edges are
derived from the projection rather than from thread shells, so they
survive the shell filtering and would have rendered as "Unavailable" —
and, sorting first, could have taken over the banner headline.

Trade-off worth stating plainly: a subagent's transcript becomes
unreachable. The Agents panel added in the previous PR shows what an
agent did and what it returned, but not its full conversation. That is
the intended shape here, and the "Open subagent thread" affordances are
removed on both clients rather than left as dead ends.
Removing the "Open subagent thread" affordance left the mobile work log
subscribing to item support it no longer reads, and the web sidebar
importing a predicate it no longer calls. Both tripped the lint rule the
CI Check job enforces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@shivamhwp
shivamhwp force-pushed the subagent-obs/04-agents-panel branch from c78d0ff to a997806 Compare July 30, 2026 00:45
@shivamhwp
shivamhwp force-pushed the subagent-obs/05-thread-visibility branch from 5e3c535 to 2430736 Compare July 30, 2026 00:45
@shivamhwp
shivamhwp marked this pull request as ready for review July 30, 2026 20:04
Comment thread apps/web/src/state/entities.ts
@macroscopeapp

macroscopeapp Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This feature PR has critical unresolved review comments identifying that worktree orphan detection may incorrectly delete worktrees still in use by hidden subagent threads, plus an inconsistent redirect predicate in mobile. These potential bugs require human review before merging.

You can customize Macroscope's approvability policy. Learn more.

Worktree bookkeeping now reads every thread in the environment, so
deleting a parent no longer flags a worktree as orphaned while hidden
subagent threads still hold it. Mobile thread routes redirect internal
subagent threads to their parent instead of rendering the hidden
transcript, matching the web route guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/mobile/src/features/threads/ThreadRouteScreen.tsx
// Internal subagent threads are hidden from user-facing lists; a deep
// link or stale selection still lands here, so send it to the parent
// thread instead of exposing the hidden transcript.
if (selectedThread.lineage.relationshipToParent === "subagent") {

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.

🟡 Medium threads/ThreadRouteScreen.tsx:185

The subagent redirect only checks selectedThread.lineage.relationshipToParent === "subagent", but internal subagent threads also include node-fork threads (forkedFrom.type === "node"). When a deep link or stale selection resolves to a node-fork thread, this branch is skipped and ThreadRouteContent renders the hidden transcript instead of redirecting to the parent. The guard should match the same predicate used elsewhere (e.g., isInternalSubagentThread) so node-fork threads are redirected as well.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/threads/ThreadRouteScreen.tsx around line 185:

The subagent redirect only checks `selectedThread.lineage.relationshipToParent === "subagent"`, but internal subagent threads also include node-fork threads (`forkedFrom.type === "node"`). When a deep link or stale selection resolves to a node-fork thread, this branch is skipped and `ThreadRouteContent` renders the hidden transcript instead of redirecting to the parent. The guard should match the same predicate used elsewhere (e.g., `isInternalSubagentThread`) so node-fork threads are redirected as well.

/** Every thread in the environment, including internal subagent threads.
Worktree bookkeeping must see all threads that can hold a worktree
reference, or deleting a parent reads its subagents' worktree as orphaned. */
export function readEnvironmentAllThreadRefs(

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.

🔴 Critical state/entities.ts:229

readEnvironmentAllThreadRefs reads environmentThreadShells.threadShellsAtom, but that snapshot no longer contains internal subagent shells after the server-side filtering change. The function therefore returns only user-facing threads, not "every thread in the environment" as its doc comment claims. In deleteThread, the survivingThreads list passed to getOrphanedWorktreePathForThread is built from this filtered set, so a worktree still referenced by a hidden subagent is classified as orphaned and can be force-removed — destroying a worktree that remains in use, including any uncommitted contents. The source of truth for all shells (including internal subagents) needs to be used here, or the worktree-orphan check needs to consult a source that still includes subagent threads.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/state/entities.ts around line 229:

`readEnvironmentAllThreadRefs` reads `environmentThreadShells.threadShellsAtom`, but that snapshot no longer contains internal subagent shells after the server-side filtering change. The function therefore returns only user-facing threads, not "every thread in the environment" as its doc comment claims. In `deleteThread`, the `survivingThreads` list passed to `getOrphanedWorktreePathForThread` is built from this filtered set, so a worktree still referenced by a hidden subagent is classified as orphaned and can be force-removed — destroying a worktree that remains in use, including any uncommitted contents. The source of truth for all shells (including internal subagents) needs to be used here, or the worktree-orphan check needs to consult a source that still includes subagent threads.

…into stack-05

# Conflicts:
#	apps/server/src/orchestration-v2/ThreadManagementService.test.ts
#	apps/server/src/ws.ts

@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 1 potential issue.

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 d5f9b62. Configure here.

? allThreads.filter(
(entry) => entry.id === threadRef.threadId || !deletedIds.has(entry.id),
)
: allThreads;

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.

Orphan worktree misses hidden subagents

High Severity

The filtering of non-user-facing threads from the client-side threadShellsAtom means readEnvironmentAllThreadRefs no longer includes hidden subagents. This leads to inaccurate worktree orphan detection during thread deletion, where the UI might offer to delete worktrees still in use by active subagents.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d5f9b62. Configure here.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant