How Task Delegation and Spawning Create Subtasks in Roo Code: A Complete Technical Guide

Task delegation in Roo Code creates subtasks through the delegateParentAndOpenChild method in ClineProvider, which flushes parent state, instantiates a child task with linked metadata, and enforces a single-active-task invariant while preserving conversation history for seamless resumption.

Roo Code implements task delegation as a deterministic, metadata-driven workflow where parent tasks are paused and child tasks are spawned with explicit lineage tracking. This architecture, defined in the RooCodeInc/Roo-Code repository, ensures that only one task remains active at any moment while maintaining a complete audit trail through the history schema. Understanding how task delegation and spawning create subtasks is essential for developers extending Roo Code's multi-agent capabilities or debugging complex task hierarchies.

The Delegation Architecture

Roo Code treats every subtask as a child task created by delegating the current parent task to a new task instance. The entire flow is orchestrated by the new-task tool (newTaskTool) and the delegateParentAndOpenChild method of the ClineProvider class.

When delegation occurs, the system transitions from a single active parent to a delegated parent with an active child. This design guarantees that the global state always maintains exactly one open task while preserving the ability to resume parent conversations once children complete their work.

The Eight-Step Delegation Process

The delegateParentAndOpenChild method in src/core/webview/ClineProvider.ts executes a precise sequence to ensure data integrity and task isolation.

1. Flush Pending Tool Results

Any tool results belonging to the parent's conversation are persisted to the API history so the parent can resume later. This occurs in ClineProvider.delegateParentAndOpenChild() at lines 61-66.

2. Dispose the Parent Task

The parent is removed from the active stack, enforcing Roo Code's strict single-open invariant. This happens at lines 85-90 using removeClineFromStack({ skipDelegationRepair: true }).

3. Switch Provider Mode

The provider switches to the mode requested by the child, such as a specialized agent configuration. This mode transition occurs at lines 99-104 via handleModeSwitch.

4. Create the Child Task

A fresh Task instance is constructed with initialStatus: "active" and startTask: false to prevent premature execution before parent metadata is saved. This creation happens at lines 124-131.

5. Persist Parent Delegation Metadata

The parent's history entry receives critical linking fields at lines 132-141 through updateTaskHistory:

  • status is set to "delegated"
  • delegatedToId stores the child task ID
  • awaitingChildId stores the child task ID for resumption tracking
  • childIds array is extended to include the new child

6. Start the Child Task

Once the parent's delegation record is durable, the child safely initializes and begins execution at line 150 via child.start().

7. Emit Delegation Events

The system broadcasts TaskDelegated events for UI updates and external listeners at lines 154-156.

Data Model and Parent-Child Linking

The relationship between parent and child tasks is defined in packages/types/src/history.ts through a strict Zod schema:

export const historyItemSchema = z.object({
  // ...
  status: z.enum(["active", "completed", "delegated"]).optional(),
  delegatedToId: z.string().optional(),
  childIds: z.array(z.string()).optional(),
  awaitingChildId: z.string().optional(),
})

When a child is created, the parent's record updates with childIds and awaitingChildId to establish the lineage. When the child finishes, reopenParentFromDelegation() (lines 660-720 in ClineProvider.ts) reads this metadata and rewrites the parent's status back to "active", enabling seamless continuation of the original conversation.

Entry Point: The New-Task Tool

The delegation flow is triggered by the model emitting a new_task tool call. The NewTaskTool.handle() method in src/core/tools/NewTaskTool.ts serves as the entry point:

const child = await (provider as any).delegateParentAndOpenChild({
  parentTaskId: task.taskId,
  message: unescapedMessage,
  initialTodos: todoItems,
  mode,
})
pushToolResult(`Delegated to child task ${child.taskId}`)

The tool forwards arguments to the provider without pausing the parent directly, as delegateParentAndOpenChild already guarantees the single-open invariant through its internal synchronization.

Resuming Parent Tasks After Delegation

When a child task completes, reopenParentFromDelegation restores the parent context through a specific sequence:

  1. Loads the parent's persisted history and messages from storage
  2. Injects a synthetic subtask_result message into the parent's conversation so the API sees the child's outcome
  3. Clears the awaitingChildId field and updates status to "active"
const { historyItem } = await this.getTaskWithId(parentTaskId)
// ... inject synthetic records ...
await this.updateTaskHistory({ 
  ...historyItem, 
  status: "active", 
  awaitingChildId: undefined 
})

Practical Implementation Examples

Delegating from a Provider Instance

To programmatically create a subtask from within a provider:

// Assuming `provider` is a ClineProvider instance
await provider.delegateParentAndOpenChild({
  parentTaskId: "a1b2c3",
  message: "Write the unit tests for the new feature",
  initialTodos: [{ title: "Create test file", done: false }],
  mode: "code",
});

This creates a child task, marks parent a1b2c3 as delegated, and activates the child as the sole active task.

Tool-Based Delegation in Prompts

When the model returns a delegation request:

{
  "tool": "newTask",
  "mode": "code",
  "content": "Implement the missing API endpoint",
  "todos": ["Add route", "Write handler", "Add tests"]
}

The newTaskTool.handle() function processes this JSON and executes the delegation flow described above.

Monitoring Delegation Events

Track task relationships through event listeners:

provider.on(RooCodeEventName.TaskDelegated, (parentId, childId) => {
  console.log(`Parent ${parentId} delegated to child ${childId}`);
});

You can also listen for TaskSpawned events emitted by the task itself to capture the exact moment a child is instantiated.

Summary

  • Task delegation in Roo Code creates subtasks by pausing the parent and instantiating a child with linked metadata, ensuring only one task remains active at any time.
  • The delegateParentAndOpenChild method in src/core/webview/ClineProvider.ts orchestrates the eight-step workflow including state flushing, mode switching, and history persistence.
  • Parent-child relationships are tracked through the history schema fields delegatedToId, childIds, and awaitingChildId defined in packages/types/src/history.ts.
  • Resumption occurs via reopenParentFromDelegation, which restores parent context by injecting synthetic subtask results and clearing delegation metadata.
  • The newTaskTool in src/core/tools/NewTaskTool.ts provides the primary interface for model-initiated delegation.

Frequently Asked Questions

What happens to the parent task when a subtask is created?

The parent task is marked with status: "delegated" and removed from the active task stack. Its state is flushed to persistent storage including any pending tool results, allowing it to resume exactly where it left off once the child completes. The parent remains in a suspended state tracked by awaitingChildId until reopenParentFromDelegation reactivates it.

How does Roo Code prevent multiple tasks from running simultaneously?

Roo Code enforces a single-open invariant through the removeClineFromStack method called during delegation with skipDelegationRepair: true. This ensures the parent is disposed before the child starts, guaranteeing exactly one active task at any moment. The provider maintains this invariant by strictly sequencing the disposal and creation operations within delegateParentAndOpenChild.

Can a child task create its own subtasks?

Yes, the delegation mechanism supports nested hierarchies. When a child task invokes the new_task tool or calls delegateParentAndOpenChild, it becomes a parent to its own child task. The childIds array in the history schema tracks all descendants, and the system maintains the delegation chain through successive delegatedToId references, allowing for arbitrarily deep task nesting while preserving the single-active-task constraint at each level.

Where is the delegation state stored between sessions?

Delegation metadata persists in the VS Code global state through the history schema defined in packages/types/src/history.ts. Fields including status, delegatedToId, childIds, and awaitingChildId are written to storage via updateTaskHistory before the child task starts, ensuring that parent-child relationships survive application restarts and can be reconstructed when reopenParentFromDelegation executes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →