You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Task.ts line ~169-170interfaceTask{readonlyparentTaskId?: string// single parent onlychildTaskId?: string// single child only}// ClineProvider.ts — delegation metadatainterfaceHistoryItem{status: "delegated"|"active"| ...
delegatedToId: string// single childawaitingChildId: string// single childchildIds: string[]// array but only used for linear chain}
Current Delegation Flow (Linear)
delegateParentAndOpenChild():
1. Flush parent tool results to history
2. removeClineFromStack(parent) ← parent removed from stack
3. createTask(child, parent=parent) ← child created with parent reference
4. persist metadata: status="delegated", awaitingChildId=child.taskId
5. child.start()
reopenParentFromDelegation():
1. Load parent from history
2. Validate delegation state (status === "delegated")
3. Call resumeAfterDelegation() on parent
4. Parent continues task loop
Limitation: This is a stack-based LIFO model. Only ONE child can be active at a time. No parallelism, no tree structure.
Existing Fields That Support Tree (But Unused)
// Task.ts line ~169-170 — these ALREADY support tree!
readonly parentTaskId?: string// ← single parent ✓
childTaskId?: string// ← could be childIds: string[] for multiple children// ClineProvider.ts line 3533constchildIds=Array.from(newSet([...(historyItem.childIds??[]),child.taskId]))// ← childIds IS an array! But only used to track lineage, not execution order
Key Insight: The data model ALREADY supports tree structure. We just need to:
Change childTaskId → childIds: string[] in Task interface
Modify delegation flow to support multiple children (parallel + sequential)
Add nesting depth tracking + auto-flatten
Proposed Design: Task Tree Abstraction
1. New Data Model
// Task.ts — NEW fieldsinterfaceTask{// Existing (keep for backward compat)readonlyparentTaskId?: string// NEW: Tree structurechildIds: string[]// ← replaces single childTaskIdsiblingIds: string[]// ← children of same parentdepth: number// ← nesting level (root = 0)// NEW: Execution modeexecutionMode: "sequential"|"parallel"|"inline"maxDepth: number// ← per-task nesting limit// NEW: Checkpoint supportcheckpointEnabled: boolean// ← replaces #854lastCheckpointId?: string// ← checkpoint reference}// ClineProvider.ts — NEW tree stateinterfaceTaskTree{rootTaskId: string// ← always the user's original taskactiveTasks: Map<string,Task>// ← all active tasks (not just one)completedTasks: Set<string>// ← finished tasks// Tree operationsaddChild(parentId: string,child: Task): voidremoveChild(parentId: string,childId: string): voidgetChildren(taskId: string): Task[]getDepth(taskId: string): numberflattenExcess(taskId: string,maxDepth: number): void}
Note on #355: Issue #355 focuses on tool-level parallelism (parallel tool calls within ONE task). Task Tree's Parallel mode is task-level delegation (parent spawns multiple concurrent child tasks, each with its own conversation). These are different scopes and can coexist.
Parent conversation
└── [subtask executes inline]
→ tool_result injected into same conversation
→ Parent continues naturally
No tab switch, no new Task instance
Subtask runs in parent's conversation as a "virtual child"
Results injected as tool_results
Inline Mode Execution Mechanism (How It Actually Works)
Inline mode executes subtasks within the parent's existing conversation without creating a new Task instance or tab switch. But how does this actually work? The implementation needs to answer: when depth exceeds maxDepth and auto-flatten triggers, what mechanism runs the subtask?
Decision: Inline mode uses AI-driven execution (not direct tool calls).
Inline subtask flow:
1. Parent receives delegation request (depth > maxDepth → auto-flatten)
2. System constructs inline prompt:
┌─────────────────────────────────────────────┐
│ [System Prompt] │
│ [Parent's recent conversation history] │ ← Limited to last N messages
│ │
│ [INLINE SUBTASK CONTEXT] │
│ "You are now executing a subtask inline." │
│ Original goal: "{subtask message}" │
│ Parent context: {relevant parent state} │
│ │
│ Instructions: │
│ - Execute the subtask using available tools │
│ - When done, call attempt_completion with │
│ your result (no approval popup needed) │
│ - Your result will be injected into parent's │
│ conversation automatically │
└─────────────────────────────────────────────┘
3. AI agent processes the inline prompt → generates tool calls → executes
4. When AI calls attempt_completion:
- Result is captured (no approval popup)
- Result injected as tool_result into parent's conversation
- Parent resumes via resumeAfterDelegation()
Why AI-driven, not direct tool execution?
Approach
Pros
Cons
AI-driven (chosen)
Full reasoning capability; handles complex subtasks; consistent with existing delegation flow
Slightly more tokens (prompt overhead); one extra API call
Direct tool calls
Faster, fewer tokens; no prompt overhead
Loses AI reasoning; can't handle conditional logic; limited to simple operations
AI-driven is the right choice because: Inline subtasks are meant for complex work that needs AI reasoning (e.g., "fix the auth bug in middleware"). Direct tool execution would only work for trivial operations, which defeats the purpose of inline mode. The token overhead (~500-1000 tokens for prompt) is negligible compared to the actual subtask work.
Context management for inline prompts:
// Build inline prompt with bounded contextasyncfunctionbuildInlinePrompt(parent: Task,subtaskMessage: string): Promise<string>{// Include parent's recent history (bounded — don't flood the prompt)constrecentHistory=parent.apiConversationHistory.slice(-15)// Last ~15 messages// Extract relevant state from parent (not full conversation)constparentState=awaitextractParentContext(parent,{includeTodos: true,// Current todo listincludeRecentFiles: true,// Files recently edited by parentexcludeToolOutputs: true,// Skip verbose tool outputs to save tokens})return`[INLINE SUBTASK]You are executing a subtask inline within the parent conversation.Parent's current goal: ${parentState.currentGoal}Subtask instruction: ${subtaskMessage}Current todo status: ${parentState.todoStatus}Recently edited files: ${parentState.recentFiles.join(", ")}Execute this subtask using available tools. When complete, call attempt_completion with your result.Your result will be automatically injected into the parent conversation — no approval needed.`}
Error handling in inline mode:
// Inline error scenarios and how they're handled:// Scenario 1: Subtask AI fails to produce a valid result// → attempt_completion called with empty/error message// → Parent receives tool_result: "Subtask failed: {error}"// → Parent's resumeAfterDelegation() continues — parent can decide next steps// Scenario 2: Subtask AI enters infinite loop (tool call without completion)// → Same as regular task: API timeout or max turns limit kicks in// → Result injected as error tool_result to parent// → Parent resumes and handles the failure// Scenario 3: Inline subtask needs more context than available// → AI calls compress_context() first (same tools available as regular tasks)// → If still stuck, fails with "insufficient context" error// → Parent can decide whether to retry with different approach// Key point: Inline mode reuses the same error handling infrastructure as tab mode.// The only difference is no approval popup — errors are silently injected into parent.
ResumeAfterDelegation compatibility:
// resumeAfterDelegation() works identically for inline and tab modes:asyncfunctionresumeAfterDelegation(task: Task): Promise<void>{// 1. Clear ask states (already done by inline completion)task.clearAskStates()// 2. Reset abort flagstask.abortFlag=false// 3. Load conversation history (inline mode: same conversation, already loaded)awaittask.loadHistory()// 4. Continue task loop — parent sees the tool_result and proceeds naturallyawaittask.continueTaskLoop()}// The only difference: in tab mode, loadHistory() loads from storage.// In inline mode, history is already in memory (same conversation).// No state change needed — resumeAfterDelegation() still works correctly.
Inline Mode Escalation: Upgrading Back to Tab Mode
Problem: The current design makes inline mode permanent once triggered. But what if an inline subtask grows unexpectedly complex? For example, user says "fix the auth bug" (simple), but during execution the AI discovers it needs to update 3 different modules, add tests, and update docs — suddenly this is a large task that deserves its own tab for clarity.
Solution: Allow inline mode to escalate back to tab mode when conditions warrant it. This is one-way escalation (inline → tab), not bidirectional oscillation.
// Inline execution with optional escalationasyncfunctionexecuteInlineSubtask(parent: Task,message: string): Promise<void>{// 1. Start inline (no new tab)constresult=awaitrunInlineExecution(parent,message)// 2. Check if we should escalate back to tab modeif(shouldEscalateToTab(result)){// Create a NEW tab with the inline work so far as contextconstchild=awaitcreateTaskFromInline(parent,result)// Continue in new tab — depth stays same but now has its own conversation spaceawaitchild.start()return}// 3. Normal path: inject result into parent (no escalation needed)parent.say({type: "tool_result",text: `Subtask completed: ${result}`})}// Heuristics for when inline is no longer sufficientfunctionshouldEscalateToTab(result: InlineResult): boolean{// 1. Result contains multiple distinct sub-goals (e.g., "fix auth" turned into // "update middleware + add tests + update docs")if(countDistinctGoals(result)>config.inlineEscalation.minGoals)returntrue// 2. Inline conversation grew too large — context is getting unwieldyif(result.conversationTokens>config.inlineEscalation.tokenThreshold)returntrue// 3. User explicitly requests new tab during inline executionif(result.userRequestedTabSwitch)returntrue// 4. AI detects need for parallel work within this subtaskif(result.detectedParallelNeeds)returntruereturnfalse}// Create a new task from inline execution resultsasyncfunctioncreateTaskFromInline(parent: Task,result: InlineResult): Promise<Task>{constchild=awaitprovider.createTask(result.summary,undefined,parentasany,{initialTodos: extractTodosFromResult(result),mode: "sequential",startTask: false,depth: parent.depth+1,// Same depth as inline would have beenexecutionMode: "sequential",// Now in tab mode, not inline})// Inject the inline work so far as context for the new taskchild.apiConversationHistory=[{role: "user",content: `Continuing from inline subtask:\n${result.fullContext}`},
...child.apiConversationHistory,]returnchild}
When escalation triggers:
Scenario
Escalation?
Why
Subtask = "fix auth bug" (simple, 5 turns)
❌ No
Stays inline, inject result to parent
Subtask = "migrate DB + update API + fix tests" (complex, 30+ turns)
✅ Yes
Multiple distinct goals → needs own tab for clarity
User says "this is getting big, open a new task" during inline
✅ Yes
Explicit user request
Inline AI detects it needs to spawn parallel sub-subtasks
✅ Yes
Parallel work better in dedicated tab
VSCode settings:
{"zooCode.inlineEscalation.enabled": true,// Allow inline → tab upgrade"zooCode.inlineEscalation.tokenThreshold": 30000,// Escalate if tokens > this"zooCode.inlineEscalation.minGoals": 3,// Escalate if > N distinct goals}
Key constraints:
One-way only: Once escalated to tab mode, the task follows normal depth tracking. It cannot go back to inline unless it hits maxDepth again (which would trigger a new inline subtask). This prevents oscillation between modes.
No data loss: The inline conversation context is preserved and injected into the new tab as starting context. User sees what happened during inline execution.
Depth stays same: Escalation doesn't increment depth — it just moves existing work to a tab. If the task was at depth 3 (inline), the new tab also starts at depth 3, preserving the nesting limit logic.
// VSCode settings{"zooCode.maxNestingDepth": 2,// ← default: 2 levels"zooCode.autoFlattenOnLimit": true,// ← auto-switch to inline when limit reached"zooCode.parallelByDefault": false,// ← sequential by default for safety}
Auto-Flatten Logic
functionshouldAutoFlatten(task: Task): boolean{returntask.depth>=task.maxDepth&&config.autoFlattenOnLimit}// When auto-flatten triggers:// 1. Instead of creating new tab, execute inline// 2. Inject result as tool_result into parent conversation// 3. No state change needed — resumeAfterDelegation() still works
How it's triggered: The AI agent decides to delegate based on system prompt guidance (encouraging flat structure over deep nesting). When maxDepth is reached, the system enforces inline execution regardless of what the AI tries. This means even if the AI calls new_task, the runtime checks depth first and falls back to inline mode automatically.
Example:
User: "Do X, then Y, then Z" (depth=0)
AI: [delegates to subtask for X] → depth=1 ✓
[subtask delegates to sub-subtask for X.1] → depth=2 ✓
[sub-subtask delegates again] → depth=3 > maxDepth(2) → AUTO-FLATTEN!
→ Execute inline, inject result as tool_result
Key difference from #850: #850 requires the user/AI to manually choose subtaskMode: "inline". Task Tree's auto-flatten is enforced at runtime — no manual decision needed.
Important distinction: Zoo Code's existing checkpoint system (src/core/checkpoints/) snapshots workspace files via shadow git repo. That's a different feature.
Task Tree's checkpoint is about conversation-level state management: saving the AI's reasoning context so it can be compressed, analyzed, and used to generate systematic next moves with clean context for inline continuation.
Core Flow: Compress → Analyze → Continue Inline
User: "Do X, then Y, then Z" (depth=0)
AI: [delegates to subtask for X] → depth=1 ✓
[subtask delegates again] → depth=2 ✓
┌─── CHECKPOINT TRIGGERED (depth >= maxDepth - 1) ─────────────┐
│ │
│ 1. Save conversation snapshot (checkpoint) │
│ → Full history stored, not lost │
│ │
│ 2. Compress conversation history │
│ → compress_context("balanced") reduces token count │
│ → Keeps reasoning chain intact, removes redundant details │
│ │
│ 3. Feed compressed context to AI for analysis │
│ → "Based on current progress, what are the next steps?" │
│ → AI generates systematic next move suggestions │
│ │
│ 4. User reviews suggestions + adjusts context if needed │
│ → Can add/remove/modify suggestions │
│ → Can choose different compression level │
│ → Helps avoid weak models that struggle with long context │
│ │
│ 5. Create inline task with clean, focused context │
│ → No need to carry full conversation history │
│ → AI continues work with clear direction │
└────────────────────────────────────────────────────────────────┘
[inline subtask executes] → depth=3 > maxDepth(2) → inline mode
Checkpoint Data Structure (Conversation-Level)
interfaceConversationCheckpoint{id: string// Unique checkpoint IDtaskId: string// Which task this belongs totimestamp: number// When created// Full conversation history (preserved, not lost)fullHistory: Anthropic.Messages.MessageParam[]// Compressed version (for AI analysis)compressedSummary: {tokensOriginal: number// Original token counttokensCompressed: number// After compressionstrategy: "minimal"|"balanced"|"aggressive"// Compression level used// Structured summary of what happenedcompletedTasks: string[]// What's been donecurrentProgress: string// Where we are nowpendingWork: string[]// What remainsblockers?: string[]// Any issues encountered}// AI-generated next move suggestions (from compressed context)suggestedNextMoves: {id: stringdescription: stringpriority: "high"|"medium"|"low"estimatedEffort: string// e.g., "quick fix", "requires investigation"requiresContext?: string[]// What context this move needs}[]// User adjustments (if any)userAdjustedMoves?: {selectedMoveIds: string[]// Which suggestions to followcustomInstructions?: string// User's own additions/modificationscompressionOverride?: "minimal"|"balanced"|"aggressive"// Override auto-level}}
Checkpoint uses the context compression tools from #849:
// When checkpoint is triggered (auto or manual):asyncfunctioncreateConversationCheckpoint(task: Task): Promise<ConversationCheckpoint>{// 1. Save full historyconstfullHistory=[...task.apiConversationHistory]// 2. Compress using #849 toolsconstcompressionStrategy=task.depth>=task.maxDepth-1 ? "balanced" : "minimal"constcompressed=awaitcompressContext(task,compressionStrategy)// 3. Feed to AI for analysis — generate next move suggestionsconstsuggestedMoves=awaitanalyzeCompressedContext(compressed,fullHistory)return{id: crypto.randomUUID(),taskId: task.taskId,timestamp: Date.now(),
fullHistory,compressedSummary: compressed.summary,suggestedNextMoves: suggestedMoves,}}// AI analysis prompt (uses compressed context):constanalysisPrompt=`Based on the following compressed conversation history:- Completed: ${completedTasks.join(", ")}- Current progress: ${currentProgress}- Pending work: ${pendingWork.join("; ")}Generate systematic next move suggestions. For each suggestion include:1. Description of what to do2. Priority (high/medium/low)3. Estimated effort4. Any context neededConsider the full workflow goal and suggest moves that make sense together, not just the immediate next step.`
User Control Points (Key Design Principle)
The checkpoint system gives users explicit control at critical moments:
When
What User Sees
User Can Do
Auto-trigger (depth >= maxDepth - 1)
"Checkpoint created. AI suggests next moves:" + list of suggestions
Accept all / Pick some / Modify / Reject all and write custom instructions
Manual trigger (create_checkpoint tool)
Same as above, but user chose when
Same options
After compression
Shows token reduction stats (e.g., "Compressed from 45K to 12K tokens")
Choose different compression level if current one loses too much info
Before inline task starts
Shows clean context summary + selected next moves
Edit the context, add/remove moves, adjust instructions
Why this matters for weak models: Some models struggle with long conversation contexts (e.g., older models, smaller models). By compressing and presenting structured suggestions:
User can see what info is being preserved vs. discarded
Can choose "minimal" compression if model handles short context well but needs dense information
Can choose "aggressive" compression if model prefers focused prompts
Can manually add/remove context before the inline task starts
Checkpoint Auto-Trigger Conditions
// Auto-create checkpoint when:// 1. Task depth reaches maxDepth - 1 (one level before auto-flatten)// → Gives user a chance to review and adjust BEFORE inline execution kicks in// 2. User explicitly calls create_checkpoint tool (manual, always available)// 3. After each completed subtask (configurable — saves progress snapshot for parent's context)// VSCode settings:{"zooCode.autoCheckpointOnNesting": true,// Auto-trigger at maxDepth - 1"zooCode.checkpointCompressionDefault": "balanced",// Default compression level"zooCode.showSuggestionsBeforeInline": true,// Show AI suggestions before inline task starts}
Change Detection for Checkpoint Triggers (Enhancement)
Problem: Depth-based triggers alone can create unnecessary checkpoints. If a subtask has been running for 20 turns but only made minor edits to the same file, creating a new checkpoint each time wastes tokens and creates noise. Users don't want spam — they want checkpoints when something meaningful changed.
Solution: Add change detection logic that compares current conversation state with the last checkpoint before deciding whether to create a new one:
// Enhanced checkpoint trigger with change detectionasyncfunctionshouldCreateCheckpoint(task: Task): Promise<boolean>{// 1. Depth-based trigger (existing — always fires)if(task.depth>=task.maxDepth-1)returntrue// 2. Manual trigger (existing — always fires)if(userRequestedCheckpoint)returntrue// 3. After subtask completion (existing — saves progress for parent)if(task.isSubtaskCompleted)returntrue// 4. NEW: Change detection — compare with last checkpointconstlastCheckpoint=awaitloadLastCheckpoint(task.taskId)if(!lastCheckpoint)returnfalse// No previous checkpoint, skipconsthasMeaningfulChange=detectSemanticDifference(task.apiConversationHistory.slice(-10),lastCheckpoint.conversationHistory.slice(-10))// 5. NEW: Stuck task detection — N turns with no progress → auto-trigger for fresh analysisconstisStuck=awaitdetectStuckTask(task)returnhasMeaningfulChange||isStuck}// Detect whether the conversation has changed meaningfully since last checkpointfunctiondetectSemanticDifference(currentMessages: MessageParam[],previousMessages: MessageParam[]): boolean{// Check for new tool calls (different files edited, different commands run)constcurrentTools=extractToolCalls(currentMessages)constpreviousTools=extractToolCalls(previousMessages)if(!arraysOverlap(currentTools,previousTools))returntrue// Check for status changes in todo listconstcurrentTodos=extractTodoStatus(currentMessages)constpreviousTodos=extractTodoStatus(previousMessages)if(todosChanged(currentTodos,previousTodos))returntrue// Check for new errors or blockers mentionedif(hasNewErrors(currentMessages,previousMessages))returntruereturnfalse// No meaningful change — skip checkpoint}// Detect when a task seems stuck (no progress for N turns)asyncfunctiondetectStuckTask(task: Task): Promise<boolean>{constrecentMessages=task.apiConversationHistory.slice(-20)// Check if last N messages are all the same tool call patternconsttoolCallPattern=extractToolCallPatterns(recentMessages)constisRepetitive=isPatternRepeating(toolCallPattern,threshold: 5)// Check if todo status hasn't changed in recent turnsconsttodos=extractTodoStatus(recentMessages)consthasNoProgress=todos.every(t=>t.status==='pending'||t.status==='in_progress')returnisRepetitive&&hasNoProgress}
When change detection kicks in (non-depth-based scenarios):
Scenario
Depth Trigger?
Change Detection Result
Checkpoint Created?
Subtask just started, first few turns
No (depth < maxDepth-1)
First run → no previous checkpoint
❌ Skip
Subtask running 20 turns, same file edits
No
detectSemanticDifference = false
❌ Skip — avoids spam
Subtask running 20 turns, new files edited
No
detectSemanticDifference = true
✅ Create — meaningful change
Task stuck (repetitive tool calls, no progress)
No
detectStuckTask = true
✅ Create — fresh analysis needed
Depth reaches maxDepth - 1
Yes
Always fires regardless of changes
✅ Create — safety net
VSCode settings for change detection:
{"zooCode.checkpointChangeDetection.enabled": true,// Enable semantic diff check"zooCode.checkpointChangeDetection.minTurnsBeforeCheck": 5,// Don't check until N turns after last checkpoint"zooCode.checkpointStuckDetection.turnThreshold": 10,// Flag as stuck after 10 turns with no progress"zooCode.checkpointStuckDetection.forceCheckpoint": true,// Auto-create checkpoint when stuck}
Why this matters: Without change detection, a long-running subtask that's iterating on the same file (e.g., fixing bugs in a function) would create checkpoints every time depth-based conditions are met — even though nothing meaningful changed. Change detection ensures checkpoints are only created when there's actually new information to preserve, reducing token waste and keeping the checkpoint history clean and useful.
Relationship to Existing Checkpoint System (Shadow Git)
Manage context size, get systematic next moves, continue with clean focus
Integration
Independent feature
Integrated into Task Tree flow (auto-trigger on nesting depth)
Both can coexist: shadow git for workspace recovery, conversation checkpoint for context management. They serve different purposes and don't conflict.
AI-Native Tools for Shadow Git Checkpoints (New)
Problem: The existing shadow git checkpoint system (src/core/checkpoints/) creates git commits per task, but the AI has to interact with it using raw git CLI commands. Weak models struggle with this:
Don't know which commit hash to restore from
May use wrong flags (--hard vs --soft vs --mixed)
Can accidentally restore wrong files or overwrite current work
No way to preview what will change before restoring
Solution: Provide AI-native tools that wrap shadow git operations, so the AI can safely interact with checkpoints without needing deep git knowledge:
// Native tools for AI to interact with shadow git checkpointsinterfaceShadowGitCheckpointTools{// List available checkpoints for current task (with file diff preview)list_available_checkpoints(): CheckpointListResult// Restore workspace files from a specific checkpointrestore_checkpoint_files(checkpointId: string,options?: {mode: 'hard'|'soft'|'mixed',// git reset --hard/--soft/--mixedtargetFiles?: string[]// Only restore specific files (not full reset)dryRun?: boolean// Preview changes before applying}): RestoreResult// Compare current workspace with a checkpoint (what would change on restore)diff_with_checkpoint(checkpointId: string): FileDiffResult// Create a new shadow git checkpoint manually (in addition to auto-triggers)create_shadow_git_checkpoint(message?: string): CheckpointInfo// Get status of current task's shadow git repoget_shadow_git_status(): ShadowGitStatus}interfaceCheckpointListResult{checkpoints: Array<{id: string// Commit hashtimestamp: number// When createdmessage: string// Git commit message (auto-generated from task state)fileCount: number// How many files changed in this checkpointdiffPreview?: FileDiffResult// Preview of what changed (if dryRun=true)}>}interfaceRestoreResult{success: booleanrestoredFiles: string[]// Files that were restoredunchangedFiles: string[]// Files not affected by restoremodeUsed: 'hard'|'soft'|'mixed'message?: string// Error or info message}interfaceFileDiffResult{added: string[]// Files added since checkpointmodified: Array<{// Files modified (with diff summary)path: stringoldContentPreview: string// First 200 chars of originalnewContentPreview: string// First 200 chars of currenthunks?: number// Number of changed sections}>deleted: string[]// Files deleted since checkpoint}interfaceShadowGitStatus{hasShadowRepo: boolean// Does this task have a shadow git repo?lastCheckpointId?: string// Most recent checkpoint commit hashuncommittedChanges: number// Number of unstaged changesbranchName: string// Current branch in shadow repo}
How the AI uses these tools (practical examples):
// Example 1: AI detects bad file edits, wants to restore from checkpointasyncfunctionrecoverFromBadEdits(): Promise<void>{// Step 1: List available checkpoints with diff previewconstcheckpoints=awaitlist_available_checkpoints()// Step 2: Show user the most recent checkpoint (before bad edits)constbestCheckpoint=checkpoints.checkpoints[checkpoints.checkpoints.length-1]// Step 3: Dry run to see what will changeconstdiff=awaitdiff_with_checkpoint(bestCheckpoint.id,{dryRun: true})// Step 4: If user approves (or auto-approve for critical files), restoreawaitrestore_checkpoint_files(bestCheckpoint.id,{mode: 'hard',targetFiles: diff.modified.map(f=>f.path)// Only restore modified files})}// Example 2: AI wants to check shadow git status before checkpointingasyncfunctionprepareForCheckpoint(): Promise<void>{conststatus=awaitget_shadow_git_status()if(!status.hasShadowRepo){// Create one if it doesn't exist (first time use)awaitcreate_shadow_git_checkpoint('Initial workspace state')}// Now safe to proceed — shadow git is ready for checkpointing}// Example 3: Weak model scenario — AI uses simple, guided restoreasyncfunctionweakModelSafeRestore(): Promise<void>{// Step 1: Get list of checkpoints (AI just sees IDs and timestamps)constcheckpoints=awaitlist_available_checkpoints()// Step 2: Pick the most recent one (simple logic, no git knowledge needed)constlatestId=checkpoints.checkpoints[checkpoints.checkpoints.length-1].id// Step 3: Restore with default mode (--mixed keeps working tree changes staged)awaitrestore_checkpoint_files(latestId,{mode: 'mixed'})// No need to know commit hashes, git flags, or branch names}
VSCode settings for shadow git checkpoint tools:
{"zooCode.shadowGit.autoCreateOnFirstEdit": true,// Create shadow repo on first file edit"zooCode.shadowGit.defaultRestoreMode": "mixed",// Default: --mixed (keeps changes staged)"zooCode.shadowGit.showDiffPreviewBeforeRestore": true,// Show diff before applying restore"zooCode.shadowGit.maxCheckpointsToKeep": 10,// Limit checkpoint history to avoid bloat}
Why this matters for weak models:
Scenario
Without Native Tools (raw git CLI)
With Native Tools
Restore from checkpoint
AI must know: commit hash, git reset --hard <hash>, correct branch
AI calls restore_checkpoint_files(id) — no git knowledge needed
Preview changes
AI runs git diff HEAD~1 (may not understand output format)
AI calls diff_with_checkpoint(id) → structured JSON with file previews
Selective restore
AI must know: git checkout <hash> -- file1 file2 syntax
AI passes targetFiles: ['file1', 'file2'] — simple array
Safe recovery
AI might use --hard and lose current work accidentally
Default mode is --mixed (keeps changes staged, safer)
Status check
AI runs git status, parses messy output
AI calls get_shadow_git_status() → clean structured data
Integration with conversation checkpoint system:
Shadow git checkpoints and conversation checkpoints serve different purposes but can work together:
// When a conversation checkpoint is created, also create shadow git snapshotasyncfunctioncreateConversationCheckpoint(task: Task): Promise<ConversationCheckpoint>{// 1. Save conversation history (existing logic)constconversationCheckpoint=awaitsaveConversationSnapshot(task)// 2. Also create shadow git checkpoint for workspace stateif(config.zooCode.shadowGit.autoCreateOnFirstEdit){constgitStatus=awaitget_shadow_git_status()if(gitStatus.uncommittedChanges>0){awaitcreate_shadow_git_checkpoint(`Checkpoint: ${conversationCheckpoint.id}`)}}returnconversationCheckpoint}// When restoring from a checkpoint, restore BOTH conversation AND workspace filesasyncfunctionrestoreFullCheckpoint(checkpointId: string): Promise<void>{// 1. Restore conversation history (existing logic)awaitloadConversationSnapshot(checkpointId)// 2. Find corresponding shadow git commit and restore workspaceconstgitStatus=awaitget_shadow_git_status()if(gitStatus.lastCheckpointId===checkpointId){awaitrestore_checkpoint_files(gitStatus.lastCheckpointId,{mode: 'mixed'})}}
This means users get complete recovery — both conversation context AND workspace files are restored together. The AI doesn't need to coordinate between the two systems manually; it just calls restore_full_checkpoint(id) and everything is restored consistently.
#854's original intent was "review history and decide next move." This design fulfills that by:
Compressing the full conversation (not just viewing raw history)
Feeding compressed context to AI for systematic analysis (generating structured suggestions, not just showing history)
Giving user control over compression level and which suggestions to follow
Creating inline task with clean context — focused on selected next moves, no need to carry full conversation
This addresses the real pain point: users don't want to manually read through hundreds of messages to figure out what to do next. They want AI to analyze progress systematically and suggest clear next steps, while giving them control over how much context is preserved (important for models that struggle with long contexts).
Problem: Current design has compress_context() as a manual tool, but the AI doesn't know:
How much context it currently has (token count)
What the model's context limit is
Whether compression is needed or when to trigger it
Solution: Provide tools that let the AI itself manage context, not just the user:
// Tools for AI to understand and manage its own context:interfaceContextAwareTools{// READ: Get current context state (for AI decision making)get_context_status(): {currentTokens: number// How many tokens currently in contextmodelContextLimit: number// Max tokens the model supports (e.g., 128K, 200K)usagePercent: number// currentTokens / modelContextLimit * 100autoCompressRate?: number// Configured compression rate from zoo_code configestimatedCompressionRatio?: {// Expected reduction with each strategyminimal: number// e.g., 0.7 (30% reduction)balanced: number// e.g., 0.4 (60% reduction)aggressive: number// e.g., 0.2 (80% reduction)}}// WRITE: Compress context with strategy chosen by AI (or user override)compress_context(strategy: "minimal"|"balanced"|"aggressive"): {tokensBefore: numbertokensAfter: numbercompressionRatio: numberpreservedInfo: string[]// What info was keptdiscardedInfo: string[]// What info was removed (for transparency)}// READ: View compressed history in structured formatview_compressed_history(): CompressedHistoryView// WRITE: Restore to a checkpointrestore_checkpoint(checkpointId: string): void}// Zoo Code config for auto-compression behavior:interfaceZooCodeContextConfig{autoCompressRate?: number// e.g., 0.8 = compress when context > 80% of limitautoCompressStrategy?: "minimal"|"balanced"|"aggressive"// Default strategypreserveReasoningChain: boolean// Keep AI's reasoning intact during compressionmaxContextTokens?: number// Override model default (e.g., force 64K for weak models)}
How AI Uses These Tools (Autonomous Context Management)
// Example: AI decides when to compress based on context statusasyncfunctionaiDecidesToCompress(): Promise<boolean>{conststatus=awaitget_context_status()// Check if compression is needed (configurable threshold from zoo_code config)constautoCompressRate=config.zooCode.autoCompressRate??0.8// Default: compress at 80%if(status.usagePercent<autoCompressRate*100){returnfalse// Not yet time to compress}// Choose strategy based on current situationletstrategy: "minimal"|"balanced"|"aggressive"="balanced"if(status.usagePercent>95){strategy="aggressive"// Critical — need maximum reduction}elseif(status.currentTokens<20000){strategy="minimal"// Small context, preserve as much as possible}// Compress and report resultsconstresult=awaitcompress_context(strategy)console.log(`Compressed: ${result.tokensBefore} → ${result.tokensAfter} (${(result.compressionRatio*100).toFixed(0)}% remaining)`)returntrue}// AI can also proactively check before delegating to subtask:asyncfunctiondelegateToSubtask(message: string): Promise<void>{// Check context BEFORE creating new task (new task adds more tokens)conststatus=awaitget_context_status()if(status.usagePercent>70){// Compress first to make room for delegation overheadawaitcompress_context("balanced")}// Now safe to delegate — context has roomawaitnew_task({ message,mode: "sequential"})}
These tools are still available for user-initiated actions:
interfaceContextTools{// User can manually trigger compression with specific strategycompress_context(strategy: "minimal"|"balanced"|"aggressive"): void// View what's currently in context (for debugging/inspection)view_compressed_history(): CompressedHistoryView// Restore to a checkpointrestore_checkpoint(checkpointId: string): void}// User can also override AI's compression choice:// 1. "Use minimal compression" → AI uses "minimal" instead of auto-chosen strategy// 2. "Compress now" → Triggers immediate compression (AI decides when to do it)// 3. "Show me context usage" → Calls get_context_status() for user inspection
Tree-Aware Context Pruning
When parent task needs context from completed children:
// When a subtask completes:asyncfunctiononSubtaskComplete(childTask: Task,parentTask: Task): Promise<void>{// 1. Compress child's conversation history (AI decides strategy based on context status)constcompressed=awaitcompressConversationHistory(childTask.apiConversationHistory,"balanced"// Default for completed subtasks)// 2. Inject summary into parent (not full history!)constsummary=formatCompressionSummary(compressed)awaitparentTask.say({type: "text",text: `Subtask completed:\n${summary}`})// 3. Remove child's full history from active context// (keep only in storage for reference)}// When parent task needs context:asyncfunctiongetContextForTask(task: Task): Promise<Anthropic.Messages.MessageParam[]>{// 1. Include own conversation history (full)constownHistory=task.apiConversationHistory// 2. Include summaries of completed siblings (compressed)constsiblingSummaries=awaitPromise.all(task.siblingIds.map(id=>loadCompressedSummary(id)))// 3. Include summary of parent's context (not full history!)constparentContext=awaitloadParentContext(task.parentTaskId)return[...parentContext, ...ownHistory, ...siblingSummaries]}
Integration with Checkpoint System
Checkpoint uses these same tools:
// When checkpoint is triggered (auto or manual):asyncfunctioncreateConversationCheckpoint(task: Task): Promise<ConversationCheckpoint>{// 1. Save full history first (before compression)constfullHistory=[...task.apiConversationHistory]// 2. Get context status to inform compression strategyconststatus=awaitget_context_status()constcompressionStrategy=task.depth>=task.maxDepth-1 ? "balanced" : "minimal"// 3. Compress using #849 tools (AI can override if needed)constcompressed=awaitcompressContext(task,compressionStrategy)// 4. Feed to AI for analysis — generate next move suggestionsconstsuggestedMoves=awaitanalyzeCompressedContext(compressed,fullHistory)return{id: crypto.randomUUID(),taskId: task.taskId,timestamp: Date.now(),
fullHistory,compressedSummary: compressed.summary,suggestedNextMoves: suggestedMoves,}}
VSCode Settings for Context Management
{// Auto-compression triggers"zooCode.autoCompressRate": 0.8,// Compress when context > 80% of limit"zooCode.autoCompressStrategy": "balanced",// Default strategy (minimal/balanced/aggressive)// AI autonomy"zooCode.allowAIContextManagement": true,// Let AI decide when/how to compress"zooCode.showCompressionStats": true,// Show token reduction stats after compression// Model-specific overrides"zooCode.maxContextTokens": undefined,// Override model default (e.g., 64000 for weak models)// Checkpoint integration"zooCode.autoCheckpointOnNesting": true,// Auto-trigger at maxDepth - 1"zooCode.showSuggestionsBeforeInline": true,// Show AI suggestions before inline task starts}
Why This Matters for Weak Models
Some models struggle with long conversation contexts (e.g., older models, smaller models). These tools help:
Scenario
How Tools Help
Model has small context limit
User sets maxContextTokens to match model's actual capacity
Model loses info in long context
AI monitors usagePercent and compresses proactively before degradation
User wants more control
Can manually choose "minimal" compression if model handles short context well but needs dense information
AI needs to decide when to compress
get_context_status() gives AI the data it needs (current tokens, limit, usage %)
This addresses the real pain point: users don't want to manually track token counts or guess when context is getting too long. The AI can monitor its own context and compress automatically based on configurable thresholds — while still giving users control over strategy and compression level.
Cross-Model Context Analysis (New)
Problem: Some models are weak at understanding their own progress and generating good next move suggestions, even with compressed context. They might miss important details or generate poor recommendations.
Solution: Let the current model compress its context and delegate analysis to a different model provider. The target model returns better structured suggestions that the original model can follow.
// Tools for cross-model context analysis:interfaceCrossModelAnalysisTools{// Current model generates compression prompt based on its understandinggenerate_compression_prompt(): CompressionExportData// Delegate compressed context to another model provider for analysisdelegate_context_analysis(targetProvider: string,// e.g., "anthropic", "openai"targetModel: string,// e.g., "claude-sonnet-4", "gpt-4o"exportData: CompressionExportData,analysisConfig?: {maxTokens?: number// How many tokens the target model can usefocusAreas?: ("progress"|"decisions"|"errors"|"all")[]// What to emphasizeoutputFormat?: "structured"|"narrative"// Return format preference}): AnalysisResult// Apply suggestions from external analysis back to current taskapply_external_analysis(taskId: string,analysisId: string,// Reference to the external analysissourceProvider: string,// Which provider generated this analysisconfidenceThreshold?: number// Only accept if confidence > threshold): void}// Data structure for context export (what gets sent to target model)interfaceCompressionExportData{taskId: stringtimestamp: number// What the current model thinks is important to preservekeyDecisions: string[]// Major decisions made so farcurrentGoal: string// What we're trying to achieve nowblockers: string[]// Any issues encounteredpendingActions: string[]// Actions still needed// Compression instructions for the target modelcompressionInstructions: {preserveReasoningChain: boolean// Keep AI's thought process intactfocusOn: "progress"|"decisions"|"errors"|"all"// What to emphasizeexcludePatterns?: string[]// e.g., ["file diffs", "tool outputs"]}// Metadata for the target modelcontextStats: {totalTokens: numbermessageCount: numbertoolCallsCount: numberlastCheckpointHash?: string}}// Result from external analysis (what comes back)interfaceAnalysisResult{analysisId: string// Unique ID for this analysis sessiontimestamp: numbersuggestedNextMoves: NextMoveSuggestion[]confidenceScore: number// 0.0 - 1.0, how confident is the target modelreasoningSummary: string// Brief explanation of why these moves were chosenalternativeApproaches?: string[]// Other valid strategies to consider// Metadata for tracking and auditingprovider: string// Which provider was used (e.g., "anthropic")model: string// Which model was used (e.g., "claude-sonnet-4")tokensUsed: number// How many tokens the target model consumed}interfaceNextMoveSuggestion{id: stringdescription: string// What to do nextpriority: "high"|"medium"|"low"estimatedEffort: string// e.g., "quick fix", "requires investigation"requiresContext?: string[]// What context this move needsreasoning: string// Why this is the right next step (from target model)}
How it works in practice:
// Example flow when AI decides to delegate analysis to another provider:asyncfunctiongetBetterNextMoves(task: Task): Promise<void>{// 1. Current model generates compression promptconstexportData=awaitgenerate_compression_prompt()// 2. Compress context using current strategyconstcompressed=awaitcompress_context("balanced")// 3. Delegate to different provider for analysisconstanalysisResult=awaitdelegate_context_analysis("anthropic",// Target provider"claude-sonnet-4",// Target modelexportData,// Compressed context data{// Analysis configurationmaxTokens: 8000,focusAreas: ["progress","decisions"],outputFormat: "structured"})// 4. Check confidence threshold (configurable)if(analysisResult.confidenceScore<0.7){console.log("Target model not confident, falling back to local analysis")return// Use current model's own suggestions instead}// 5. Apply external analysis resultsawaitapply_external_analysis(task.taskId,analysisResult.analysisId,"anthropic",0.7// Minimum confidence threshold)}// User can also manually trigger cross-model analysis:// "Ask another model to analyze my progress and suggest next steps"// → AI calls generate_compression_prompt() + delegate_context_analysis()
VSCode settings for cross-model analysis:
{// Cross-model context analysis"zooCode.crossModelAnalysis.enabled": true,// Enable feature"zooCode.crossModelAnalysis.defaultTargetProvider": "anthropic",// Default target provider"zooCode.crossModelAnalysis.defaultTargetModel": "claude-sonnet-4",// Default target model"zooCode.crossModelAnalysis.confidenceThreshold": 0.7,// Min confidence to accept suggestions// When to auto-trigger cross-model analysis"zooCode.crossModelAnalysis.autoTriggerOnNesting": true,// Trigger at maxDepth - 1"zooCode.crossModelAnalysis.autoTriggerOnHighComplexity": true,// Trigger when context > 90% usage// Cost control (cross-model calls can be expensive)"zooCode.crossModelAnalysis.maxCallsPerSession": 5,// Limit per task session"zooCode.crossModelAnalysis.showCostEstimate": true,// Show estimated cost before calling}
Use cases:
Scenario
How it helps
Weak model doing complex task
Current model generates compression prompt → target provider analyzes → better suggestions returned
User wants expert opinion
User manually triggers "ask another model" → gets structured next moves from different reasoning capability
Checkpoint with high confidence
At checkpoint trigger, auto-delegate to target provider if context is large and complex
Debugging stuck tasks
When task seems stuck (no progress for N turns), delegate to target provider for fresh perspective
Cost considerations:
Cross-model calls are more expensive than local compression
AI should only use this when necessary (high complexity, low confidence in own suggestions)
User can set maxCallsPerSession to control costs
Target model's reasoning summary is included so user understands why those moves were suggested
Key difference from regular context compression:
Regular: Current model compresses its own context → analyzes it itself (fast, cheap, but may miss details)
Cross-model: Current model generates export data + compressed context → delegates to different provider → gets better analysis (slower, more expensive, but higher quality suggestions)
Both can coexist: use regular compression for routine tasks, cross-model for complex decisions or when the current model's confidence is low.
Unified UI: Task Tree View
Replace Tab Switching with Tree View
Current: Each subtask opens a new tab → hard to manage deep nesting
New: Single tree view showing all active tasks + status
The following requirements should be addressed during implementation but are not part of this design spec:
Task Tree Persistence — childIds, siblingIds, and depth fields must survive VSCode restart. Current persistence only saves individual task history; tree topology (parent → child relationships) needs to be persisted alongside each task so the tree structure can be reconstructed on reload.
Cancellation cascade — When a parent task is cancelled, decide whether active children are also cancelled or allowed to finish. This is a UX detail best handled in PR stage with user testing.
Circular reference protection — Add validation to prevent A → B → A cycles when modifying tree edges. Edge case that can be caught during code review.
Feature flags: Each new feature has a VSCode setting to enable/disable
Backward compat: Existing delegation flow unchanged unless user opts in
Gradual rollout: Phase 1 → test → Phase 2 → test → etc.
Monitoring: Track nesting depth, context size, checkpoint frequency
Manual Usage Flow & Permission Handling
When a user manually works with the AI (not auto-task), the delegation flow involves two permission checkpoints:
Current Flow (Tab Mode)
User: "Do X, then Y"
AI: [calls new_task for X] → VSCode approval popup appears
→ User clicks YES → parent pauses, child opens in NEW TAB
→ Child works independently
AI: [subtask calls attempt_completion] → approval popup appears
→ User clicks YES → parent resumes with result injected as tool_result
Two permission points:
askApproval on new_task — user approves creating a new tab
askFinishSubTaskApproval on attempt_completion — user approves finishing the subtask and resuming parent
Both are VSCode notification popups. The existing flow is unchanged for sequential/tab mode.
New Flow: Auto-Flatten (Inline Mode)
User: "Do X, then Y" (depth=0)
AI: [calls new_task for X] → depth=1 ✓ → user approves (same as current)
→ Child opens in NEW TAB
AI: [subtask calls new_task for X.1] → depth=2 ✓ → user approves (same as current)
→ Grandchild opens in NEW TAB
AI: [grandchild calls new_task again] → depth=3 > maxDepth(2)
→ AUTO-FLATTEN! Execute inline, no tab switch
Three Permission Changes Needed
1. Auto-Flatten Bypasses new_task Approval Popup
Problem: If AI calls new_task but runtime auto-flattens (depth > maxDepth), showing an approval popup that does nothing is confusing.
Solution: Check depth in NewTaskTool.ts BEFORE showing the approval popup:
// NewTaskTool.ts — NEW logic at line ~113if(task.depth>=task.maxDepth&&config.autoFlattenOnLimit){// Execute inline, no approval popup neededawaitthis.executeInline(task,unescapedMessage,mode)pushToolResult(`Executed inline (auto-flattened at depth ${task.depth})`)return}// Existing flow — unchanged for tab/sequential modeconstchild=awaitprovider.delegateParentAndOpenChild({ ... })
User experience: No popup appears. The tool result shows Executed inline (auto-flattened at depth 3). User sees the inline execution happen in the same conversation.
2. Inline Mode Bypasses askFinishSubTaskApproval
Problem: In inline mode, the subtask executes within the parent's conversation. The user already sees the result directly. If attempt_completion still triggers an approval popup, it creates double friction — user has already seen and accepted the result visually.
Solution: Check executionMode in AttemptCompletionTool.ts:
// AttemptCompletionTool.ts — NEW logic at line ~85if(task.executionMode==="inline"){// Inject result directly, no approval popup neededparent.say({type: "tool_result",text: `Subtask completed: ${result}`})awaitparent.resumeAfterDelegation()// ← same state reset logicreturn}// Existing flow for tab mode — unchangedif(task.parentTaskId){constdidApprove=awaitaskFinishSubTaskApproval()if(!didApprove)return"denied"awaitprovider.reopenParentFromDelegation(...)}
User experience: Inline subtask completes → result appears in conversation → parent continues naturally. No popup interruption.
3. Parallel Mode: Multiple Children Completing Simultaneously (Future)
Problem: If parent spawns multiple children in parallel, they may call attempt_completion at the same time. Current code uses awaitingChildId: string (single child), which would cause a race condition.
Solution (Phase 2): Change state model to support multiple awaiting children:
// ClineProvider.ts — NEW fields for parallel modeinterfaceHistoryItem{awaitingChildrenIds: string[]// ← replaces awaitingChildIdcompletedChildrenIds: string[]// ← track which ones finished}
Note: This is a medium-risk change deferred to Phase 2. Sequential and inline modes are unaffected.
Summary of Permission Changes
Scenario
new_task Approval
askFinishSubTaskApproval
Risk Level
Tab mode (existing)
✅ Show popup
✅ Show popup
Unchanged
Sequential mode
✅ Show popup
✅ Show popup
Unchanged
Inline mode (auto-flatten)
Bypassed (no tab)
Bypassed (user sees result)
Low — just condition check
Parallel mode
✅ Show popup per child
Race condition fix needed
Medium — Phase 2
Key takeaway: The permission changes are all simple conditional checks (if depth >= maxDepth / if executionMode === "inline"). No new permission types, no new UI components. Existing tab/sequential flow is completely unchanged.
Appendix: Code Examples
Example 1: NewTaskTool with executionMode
// NEW: NewTaskTool.ts — add executionMode parameterinterfaceNewTaskParams{mode: stringmessage: stringtodos?: stringexecutionMode?: "sequential"|"parallel"|"inline"// ← NEW}asyncexecute(params: NewTaskParams,task: Task,callbacks: ToolCallbacks): Promise<void>{const{ mode, message, todos, executionMode ="sequential"}=paramsif(executionMode==="inline"){// Execute inline — no tab switchawaitthis.executeInline(task,message,mode)}else{// Existing delegation flow (unchanged)constchild=awaitprovider.delegateParentAndOpenChild({parentTaskId: task.taskId,
message,initialTodos: todos,
mode,
executionMode,// ← pass to child})}}// NEW: Inline execution (no tab switch)asyncexecuteInline(parent: Task,message: string,mode: string): Promise<void>{// Create virtual subtask in same conversationconstresult=awaitthis.runSubtaskInConversation(parent,message,mode)// Inject result as tool_result (same flow as resumeAfterDelegation)parent.say({type: "tool_result",text: `Subtask completed: ${result}`})}
Example 2: Auto-Flatten in delegateParentAndOpenChild
// NEW: ClineProvider.ts — auto-flatten logicpublicasyncdelegateParentAndOpenChild(params): Promise<Task>{const parent =this.getCurrentTask()// Check if we should auto-flattenif(parent.depth>=parent.maxDepth&&config.autoFlattenOnLimit){// Execute inline instead of creating new tabawaitthis.executeInlineSubtask(parent,params.message,params.mode)returnnull// No new task created}// Existing flow (unchanged) — create child with incremented depthconstchild=awaitthis.createTask(params.message,undefined,parentasany,{initialTodos: params.initialTodos,mode: params.mode,startTask: false,depth: parent.depth+1,// ← NEW: track depth})// ... rest of existing flow}
Example 3: Tree-Aware Context Loading
// NEW: Task.ts — tree-aware context loadingprivateasyncgetContextForApiCall(): Promise<Anthropic.Messages.MessageParam[]>{constownHistory=this.apiConversationHistoryif(!this.parentTaskId){returnownHistory// Root task — full history}// Load parent's compressed context (not full history!)constparentContext=awaitthis.loadCompressedParentContext(this.parentTaskId)// Include summaries of completed siblingsconstsiblingSummaries=awaitPromise.all(this.siblingIds.filter(id=>this.isCompleted(id)).map(id=>this.loadCompressedSummary(id)))return[...parentContext, ...ownHistory, ...siblingSummaries]}
Conclusion
Task Tree abstraction solves ALL THREE problems (#849, #850, #854) with ONE unified design:
Auto-nesting limit prevents infinite tab growth → enforced inline mode when depth exceeded (no manual decision needed)
Checkpoint as tree node property integrates with execution flow instead of being a separate feature
Tree-aware context compression optimizes memory usage based on structure
This is primarily a data model + delegation flow change, not just a UI change. The existing data model ALREADY supports this (childIds array, parentTaskId), we just need to use it properly.
Task Tree Abstraction: Unified Subtask Management with Auto-Nesting Limit
Problem Statement
Zoo Code's current delegation system has three major problems:
parentTaskId+childTaskIdonly supports a single path (A→B→C), but actual workflows need tree structure (A→{B,C}→D)Current Architecture Analysis
Existing Data Model (Linear Chain)
Current Delegation Flow (Linear)
Limitation: This is a stack-based LIFO model. Only ONE child can be active at a time. No parallelism, no tree structure.
Existing Fields That Support Tree (But Unused)
Key Insight: The data model ALREADY supports tree structure. We just need to:
childTaskId→childIds: string[]in Task interfaceProposed Design: Task Tree Abstraction
1. New Data Model
2. Execution Modes
Mode A: Sequential (Current Default)
Mode B: Parallel (task-level — complements #355)
childIds: string[]+ parallel task managementNote on #355: Issue #355 focuses on tool-level parallelism (parallel tool calls within ONE task). Task Tree's Parallel mode is task-level delegation (parent spawns multiple concurrent child tasks, each with its own conversation). These are different scopes and can coexist.
Mode C: Inline (supersedes #850)
Inline Mode Execution Mechanism (How It Actually Works)
Inline mode executes subtasks within the parent's existing conversation without creating a new Task instance or tab switch. But how does this actually work? The implementation needs to answer: when depth exceeds maxDepth and auto-flatten triggers, what mechanism runs the subtask?
Decision: Inline mode uses AI-driven execution (not direct tool calls).
Why AI-driven, not direct tool execution?
AI-driven is the right choice because: Inline subtasks are meant for complex work that needs AI reasoning (e.g., "fix the auth bug in middleware"). Direct tool execution would only work for trivial operations, which defeats the purpose of inline mode. The token overhead (~500-1000 tokens for prompt) is negligible compared to the actual subtask work.
Context management for inline prompts:
Error handling in inline mode:
ResumeAfterDelegation compatibility:
Inline Mode Escalation: Upgrading Back to Tab Mode
Problem: The current design makes inline mode permanent once triggered. But what if an inline subtask grows unexpectedly complex? For example, user says "fix the auth bug" (simple), but during execution the AI discovers it needs to update 3 different modules, add tests, and update docs — suddenly this is a large task that deserves its own tab for clarity.
Solution: Allow inline mode to escalate back to tab mode when conditions warrant it. This is one-way escalation (inline → tab), not bidirectional oscillation.
When escalation triggers:
VSCode settings:
Key constraints:
3. Auto-Nesting Limit (supersedes #850 + adds depth control)
Configuration
Auto-Flatten Logic
How it's triggered: The AI agent decides to delegate based on system prompt guidance (encouraging flat structure over deep nesting). When
maxDepthis reached, the system enforces inline execution regardless of what the AI tries. This means even if the AI callsnew_task, the runtime checks depth first and falls back to inline mode automatically.Example:
Key difference from #850: #850 requires the user/AI to manually choose
subtaskMode: "inline". Task Tree's auto-flatten is enforced at runtime — no manual decision needed.4. Checkpoint System (supersedes #854)
What "Checkpoint" Means in Task Tree Context
Important distinction: Zoo Code's existing checkpoint system (
src/core/checkpoints/) snapshots workspace files via shadow git repo. That's a different feature.Task Tree's checkpoint is about conversation-level state management: saving the AI's reasoning context so it can be compressed, analyzed, and used to generate systematic next moves with clean context for inline continuation.
Core Flow: Compress → Analyze → Continue Inline
Checkpoint Data Structure (Conversation-Level)
Integration with Context Compression Tools (#849)
Checkpoint uses the context compression tools from #849:
User Control Points (Key Design Principle)
The checkpoint system gives users explicit control at critical moments:
create_checkpointtool)Why this matters for weak models: Some models struggle with long conversation contexts (e.g., older models, smaller models). By compressing and presenting structured suggestions:
Checkpoint Auto-Trigger Conditions
Change Detection for Checkpoint Triggers (Enhancement)
Problem: Depth-based triggers alone can create unnecessary checkpoints. If a subtask has been running for 20 turns but only made minor edits to the same file, creating a new checkpoint each time wastes tokens and creates noise. Users don't want spam — they want checkpoints when something meaningful changed.
Solution: Add change detection logic that compares current conversation state with the last checkpoint before deciding whether to create a new one:
When change detection kicks in (non-depth-based scenarios):
detectSemanticDifference= falsedetectSemanticDifference= truedetectStuckTask= trueVSCode settings for change detection:
Why this matters: Without change detection, a long-running subtask that's iterating on the same file (e.g., fixing bugs in a function) would create checkpoints every time depth-based conditions are met — even though nothing meaningful changed. Change detection ensures checkpoints are only created when there's actually new information to preserve, reducing token waste and keeping the checkpoint history clean and useful.
Relationship to Existing Checkpoint System (Shadow Git)
git reset --hardto commit hashBoth can coexist: shadow git for workspace recovery, conversation checkpoint for context management. They serve different purposes and don't conflict.
AI-Native Tools for Shadow Git Checkpoints (New)
Problem: The existing shadow git checkpoint system (
src/core/checkpoints/) creates git commits per task, but the AI has to interact with it using rawgitCLI commands. Weak models struggle with this:--hardvs--softvs--mixed)Solution: Provide AI-native tools that wrap shadow git operations, so the AI can safely interact with checkpoints without needing deep git knowledge:
How the AI uses these tools (practical examples):
VSCode settings for shadow git checkpoint tools:
Why this matters for weak models:
git reset --hard <hash>, correct branchrestore_checkpoint_files(id)— no git knowledge neededgit diff HEAD~1(may not understand output format)diff_with_checkpoint(id)→ structured JSON with file previewsgit checkout <hash> -- file1 file2syntaxtargetFiles: ['file1', 'file2']— simple array--hardand lose current work accidentally--mixed(keeps changes staged, safer)git status, parses messy outputget_shadow_git_status()→ clean structured dataIntegration with conversation checkpoint system:
Shadow git checkpoints and conversation checkpoints serve different purposes but can work together:
This means users get complete recovery — both conversation context AND workspace files are restored together. The AI doesn't need to coordinate between the two systems manually; it just calls
restore_full_checkpoint(id)and everything is restored consistently.Key Difference from #854
#854's original intent was "review history and decide next move." This design fulfills that by:
This addresses the real pain point: users don't want to manually read through hundreds of messages to figure out what to do next. They want AI to analyze progress systematically and suggest clear next steps, while giving them control over how much context is preserved (important for models that struggle with long contexts).
5. Context Management (supersedes #849)
AI-Aware Context Tools (Key Addition)
Problem: Current design has
compress_context()as a manual tool, but the AI doesn't know:Solution: Provide tools that let the AI itself manage context, not just the user:
How AI Uses These Tools (Autonomous Context Management)
Manual Control Tools (from #849, now integrated)
These tools are still available for user-initiated actions:
Tree-Aware Context Pruning
When parent task needs context from completed children:
Integration with Checkpoint System
Checkpoint uses these same tools:
VSCode Settings for Context Management
Why This Matters for Weak Models
Some models struggle with long conversation contexts (e.g., older models, smaller models). These tools help:
maxContextTokensto match model's actual capacityget_context_status()gives AI the data it needs (current tokens, limit, usage %)This addresses the real pain point: users don't want to manually track token counts or guess when context is getting too long. The AI can monitor its own context and compress automatically based on configurable thresholds — while still giving users control over strategy and compression level.
Cross-Model Context Analysis (New)
Problem: Some models are weak at understanding their own progress and generating good next move suggestions, even with compressed context. They might miss important details or generate poor recommendations.
Solution: Let the current model compress its context and delegate analysis to a different model provider. The target model returns better structured suggestions that the original model can follow.
How it works in practice:
VSCode settings for cross-model analysis:
Use cases:
Cost considerations:
maxCallsPerSessionto control costsKey difference from regular context compression:
Both can coexist: use regular compression for routine tasks, cross-model for complex decisions or when the current model's confidence is low.
Unified UI: Task Tree View
Replace Tab Switching with Tree View
Current: Each subtask opens a new tab → hard to manage deep nesting
New: Single tree view showing all active tasks + status
Features:
Implementation Requirements (For PR Stage)
The following requirements should be addressed during implementation but are not part of this design spec:
childIds,siblingIds, anddepthfields must survive VSCode restart. Current persistence only saves individual task history; tree topology (parent → childrelationships) needs to be persisted alongside each task so the tree structure can be reconstructed on reload.A → B → Acycles when modifying tree edges. Edge case that can be caught during code review.Migration Path from Existing Issues
executionMode: "inline"+ auto-flattenRecommendation: Close #849, #850, #854 as duplicates of this new issue. Reference them in the new issue for context.
Benefits Over Separate Issues
Risk Assessment
Low Risk Changes
childIds,depthfields to Task interface (backward compatible)Medium Risk Changes
Mitigation Strategies
Manual Usage Flow & Permission Handling
When a user manually works with the AI (not auto-task), the delegation flow involves two permission checkpoints:
Current Flow (Tab Mode)
Two permission points:
askApprovalonnew_task— user approves creating a new tabaskFinishSubTaskApprovalonattempt_completion— user approves finishing the subtask and resuming parentBoth are VSCode notification popups. The existing flow is unchanged for sequential/tab mode.
New Flow: Auto-Flatten (Inline Mode)
Three Permission Changes Needed
1. Auto-Flatten Bypasses
new_taskApproval PopupProblem: If AI calls
new_taskbut runtime auto-flattens (depth > maxDepth), showing an approval popup that does nothing is confusing.Solution: Check depth in
NewTaskTool.tsBEFORE showing the approval popup:User experience: No popup appears. The tool result shows
Executed inline (auto-flattened at depth 3). User sees the inline execution happen in the same conversation.2. Inline Mode Bypasses
askFinishSubTaskApprovalProblem: In inline mode, the subtask executes within the parent's conversation. The user already sees the result directly. If
attempt_completionstill triggers an approval popup, it creates double friction — user has already seen and accepted the result visually.Solution: Check executionMode in
AttemptCompletionTool.ts:User experience: Inline subtask completes → result appears in conversation → parent continues naturally. No popup interruption.
3. Parallel Mode: Multiple Children Completing Simultaneously (Future)
Problem: If parent spawns multiple children in parallel, they may call
attempt_completionat the same time. Current code usesawaitingChildId: string(single child), which would cause a race condition.Solution (Phase 2): Change state model to support multiple awaiting children:
Note: This is a medium-risk change deferred to Phase 2. Sequential and inline modes are unaffected.
Summary of Permission Changes
new_taskApprovalaskFinishSubTaskApprovalKey takeaway: The permission changes are all simple conditional checks (
if depth >= maxDepth/if executionMode === "inline"). No new permission types, no new UI components. Existing tab/sequential flow is completely unchanged.Appendix: Code Examples
Example 1: NewTaskTool with executionMode
Example 2: Auto-Flatten in delegateParentAndOpenChild
Example 3: Tree-Aware Context Loading
Conclusion
Task Tree abstraction solves ALL THREE problems (#849, #850, #854) with ONE unified design:
This is primarily a data model + delegation flow change, not just a UI change. The existing data model ALREADY supports this (childIds array, parentTaskId), we just need to use it properly.