Agents
Stateful code with model-powered loops.
An agent is defined through code and is stateful — agent instances keep state across messages. Yoke offers two levels of agents:
- Low-level:
AgentDefinition+AgentManager— you own everything. - High-level:
Agent({ ... })andPrompt({ ... })— Yoke wraps the agent-owned tool loop, structured IO, and optional persistence.
AgentManager (low level)
Register a definition and the manager handles spawning instances and dispatching messages:
runtime.agent({
name: "assistant",
initialState: {},
async run(ctx, input) {
// ctx.inference, ctx.tools, ctx.agents are all available
return { answer: input.text };
},
});The runtime (or an actor, or another agent) can spawn a new instance or send a message to an existing one:
ctx.agents.assistant("instance-1").sendMessage({ text: "hi" });Agent (high-level primitive)
Agent({ name, model, instructions, tools, persist }) wraps the tool loop,
error recovery, and optional durable thread/state:
import { Agent } from "yoke";
const weatherAgent = Agent({
name: "weather-bot",
model: "deepseek/deepseek-v4-flash-0731",
instructions:
"You answer weather questions for any city. Use the forecast toolchain.",
tools: [forecastToolchain],
persist: true, // durable thread + state (SQLite backend)
});
const runtime = Runtime({ inference: stack })
.agent(weatherAgent)
.actor({
name: "inbox",
topics: ["messages.incoming"],
act: (env, ctx) =>
ctx.agents["weather-bot"]("default").sendMessage(env.payload),
});The reply shape is { message, steps, usage, data? } — the final message, how
many model calls the loop took, usage totals, and any extracted structured data.
Options
| Option | What it does |
|---|---|
name | Literal agent name (typed on ctx.agents) |
model | A model id or a Router |
instructions | System instructions |
tools | A tool, a toolchain, or a list |
maxSteps | Cap on tool-loop iterations |
persist | Persist thread + state across restarts |
inputSchema | zod schema for structured input |
messageBuilder | Convert structured input into messages (spawn only) |
outputSchema | zod schema for structured output (extraction) |
Structured input
A messageBuilder(payload) → Message[] converts structured input into the
messages sent to the model — used only when spawning a new instance.
Without a builder, the input is JSON-stringified into one message.
Structured output
The agent never receives the schema as a structured-output parameter. It runs
its normal tool loop; the final text is fed to a single-turn extraction call
(extractStructured) that converts it into the structured object carried in
reply.data.
Prompt (single turn)
Prompt({ name, model, instructions, inputSchema?, outputSchema? }) is a
single, no-tool-call, single-turn agent:
import { Prompt } from "yoke";
const summarizer = Prompt({
name: "summarizer",
model: "gpt-4o",
instructions: "Summarize the input in under 100 words.",
outputSchema: z.object({ summary: z.string() }),
});
const { text, data } = await ctx.agents.summarizer("default").sendMessage({
text: longDocument,
});Reply shape: { text, message, steps: 1, data? }.
Agents call other agents
Agents compose naturally — either via ctx.agents or by turning an agent into
a tool:
import { createToolFromAgent } from "yoke";
const research = Agent({ name: "researcher", /* ... */ });
const editor = Agent({
name: "editor",
model: "gpt-4o",
instructions: "Edit the text handed to you.",
tools: [createToolFromAgent(research, { description: "Do web research" })],
});The agent-owned tool loop
Model calls are stateless — messages in, assistant message out. The SDK
never executes tools and never loops. The agent owns the loop: declare tools
in chat({ tools }), receive the tool calls in the response, execute them
yourself via ctx.tools / ctx.callTool, append toolResultMessage(...), and
call again.
inferenceLoop codifies this — and feeds tool errors back to the model so
it can correct and retry. See inference for details.