Skip to content

feat(mship): add parallel subagents, improve streaming performance#5122

Merged
Sg312 merged 13 commits into
stagingfrom
dev
Jun 18, 2026
Merged

feat(mship): add parallel subagents, improve streaming performance#5122
Sg312 merged 13 commits into
stagingfrom
dev

Conversation

@Sg312

@Sg312 Sg312 commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds parallel subagents to the mothership, and also improves streaming performance
Companion: https://github.com/simstudioai/copilot/pull/320

Type of Change

  • New feature

Testing

Manual

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@Sg312 Sg312 changed the title Dev feat(mship): add parallel subagents, improve streaming performance Jun 17, 2026
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 18, 2026 6:50am

Request Review

@github-actions github-actions Bot added the requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep label Jun 17, 2026
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

⚠️ Cross-repo companion check

One or more companion PRs aren't merged into staging yet. Merging this without them will leave copilot and sim out of sync — merge them in lockstep.

  • simstudioai/copilot#320OPEN, not merged (targets staging) — feat(subagents): add parallel subagents and improve checkpoint performance

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the mutable ContentBlock[] streaming state with a normalized TurnModel (a Map-based node graph) as the single source of truth, using a serialization bridge to convert back to ContentBlock[] for rendering and persistence. It also adds concurrent per-subagent checkpoint resume (driveSubagentChains) and per-channel scoping of file-write intents and preview sessions.

  • turn-model.ts / turn-model-serialize.ts: New reducer and serialization bridge; all handlers become side-effect-only.
  • lifecycle/run.ts: Adds driveSubagentChains for concurrent per-subagent checkpoint fan-out with failure isolation via a scoped AbortController.
  • file-preview-adapter.ts / file-intent-store.ts: activeFileIntent scalar replaced with a Map<channelId, ActiveFileIntent> so concurrent file subagents maintain isolated preview state.

Confidence Score: 3/5

The core streaming refactor is architecturally sound, but a gap in the edit_content alias reconstruction after a mid-operation reconnect can silently skip preview cleanup and file-resource promotion, and synthetic seq values from contentBlocksToModel can cause live events to be dropped on resume.

Two independent gaps both involve the reconnect path: the edit_content alias isn't rebuiltable from a snapshot when the cursor falls between the call and result events; and contentBlocksToModel sets model.lastSeq = N so live events with wire seq <= N are silently swallowed.

turn-model.ts (lastSeq in contentBlocksToModel), handle-tool-event.ts (edit_content alias gap), handle-span-event.ts (isNewLane seq comparison)

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.ts New deterministic TurnModel reducer. Solid architecture but synthetic seq values in contentBlocksToModel can interfere with the lastSeq idempotency guard on reconnect.
apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model-serialize.ts New serialization bridge between TurnModel and ContentBlock[]. Correctly handles subagent nesting, edit_content merge, and endedAt-vs-subagent_end duality.
apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.ts Simplified to side-effects-only; contains the edit_content alias gap on mid-operation reconnect that can cause preview sessions to remain open and file resources to be unpromoted.
apps/sim/app/workspace/[workspaceId]/home/hooks/stream/stream-context.ts StreamLoopState simplified to a single TurnModel; flush now serialises via modelToContentBlocks. Reconnect rebuild via contentBlocksToModel is correct for most paths.
apps/sim/lib/copilot/request/lifecycle/run.ts Adds concurrent per-subagent checkpoint resume. Failure isolation via AbortController fan-out and per-leg error arrays is well-reasoned.
apps/sim/lib/copilot/request/go/file-preview-adapter.ts Replaces single activeFileIntent slot with a per-channel Map; correctly scopes workspace_file/edit_content intent pairing to one file subagent lane.
apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-span-event.ts Stripped to side-effects-only. isNewLane detection is fragile when parsed.seq is undefined.
apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx React key stability improved with stable ordinal counters for text segments and canonical dispatch-tool-call-id for agent group keys.
apps/sim/lib/copilot/request/types.ts ResumeContinuation/ResumeFrame extracted as named types; subagentThinkingBlocks and activeFileIntents migrated to per-channel Maps.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Go as Go Stream
    participant Dispatch as dispatchStreamEvent
    participant Model as TurnModel (reduceEvent)
    participant Handler as Event Handler
    participant React as React / QueryClient

    Go->>Dispatch: PersistedStreamEventEnvelope
    Dispatch->>Model: reduceEvent(model, envelope)
    Model-->>Dispatch: mutated model
    Dispatch->>Handler: handleToolEvent / handleSpanEvent
    Handler->>React: invalidateQueries, promoteFileResource
    Handler->>React: "ops.flush() => modelToContentBlocks"

    Note over Go,React: Per-subagent concurrent resume
    Go->>Dispatch: checkpoint_pause (frames[])
    Handler->>Handler: driveSubagentChains (fan-out)
    par Child leg A
        Handler->>Go: POST /resume (checkpointId A)
        Go-->>Handler: subagent A events
    and Child leg B
        Handler->>Go: POST /resume (checkpointId B)
        Go-->>Handler: subagent B events
    end
    Handler->>Go: POST /resume (join continuation)
    Go-->>Handler: orchestrator stream
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Go as Go Stream
    participant Dispatch as dispatchStreamEvent
    participant Model as TurnModel (reduceEvent)
    participant Handler as Event Handler
    participant React as React / QueryClient

    Go->>Dispatch: PersistedStreamEventEnvelope
    Dispatch->>Model: reduceEvent(model, envelope)
    Model-->>Dispatch: mutated model
    Dispatch->>Handler: handleToolEvent / handleSpanEvent
    Handler->>React: invalidateQueries, promoteFileResource
    Handler->>React: "ops.flush() => modelToContentBlocks"

    Note over Go,React: Per-subagent concurrent resume
    Go->>Dispatch: checkpoint_pause (frames[])
    Handler->>Handler: driveSubagentChains (fan-out)
    par Child leg A
        Handler->>Go: POST /resume (checkpointId A)
        Go-->>Handler: subagent A events
    and Child leg B
        Handler->>Go: POST /resume (checkpointId B)
        Go-->>Handler: subagent B events
    end
    Handler->>Go: POST /resume (join continuation)
    Go-->>Handler: orchestrator stream
