Back to writing
July 22, 2026·13 min read

Orchestrator loops are a trap. Process-level orchestration wins.

Why recursive Agent/Task tool calls collapse in production and how Agent Swarm uses independent Claude Code processes with external runners instead.

agent orchestrationClaude Codeprocess architecturedistributed systemscontext windowsagent-swarm
Yo Dawg meme about putting subagents inside an orchestrator loop until the context window crashes
Recursive intelligence is elegant right up until the parent context becomes the failure domain.

We learned this the hard way: when your orchestrator Claude Code instance hits the context-window limit because it has absorbed the full execution traces of seventeen failed subagents, you do not have a distributed system. You have a very expensive single point of failure.

This is the trap of the orchestrator-loop pattern. It looks elegant in demos. One Claude Code session uses the Agent or Task tool to spawn subagents, which spawn subagents, creating a recursive tree of intelligence. Anthropic's Making of Claude Code story showcased the appeal. But a demonstration pattern and a production system handling thousands of tasks across weeks of runtime optimize for different things.

What is an orchestrator loop?

An orchestrator loop is the pattern where a single Claude Code session acts as the brain, recursively calling an Agent or Task tool to spawn subagents for parallel or sequential work. The parent maintains the conversation history, tool results, and subagent outputs in its own context window.

// orchestrator-loop.ts
// This runs INSIDE one Claude Code session

async function orchestrateTask(task: Task) {
  const subtasks = await planSubtasks(task);

  const results = await Promise.all(
    subtasks.map((sub) =>
      claude.agent({
        prompt: sub.instruction,
        tools: ["Read", "Edit", "Bash"],
      }),
    ),
  );

  // The parent context grows with every returned trace.
  return synthesizeResults(results);
}

The problem is not the recursion. It is the shared fate. When that parent context fills up, the entire swarm dies. When the parent crashes, every in-flight subagent loses its coordinator. When you need to debug why subagent number seven behaved strangely, you are digging through one enormous context soup.

The four failure modes that killed our loops

In our early prototypes, the loop pattern worked for simple demos: three to five subagents handling small tasks. Once the workload grew to complex dependency chains and long-running operations, we hit four non-negotiable walls.

1. Context-window blowups compound

Every time a subagent returns, its execution trace—tool calls, files read, and intermediate output—gets appended to the parent context. If you spawn five subagents and each reads ten files, the parent inherits fifty file contents plus all the surrounding metadata.

Nested trees deeper than three levels reliably exhausted their useful context during active work. The parent did not merely lose track of early instructions. It began forgetting constraints from the original task because they were buried under thousands of tokens of intermediate output.

Context growth comparison

PatternAfter 10 subagentsAfter 50 subagentsRecovery
Orchestrator loop~45K tokensContext limit exceededTotal loss
Process-level~2K tokens (coordination only)~8K tokensIndependent retry

2. Observability becomes a nightmare

With nested loops, you get one log stream: the parent's. When subagent twelve goes rogue, you cannot isolate its trace, set different alerting thresholds for that agent type, or attach diagnostics to only the child that is looping.

Debugging failures in nested loops took dramatically longer than comparable failures in process-isolated architectures. The signal-to-noise ratio of one shared context made forensic analysis a manual archaeology exercise.

3. There is no independent crash recovery

If a child in a loop hits an unrecoverable error or becomes stuck, you cannot restart only that child with a fresh context and a durable task claim. Restarting the parent risks losing the progress of every sibling because their coordination state lives inside the same conversation.

4. The blast radius is shared

When the parent process dies—from resource exhaustion, context limits, or a network failure—all children lose their control plane at once. In a production swarm with many concurrent tasks, that turns a localized resource issue into a system-wide outage.

Why not use the Agent or Task tool recursively?

Because those tool calls create tight coupling. The parent must remain alive and attentive for the duration of every child operation. That is the opposite of the durable execution model production agent systems need.

The alternative: process-level orchestration

We moved to an architecture where every agent is an independent headless Claude Code process coordinated by an external runner and a task-lifecycle queue. This is not an academic distinction. It is the difference between a demo and a system that can survive failures while other work continues.

// runner.ts
// An external process coordinates independent Claude Code sessions.

class AgentProcess {
  private process: ChildProcess;
  private taskQueue: Queue<Task>;
  private state: "idle" | "running" | "crashed";

  async executeTask(task: Task): Promise<Result> {
    const session = await this.spawnClaudeCode({
      workDir: task.isolatedWorkspace,
      maxTokens: task.budget,
      timeout: task.deadline,
    });

    try {
      const result = await session.runTask(task.instruction);
      await this.checkpointState(task.id, result);
      return result;
    } catch (error) {
      await this.handleFailure(task, error);
      throw error;
    } finally {
      await session.terminate();
    }
  }
}

