// A harness runs the agent loop for one task. Machines never call a model // directly; they hand a request to a harness adapter, so the loop (fx, a plain // chat-completions client, a test double) can change without touching specs. import type { HarnessEvent } from "./events.ts"; import type { Sandbox } from "./sandbox.ts"; import type { HarnessResult, Message } from "./types.ts"; export type ToolContext = { sandbox: Sandbox; /** * How much the model reasons before answering, for endpoints that support * it: an OpenAI effort ("none", "low", "medium", "high", "xhigh") or a * number in [0, 0.99] (Tinker). Unset leaves the model's default. */ reasoningEffort?: string | number; /** Upper bound on model calls for this task, counting tool round trips; overrides the harness default. */ maxSteps?: number; /** Progress while the task runs (model steps, tool calls), for live views. Harnesses that cannot report it skip it. */ onEvent?: (event: HarnessEvent) => void; signal?: AbortSignal; /** * True when a person is at the other end of the conversation, as in * `machine `; unset in suites, training, and other unattended runs. * Tools that spend money or change machines can refuse without it. */ interactive?: boolean; /** Progress lines from a long-running tool, shown live where the session can (Pi's tool output). */ progress?: (line: string) => void; /** * In an interactive session, the conversation so far as the person saw it: * earlier exchanges (with their tool calls) and the prompt being answered. * Lets a tool check what the person actually said, e.g. that they agreed * to a cost. */ conversation?: () => Message[]; }; export type Tool = { name: string; description: string; /** JSON Schema for the tool's input object. */ inputSchema: Record; execute(input: any, context: ToolContext): Promise; /** * Whether a result the tool returned (rather than threw) means the call * failed, such as an input it rejected. Steps whose calls all failed are * left out of training data. Thrown errors always count as failures. */ failed?(result: string): boolean; }; export type HarnessRequest = { /** System instructions for this task. */ instructions: string; /** Prior conversation, oldest first. */ history: Message[]; /** The new user input. */ input: string; model: string; tools: Tool[]; sandbox: Sandbox; /** * How much the model reasons before answering, for endpoints that support * it: an OpenAI effort ("none", "low", "medium", "high", "xhigh") or a * number in [0, 0.99] (Tinker). Unset leaves the model's default. */ reasoningEffort?: string | number; /** Upper bound on model calls for this task, counting tool round trips; overrides the harness default. */ maxSteps?: number; /** Progress while the task runs (model steps, tool calls), for live views. Harnesses that cannot report it skip it. */ onEvent?: (event: HarnessEvent) => void; signal?: AbortSignal; }; export interface Harness { /** Short adapter name recorded on every run, e.g. "fx" or "chat-completions". */ readonly name: string; run(request: HarnessRequest): Promise; /** Release long-lived resources such as a harness server. */ close?(): Promise; }