delegate

Source: src/delegate.ts

Letting a subagent have subagents of its own.

An agent whose tools: names subagent is handed this, and can then split its task across children of its own. Nothing else changes: the tool is built here and passed through SpawnOptions.customTools, so spawn never learns what a roster is.

This is one of the three exceptions to “a subagent inherits nothing”, beside situate() and skills:. Granting it is not inheritance either: the tool comes from combo rather than from the user’s machine, the roster is the one the caller chose, and an agent that does not name it in its own file cannot have it. What an agent can do stays readable in its definition, which is the part of the invariant that was ever load-bearing.

The depth guard ships with the feature rather than after it. Delegation that can go on forever is a bill discovered afterwards, and the bound is carried in a closure rather than read from anywhere: an ambient variable is how the model hole in invariant 5 existed, and nothing here reads the environment.

declaresDelegate

function

export function declaresDelegate(tools: readonly string[] | undefined): boolean { /* … */ }

Whether an agent’s definition asks to be allowed children of its own.

DelegateOptions

type

export type DelegateOptions = Omit<WorkflowOptions, "customTools" | "lifetime"> & {
	/** Branches at once, when the holder's definition does not say. */
	concurrency?: number;
	/** The roster a child may name. A name not on it is refused, never guessed. */
	agents: readonly Agent[];
	/**
	 * The agent being handed this tool.
	 *
	 * Its `concurrency:` decides how many children it runs at once. Passing it is
	 * what lets that number live in the agent's own file rather than in every
	 * call site, and a child that delegates in turn is read the same way.
	 */
	holder?: Agent;
	/** How many levels of delegation are allowed. Defaults to {@link MAX_DEPTH}. */
	maxDepth?: number;
	/** The depth of whoever is being handed this tool. The first call is 1. */
	depth?: number;
	/**
	 * The id of the subagent holding this tool, so its children can name it.
	 *
	 * Absent at the top level only when nobody could say: the holder's id is
	 * minted by `spawn`, so a caller building this tool by hand takes it from
	 * `SpawnOptions.customTools` in its function form. Without it the children
	 * are still spawned and still measured - they simply read as roots, which
	 * is a measurement that has lost a fact rather than a run that failed.
	 */
	parentId?: string;
};

Who a child may delegate to, how deep, and what its own children inherit.

delegateTool

function

export function delegateTool(options: DelegateOptions): ToolDefinition { /* … */ }

Builds the subagent tool for an agent at a given depth.

Pass it through SpawnOptions.customTools. An agent whose tools: does not name subagent will not be given it by pi, so offering it costs nothing.

At the bound the tool is still handed over and refuses when called, saying how deep it is and how deep it may go. Withholding it instead would leave a model calling a tool that does not exist, getting “unknown tool” back, and trying again - which is the runaway turn timeoutMs exists to survive rather than a thing to cause on purpose.

MAX_DEPTH

const

export const MAX_DEPTH = 2;

How deep delegation goes by default: the session, a child, a grandchild.

Two is where a split stops paying. A grandchild has been handed one slice of one slice, and rarely knows enough about the whole to divide it usefully - it spends a turn deciding that instead of reading.