Agents
Static configuration — an agent is defined once and never mutated by a run.
An Agent is static configuration. A run never mutates it, so one instance
can safely serve many concurrent runs.
import { Agent } from "@rohhitrz/jonaki";
const agent = new Agent({ name: "Assistant" });name is the only required field.
Options
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Required. Identifies the agent in traces and handoff tool names. |
instructions | string | none | The system prompt. |
model | string | string[] | env-resolved | An array is a fallback ladder, tried in order. |
tools | AnyTool[] | [] | Built with tool(). |
handoffs | HandoffInput[] | [] | See Handoffs. |
guardrails | Guardrail[] | [] | See Guardrails. |
output | z.ZodType<T> | none | Makes run() return RunResult<T>. |
maxTurns | number | 10 | Model calls before MAX_TURNS_EXCEEDED. |
maxCostUSD | number | none | Checked before each model call. |
timeoutMs | number | none | Ceiling for the whole run. |
provider | Provider | env-resolved | Override the adapter entirely. |
context | ContextOptions | no windowing | See Memory & sessions. |
Instructions
instructions becomes the system prompt.
import { Agent } from "@rohhitrz/jonaki";
const agent = new Agent({
name: "Support",
instructions: [
"You are a support agent for an online bookshop.",
"Be concise. Never invent order numbers.",
"If you cannot help, say so plainly.",
].join("\n"),
});Choosing a model
Pass the provider's own model id. Jonaki infers the provider from the prefix — see Model providers.
import { Agent } from "@rohhitrz/jonaki";
const agent = new Agent({ name: "A", model: "claude-sonnet-4-5" });Fallback ladders
An array is tried in order until one succeeds. The ladder can cross providers, so an Anthropic outage falls through to OpenAI:
import { Agent } from "@rohhitrz/jonaki";
const agent = new Agent({
name: "Resilient",
model: ["claude-sonnet-4-5", "gpt-4.1", "gemini-2.5-flash"],
});If every rung fails you get PROVIDER_ERROR listing what each one said.
Limits
Every limit has its own error code, and cost is checked before the call that would breach it rather than after:
import { Agent } from "@rohhitrz/jonaki";
const agent = new Agent({
name: "Bounded",
maxTurns: 6, // MAX_TURNS_EXCEEDED
maxCostUSD: 0.25, // BUDGET_EXCEEDED
timeoutMs: 30_000, // RUN_TIMEOUT
});Run-level options override the agent's:
import { run } from "@rohhitrz/jonaki";
await run(agent, "hi", { maxTurns: 3 });Typed output
Give the agent a Zod schema and run() returns that type — no cast:
import { Agent, run } from "@rohhitrz/jonaki";
import * as z from "zod";
const Ticket = z.object({
customer: z.string(),
urgent: z.boolean(),
});
const extractor = new Agent({ name: "Extractor", output: Ticket });
const result = await run(extractor, "Priya is furious about order A-77.");
result.output.urgent; // booleanSee Structured output.
Reusing one agent
Because an agent is immutable, concurrency is free:
import { Agent, run } from "@rohhitrz/jonaki";
const agent = new Agent({ name: "Assistant" });
const answers = await Promise.all([
run(agent, "What is 2 + 2?"),
run(agent, "Name a colour."),
run(agent, "Say hello."),
]);Each run gets its own RunState; the agent itself is untouched.
Inspecting an agent
import { Agent } from "@rohhitrz/jonaki";
const agent = new Agent({ name: "Assistant", tools: [getWeather] });
agent.name; // "Assistant"
agent.tools.map((t) => t.name); // ["get_weather"]
agent.toolSpecs(); // what the model is shown, handoffs included
agent.registry(); // Map of every agent reachable via handoffs