/** * A subagent: a live session, a memory, a state. * * This is the heart of the library. Everything else - `run`, `chain`, * `fanOut` - is built on the three methods below: `ask`, `usage`, `close`. * * The rule that governs this file: **whoever opens, closes**. A `Subagent` is * an explicit object with an explicit owner. There is no global session cache * hidden anywhere. */ import { type Agent, type Lifetime } from "./agent.ts"; import { type EventBus, type EventListener, type SubagentEvent } from "./events.ts"; import { type SessionExport } from "./measure/index.ts"; import { type Result } from "./result.ts"; import { type CreateSession, type ThinkingLevel, type ToolDefinition } from "./session.ts"; import { type Usage } from "./usage.ts"; /** * Tools built once the subagent's id is known. * * The one thing a caller cannot decide before `spawn`: a tool that spawns * children has to name their parent, and the parent does not exist yet. */ export type CustomToolsFor = (id: string) => ToolDefinition[] | undefined; /** * What an offer amounts to for the subagent about to get `id`. * * The one reader of {@link SpawnOptions.customTools}'s two shapes: `spawn` * reads through it, and so does anything that composes two offers or asserts * on one. A list is a list; a function is asked, now that the id exists. */ export declare function toolsOffered(offer: SpawnOptions["customTools"], id: string): ToolDefinition[]; /** Everything that can be decided about a subagent before it exists. */ export type SpawnOptions = { /** Overrides the lifetime declared by the agent. Absent from both, `"task"`. */ lifetime?: Lifetime; /** Working directory the subagent's tools act in. Defaults to the process's own. */ cwd?: string; /** Dedicated session directory, required for the session to be exportable. */ sessionDir?: string; /** * Where this subagent writes its HTML and JSONL when it closes. * * Setting it **implies a session directory** (`/.sessions`), * because pi cannot render an in-memory session to HTML. That is one * decision, not two: asking for an export is asking for the session to be * kept long enough to export it. Pass {@link SpawnOptions.sessionDir} * explicitly to put it somewhere else. * * Still opt-in: with no `exportDir`, a subagent stays in memory and leaves * nothing behind - not in `~/.pi`, not in the working directory. */ exportDir?: string; /** The name its files take in `exportDir`, without the extension. Defaults to its id's, `reviewer-2`. */ exportName?: string; /** Subscribed to the event stream for the subagent's whole life. */ onEvent?: EventListener; /** Shared bus, when several subagents must report to the same place. */ bus?: EventBus; /** Session factory. Injection point for tests - defaults to a real pi session. */ createSession?: CreateSession; /** * Tools combo defines, offered to this subagent. * * Offered, not granted: the agent's `tools:` is an allowlist and covers these * too, so one it does not name is not enabled. See * {@link CreateSessionOptions.customTools}. * * A function receives the id this subagent is about to get, before its * session opens. That is what a tool spawning children needs in order to * name their parent, and it is the only way to have it: the id is minted * here, after the caller has built everything it could. */ customTools?: ToolDefinition[] | CustomToolsFor; /** * The subagent that had this one spawned, when one did. * * Set by `delegateTool`, never guessed: a name is ambiguous the moment two * explorers run at once, so the link is an id or it is nothing. It reaches * the reporters on the `spawn` event and nothing else reads it. */ parentId?: string; /** The flow visit this subagent is spawned for. It reaches the reporters on the `spawn` event. */ visit?: string; /** Where a flow keeps it: its memory scope's path, else its visit's. It reaches the reporters on the `spawn` event. */ home?: string; /** * Model pattern for this subagent, e.g. `"anthropic/claude-sonnet-5"`. * * An override, not a default: the argument wins over the agent's * frontmatter, which wins over pi's own settings - the same rule as * {@link SpawnOptions.lifetime}. It exists so one workflow can run against * different models without editing a single agent file; a frontmatter model * surviving a sweep would make an experiment measure a mixture. * * A pattern that resolves to nothing throws at spawn: better than running * a whole workflow on the wrong model. */ model?: string; /** * Give this subagent its own herdr split, when running inside herdr. * * Opt-in per subagent, like {@link SpawnOptions.lifetime}, and resolved the * same way: this argument wins over the agent's frontmatter, which wins over * `false`. A fan-out of twenty branches must not carpet the screen unless * someone asked for it. Outside herdr it is simply ignored. */ openInHerdr?: boolean; }; /** What governs one turn: how it can be stopped, and when it must be. */ export type AskOptions = { /** * Cancels this turn. * * The turn fails with `"aborted"`, unless the signal's reason is a * `TimeoutError`, as `AbortSignal.timeout` gives: then it is a deadline, * and fails with the reason's message, as {@link AskOptions.timeoutMs} does. */ signal?: AbortSignal; /** * Deadline for this turn, in milliseconds. No default: an `ask` waits * forever unless you say otherwise. * * This matters more than it looks. One `ask` is one turn of the session, and * pi's agent loop is a `while (true)` that runs as long as the model keeps * requesting tools - there is no step cap in pi. A model that hallucinates a * tool name, gets "unknown tool" back and asks again will loop until * something stops it. Nothing will, unless it is this. */ timeoutMs?: number; /** The level this turn thinks at. Absent: the subagent's own, from its model's suffix or pi's settings. */ thinking?: ThinkingLevel; }; /** A living subagent. Its owner is whoever called {@link spawn}. */ export type Subagent = { /** Unique for the process, e.g. `reviewer#2`. Names its transcript files. */ readonly id: string; /** The definition it was spawned from. Inert data - it is not re-read. */ readonly agent: Agent; /** Resolved once, at spawn: the argument, then the frontmatter, then `"task"`. */ readonly lifetime: Lifetime; /** `provider/id` as pi resolved it, which only the session knows. Absent when pi could not say. */ readonly model?: string; /** Cumulative measurements since spawn. Read `Result.usage` for a single turn. */ readonly usage: Usage; /** Runs one turn of work. Never throws on a model failure - returns `ok: false`. */ ask(task: string, options?: AskOptions): Promise; /** * Stops this subagent, for good: the turn in flight is cut short, and any * later `ask` fails at once with `"stopped"`. * * One-way and idempotent, because that is what a person pressing a key * means. It is **not** `close()`: the session is still there, so the * transcript of what it did before it was stopped is still exportable, and * whoever opened it still owes it a `close()`. */ stop(): void; /** * Writes this subagent's transcript into `dir` - HTML and JSONL, pi's own. * * Callable at any moment while the subagent lives, not only at the end: an * interrupted workflow must still be able to export what it did. After * `close()` the session is gone, so this reports an error instead of * throwing - losing an export must never be worse than losing the run. */ export(dir?: string): Promise; /** Releases the session. Idempotent. */ close(): Promise; }; /** * Brings an agent to life. * * The lifetime is **explicit and local**: the argument wins over the agent's * frontmatter, which itself wins over the `"task"` default. Persistence is * asked for; it is never obtained by accident. * * The caller owns the returned object and must `close()` it, ideally in a * `finally`. */ export declare function spawn(agent: Agent, options?: SpawnOptions): Promise; export type { SubagentEvent }; //# sourceMappingURL=subagent.d.ts.map