How OpenClaude Executes Sub-Agents in Separate Processes: Complete Fork Architecture Guide

OpenClaude runs sub-agents in isolated processes by spawning a new Node.js instance via child_process.spawn, passing the --fork flag and an OPENCLAUDE_SUBAGENT environment variable to establish separate execution contexts that communicate via JSON streams over stdin and stdout.

OpenClaude, an open-source AI agent framework maintained in the Gitlawb/openclaude repository, implements process-level isolation to prevent state corruption during complex multi-step operations. When a tool or skill requires sandboxed execution, the system forks the current process into a child sub-agent with its own memory space, token budget, and conversation state. This architecture ensures that computationally intensive or risky operations cannot interfere with the parent agent's deterministic execution flow.

Detecting Fork Requirements

Sub-agent execution is not automatic; it must be explicitly requested and enabled through a two-stage detection mechanism.

Execution Context Declaration

Tools and skills declare their isolation requirements through the executionContext property defined in src/types/command.ts. When a tool specifies executionContext: 'fork', it signals to the orchestration layer that this operation must run in a separate process rather than inline within the main agent loop.

Runtime Fork Validation

Before spawning, AgentTool checks isForkSubagentEnabled() from src/tools/AgentTool/forkSubagent.ts. This function evaluates Copilot-specific settings and global configuration to determine whether process forking is permitted for the current session. If disabled, the tool may fall back to inline execution or refuse the operation entirely.

Spawning the Sub-Agent Process

The core process creation logic resides in src/tools/AgentTool/forkSubagent.ts, which uses Node.js's native child_process module to create an isolated OS-level process.

Process Initialization

The parent process spawns a new Node.js instance executing the same CLI binary, but with specific flags and environment markers that trigger sub-agent mode:

import { spawn } from 'child_process';

const child = spawn(process.execPath, [process.argv[1], '--fork'], {
  env: { ...process.env, OPENCLAUDE_SUBAGENT: '1' },
  stdio: ['pipe', 'pipe', 'pipe'],
});

This invocation passes three critical parameters:

  • process.execPath – The same Node.js binary running the parent, ensuring version consistency
  • process.argv[1] – The current script path, typically the OpenClaude CLI entry point
  • --fork – A command-line flag that instructs the spawned process to initialize in sub-agent mode rather than starting a new primary session
  • OPENCLAUDE_SUBAGENT – An environment variable set to '1' that marks the process as a forked sub-agent, enabling specialized initialization logic in src/tools/AgentTool/runAgent.ts

Message Preparation

Before streaming begins, buildForkedMessages() constructs a minimal transcript containing the system prompt, user request, and any required context from the parent session. This payload excludes sensitive parent state that does not require sharing, enforcing the principle of least privilege.

Inter-Process Communication Protocol

Once spawned, the parent and child communicate through standard Unix pipes rather than shared memory, ensuring complete address space isolation.

JSON Lines Streaming

The parent writes the prepared messages to the child's stdin as newline-delimited JSON:

import { buildForkedMessages } from './forkSubagent.js';

function runSubAgent(messages) {
  const child = spawn(process.execPath, [process.argv[1], '--fork'], {
    env: { ...process.env, OPENCLAUDE_SUBAGENT: '1' },
    stdio: ['pipe', 'pipe', 'inherit'],
  });

  // Stream the prepared transcript to child's stdin
  child.stdin.write(JSON.stringify(buildForkedMessages(messages)) + '\n');
  child.stdin.end();

  // Collect response chunks from stdout
  const chunks = [];
  child.stdout.on('data', (c) => chunks.push(c));
  
  return new Promise((resolve) => {
    child.on('close', () => resolve(Buffer.concat(chunks).toString()));
  });
}

The child process, managed by runAgent.ts, consumes these messages through its own agent loop, performs the requested tool calls, and writes structured responses back to stdout. This unidirectional streaming protocol prevents deadlocks while maintaining deterministic message ordering.

Process Isolation and Safety Guarantees

Running sub-agents in separate processes provides three critical safety properties that inline execution cannot guarantee.

