TypeScript API: Agents

The agent: a model, tools and instructions; runs, threads, streams and approvals. Generated from the declarations and doc comments of criogenai 0.1.7. See also: TypeScript API overview, the same module in Python.

import {
  Agent,
  AgentOptions,
  Thread,
  Stream,
  Reply,
  RunResult,
  RunOptions,
  CallOptions,
  Decision,
  ApprovalRequest,
  Retry,
  AgentTool,
  StreamEvent,
} from "criogenai";

Contents#

NameKindSummary
AgentclassA model with tools, instructions and (optionally) approval, typed output, storage and memory.
AgentOptionsinterfaceEverything an Agent is built from.
ThreadclassOne conversation, kept in the agent's storage.
StreamclassA streamed thread call: for await (const event of stream), then await stream.reply().
ReplyinterfaceA thread call's result, saved as the thread's new head.
RunResultinterfaceWhat a run produced; durationMs, answeredBy and models say which models answered and how long it took.
RunOptionsinterfaceOptions of agent.run and thread.send.
CallOptionsinterfaceOptions of every thread call that takes no input (approve, reject, decide, retry).
DecisiontypeA reviewer's answer to one call (see Thread.decide).
ApprovalRequestinterfaceA tool call waiting for a reviewer's decision.
RetryinterfaceHow often and how patiently to retry; missing fields keep the defaults.
AgentToolclassAn agent as a tool of another agent (agent.asTool): it takes { input } and returns the sub-agent's answer.
StreamEventtypeOne streamed event.

Agent#

class Agent<Output = unknown> {
  constructor(options: AgentOptions);
}

A model with tools, instructions and (optionally) approval, typed output, storage and memory. The loop runs in Rust.

Each run calls the model, runs the tools it asks for (concurrently, in call order), feeds the results back, and repeats until the model answers without tools or a limit is reached. Use run for one-off tasks and thread(id) for conversations that are saved between calls.

Output types result.output; it is not checked at compile time, so pass the matching JSON Schema as the output option.

Parameters

  • options (AgentOptions): The model, instructions, tools and the rest (see AgentOptions).

Raises

  • TypeError: An entry of tools is not a tool, or a model string names no known provider.
  • ValidationError: A model string's provider has no API key in the environment (invalid_value).
  • ValidationError: An option the engine does not accept (e.g. an unknown fallbackOn kind).

Example

import { Agent, FakeModel, FakeReply, tool } from "criogenai";

const add = tool<{ a: number; b: number }>({
  name: "add",
  description: "Add two numbers.",
  input: { type: "object", properties: { a: { type: "number" }, b: { type: "number" } }, required: ["a", "b"] },
  run: ({ a, b }) => a + b,
});
const agent = new Agent({
  model: new FakeModel([FakeReply.toolCall("add", { a: 2, b: 3 }), "2 + 3 = 5"]), // or "openai:gpt-5-mini"
  instructions: "Use the add tool for arithmetic.",
  tools: [add],
});
const result = await agent.run("What is 2 + 3?");
console.log(result.text, result.toolCalls); // "2 + 3 = 5" 1

Agent.storage#

readonly storage: Storage

The storage that keeps this agent's threads (in memory unless the storage option says otherwise).

Agent.run#

run(input: string, options?: RunOptions): Promise<RunResult<Output>>

Runs once (no thread). Nothing is saved: a run that stops for approval cannot be continued, so use thread(id).send(...) with approve.

Parameters

  • input (string): The user's message.
  • options (RunOptions, default {}): state (run state, e.g. the key per-user memory reads) and signal (cancels the run).

Returns: The answer, the transcript, token usage and call counts.

Raises

  • ModelError: The model failed after its retries and fallbacks.
  • LimitExceededError: A limit in limits was reached.
  • ToolExecutionError: A tool threw and onToolException is "fail".
  • CancelledError: signal was aborted.

Agent.asTool#

asTool(name: string, description: string): AgentTool

This agent as a tool of another agent: the tool takes input (text), runs this agent, and returns its answer. How deep agents may call agents is bounded by the nesting limit (default 8).

Parameters

  • name (string): The tool name the calling model sees (letters, digits, _ and -).
  • description (string): What this agent does, so the calling model knows when to use it.

Example

import { Agent, FakeModel, FakeReply } from "criogenai";

