Studio Notebook

Claude Code Atlas

Tasks, Agents, And Background Execution

Learn how long-running work is represented and tracked.

Why this matters

Some work cannot finish inside one synchronous tool call. Claude Code uses the task layer to keep that work named, tracked, and visible while it is still running.

That same shared task model can support several concrete task kinds. One page can explain the contract, while the child pages each study a specific implementation.

How task work stays understandable

A shared model defines the common fields, concrete implementations fill in the real task kinds, and background visibility decides what shows up in the indicator.

shared model concrete implementation background visibility

One mental model for many kinds of work

Task.ts defines the common contract every task state starts from. It gives the system shared labels for task type, status, and the base fields that every task needs.

tasks/types.ts then decides which concrete task states count as background tasks. That keeps the user-facing background indicator separate from the shared task contract itself.

The child pages study the concrete task kinds one by one. Start with the shared model here, then move into the specific local shell, local agent, and remote agent flows after this.

export type TaskType =
  | 'local_bash'
  | 'local_agent'
  | 'remote_agent'
  | 'in_process_teammate'
  | 'local_workflow'
  | 'monitor_mcp'
  | 'dream'

export type TaskStatus =
  | 'pending'
  | 'running'
  | 'completed'
  | 'failed'
  | 'killed'

/**
 * True when a task is in a terminal state and will not transition further.
 * Used to guard against injecting messages into dead teammates, evicting
 * finished tasks from AppState, and orphan-cleanup paths.
 */
export function isTerminalTaskStatus(status: TaskStatus): boolean {
  return status === 'completed' || status === 'failed' || status === 'killed'
}

export type TaskHandle = {
  taskId: string
  cleanup?: () => void
}

export type SetAppState = (f: (prev: AppState) => AppState) => void

export type TaskContext = {
  abortController: AbortController
  getAppState: () => AppState
  setAppState: SetAppState
}

// Base fields shared by all task states
export type TaskStateBase = {
  id: string
  type: TaskType
  status: TaskStatus
  description: string
  toolUseId?: string
  startTime: number
  endTime?: number
  totalPausedMs?: number
  outputFile: string
  outputOffset: number
  notified: boolean
}

export type LocalShellSpawnInput = {
  command: string
  description: string
  timeout?: number
  toolUseId?: string
  agentId?: AgentId
  /** UI display variant: description-as-label, dialog title, status bar pill. */
  kind?: 'bash' | 'monitor'
}

// What getTaskByType dispatches for: kill. spawn/render were never
// called polymorphically (removed in #22546). All six kill implementations
// use only setAppState — getAppState/abortController were dead weight.
export type Task = {
  name: string
  type: TaskType
  kill(taskId: string, setAppState: SetAppState): Promise<void>
}

Task.ts is the common contract. It defines the names the rest of the system can share before any individual task kind gets involved.

// Union of all concrete task state types
// Use this for components that need to work with any task type

import type { DreamTaskState } from './DreamTask/DreamTask.js'
import type { InProcessTeammateTaskState } from './InProcessTeammateTask/types.js'
import type { LocalAgentTaskState } from './LocalAgentTask/LocalAgentTask.js'
import type { LocalShellTaskState } from './LocalShellTask/guards.js'
import type { LocalWorkflowTaskState } from './LocalWorkflowTask/LocalWorkflowTask.js'
import type { MonitorMcpTaskState } from './MonitorMcpTask/MonitorMcpTask.js'
import type { RemoteAgentTaskState } from './RemoteAgentTask/RemoteAgentTask.js'

export type TaskState =
  | LocalShellTaskState
  | LocalAgentTaskState
  | RemoteAgentTaskState
  | InProcessTeammateTaskState
  | LocalWorkflowTaskState
  | MonitorMcpTaskState
  | DreamTaskState

// Task types that can appear in the background tasks indicator
export type BackgroundTaskState =
  | LocalShellTaskState
  | LocalAgentTaskState
  | RemoteAgentTaskState
  | InProcessTeammateTaskState
  | LocalWorkflowTaskState
  | MonitorMcpTaskState
  | DreamTaskState

/**
 * Check if a task should be shown in the background tasks indicator.
 * A task is considered a background task if:
 * 1. It is running or pending
 * 2. It has been explicitly backgrounded (not a foreground task)
 */
export function isBackgroundTask(task: TaskState): task is BackgroundTaskState {
  if (task.status !== 'running' && task.status !== 'pending') {
    return false
  }
  // Foreground tasks (isBackgrounded === false) are not yet "background tasks"
  if ('isBackgrounded' in task && task.isBackgrounded === false) {
    return false
  }
  return true
}

tasks/types.ts is the background-visibility filter. It decides which concrete task states count as background tasks, while the child pages go deeper into the specific local shell, local agent, and remote agent implementations one by one.

How this part breaks down

  1. task-model-and-lifecycle-basics Start with the shared language every task uses: task types, statuses, state fields, and the rule for when a task counts as background work.
  2. task-and-agent-data-structures Read this appendix early if the task state objects feel abstract. It introduces the recurring shapes before later pages depend on them.
  3. local-shell-task-and-background-commands See how long-running bash commands become durable tasks with output files, stall detection, and notifications.
  4. local-agent-task-lifecycle Learn how Claude Code tracks a background local agent, including its progress, pending messages, and foreground/background transitions.
  5. remote-agent-task-lifecycle Follow the remote version of the same idea: eligibility checks, remote metadata, completion polling, and plan extraction.