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.