const researcher = new Agent({ model: new FakeModel(["The order shipped on Monday."]), instructions: "Find facts." });
const lead = new Agent({
  model: new FakeModel([FakeReply.toolCall("research", { input: "When did order A-1 ship?" }), "It shipped on Monday."]),
  tools: [researcher.asTool("research", "Looks up facts about orders.")],
});
console.log((await lead.run("When did A-1 ship?")).text);

Agent.thread#

thread(threadId: string, options?: { actor?: string; }): Thread<Output>

The conversation threadId (created on first use); actor names who acts, for the audit trail.

Nothing is read or written until you call a method of the thread.

Parameters

  • threadId (string): Any string that names the conversation in this agent's storage.
  • options ({ actor?: string; }, default {}): actor: who makes the calls (a user or service id), recorded in audit records.

Example

import { Agent, FakeModel } from "criogenai";

const agent = new Agent({ model: new FakeModel(["Hi Sam.", "Your name is Sam."]) });
const thread = agent.thread("support-42");
await thread.send("My name is Sam.");
const reply = await thread.send("What is my name?"); // the model sees the earlier turn
console.log(reply.text, reply.version);

AgentOptions#

interface AgentOptions {
  model: Model;
  instructions?: string;
  tools?: (Tool<any> | AgentTool | McpServer | McpTool | CollectionTool)[];
  approve?: string[];
  output?: JsonSchema;
  limits?: { modelCalls?: number; toolCalls?: number; tokens?: number; toolTimeoutSecs?: number };
  onToolException?: "returnToModel" | "returnGeneric" | "fail";
  storage?: Storage | string;
  memory?: Memory;
  fallbacks?: Model[];
  fallbackOn?: string[];
  modelRetry?: Retry;
  toolRetry?: Retry;
  maxRetryAfterSecs?: number;
  telemetry?: { readonly native: { readonly handle: number } };
  hooks?: Hook[];
  redact?: Redactor;
}

Everything an Agent is built from. Only model is required.

