Skip to content

Kuralle

Kuralle

Build conversational agents with structured flows, routing, and durable tool execution. The same agent runs over text or voice.

Get started

Kuralle is a TypeScript framework for building conversational AI agents — text and voice — with structured flows, routing, and durable tool execution.

Most agent frameworks give you a prompt and a tool-call loop. That’s fine until the conversation has steps — collect these fields, confirm, then book. Kuralle puts those steps in a flow: a small graph of typed nodes with real control flow, so your procedure lives in code you can test, not in a sprawling system prompt.

One tagless primitive (defineAgent) derives its behavior from the fields you set. Add flows to get a structured flow agent. Add routes to get a router. Add agents to compose them. The runtime, session management, and streaming stay the same across all three.

  • AgentsdefineAgent with instructions and tools. Behavior is derived from what you populate, not a type flag.
  • Flows — node graphs (reply, collect, action, decide) where each node returns its next transition. Your SOP becomes a typed state machine you didn’t have to hand-write.
  • ToolsdefineTool with a Zod input schema and an async executor. Every tool effect is logged so a retried turn never double-executes.
  • Routing & Handoffs — structured routing (routes) picks the right specialist without leaking dispatch text to the user. handoffs transfer session context between agents.
  • RuntimecreateRuntime wires agents, sessions, and streaming. runtime.run() returns a TurnHandle: stream events with handle.events, await the result, or pipe to HTTP with handle.toResponseStream('sse').

Procedures belong in flows, not prompts. A booking or intake procedure becomes a handful of typed nodes you can test — not a long prompt you hope the model follows every turn.

One agent config, text and voice. The same defineAgent runs over chat text and over provider-native realtime voice. You don’t maintain two stacks.

Durable tool execution. Every defineTool call is logged in an append-only effect log. Retries replay against the log — a payment tool doesn’t charge twice, a booking tool doesn’t double-book.

Few primitives, composed. defineAgent, defineFlow, defineTool, createRuntime. That’s the core API. Structured routing, multi-agent composition, and session persistence are all derived from these.

Terminal window
npm install @kuralle-agents/core @ai-sdk/openai ai zod

Peer dependencies: ai@^6, zod. Bring your own provider package (@ai-sdk/openai, @ai-sdk/anthropic, etc.).

agent.ts
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { defineAgent, defineTool, createRuntime, buildToolSet } from '@kuralle-agents/core';
const echo = defineTool({
name: 'echo',
description: 'Echo back the provided text',
input: z.object({ text: z.string() }),
execute: async ({ text }) => ({ echoed: text }),
});
const agent = defineAgent({
id: 'support',
name: 'Support Agent',
instructions: 'Helpful support agent. Use the echo tool when asked.',
model: openai('gpt-4o-mini'),
tools: buildToolSet({ echo }), // make the tool model-visible
effectTools: { echo }, // wire the durable executor
});
const runtime = createRuntime({
agents: [agent],
defaultAgentId: 'support',
});
let sessionId: string | undefined;
async function chat(input: string) {
const handle = runtime.run({ input, sessionId });
for await (const part of handle.events) { // events is a property, not a method
if (part.type === 'text-delta') process.stdout.write(part.text);
if (part.type === 'done') sessionId = part.sessionId;
}
await handle;
}
await chat('Use echo to say "hello"');
await chat('What did I just ask you to do?');

Run it:

Terminal window
OPENAI_API_KEY=sk-... npx tsx agent.ts