Loading

Comments Outside Diff (1)

  1. apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.ts, line 248-264 (link)

    P1 edit_content result side effects dropped on mid-operation reconnect

    After a reconnect where the cursor is positioned after the edit_content call event but before its result, the edit_content → workspace_file alias in TurnModel is never rebuilt from the snapshot (contentBlocksToModel serialises only the merged workspace_file block, not the edit_content call). When the live edit_content result subsequently arrives, resolveToolId(state.model, rawId) returns the bare edit_content_id (no alias), state.model.nodes.get(edit_content_id) is undefined, and runToolResultSideEffects is never called — leaving the file preview session open and the file resource un-promoted until the turn terminal sweeps all nodes to a final status.

Reviews (1): Last reviewed commit: "remove debug logs" | Re-trigger Greptile

@cursor

cursor Bot commented Jun 17, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large refactor of the live streaming and message projection path with broad test coverage, but regressions in subagent nesting, reconnect, or spinner/state would be highly visible in core chat UX.

Overview
Refactors mothership streaming so reduceEvent folds wire events into a TurnModel, then flush serializes to contentBlocks—stream handlers mostly run side effects (previews, resources, client workflow tools) instead of mutating block arrays. Reconnect rehydrates the model from snapshots via contentBlocksToModel.

Parallel subagents and rendering: span-tree parsing gets stable React keys (dispatch parentToolCallId), separate concurrent lanes with interleaved text, parent delegating cleared once a child span attaches, and persisted endedAt so reloaded transcripts close lanes without stuck spinners. isAgentGroupResolved drives subagent spinners/expansion from lane state (not isStreaming gating). deriveMessagePhase / trailing reveal callbacks gate “between steps” thinking and message actions until text finishes revealing.

Chat performance: useDeferredValue on the message list, auto-scroll while the last row is still animating, and pinned last virtual row to avoid remount flashes.

Transcript parity: foldFileWriteBlocks on load merges persisted edit_content into workspace_file rows; effective-transcript replay uses scope-only subagent attribution (no legacy active-subagent fallback).

Turn terminals propagate via applyTurnTerminal (stragglers settle as success/cancelled/error, not interrupted); residual block finalization still stamps open subagent lanes on error paths.

Reviewed by Cursor Bugbot for commit ed9d5ba. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ed9d5ba. Configure here.

@Sg312 Sg312 merged commit e5f3965 into staging Jun 18, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants