Quickstart
Project setup
Section titled “Project setup”-
Install Kuralle and its peers. The
aipackage is the Vercel AI SDK; bring your own provider.Terminal window npm install @kuralle-agents/core @ai-sdk/openai ai zod -
Set your API key.
Terminal window export OPENAI_API_KEY=sk-... -
Define a tool.
defineTooltakes a Zod input schema and an asyncexecutefunction. The return value is passed back to the model as a tool result.import { z } from 'zod';import { defineTool } 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 }),}); -
Define an agent and wire the tool.
tools: buildToolSet({ echo })makes it model-visible;tools: { echo }wires the durable executor.import { openai } from '@ai-sdk/openai';import { defineAgent, buildToolSet } from '@kuralle-agents/core';const agent = defineAgent({id: 'support',instructions: 'Helpful support agent. Use the echo tool when asked.',model: openai('gpt-4o-mini'),tools: { echo },}); -
Create a runtime and run a turn.
runtime.run()returns aTurnHandle. Stream events by iteratinghandle.events— it’s a property, not a method call.import { createRuntime } from '@kuralle-agents/core';const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });const handle = runtime.run({ input: 'Use echo to say "hello"' });for await (const part of handle.events) {if (part.type === 'text-delta') process.stdout.write(part.delta);if (part.type === 'done') console.log('\nSession:', part.sessionId);}await handle;
Full example
Section titled “Full example”Put it all together in agent.ts:
import { openai } from '@ai-sdk/openai';import { z } from 'zod';import { defineAgent, defineTool, createRuntime } 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: { echo },});
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.delta); 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:
OPENAI_API_KEY=sk-... npx tsx agent.tsStreaming events
Section titled “Streaming events”The handle.events async iterable emits HarnessStreamPart events. The most common:
| Event type | When | Key fields |
|---|---|---|
| text-start | Assistant message begins | part.id: string |
| text-delta | Each text chunk from the model | part.id: string, part.delta: string |
| text-end | Assistant message complete | part.id: string |
| text-cancel | Partial message retracted (gate block) | part.id: string, part.reason: string |
| tool-call | Before a tool executes | part.toolName, part.args |
| tool-result | After a tool executes | part.toolName, part.result |
| done | Turn complete | part.sessionId |
Save part.sessionId and pass it back on subsequent turns to continue in the same session.
Session continuity
Section titled “Session continuity”let sessionId: string | undefined;
async function chat(input: string) { const handle = runtime.run({ input, sessionId }); for await (const part of handle.events) { if (part.type === 'text-delta') process.stdout.write(part.delta); if (part.type === 'done') sessionId = part.sessionId; } await handle;}The runtime auto-assigns a session ID on the first turn and persists conversation history. By default it uses an in-process MemoryStore; swap for Redis or Postgres in production.
Web UI with useChat (no bridge)
Section titled “Web UI with useChat (no bridge)”As of 0.5.0, @kuralle-agents/hono-server returns a native AI SDK UIMessageStream from POST /api/chat/sse by default. Point useChat at that endpoint — no hand-rolled HarnessStreamPart → UIMessageChunk bridge.
'use client';
import { useChat } from '@ai-sdk/react';import type { KuralleUIMessage } from '@kuralle-agents/core';
export function Chat() { const { messages, sendMessage } = useChat<KuralleUIMessage>({ api: '/api/chat/sse', onData: (part) => { // transient flow/node telemetry — delivered live, not stored in message.parts if (part.type === 'data-kuralle-flow') console.log(part.data); }, });
return ( <div> {messages.map((m) => ( <div key={m.id}> {m.parts.map((part, i) => part.type === 'text' ? <span key={i}>{part.text}</span> : null )} </div> ))} <button onClick={() => sendMessage({ text: 'Hello' })}>Send</button> </div> );}Kuralle orchestration events arrive as typed data-kuralle-* parts:
| Part type | Persisted? | Use for |
|---|---|---|
| data-kuralle-node, data-kuralle-flow, data-kuralle-control | transient (onData only) | flow/node telemetry |
| data-kuralle-interactive, data-kuralle-safety, data-kuralle-handoff, data-kuralle-outcome | persistent (message.parts) | UI controls, safety blocks, handoffs |
For non-UI consumers that need raw HarnessStreamPart JSON-SSE (curl, Studio), append ?format=raw to /api/chat/sse. See Deployment.
Next steps
Section titled “Next steps”- Agents — how
defineAgentderives behavior from the fields you set. - Flows — multi-step procedures as typed node graphs.
- Tools — tool wiring, durable execution, and the effect log.
- Sessions & State — durable session backends for production.
- Deployment — serve agents with
createKuralleChatRouter.