COMPOSE / ORCHESTRATOR
Coordinate child agents explicitly.
Orchestrator runs bounded subtasks with fresh transcripts and controlled workspaces. Your code decides what to delegate, how much can run at once, and how results are reviewed and integrated.
When to use an orchestrator
Use runAgent for one agent working through one task. Add an Orchestrator when that task can be split into independent assignments that benefit from separate context or workspaces—for example, reviewing separate modules or implementing changes that do not depend on each other.
Orchestrator does not decompose a prompt automatically and does not merge child work. The caller or parent agent writes each assignment, starts the children, and reviews their reports and artifacts.
parent plans independent subtasks
→ orchestrator starts child runs
→ each child gets its own transcript + workspace strategy
→ results return with status, usage, report, and optional diff
→ parent reviews and decides what to integrateCreate an orchestrator
Provide a model, system instructions, and a tool factory. The factory receives each child’s workspace root, so tools can be confined to that child. Git worktree isolation is useful when children may edit files.
import {
createCodingTools,
createGitWorktreeIsolation,
Orchestrator,
orchestratorPrompt,
} from "not-another-harness";
import { createNodeEnvironment } from "not-another-harness/node";
const orchestrator = new Orchestrator({
model,
system: [
"You are a coding agent.",
orchestratorPrompt({ concurrency: 3 }),
].join("\n\n"),
isolation: createGitWorktreeIsolation({ cwd }),
createTools: (root) =>
createCodingTools(createNodeEnvironment(root), {
withBash: true,
approveToolCall,
}),
maxConcurrency: 3,
maxSteps: 20,
taskTimeoutMs: 10 * 60_000,
onUsage: (usage) => session.rollSpend(usage),
});Run one task or a plan
Call run for a single subtask. Use runAll for a plan: it schedules tasks in waves up to the concurrency limit and returns results in submission order. Give every task a self-contained assignment with scope and acceptance criteria because children do not receive the parent’s transcript.
const results = await orchestrator.runAll([
{
title: "Review the parser",
task: "Inspect src/parser.ts for correctness bugs. Do not edit. Report each issue with a location and reproduction.",
createTools: (root) => createCodingTools(createNodeEnvironment(root), {
withBash: false,
}),
},
{
title: "Review the API handlers",
task: "Inspect src/api for authorization gaps. Do not edit. Report concrete findings.",
},
]);
for (const result of results) {
console.log(result.title, result.status, result.usage);
if (result.artifact) {
console.log(result.artifact.changedPaths);
reviewDiff(result.artifact.diff);
}
}Each SubtaskResult includes the status, step and tool-call counts, usage, elapsed time, final text, and—when isolation collects one—changed paths and a diff. A child failure is returned as a result with status: "error", so other tasks can still finish.
Concurrency, budgets, and cancellation
maxConcurrency defaults to three. runAll accepts plans larger than the limit and runs them in waves. A direct run submitted when all slots are occupied rejects with OrchestratorBusyError rather than queueing unexpectedly.
const controller = new AbortController();
const orchestrator = new Orchestrator({
model, system, createTools,
maxConcurrency: 2,
maxSteps: 18,
maxSpendUsd: 0.40,
rates: { input: 3, output: 15 },
signal: controller.signal,
onEvent: (event) => {
if (event.type === "subtask-start") showStarted(event.title);
if (event.type === "subtask-event") forwardChildEvent(event);
if (event.type === "subtask-finish") showFinished(event.result);
},
});
controller.abort(); // abort children still in flightA task can override its own maxSteps, maxSpendUsd, signal, isolation strategy, or tool factory. Use onUsage to fold completed child usage into the parent application’s accounting.
Choose a workspace strategy
createGitWorktreeIsolation({ cwd }) gives each child a separate checkout. By default it snapshots the parent working tree at child start, including uncommitted changes. That snapshot is frozen: later parent edits are not visible to the child. Siblings cannot write into one another’s worktrees.
Once a child finishes, its worktree is cleaned up by default and its diff is returned for review. If the diff is too large to include, the workspace is retained and its path is included in the result. Set retain: true to keep worktrees for inspection. sharedWorkspaceIsolation() is available when sharing files is intentional, but concurrent writers can conflict.
Observe child runs
Use onEvent to observe starts, each nested harness event, and completion. The child event stream is drained by the orchestrator even if your callback does not render it, so progress handling is optional. totalUsage reports cumulative usage for completed children.
const result = await orchestrator.run({
title: "Find the migration path",
task: "Trace how schema migrations are loaded. Return the relevant files and a concise explanation.",
});
console.log(result.status, result.text);
console.log(orchestrator.active); // children currently running
console.log(orchestrator.totalUsage); // completed-child usageForward subtask-event payloads to the same UI renderer used for a normal runAgent, while using the outer subtask ID and title to group child activity.
Use orchestration from workflows
For repeatable sequences, combine the orchestrator with a workflow. runWorkflow supplies delegate and delegateAll in each step’s context, so deterministic steps stay code and only judgement-heavy steps use child agents. A suspended workflow can be found with pendingWorkflows() and continued with resumeWorkflow(runId, answer).
const outcome = await orchestrator.runWorkflow(reviewWorkflow, {
inputData: { base: "main" },
onEvent: (event) => renderWorkflowProgress(event),
});
if (outcome.status === "success") {
useWorkflowResult(outcome.result);
} else if (outcome.status === "suspended") {
showQuestion(outcome.suspendPayload);
}See Workflows for defining and composing those sequences, or Delegation for the parent/child integration patterns used by the CLI.