/** * Reading a plan an agent wrote: the prompt, the parser, the validation. * * Split out from `orchestrate` so the prompt, the parser and the validation * can be read, and tested, apart from the fan-out that follows them. * * The open question this file answers is **how to read what an agent decided**. * Three candidates were weighed: a tool call, structured output, or a parsed * convention. This is a parsed convention, and the reasoning is worth keeping: * * - **A tool call** is possible (`createAgentSession` takes `customTools`), and * it would give validated arguments for free. It also means teaching * `SessionPort` about tool definitions, and betting the whole thing on a model * that reliably emits tool calls. The weak models this library is routinely * run against do not. * - **Structured output** is not uniformly available across providers, and pi's * `prompt()` returns text either way. * - **A parsed convention** costs one function, works on every provider, and the * plan is validated against the known agents regardless - which is the check * that actually matters. A schema would not have caught a hallucinated agent * name; the name lookup does. * * {@link parsePlan} is therefore lenient, and *only* it: its input is a language * model, not a caller. Everywhere else in this library a malformed input is an * error. */ import type { Agent } from "./../agent.ts"; import type { Result } from "./../result.ts"; import type { WorkflowOptions } from "./options.ts"; /** One step of a plan: a *resolved* agent - an unknown name never gets this far - and its task. */ export type PlannedTask = { /** Already resolved: an unknown name is dropped by the parser, never carried here. */ agent: Agent; /** What that agent is asked to do, in the planner's own words. */ task: string; }; /** Who plans, who may be delegated to, and how large a plan may get. */ export type PlanOptions = WorkflowOptions & { /** The agent that decides the split. */ planner: Agent; /** Who it may delegate to. Their `description` is what the planner reads. */ workers: Agent[]; /** What is to be split. The planner sees this and the roster, nothing else. */ input: string; /** * How many subtasks a plan may contain. Defaults to 8. * * Same reasoning as `loop`'s `maxIterations`: every subtask is a session and * a bill, so "a plan of two hundred steps" must not be reachable by a * hallucination. Exceeding it fails **before** anything is spawned - losing * a run costs less than paying for a runaway one. */ maxTasks?: number; /** Overrides how the plan is read. See {@link parsePlan}. */ parse?: (output: string, workers: readonly Agent[]) => PlannedTask[]; /** Overrides the question put to the planner. See {@link planningPrompt}. */ format?: (input: string, workers: readonly Agent[], maxTasks: number) => string; }; /** A validated plan, or the reason there is none - decided before anything is spawned. */ export type PlanOutcome = { /** What the planner asked for, after validation. Empty when nothing is runnable. */ plan: PlannedTask[]; /** The planner's own turn. Kept whatever happened next. */ planning: Result; /** False when the planner failed, or produced nothing runnable. */ ok: boolean; /** Set if and only if `ok` is false. It carries what the planner actually wrote. */ error?: string; }; /** * Asks an agent how to split the work, and validates what comes back. * * Nothing is spawned beyond the planner itself: a plan is checked in full - * every agent name known, the cap respected - **before** any of it runs. * A step naming an unknown agent is dropped, never remapped: guessing which * agent a hallucinated name meant is how a workflow silently does the wrong * work. */ export declare function makePlan(options: PlanOptions): Promise; /** The question put to the planner: who is available, and how to answer. */ export declare function planningPrompt(input: string, workers: readonly Agent[], maxTasks: number): string; /** * Reads a plan out of whatever the planner actually wrote. * * Lenient by design - the input is a model. Two shapes are accepted, because * both are what models really produce when asked for the first: * * - **any JSON objects carrying `agent` and `task`**, in the order they appear: * inside an array, alone, one per line, wrapped in a code fence or in prose. * Asked for an array, a real planner answered with bare objects and no * brackets - so the parser collects objects rather than requiring the shape; * - one subtask per line, `agent: task`. * * A step naming an unknown agent is **dropped**, never remapped: guessing which * agent a hallucinated name meant is how a workflow silently does the wrong * work. An empty result means "nothing usable was said", which the caller * reports rather than papers over. */ export declare function parsePlan(output: string, workers: readonly Agent[]): PlannedTask[]; //# sourceMappingURL=plan.d.ts.map