Memory and State Isolation

Because the sub-agent executes in its own OS process, it receives an independent V8 heap and cannot directly access the parent's in-memory data structures. This isolation eliminates race conditions and cross-talk bugs that could occur when multiple agent loops operate on shared mutable state.

Resource Budgeting

Each forked sub-agent obtains its own token budget and conversation history constraints. When the child exceeds its allocated limits, it terminates without consuming the parent's token quota or corrupting the main session's conversation state.

Safety Classification on Return

After the child process exits, the parent retrieves the final output and passes it through a safety classifier implemented in src/tools/AgentTool/agentToolUtils.ts. This validation step ensures that sub-agent results meet safety policies before integration into the main transcript.

Concurrency Control and Resource Limits

The system respects several Copilot-specific environment variables defined in src/utils/copilotOptimization.ts to govern sub-agent execution patterns:

  • GITHUB_COPILOT_MAX_SUBAGENTS – Hard caps the maximum number of concurrent sub-agent processes
  • GITHUB_COPILOT_ALLOW_SUBAGENTS – Boolean flag to globally enable or disable forking capabilities
  • GITHUB_COPILOT_FORCE_SYNC_SUBAGENTS – When set, forces sequential (synchronous) execution rather than parallel forking

These settings allow OpenClaude to safely operate in resource-constrained environments or comply with organizational policies regarding process spawning.

Integrating Sub-Agent Results

The AgentTool component in src/tools/AgentTool/AgentTool.tsx orchestrates the complete lifecycle. After the child process terminates, it captures the exit code, parses the JSON response from stdout, runs the safety validation, and merges the sub-agent's transcript into the parent session history. This "fork-and-join" model provides the parent with a complete audit trail of the sub-agent's reasoning while maintaining strict execution boundaries during the operation.

Summary

  • Fork Detection – Tools declare executionContext: 'fork' in src/types/command.ts, validated by isForkSubagentEnabled() before spawning
  • Process Spawning – forkSubagent.ts uses child_process.spawn with the --fork flag and OPENCLAUDE_SUBAGENT environment variable to create isolated Node.js processes
  • Communication – Parent and child exchange data via JSON lines over stdin/stdout pipes, avoiding shared memory
  • Isolation Benefits – Separate processes provide independent token budgets, memory safety, and elimination of cross-talk bugs
  • Safety Controls – Results pass through classifiers in agentToolUtils.ts before integration into the parent session
  • Resource Governance – Copilot-specific settings like GITHUB_COPILOT_MAX_SUBAGENTS control concurrency and execution modes

Frequently Asked Questions

How does OpenClaude decide when to fork a sub-agent?

OpenClaude checks for executionContext: 'fork' declarations in tool definitions from src/types/command.ts and validates runtime permissions through isForkSubagentEnabled() in forkSubagent.ts. Only tools explicitly requesting forked execution and passing environment checks spawn separate processes.

What environment variables control sub-agent behavior?

The OPENCLAUDE_SUBAGENT variable marks forked processes for initialization routing. Copilot mode supports three additional controls: GITHUB_COPILOT_ALLOW_SUBAGENTS enables the feature, GITHUB_COPILOT_MAX_SUBAGENTS limits concurrency, and GITHUB_COPILOT_FORCE_SYNC_SUBAGENTS disables parallel execution.

How do sub-agents communicate with the parent process?

Sub-agents communicate via streaming JSON lines over standard Unix pipes. The parent writes prepared messages to the child's stdin using buildForkedMessages(), and the child writes responses to stdout as it completes its agent loop. This protocol ensures complete memory isolation while maintaining structured data exchange.

Can sub-agents run in parallel?

Yes, unless restricted by GITHUB_COPILOT_FORCE_SYNC_SUBAGENTS. The architecture supports parallel execution up to the limit defined by GITHUB_COPILOT_MAX_SUBAGENTS. Each parallel sub-agent operates in its own process with independent resources, though the parent coordinates their initialization and result integration sequentially.

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 →