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 consistencyprocess.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 sessionOPENCLAUDE_SUBAGENT– An environment variable set to'1'that marks the process as a forked sub-agent, enabling specialized initialization logic insrc/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 processesGITHUB_COPILOT_ALLOW_SUBAGENTS– Boolean flag to globally enable or disable forking capabilitiesGITHUB_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'insrc/types/command.ts, validated byisForkSubagentEnabled()before spawning - Process Spawning –
forkSubagent.tsuseschild_process.spawnwith the--forkflag andOPENCLAUDE_SUBAGENTenvironment 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.tsbefore integration into the parent session - Resource Governance – Copilot-specific settings like
GITHUB_COPILOT_MAX_SUBAGENTScontrol 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →