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.

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
| Pattern | After 10 subagents | After 50 subagents | Recovery |
|---|---|---|---|
| Orchestrator loop | ~45K tokens | Context limit exceeded | Total loss |
| Process-level | ~2K tokens (coordination only) | ~8K tokens | Independent 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.
Related field notes
The Write-Only Radar: Our Curation Agent Proposed the Same Story for 21 Days
When every node stays green while producing duplicate work, your data model is lying to you.
Nobody Prompt-Injected Our Agents — They Escalated Their Own Privileges
How the OWASP Top 10 for Agentic Applications maps to real threats in autonomous swarms. Spoiler: the danger is inside.
Multi-Agent Systems Reproduce Every Organizational Anti-Pattern You Already Hate
When autonomous AI agents share resources, they naturally replicate human organizational dysfunction. We catalog 5 production anti-patterns from our swarm of 11+ agents.