Agents
An agent wraps a model, optional tools, instructions, handoffs, and an event sink. The tool loop state machine lives in Rust. Prefer stream() when you need progressive UI updates.
Create and run
import { agent, createOtelListener } from "@monorch/ai";
import { openai } from "@monorch/ai/openai";
const otel = createOtelListener({ serviceName: "support" });
const support = agent({
name: "support",
model: openai("gpt-4.1-mini", { baseUrl: process.env.LITELLM_URL }),
instructions: "Be concise. Prefer tools for facts.",
tools: [lookupOrder],
maxSteps: 8,
onEvent: otel,
});
const { text, runId, events } = await support.run(userMessage);
for await (const ev of support.stream(userMessage)) {
// run_start | text | tool_call | tool_result | handoff | run_end | error
}Per-run options
Both run and stream accept AgentRunOptions: load/append thread memory, and abort in-flight model calls.
import { agent, inMemoryThreads } from "@monorch/ai";
const threads = inMemoryThreads();
const bot = agent({ name: "mem", model, instructions: "Remember prior turns." });
await bot.run("hello", { threadId: "t1", memory: threads });
await bot.run("again", { threadId: "t1", memory: threads });
const ctrl = new AbortController();
const pending = bot.stream("long task", { signal: ctrl.signal });
ctrl.abort(); // stops the loop / aborts provider fetchOptions
name (default agent), model (provider or model handle), instructions, tools (registered tool defs), handoffs (other agents), maxSteps (default 8), onEvent.
Registry
Creating an agent registers it by name (default agent). Use getAgent(name) from graphs (agentNode) or your own wiring. Later agent({ name }) with the same name replaces the registry entry.
Handoffs
Pass handoffs: [billing]. Monorch exposes handoff_to_<name> tools to the model. You can also force a transfer with agent.handoff(target, input). Target must be listed in handoffs.
const billing = agent({
name: "billing",
model: mock([{ text: "Refund initiated." }]),
instructions: "Handle billing.",
});
const triage = agent({
name: "triage",
model,
instructions: "Route billing issues to billing.",
handoffs: [billing],
});
// model-driven: handoff_to_billing tool
await triage.run("I need a refund");
// programmatic
await triage.handoff(billing, "Customer wants a refund");What Rust owns
Run id, message history, step counts, pending tool calls, handoff targets, terminal states. JavaScript owns model generate/stream and tool execute.
Result shape
type AgentResult = {
text: string;
runId: string;
events: AiEvent[];
};