The key insight is that the orchestrator is not another Claude Code instance managing children through tool calls. It is a lightweight runner that supervises process lifecycles, enforces queue semantics, and maintains durability guarantees. Claude Code instances reason about code; the runner handles distributed-systems concerns.

Separation of concerns

  • Runner: queue management, process supervision, retry logic, metrics collection, and secret injection.
  • Agent process: reasoning, file operations, tool use, and context-local state.
  • Queue: durability, ordering, priority, and dead-letter handling for failed tasks.

When an agent process hits a context limit, it dies alone. The runner detects the exit, checks retry eligibility, and either spawns a fresh process or moves the task into a human-review path. The rest of the swarm continues unaffected.

What the demo pattern gets wrong at production scale

The in-session orchestrator pattern is designed to demonstrate model capability, not production durability. It optimizes for elegance and immediacy. Production systems must optimize for work that survives the coordinator, emits inspectable state, and can be retried without restarting every sibling.

Intelligence cannot compensate for architectural constraints. A strong model in a shared failure domain is still a fragile system. The assumptions that work for generating one component collapse when the job spans a large codebase and hundreds of dependent operations.

Specifically, the demo pattern underweights:

  • Time: real tasks can run for hours or days, not seconds.
  • Scale: production swarms carry far more concurrent work than a demo.
  • Failure: networks partition, disks fill, processes crash, and APIs rate-limit.
  • Observability: demos need output; production systems need per-agent traces and state transitions.

Implementation: the task-lifecycle queue

The heart of the production design is a durable task queue rather than an in-memory array. Task state outlives any individual agent process, and claims, completions, and retry decisions become explicit state transitions.

// task-lifecycle.ts
interface Task {
  id: string;
  instruction: string;
  parentId?: string;
  workspaceSnapshot: string;
  budget: {
    maxTokens: number;
    maxTimeMs: number;
    maxCostCents: number;
  };
  retryCount: number;
  state: "pending" | "running" | "completed" | "failed";
}

class TaskRunner {
  async claimTask(agentId: string): Promise<Task | null> {
    return this.queue.claimOldestPending(agentId);
  }

  async completeTask(taskId: string, result: Result) {
    await this.storeResult(taskId, result);
    await this.queue.ack(taskId);
    await this.notifyParentIfBlocked(taskId);
  }

  async handleCrash(taskId: string, error: Error) {
    const task = await this.queue.get(taskId);

    if (task.retryCount < MAX_RETRIES) {
      await this.queue.requeue(taskId, {
        retryCount: task.retryCount + 1,
        delayMs: exponentialBackoff(task.retryCount),
      });
      return;
    }

    await this.deadLetterQueue.push(task);
    await this.alertOps(task, error);
  }
}

This architecture provides something loops cannot: survival of the coordinator. If the runner restarts, persisted task state remains. If an agent dies mid-task, its claim can expire and the task can return to pending. Runners can scale horizontally without folding every active agent into one context window.

The migration path

If you currently run orchestrator loops, you do not need to rewrite the system overnight. Start with the long tail: work expected to run longer than a few minutes, touch many files, or depend on external APIs. Move those tasks behind durable claims and isolated processes first.

Keep lightweight loops for genuinely trivial subtasks where failure is acceptable and context growth is bounded by strict limits. Put a hard cap on depth. If the recursion needs a third level, you are probably looking at a worker that deserves its own lifecycle.

Migration checklist

  • Expected output above 10KB: process-level.
  • Expected runtime above two minutes: process-level.
  • External API dependencies: process-level.
  • Trivial validations and format checks: bounded loop.
  • Implement durable task state before migrating critical paths.

Agents are workers, not function calls

The orchestrator loop is the monolith of agent architecture. It works until it does not, and when it breaks, everything breaks at once. Process-level orchestration acknowledges a fundamental truth: agents are independent workers with their own resource constraints, failure modes, and lifecycle requirements.

Treat agents as processes, not tools. Your future self debugging a 3 AM production incident will thank you.

FAQ

What is process-level orchestration?

Running each agent as an independent OS process coordinated by an external runner and task queue, rather than as nested tool calls within a single session.

Why do orchestrator loops fail in production?

They create shared context windows that grow rapidly, eliminate per-agent observability, prevent independent crash recovery, and create a cascading failure domain when the parent process dies.

What is the overhead of process-level orchestration?

Each agent needs its own process startup and memory allocation, but gains an independent crash domain, a separate context window, and granular resource controls that an in-session loop cannot provide.

Can I mix both approaches?

Yes. Use process-level orchestration for long-running or critical work that requires durability, and lightweight loops only for bounded, trivial subtasks where failure is acceptable.

/ keep reading
/ get started

Build your swarm tonight.

A 7-day free trial on Cloud, or fork it on GitHub. Either way, your agents start compounding today.