This design makes Claude Code more responsive by allowing the main agent to continue working while background tasks run. Currently, when Claude spawns multiple long-running tasks, everything freezes - the agent can't continue working on other things, and you can't interact with it. With this change, Claude can process results as they arrive, continue working on independent tasks, and remain available for user interaction.
To understand the problem and solution, you need to know how Claude Code works today.
Claude Code elegantly solves the problem of complex coding tasks through two systems that work together:
-
Todos - A task tracking system that helps Claude organize work. Think of it as Claude's smart to-do list. Todos have states that track progress:
pending
(not started) →in_progress
(working on it) →completed
(done). -
Task Tool - Allows Claude to spawn "sub-agents" - independent AI agents that handle complex, long-running work. Think of it as Claude delegating work to specialized assistants.
When you ask Claude to "analyze all error handling in the codebase," this might require:
- Reading hundreds of files
- Following complex code paths
- Identifying patterns across modules
- Synthesizing findings
Doing this in the main conversation would:
- Clutter the context with intermediate steps
- Make responses unwieldy
- Use up the context window
Instead, Claude spawns a task (sub-agent) that:
- Works in its own isolated context
- Has full tool access (read files, search, etc.)
- Explores extensively without affecting the main conversation
- Returns only a clean summary of findings
This thoughtful architecture keeps the main conversation focused while enabling deep exploration - a key strength of Claude Code. Tasks typically take 1-2+ minutes to complete.
When Claude spawns multiple tasks, everything freezes:
User: Please analyze our database schema, research migration best practices,
and find performance bottlenecks.
Claude: I'll work on those 3 tasks...
Task(description="Analyze current database schema")
Task(description="Research migration best practices")
Task(description="Find performance bottlenecks via query analysis")
[... 3+ minutes of complete silence ...]
Claude: Here are all the results:
1. Database schema: 47 tables with foreign key relationships...
2. Migration approach: Use versioned migrations with rollback...
3. Bottlenecks: Missing indexes on orders.customer_id...
- Agent is stuck - Claude can't work on anything else, even unrelated tasks
- Wasted early results - If one task finishes in 30 seconds, Claude still waits for all tasks
- No interaction - Can't ask questions or provide guidance while waiting
- Inefficient workflow - Claude can't use early results to inform other work or adjust approach
Task is just another tool in Claude's toolkit - but while most tools complete in seconds, tasks take minutes. The current runtime treats all tools the same:
// Current behavior - wait for ALL tools to complete
async function processToolCalls(toolCalls: ToolCall[]) {
return Promise.all(
toolCalls.map(toolCall => executeTool(toolCall))
)
// Promise.all waits for EVERY tool to finish before returning ANY results
}
This makes sense for quick tools but creates the blocking problem for tasks:
- Multiple tasks DO run in parallel (good!)
- But Claude waits for ALL to complete (bad!)
- If 3 tasks take [30s, 60s, 120s], you wait 120s for everything
Make the Task tool non-blocking. When a task is launched, it immediately creates a todo and returns, allowing Claude to continue working while the task runs in the background.
User: Please analyze our database schema, research migration best practices,
and find performance bottlenecks.
Claude: I'll work on those 3 areas.
Task(description="Analyze current database schema", priority="high")
Task(description="Research migration best practices", priority="medium")
Task(description="Find performance bottlenecks via query analysis", priority="high")
These tasks have implicit dependencies - bottleneck analysis needs
schema context. While they run, let me outline our migration approach...
[30 seconds later]
Claude: The migration best practices task just completed. I can see the
recommended pattern is versioned migrations. Let me document this
approach while waiting for the schema analysis...
[45 seconds later]
Claude: Perfect! The schema analysis is done. We have 47 tables with complex
relationships. This helps me understand what the bottleneck analysis
will reveal. Let me start mapping table dependencies...
[90 seconds later]
Claude: The performance analysis found the issue - missing indexes on foreign
keys in the orders table. Now I can create a comprehensive migration plan
that addresses both the schema changes and performance improvements...
Total: 90 seconds of productive work vs 3+ minutes of frozen waiting.
- Task tool becomes non-blocking - Returns immediately after creating a todo
- Todos get one new state -
resolved
(task complete, results available) - Task tool gets priority field - Two reasons: (1) Tasks create todos, and todos require a priority field - it needs to come from somewhere. (2) When multiple tasks resolve, priority helps Claude decide which results to review first
// Todo structure - gets one new state and field
interface Todo {
content: string
status: "pending" | "in_progress" | "resolved" | "completed" // NEW: resolved
priority: "high" | "medium" | "low"
id: string
result?: string // NEW: holds task results when resolved
}
// Task tool - gets optional priority
interface TaskToolCall {
tool: "Task"
args: {
description: string // Brief summary shown in UI
prompt: string // Detailed instructions for sub-agent
priority?: "high" | "medium" | "low" // NEW: (1) Task creates todo,
// todo needs priority field
// (2) When multiple tasks resolve,
// guides review order
}
}
- Claude spawns a task → Creates todo with status
in_progress
, returns immediately - Task runs in background → Sub-agent works independently
- Claude continues → Can spawn more tasks, work on other things, or chat with user
- Task completes → Todo automatically updates to
resolved
with results - Claude processes → During regular todo checks, sees resolved tasks and incorporates when ready
- Manual todos:
pending
→in_progress
→completed
(unchanged) - Task todos:
in_progress
→resolved
→completed
The resolved
state means "task finished, results available for incorporation."
// Blocking - waits for task to complete
async function handleTaskTool(toolCall) {
const result = await executeTask(toolCall)
return result
}
async function handleTaskTool(toolCall) {
// Create todo immediately
const todo = {
id: generateId(),
content: toolCall.args.description,
status: "in_progress",
priority: toolCall.args.priority || "medium",
result: null
}
addTodo(todo)
// Execute in background - no await
executeTask(toolCall).then(result => {
updateTodo(todo.id, {
status: "resolved",
result: result
})
}).catch(error => {
updateTodo(todo.id, {
status: "resolved",
result: `Error: ${error.message}`
})
})
// Return immediately
return {
todoId: todo.id,
message: "Task started asynchronously"
}
}
- Runtime - Modify Task tool handler to be non-blocking
- Todo tools - Return new
resolved
state andresult
field - Model prompting - Teach Claude to check for resolved tasks
- Testing - Ensure background updates work correctly
- Progressive results - Use outputs as they arrive instead of waiting
- Continued work - Claude can work on other things while tasks run
- Better coordination - Early results can inform other work
- User interaction - Conversation continues during long operations
- Smarter workflows - Claude can adapt based on intermediate results
- Making other tools non-blocking (they complete quickly)
- Task cancellation
- Task dependencies
- Priority interrupts
This design solves a real user pain point with minimal changes. By making tasks non-blocking and tracking them through todos, we transform dead waiting time into productive work. The conversation stays dynamic, Claude remains responsive, and work gets done faster through intelligent parallelism.
The implementation is straightforward: change one tool's behavior and add one todo state. The impact is significant: a fundamentally better user experience.
We believe this focused approach balances implementation simplicity with meaningful user value.