feat: run parallel agents in linked threads - #6488
Conversation
Type /parallel <prompt> in a thread's composer to spawn a linked sibling thread that works the sub-task concurrently with whatever provider/model the composer had selected, so independent parts of a task can run side by side instead of serializing through one agent. The originating thread records when each parallel agent starts and finishes (with its final response) so results surface back into the shared conversation, and each agent gets its own Git worktree so simultaneous edits don't collide. Adds an optional parentThreadId to the thread contracts/decider/projector, a migration for the new column, and the composer-side spawn/parse logic plus docs. Model + harness: Claude Sonnet 5 via Claude Code.
|
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:
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 |
| // Parallel agents get their own worktree off the parent's branch so | ||
| // simultaneous edits don't fight over one checkout. Without git there is | ||
| // no isolation to offer: the child shares the parent's workspace. | ||
| const worktreeBaseBranch = isGitRepo ? activeThreadBranch : null; |
There was a problem hiding this comment.
🟠 High components/ChatView.tsx:4903
/parallel runs the child in activeThread.worktreePath when isGitRepo is true but activeThreadBranch is null, so parent and child edits share a checkout instead of being isolated. Reject this case (as the ordinary send path does) before starting the child, or otherwise require a valid base branch for worktree creation.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 4903:
`/parallel` runs the child in `activeThread.worktreePath` when `isGitRepo` is true but `activeThreadBranch` is `null`, so parent and child edits share a checkout instead of being isolated. Reject this case (as the ordinary send path does) before starting the child, or otherwise require a valid base branch for worktree creation.
| if (parallelAgentPrompt !== null) { | ||
| const hasNonTextContent = | ||
| composerImages.length > 0 || | ||
| sendableComposerTerminalContexts.length > 0 || |
There was a problem hiding this comment.
🟡 Medium components/ChatView.tsx:5113
An expired terminal-context chip is ignored by the /parallel guard, so a successful spawn calls clearComposerDraftContent and silently deletes that context without the expired-context warning. Check composerTerminalContexts rather than only sendableComposerTerminalContexts so /parallel remains text-only and preserves contexts until explicitly removed.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 5113:
An expired terminal-context chip is ignored by the `/parallel` guard, so a successful spawn calls `clearComposerDraftContent` and silently deletes that context without the expired-context warning. Check `composerTerminalContexts` rather than only `sendableComposerTerminalContexts` so `/parallel` remains text-only and preserves contexts until explicitly removed.
| if (parentThread === undefined || parentThread.deletedAt !== null) { | ||
| return null; | ||
| } | ||
| let resultText: string | null = null; |
There was a problem hiding this comment.
🟡 Medium orchestration/decider.ts:216
The parent completion activity can omit or truncate detail even when the child produced assistant text. parallelAgentFinishedActivityEvent reads thread.messages from the pre-thread.session.set read model, but ProviderRuntimeIngestion dispatches that session update before finalizeAssistantMessage flushes buffered text; the later message update does not refresh the activity. Generate this activity after assistant finalization or source the finalized buffer.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/decider.ts around line 216:
The parent completion activity can omit or truncate `detail` even when the child produced assistant text. `parallelAgentFinishedActivityEvent` reads `thread.messages` from the pre-`thread.session.set` read model, but `ProviderRuntimeIngestion` dispatches that session update before `finalizeAssistantMessage` flushes buffered text; the later message update does not refresh the activity. Generate this activity after assistant finalization or source the finalized buffer.
| interactionMode: bootstrap.createThread.interactionMode, | ||
| branch: bootstrap.createThread.branch, | ||
| worktreePath: bootstrap.createThread.worktreePath, | ||
| parentThreadId: bootstrap.createThread.parentThreadId ?? null, |
There was a problem hiding this comment.
🟡 Medium src/ws.ts:908
The early thread.create dispatch records parallel-agent.started on the parent before worktree preparation, setup, and the final turn start succeed. If a later bootstrap step fails, cleanupCreatedThread deletes only the child, leaving the parent permanently reporting an agent that never started; defer parentThreadId propagation until bootstrap succeeds.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/ws.ts around line 908:
The early `thread.create` dispatch records `parallel-agent.started` on the parent before worktree preparation, setup, and the final turn start succeed. If a later bootstrap step fails, `cleanupCreatedThread` deletes only the child, leaving the parent permanently reporting an agent that never started; defer `parentThreadId` propagation until bootstrap succeeds.
| // Parallel agents get their own worktree off the parent's branch so | ||
| // simultaneous edits don't fight over one checkout. Without git there is | ||
| // no isolation to offer: the child shares the parent's workspace. | ||
| const worktreeBaseBranch = isGitRepo ? activeThreadBranch : null; |
There was a problem hiding this comment.
🟡 Medium components/ChatView.tsx:4903
/parallel creates the child from activeThreadBranch even when the local checkout has been switched externally, so the agent runs against the stale recorded branch instead of localCheckoutBranchMismatch.currentBranch. Reconcile the base branch used for both createThread.branch and prepareWorktree.baseBranch with the current checkout, as the normal send path does.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 4903:
`/parallel` creates the child from `activeThreadBranch` even when the local checkout has been switched externally, so the agent runs against the stale recorded branch instead of `localCheckoutBranchMismatch.currentBranch`. Reconcile the base branch used for both `createThread.branch` and `prepareWorktree.baseBranch` with the current checkout, as the normal send path does.
What Changed
Implements a minimal version of #5733: type
/parallel <prompt>in a thread's composer to spawna linked sibling thread that works the sub-task concurrently, using whatever provider/model the
composer had selected when sent. The originating thread records a
parallel-agent.startedactivity when the child spawns and a
parallel-agent.completed/interrupted/failedactivitywhen the child's turn settles (carrying its final assistant text), so results surface back into
the shared conversation. In Git projects, each parallel agent gets its own worktree branched from
the parent's current branch.
Adds an optional
parentThreadIdto the thread contracts, decider, and projector, a migration forthe new
projection_threads.parent_thread_idcolumn, the composer-side/parallelparsing andspawn flow, and user/internals docs.
Why
There's currently no way to run multiple agents on independent sub-tasks within the same thread —
everything serializes through one agent, and users have to open separate threads and manually
merge results, losing shared context. This follows the "smallest useful scope" suggested in the
issue: threads spawned as parallel agents, linked back to the thread that spawned them, with their
outcome surfaced into the shared conversation.
UI Changes
/parallelis now listed alongside/modelin the composer's slash-command menu. No screenshotsincluded — the change is additive to an existing menu and best seen live; happy to add
before/after captures if useful for review.
Checklist
Model + harness: Claude Sonnet 5 via Claude Code.
Note
Add parallel agent support to run child threads linked to a parent thread
/parallel <prompt>slash command in the chat composer that creates a child thread linked to the current thread as a parallel agentparentThreadIdto thread contracts, projections, and persistence (migration 041 addsparent_thread_idcolumn toprojection_threads)thread.activity-appendedevent (parallel-agent.completed|interrupted|failed) with a preview of the final assistant messageparent_thread_id = NULLafter migration; decoders handle absence for backwards compatibility📊 Macroscope summarized f0589cf. 16 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.