RUNTIME / DELEGATION
Hand a plan to sub-agents.
When a task decomposes into pieces that do not depend on each other, NAH can run them as separate child agents at once — each in its own transcript, each in an isolated workspace, each coming back as a diff you review.
A parent and its children
runAgent is one agent in one workspace. Orchestrator is the parent-and-children workflow on top of it: the parent hands over a bounded subtask, the child works in a fresh transcript against an isolated workspace, and the work returns as a reviewable artifact.
The payoff is context. A wide read-and-reason job stops competing with the parent's own work for one context window, and a child that goes wrong leaves something reviewable behind instead of half-applied edits. The mechanism stays explicit — the orchestrator is a class you construct and call, not a layer that decides on its own.
Read the Orchestrator setup and API guide →Isolation is the point
An isolation strategy is three operations: hand the child a root its tools are confined to plus the boundary facts it cannot infer, collect what it produced, then clean up. createGitWorktreeIsolation creates a separate worktree from a snapshot of the parent's current working tree by default, including uncommitted changes. That snapshot is frozen at child start, so later parent edits are not visible. Siblings cannot collide in each other's checkout. sharedWorkspaceIsolation() is the fallback when there is no repository.
const isolation = {
description: "temporary Git worktree",
prepare: async ({ title, task }) => ({
cwd: worktreePath, // where the child's tools are confined
boundaryNotes: [ // what the child cannot infer
"- The parent's uncommitted working state is included in this worktree (src/, docs/), so you can build on it. It was frozen when this child started, so anything the parent changes after that is not here.",
],
collect: async () => ({ // what the parent reviews
baseRevision, changedPaths, diff,
}),
cleanup: async ({ retain }) => {
if (!retain) await runGit("worktree", "remove", "--force");
},
}),
};Plans fan out, they do not queue
run handles one subtask. runAll takes the whole plan and runs it in waves of maxConcurrency children — a plan is rarely one item, and the cap is a scheduling constraint rather than a refusal. A single task submitted above the cap throws OrchestratorBusyError instead of queueing, because a queued child can start later than the parent expected and outlive the run that asked for it.
import {
createCodingTools,
createGitWorktreeIsolation,
formatSubtaskReport,
Orchestrator,
orchestratorPrompt,
} from "not-another-harness";
import { createNodeEnvironment } from "not-another-harness/node";
const orchestrator = new Orchestrator({
model,
system: `${buildSystemPrompt({ cwdLabel: cwd })}
${orchestratorPrompt({
concurrency: 3,
})}`,
isolation: createGitWorktreeIsolation({ cwd }),
createTools: (root) =>
createCodingTools(createNodeEnvironment(root), { approveToolCall }),
maxConcurrency: 3,
maxSteps: 20,
onUsage: (usage) => session.rollSpend(usage),
});
const results = await orchestrator.runAll([
{ title: "extract the parser", task: "Move parse() into parser.ts. Keep the export." },
{ title: "cover the parser", task: "Add tests for parse() edge cases." },
]);Nothing is applied for you
Every child returns its stop reason, step and tool-call counts, token usage, changed paths, and a diff against the revision it branched from. Integrating that diff is the parent's decision, made on a transcript that still holds the parent's own reasoning.
Delegated task: extract the parser
Base revision: 4f1c9ab
status: completed; steps: 6; tool calls: 9; tokens: 41200 in / 1840 out / 43040 total; estimated usage: false; elapsed: 62140ms
Changed paths: src/parser.ts, src/index.ts
Review this diff before applying any of it:
diff --git a/src/index.ts b/src/index.ts
...A diff too large to return inline is truncated, and the report says where the full one is — because a truncated diff nobody can open is not a result.
In the CLI
Outside a sandbox the agent has two tools: delegate_task for a single independent subtask, and delegate_tasks to hand over a whole plan in one call, so the fan-out reads as one decision instead of N sequential waits. Both go through one orchestrator with up to three concurrent children, and both require approval under the ask permission mode. Child spend rolls into the session's own totals.
The system prompt tells the agent when to reach for them: an independent plan belongs to the children, dependent work belongs to the parent.
CLI modes and permission handling →