Agents
defineAgent is Kuralle's single agent primitive. Behavior is derived from which fields you populate — there's no separate FlowAgent or TriageAgent type.
The tagless primitive
Section titled “The tagless primitive”A minimal agent needs only id, instructions, and model:
import { openai } from '@ai-sdk/openai';import { defineAgent } from '@kuralle-agents/core';
const agent = defineAgent({ id: 'support', instructions: 'You are a helpful support agent.', model: openai('gpt-4o-mini'),});From there, the fields you add determine the behavior:
| Add these fields | The agent becomes |
|---|---|
tools | A durable tool-calling agent |
flows | A structured flow agent |
routes + routing | A triage router |
agents + handoffs | A composition wrapper |
workspace + skills | A file-native, progressively instructed agent |
These aren't mutually exclusive. An agent can have tools, a flow, and route to specialists at the same time.
Fields
Section titled “Fields”defineAgent({ id: string, name?: string, description?: string, instructions?: Instructions, // string | AgentPrompt | (ctx) => Instructions model?: LanguageModel, controlModel?: LanguageModel, // deterministic routing/extraction model tools?: Record<string, AnyTool>, // durable model-callable effects globalTools?: Record<string, AnyTool>, // safe tools visible in every speaking node flows?: Flow[], // structured flow graphs routes?: Route[], // route entries for triage routing?: RoutingPolicy, // routing config — see Routing guide agents?: AgentConfig[], // composed sub-agents handoffs?: string[], // agent IDs this agent can hand off to knowledge?: AgentKnowledge, // grounding sources memory?: AgentMemory, // long-term memory guardrails?: Guardrails, // input / output guardrails limits?: Limits, // step / turn limits validate?: ValidationCapability[], // post-turn validation refine?: RefinementCapability[], // pre-turn refinement policy?: Policy, // allow / ask / deny tool calls workspace?: AgentWorkspaceConfig, // portable or per-session filesystem skills?: SkillSource, // progressively disclosed procedures})instructions
Section titled “instructions”A string, an AgentPrompt, or a function of session state, describing the agent's role and rules. Keep it focused on persona and constraints — if you're writing more than ~20 lines of procedure here, move it to a flow.
tools is a single record of durable effect tools (Record<string, AnyTool> from defineTool). You author each tool once and pass the record — the runtime makes them model-visible (deriving the AI SDK ToolSet for you) and routes every model-issued call through the durable effect log for exactly-once replay:
import { defineTool } from '@kuralle-agents/core';
const tools = { echo, lookup };
defineAgent({ id: 'support', instructions: '...', model: openai('gpt-4o-mini'), tools, // durable, model-callable effect tools});Use globalTools only for safe, non-consequential capabilities that should remain visible in every speaking flow node. Mutating or narrowly scoped tools belong in tools or on the specific flow node that authorizes them. Raw AI SDK tools must be converted with wrapAiSdkTool() before they enter either record.
controlModel
Section titled “controlModel”controlModel handles routing, decisions, and extraction at deterministic temperature while model remains the speaking model. It defaults to model; set it when you want the control path pinned independently from the user-facing model.
workspace and skills
Section titled “workspace and skills”workspace attaches a Kuralle filesystem directly or resolves one per session. The model receives read-only traversal by default; executor writes and model writes are separate capabilities. skills publishes a validated discovery snapshot and adds load_skill / read_skill_resource tools for progressive disclosure.
See Workspaces, Skills, and the complete Release Governance Agent for an immutable repository mount, writable artifact mount, filesystem skill, and custom policy working together.
Attach one or more Flow objects (from defineFlow) to make the agent procedure-driven. The runtime enters the flow on the first turn and records the active node on the durable run.
See the Flows guide for the full node model, and Dynamic Flows to author the same graphs as JSON and hot-register them on a live runtime.
routes and routing
Section titled “routes and routing”Add routes to make the agent route between specialists. Pure dispatchers (routes only, no answering surface) classify silently; answering agents use host-control tools plus a lazy guard (it classifies only on a turn that produces no answer and no control tool) so dispatch never leaks as prose.
See Routing & Handoffs.
agents and handoffs
Section titled “agents and handoffs”agents registers sub-agents the runtime can activate during a handoff. handoffs declares which agent IDs this agent is allowed to transfer to. The combination enables tool-based handoffs where the agent explicitly decides when to transfer.
Handoffs are silent by default — no transfer announcement, and the target agent gets a best-effort nudge not to re-introduce itself. See Silent handoffs in the Routing guide, including the honest limit on that guarantee.
knowledge — who invokes retrieval
Section titled “knowledge — who invokes retrieval”When an agent declares knowledge, the runtime wires retrieval from HarnessConfig.knowledge (the shared KnowledgeProvider). The knowledge.autoRetrieve boolean declares who invokes that retrieval — the runtime, or the model:
knowledge.autoRetrieve | Behavior | When to use |
|---|---|---|
true (default) | Guaranteed — pre-injects retrieved snippets into the system prompt before every answering turn (## Retrieved Knowledge). Always grounded; routing turns on fused host agents pay the retrieval cost. | Regulated, factual, or policy-heavy agents where every answer must be grounded. |
false | On-demand — skips pre-injection and wires a knowledge_search tool the model calls when it needs facts. Routing/dispatch turns pay zero retrieval tax; grounding is model-discretion. | Agents that route often and need fast dispatch, or where retrieval is only needed for some answers. |
The pre-injection provider and the knowledge_search tool are mutually exclusive — the boolean picks the invoker, there is no separate mode to configure. To disable retrieval entirely, omit knowledge.
// Guaranteed (default) — pre-inject every answering turndefineAgent({ id: 'policy-bot', knowledge: { autoRetrieve: true }, // or omit — true is the default // ...});
// On-demand — retrieve only when the model answers; no routing-turn taxdefineAgent({ id: 'triage-support', knowledge: { autoRetrieve: false }, // ...});Example
Section titled “Example”import { openai } from '@ai-sdk/openai';import { defineAgent, defineTool, buildToolSet, defineFlow, reply } from '@kuralle-agents/core';import { z } from 'zod';
// Minimal: chat agent with no flows or routingconst chatAgent = defineAgent({ id: 'chat', instructions: 'You are a helpful assistant.', model: openai('gpt-4o-mini'),});
// Tool agent: model-visible tools + durable executorsconst lookup = defineTool({ name: 'lookup', description: 'Look up a product by ID', input: z.object({ id: z.string() }), execute: async ({ id }) => ({ name: `Product ${id}`, price: 49.99 }),});
const toolAgent = defineAgent({ id: 'catalog', instructions: 'Answer product questions using the lookup tool.', model: openai('gpt-4o-mini'), tools: { lookup },});
// Flow agent: behavior driven by the flow graph, not the instructions aloneconst done = reply({ id: 'done', instructions: 'Confirm and end the conversation.', next: () => ({ end: 'complete' }),});
const flowAgent = defineAgent({ id: 'booking', instructions: 'You guide users through a booking.', model: openai('gpt-4o-mini'), flows: [ defineFlow({ name: 'booking', description: 'Guide the user through the booking process', start: done, nodes: [done], }), ],});