Learn Claude Code
s08

Background Tasks

Concurrency

Background Threads + Notifications

223 LOC6 toolsTypeScriptBackgroundManager + notification queue
Run slow operations in the background; the agent keeps thinking ahead

s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12

"Run slow operations in the background; the agent keeps thinking" -- daemon threads run commands, inject notifications on completion.

Harness layer: Background execution -- the model thinks while the harness waits.

Problem

Some commands take minutes: npm install, pytest, docker build. With a blocking loop, the model sits idle waiting. If the user asks "install dependencies and while that runs, create the config file," the agent does them sequentially, not in parallel.

Solution

Main thread                Background thread
+-----------------+        +-----------------+
| agent loop      |        | subprocess runs |
| ...             |        | ...             |
| [LLM call] <---+------- | enqueue(result) |
|  ^drain queue   |        +-----------------+
+-----------------+

Timeline:
Agent --[spawn A]--[spawn B]--[other work]----
             |          |
             v          v
          [A runs]   [B runs]      (parallel)
             |          |
             +-- results injected before next LLM call --+

How It Works

  1. BackgroundManager tracks tasks with a thread-safe notification queue.
class BackgroundManager {
  tasks: Record<string, BackgroundTask> = {};
  private notificationQueue: Array<{ task_id: string; status: string; result: string }> = [];
}
  1. run() starts a daemon thread and returns immediately.
run(command: string) {
  const taskId = randomUUID().slice(0, 8);
  this.tasks[taskId] = { status: "running", result: null, command };
  const child = spawn(shell, args, { cwd: WORKDIR });
  return `Background task ${taskId} started`;
}
  1. When the subprocess finishes, its result goes into the notification queue.
child.on("close", () => {
  this.notificationQueue.push({
    task_id: taskId,
    status: "completed",
    result: result.slice(0, 500),
  });
});
  1. The agent loop drains notifications before each LLM call.
const notifications = BG.drainNotifications();
if (notifications.length) {
  messages.push({
    role: "user",
    content: `<background-results>\n${notifText}\n</background-results>`,
  });
}

The loop stays single-threaded. Only subprocess I/O is parallelized.

What Changed From s07

ComponentBefore (s07)After (s08)
Tools86 (base + background_run + check)
ExecutionBlocking onlyBlocking + background threads
NotificationNoneQueue drained per loop
ConcurrencyNoneDaemon threads

Try It

cd learn-claude-code
cd agents-ts
npm install
npm run s08
  1. Run "sleep 5 && echo done" in the background, then create a file while it runs
  2. Start 3 background tasks: "sleep 2", "sleep 4", "sleep 6". Check their status.
  3. Run pytest in the background and keep working on other things