Fields

  • model (Model): The model: a "provider:model" string such as "openai:gpt-5-mini" (the key comes from the provider's environment variable), or a model object (openai(...), azure(...), FakeModel, FunctionModel, ...).
  • instructions (string, optional): The system message the model sees on every call.
  • tools ((AgentTool | Tool<any> | McpServer | McpTool | CollectionTool)[], optional): Tools: tool({...}), agent.asTool(...), an MCP server (all its tools) or one of its tools.
  • approve (string[], optional): Names of tools that need human approval before they run. A call to one stops the run (reply.needsApproval); use a thread to approve, reject or edit it.
  • output (JsonSchema, optional): JSON Schema of the final answer (result.output).
  • limits ({ modelCalls?: number; toolCalls?: number; tokens?: number; toolTimeoutSecs?: number; }, optional): Bounds on one run: modelCalls (default 25; retries of one call do not count), toolCalls (default 200), tokens (input plus output tokens across the run, checked before each model call; no limit by default) and toolTimeoutSecs (per tool call, default 60). Reaching a limit fails the run with LimitExceededError; a tool that times out returns an error to the model instead.
  • onToolException ("returnToModel" | "returnGeneric" | "fail", optional): When a tool throws an exception that is not a ToolError: "returnToModel" (default; the model sees its name and message, the run goes on), "returnGeneric" (the model only learns that the tool failed; the text stays in the result's artifact) or "fail" (the run fails with ToolExecutionError, the exception as its cause).
  • storage (string | Storage, optional): Where threads are kept: a Storage, or a URL ("sqlite://x.db", "postgres://…"). Without it, threads live in memory and end with the process.
  • memory (Memory, optional): Long-term memory (Memory.byState(...) or Memory.shared(...)): facts recalled before each model call, and (unless the memory's options turn them off) tools for the model to remember, forget and search them. Kept in the agent's storage.
  • fallbacks (Model[], optional): Models tried in order when the previous one fails with a transient error (after its retries).
  • fallbackOn (string[], optional): Other error kinds that also move to the next model, e.g. "context_overflow".
  • modelRetry (Retry, optional): Retries of transient model errors (default: 3 attempts, backoff from 0.5 s).
  • toolRetry (Retry, optional): Retries of tools marked readOnly or idempotent (default: 3 attempts).
  • maxRetryAfterSecs (number, optional): The longest Retry-After the agent waits, in seconds (default 60).
  • telemetry ({ readonly native: { readonly handle: number; }; }, optional): Exports every run of this agent (Telemetry.langfuse(), Telemetry.otlp(url)).
  • hooks (Hook[], optional): Code around the loop (see Hook): onStart, beforeModel, afterModel, beforeTool, afterTool, onEnd.
  • redact (Redactor, optional): Applied to every text before each model call (input, replies, tool results), ahead of all hooks: e.g. strip patient identifiers before anything reaches a provider. The redacted transcript is what is saved.

Thread#

class Thread<Output = unknown> {
  constructor(agent: Agent<Output>, id: string, actor?: string);
}

One conversation, kept in the agent's storage.

Every call loads the saved conversation, runs, and saves the result as a new checkpoint, so a thread survives restarts when the storage is a database. A run that stops for approval waits in the thread until approve, reject or decide continues it; a failed or cancelled run is saved too, and retry continues it.

Parameters

  • agent (Agent<Output>): The agent whose model, tools and storage the thread uses.
  • id (string): The thread id in the agent's storage.
  • actor (string, optional): Who makes the calls, recorded in audit records.

Example

import { Agent, FakeModel, FakeReply, tool } from "criogenai";

const refund = tool<{ orderId: string }>({
  name: "refund",
  description: "Refund an order.",
  input: { type: "object", properties: { orderId: { type: "string" } }, required: ["orderId"] },
  run: ({ orderId }) => `refunded ${orderId}`,
});
const agent = new Agent({
  model: new FakeModel([FakeReply.toolCall("refund", { orderId: "A-1" }), "The order is refunded."]),
  tools: [refund],
  approve: ["refund"],
});
const thread = agent.thread("order-A-1");
const first = await thread.send("Refund order A-1.");
console.log(first.needsApproval, first.approvals[0].tool); // true "refund"
const done = await thread.approve();
console.log(done.text); // "The order is refunded."

Thread.send#

send(input: string, options?: RunOptions): Promise<Reply<Output>>

Runs one turn: the stored conversation plus input.

Parameters

  • input (string): The user's message.
  • options (RunOptions, default {}): state (run state, e.g. the key per-user memory reads) and signal (cancels the run; it is still saved).

Returns: The reply, saved as the thread's new head.

Raises

  • ThreadSuspendedError: The thread waits for approval: answer with approve, reject or decide first.
  • ThreadBusyError: Another run holds the thread (see the storage's onBusy option).
  • ModelError: The model failed after its retries and fallbacks; retry continues the saved run.
  • LimitExceededError: A limit in limits was reached.
  • CancelledError: signal was aborted.

Thread.approve#

approve(options?: CallOptions): Promise<Reply<Output>>

Approves every pending call and continues.

Parameters

  • options (CallOptions, default {}): signal cancels the run.

Thread.reject#

reject(message?: string, options?: CallOptions): Promise<Reply<Output>>

Rejects every pending call (the model sees message).

Parameters

  • message (string, default "rejected"): What the model is told instead of each call's result.
  • options (CallOptions, default {}): signal cancels the run.

Thread.decide#

decide(decisions: Record<string, Decision>, options?: CallOptions): Promise<Reply<Output>>

One Decision per pending call, by call id.

Parameters

  • decisions (Record<string, Decision>): A Decision for each pending call, keyed by its call_id (from reply.approvals).
  • options (CallOptions, default {}): signal cancels the run.

Example

import { Decision } from "criogenai";

const [request] = first.approvals; // first: the reply that needed approval
const done = await thread.decide({ [request.call_id]: Decision.reject("Refunds need a manager.") });

Thread.retry#

retry(options?: CallOptions): Promise<Reply<Output>>

Continues a failed or interrupted run. Tool calls that finished (and were saved) before the failure are not run again.

Parameters

  • options (CallOptions, default {}): signal cancels the run.

Thread.history#

history(limit?: number): Promise<Record<string, unknown>[]>

Checkpoints, newest first.

Parameters

  • limit (number, default 20): The most checkpoints to return.

Returns: One object per checkpoint of the current branch: id, parent_id, version, source, status, run_id, step, created_at_ms.

Thread.messages#

messages(): Promise<Record<string, unknown>[]>

The stored conversation.

Thread.pendingApprovals#

pendingApprovals(): Promise<ApprovalRequest[]>

The tool calls waiting for a decision, read from storage. Empty when the thread is not waiting. Use it to show a review queue without keeping the last Reply: for example after a restart, or in another process that shares the storage.

Returns: One ApprovalRequest per waiting call, in call order.

Example

const waiting = await agent.thread("ticket-42").pendingApprovals();
for (const request of waiting) console.log(request.tool, request.args);

Thread.rewind#

rewind(checkpointId: string): Promise<Record<string, unknown>>

Makes an older checkpoint current again. It is saved as a new head; the newer checkpoints stay stored but leave the history. The next call runs from there, and nothing recorded after it is replayed.

Parameters

  • checkpointId (string): An id from history() or a reply's checkpointId.

Returns: The new head's checkpoint, in the same shape as history() entries.

Thread.delete#

delete(): Promise<boolean>

Deletes the thread and its checkpoints. true if it existed.

Thread.stream#

stream(input: string, options?: { state?: Record<string, unknown>; }): Stream<Output>

send, streaming its events. To cancel, call stream.cancel() or leave the for await loop early.

Parameters

  • input (string): The user's message.
  • options ({ state?: Record<string, unknown>; }, default {}): state: run state, as for send.

Thread.streamApprove#

streamApprove(): Stream<Output>

approve, streaming the events of the run it continues.

Thread.streamReject#

streamReject(message?: string): Stream<Output>

reject, streaming the events of the run it continues.

Parameters

  • message (string, default "rejected"): What the model is told instead of each call's result.

Thread.streamDecide#

streamDecide(decisions: Record<string, Decision>): Stream<Output>

decide, streaming the events of the run it continues.

Parameters

  • decisions (Record<string, Decision>): A Decision for each pending call, keyed by its call_id.

Thread.streamRetry#

streamRetry(): Stream<Output>

retry, streaming the events of the run it continues.

Stream#

class Stream<Output = unknown> implements AsyncIterable<StreamEvent> {
  constructor(nativeStream: NativeStream, agent: Agent<Output>);
}

A streamed thread call: for await (const event of stream), then await stream.reply(). Leaving the loop early cancels the run (it is still saved, and retry continues it).

Made by thread.stream(...) and the other thread.stream* methods.

Parameters

  • nativeStream (NativeStream): The engine's stream.
  • agent (Agent<Output>): The agent the thread belongs to.

Example

import { Agent, FakeModel } from "criogenai";

const agent = new Agent({ model: new FakeModel(["Hello there."]) });
const stream = agent.thread("chat-1").stream("Hello");
for await (const event of stream) {
  if (event.type === "text") process.stdout.write(event.text);
}
const reply = await stream.reply();

Stream.reply#

reply(): Promise<Reply<Output>>

Waits for the call; its saved reply.

Raises

  • CriogenError: The run failed (the same errors as thread.send).

Stream.cancel#

cancel(): void

Cancels the run.

Reply#

interface Reply<Output = unknown> extends RunResult<Output> {
  version: number;
  checkpointId: string;
}

A thread call's result, saved as the thread's new head.

Fields

  • version (number): The thread's version after this call; it grows with every saved checkpoint.
  • checkpointId (string): The id of the checkpoint this call saved; thread.rewind takes it.
  • text (string | null): The final answer as text, or null when the output is not text (or the run stopped for approval).
  • output (Output | null): The final answer: the parsed JSON object with the output option, else the final text.
  • messages (Record<string, unknown>[]): The whole transcript: user, assistant and tool messages, in order.
  • usage (Record<string, number>): Tokens across all model calls: input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens.
  • stopReason (string | null): Why the run ended: "final_answer", "output" (typed output accepted), "return_direct", "content_filter", "hook_end" or "limit"; null while the run waits for approval.
  • needsApproval (boolean): true when the run stopped because a tool call needs approval (see approvals).
  • approvals (ApprovalRequest[]): The calls waiting for a decision; empty unless needsApproval.
  • modelCalls (number): Model calls the run made (retries of one call do not count).
  • toolCalls (number): Tool calls the run executed.
  • runId (string): The run's id, also passed to tools (ctx.runId) and hooks.
  • durationMs (number): Wall time of the call, in milliseconds.
  • answeredBy ({ provider: string; model: string; } | null): The provider and model that wrote the last answer (a fallback shows here).
  • models (ModelUse[]): Each model this run called, in first-use order.

RunResult#

interface RunResult<Output = unknown> extends RunFacts {
  text: string | null;
  output: Output | null;
  messages: Record<string, unknown>[];
  usage: Record<string, number>;
  stopReason: string | null;
  needsApproval: boolean;
  approvals: ApprovalRequest[];
  modelCalls: number;
  toolCalls: number;
  runId: string;
}

What a run produced; durationMs, answeredBy and models say which models answered and how long it took.

Fields

  • text (string | null): The final answer as text, or null when the output is not text (or the run stopped for approval).
  • output (Output | null): The final answer: the parsed JSON object with the output option, else the final text.
  • messages (Record<string, unknown>[]): The whole transcript: user, assistant and tool messages, in order.
  • usage (Record<string, number>): Tokens across all model calls: input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens.
  • stopReason (string | null): Why the run ended: "final_answer", "output" (typed output accepted), "return_direct", "content_filter", "hook_end" or "limit"; null while the run waits for approval.
  • needsApproval (boolean): true when the run stopped because a tool call needs approval (see approvals).
  • approvals (ApprovalRequest[]): The calls waiting for a decision; empty unless needsApproval.
  • modelCalls (number): Model calls the run made (retries of one call do not count).
  • toolCalls (number): Tool calls the run executed.
  • runId (string): The run's id, also passed to tools (ctx.runId) and hooks.
  • durationMs (number): Wall time of the call, in milliseconds.
  • answeredBy ({ provider: string; model: string; } | null): The provider and model that wrote the last answer (a fallback shows here).
  • models (ModelUse[]): Each model this run called, in first-use order.

RunOptions#

interface RunOptions extends CallOptions {
  state?: Record<string, unknown>;
}

Options of agent.run and thread.send.

Fields

  • state (Record<string, unknown>, optional): Run state (e.g. { user_id: "ada" } for per-user memory).
  • signal (any, optional): Aborting cancels the run (a thread is still saved, and retry continues it).

CallOptions#

interface CallOptions {
  signal?: AbortSignal;
}

Options of every thread call that takes no input (approve, reject, decide, retry).

Fields

  • signal (any, optional): Aborting cancels the run (a thread is still saved, and retry continues it).

Decision#

type Decision =
  | { type: "approve" }
  | { type: "reject"; message?: string }
  | { type: "edit"; args: Record<string, unknown>; name?: string }
  | { type: "respond"; message: string };

A reviewer's answer to one call (see Thread.decide).

Approve it, reject it (the model sees message), run it with edited args (and optionally another tool name), or skip it and give the model message as the tool's result. Decision.approve(), Decision.reject(message), Decision.edit(args, name) and Decision.respond(message) build them.

ApprovalRequest#

interface ApprovalRequest {
  call_id: string;
  tool: string;
  args: Record<string, unknown>;
  description?: string;
  allowed?: string[];
}

A tool call waiting for a reviewer's decision. A run that stops for approval lists these in reply.approvals; answer them with thread.approve(), thread.reject() or thread.decide().

Fields

  • call_id (string): The tool call's id. thread.decide takes decisions keyed by it.
  • tool (string): The name of the tool the model wants to call.
  • args (Record<string, unknown>): The arguments the model wrote for the call.
  • description (string, optional): Text for the reviewer, when the approval rule provides one.
  • allowed (string[], optional): The decisions the reviewer may make: "approve", "edit", "reject", "respond".

Retry#

interface Retry {
  maxAttempts?: number;
  initialBackoffSecs?: number;
  factor?: number;
  maxBackoffSecs?: number;
  jitter?: boolean;
}

How often and how patiently to retry; missing fields keep the defaults.

Fields

  • maxAttempts (number, optional): Attempts in total, including the first. Default 3.
  • initialBackoffSecs (number, optional): Backoff before the second attempt, in seconds. Default 0.5.
  • factor (number, optional): Multiplier for each further attempt. Default 2.
  • maxBackoffSecs (number, optional): Upper bound on any backoff, in seconds. Default 20.
  • jitter (boolean, optional): Wait a random part of the backoff. Default true.

AgentTool#

class AgentTool {
  constructor(agent: Agent<any>, name: string, description: string);
}

An agent as a tool of another agent (agent.asTool): it takes { input } and returns the sub-agent's answer.

Parameters

  • agent (Agent<any>): The agent that runs when the tool is called.
  • name (string): The tool name the calling model sees.
  • description (string): What the tool does, for the calling model.

StreamEvent#

type StreamEvent =
  | { type: "text"; messageId: string; text: string }
  | { type: "tool_call_started"; callId: string; tool: string; args: Record<string, unknown> }
  | { type: "tool_call_finished"; callId: string; tool: string; status: string }
  | { type: "message"; message: Record<string, unknown> }
  | { type: "approval_needed"; requests: ApprovalRequest[] }
  | { type: "other"; event: string; node?: string | null; step?: number | null };

One streamed event.

text (a piece of the answer as it is written), tool_call_started, tool_call_finished, message (a finished message of the transcript), approval_needed, or other (any other engine event: its name, e.g. "step_started", and the node and step; never any content).