[ENHANCEMENT] Support Fork-Join parallel modes with per-view state isolation
Problem
Issue #568's Fork-Join Agent Orchestration requires multiple modes to run simultaneously (fork phase), then aggregate results (join phase). The current view-local state isolation (PR #909) may affect fork-join phase data sharing, because each mode instance has its own independent viewLocalState buffer.
Evidence
Code locations:
ClineProvider.ts:
viewLocalState: Partial<ExtensionState> — per-instance state buffer
getState(): merges viewLocalState on top of global state
- Comment: "parallel tabs within the same extension host share memory by design"
ClineProvider.parallelMode.spec.ts: Tests view-local isolation, but NOT fork-join scenario
Fork-Join workflow (from #568):
1. Fork Phase: [Research Mode] + [Ask Mode] + [Debug Mode] run SIMULTANEOUSLY
- Each mode gathers independent data/context
2. Join Phase: [Architect Mode] receives all 3 datasets
- Aggregates results to build comprehensive plan
Current test coverage:
- ✅ View-local isolation:
ClineProvider.parallelMode.spec.ts — tests state isolation between parallel tabs
- ❌ Fork-Join: No design/test for multi-mode orchestration with view-local state
Root Cause
PR #909's view-local state isolation design:
// ClineProvider.ts (simplified)
class ClineProvider {
private viewLocalState: Partial<ExtensionState> = {}
getState() {
return { ...globalState, ...this.viewLocalState } // Local overrides global
}
}
When fork-join orchestration occurs:
- Fork Phase: 3 modes run simultaneously (Research, Ask, Debug)
- Each mode is a separate ClineProvider instance (different tabs or sub-agents)
- Each has independent viewLocalState buffer
- Join Phase: Architect Mode aggregates results from all 3 modes
- But each mode's output may be affected by its own viewLocalState
- Example: Research mode uses "code" mode settings, Ask mode uses "architect" settings
- Results are inconsistent because different modes have different configurations
Problem: Fork-join phase needs consistent state across all forked modes, but PR #909's view-local isolation makes each mode independent.
Proposed Fix
Option A: Fork-Join aware orchestration (recommended)
// In Orchestrator.ts — detect fork-join pattern and use shared state
class Orchestrator {
async executeForkJoinPhase(
forkModes: Mode[], // [Research, Ask, Debug]
joinMode: Mode // Architect
): Promise<Plan> {
// Step 1: Fork — run all modes with SHARED state (not view-local)
const sharedState = this.captureGlobalState() // Use global state, not viewLocalState
const forkResults = await Promise.all(
forkModes.map(mode =>
mode.execute({ ...sharedState, isForkPhase: true })
)
)
// Step 2: Join — aggregate results with joinMode
return joinMode.aggregate(forkResults, sharedState)
}
}
// In ClineProvider.ts — detect fork-join and use global state
async executeWithForkJoinAwareness(): Promise<void> {
if (this.isInForkPhase()) {
// Use global state for consistency across forked modes
const state = this.globalState // Not viewLocalState!
await this.executeMode(state)
} else {
// Normal mode: use view-local isolation
const state = this.getState() // Merged (viewLocal + global)
await this.executeMode(state)
}
}
Option B: Explicit state sharing via message passing
// In Orchestrator.ts — pass shared context to all forked modes
async executeForkJoinPhase(forkModes: Mode[]): Promise<Plan> {
// Create shared context (not view-local)
const sharedContext = {
mode: this.globalState.mode, // Global, not viewLocal
apiConfigName: this.globalState.currentApiConfigName,
// ... other fields needed for consistency
}
// Pass sharedContext to all forked modes via message passing
const results = await Promise.all(
forkModes.map(mode =>
mode.execute({ context: sharedContext })
)
)
return this.joinPhase(results)
}
// In ClineProvider.ts — handle shared context from orchestrator
case 'forkJoinExecute': {
const { context } = payload
// Override viewLocalState with shared context for fork phase
await this.setForkPhaseContext(context)
break
}
Option C: Hybrid approach (view-local + shared override)
- Fork phase uses global state (consistent across all modes)
- Join phase uses view-local state (Architect mode's own settings)
- Simple but limits flexibility: forked modes can't have custom configurations
Scope
Related Issues
Testing Requirements
-
Add test in ClineProvider.parallelMode.spec.ts:
it("fork-join orchestration uses consistent state across forked modes", async () => {
// Create 3 ClineProvider instances (Research, Ask, Debug)
// Each has different viewLocalState (different modes/apiConfigs)
// Execute fork-join phase with shared context
// Verify all forked modes use SAME state (not their individual viewLocalState)
})
it("join mode uses its own view-local settings", async () => {
// Fork phase: 3 modes run with shared global state
// Join phase: Architect mode runs with its own viewLocalState
// Verify join mode can have custom configuration (not forced to use fork's shared state)
})
-
Test fork-join with different viewLocalState configurations across forked modes
-
Test concurrent fork-join execution from multiple tabs
-
Test that fork phase uses global state, join phase uses view-local state (Option A)
Notes
[ENHANCEMENT] Support Fork-Join parallel modes with per-view state isolation
Problem
Issue #568's Fork-Join Agent Orchestration requires multiple modes to run simultaneously (fork phase), then aggregate results (join phase). The current view-local state isolation (PR #909) may affect fork-join phase data sharing, because each mode instance has its own independent
viewLocalStatebuffer.Evidence
Code locations:
ClineProvider.ts:viewLocalState: Partial<ExtensionState>— per-instance state buffergetState(): merges viewLocalState on top of global stateClineProvider.parallelMode.spec.ts: Tests view-local isolation, but NOT fork-join scenarioFork-Join workflow (from #568):
Current test coverage:
ClineProvider.parallelMode.spec.ts— tests state isolation between parallel tabsRoot Cause
PR #909's view-local state isolation design:
When fork-join orchestration occurs:
Problem: Fork-join phase needs consistent state across all forked modes, but PR #909's view-local isolation makes each mode independent.
Proposed Fix
Option A: Fork-Join aware orchestration (recommended)
Option B: Explicit state sharing via message passing
Option C: Hybrid approach (view-local + shared override)
Scope
Related Issues
fix(webview): isolate parallel provider view stateTesting Requirements
Add test in
ClineProvider.parallelMode.spec.ts:Test fork-join with different viewLocalState configurations across forked modes
Test concurrent fork-join execution from multiple tabs
Test that fork phase uses global state, join phase uses view-local state (Option A)
Notes