/** * conversation — the refusals that guard a runner's per-instance state. * * Pattern: teaching refusal at a boundary (the shape `runInput` uses for a * caller's message, applied to a caller's TIMING). * Role: core/ layer. An `Agent` keeps the last run's executor, run context, * answer and pause on ITSELF — that is what makes `checkpoint()`, * `getLastSnapshot()` and `followUp()` possible at all. Those fields * have exactly one owner at a time, and until 9.2.0 nothing said so. * Emits: N/A — these fire before a run starts. * * ## Why these are refusals and not warnings * * Both shapes below used to SUCCEED, which is the entire problem. Two `run()` * calls overlapping on one Agent both resolved with plausible answers, and the * per-instance state afterwards belonged to whichever finished last: the * conversation `checkpoint()` handed back was the other run's, the snapshot * `getLastSnapshot()` served was the other run's, and every event carried the * other run's meta. That is not concurrency, it is corruption — and nothing in * the recording said so, because each run's own trace looked perfect. * * A message sent while a person still owes the agent an answer used to start a * fresh run and silently abandon the pending question. A consent gate that can * be walked around by sending another message is not a consent gate. * * Neither refusal is new policy. `standingAgent` has refused both since it * existed (it serializes runs globally and calls that "a correctness * requirement rather than a tuning choice", and answers a message that arrives * mid-question with `AwaitingDecisionError`), and `recordedChat.send` refuses * an overlapping turn in the same words. 9.2.0 moves the guarantee from the * compositions down to the primitive, so it holds however you drive it. * * The names are deliberately NOT the hosting ones. `hosting/errors.ts` already * owns `ConcurrentRunError` and `AwaitingDecisionError`, both of which carry a * `sessionId` and speak about a session; core has no sessions, and two classes * sharing one name across two doors is the duplicate-type hazard this codebase * has fixed before. */ /** * Thrown when `run()` / `resume()` is called on a runner that is already * running. * * One instance answers one turn at a time. To run two turns at once, build two * agents — a chart is built once per instance and instances are cheap — or put * the turns behind `standingAgent({ onConcurrentInvoke: 'enqueue' })`, which * queues them. * * @example * ```ts * // Refused: both would write the same instance's last-run state. * await Promise.all([agent.run({ message: 'a' }), agent.run({ message: 'b' })]); * * // Fine: two instances, two sets of state. * await Promise.all([agentA.run({ message: 'a' }), agentB.run({ message: 'b' })]); * ``` */ export declare class RunInFlightError extends Error { readonly code: "ERR_RUN_IN_FLIGHT"; /** The runner that is busy, by its configured id. */ readonly agentId: string; /** The run already in flight, so a log line can be joined to its trace. */ readonly activeRunId: string; constructor(door: string, agentId: string, activeRunId: string); } /** * Thrown when a new message is sent to an agent whose last run PAUSED to ask a * person something, and that question has not been answered. * * The pause is not a failure and not a stale flag — it is unfinished work with * a person on the other end. Answer it with `resume(checkpoint, decision)`, or * say plainly that it is being dropped with `abandonPause()`; both are visible * in the record, and silently starting a fresh run was not. */ export declare class PendingQuestionError extends Error { readonly code: "ERR_PENDING_QUESTION"; /** The tool that asked, when the pause named one. */ readonly toolName?: string; /** The id of the tool call that asked, for joining back to the trace. */ readonly toolCallId?: string; constructor(door: string, pending: { toolName?: string; toolCallId?: string; question?: string; }); } /** * Thrown by `followUp()` when there is no conversation to follow up on. * * `followUp()` continues THIS agent's own last completed run. Before the first * one there is nothing to continue, and a "follow-up" that quietly became a * first turn would be the very confusion the door exists to remove. */ export declare class NoConversationError extends Error { readonly code: "ERR_NO_CONVERSATION"; constructor(door: string, reason: 'never-run' | 'last-run-unfinished'); }