Skip to content

Quickstart

Syrinx is published on npm as @kuralle-syrinx/*. Install the packages you need, plug in your providers, and you have a voice agent — no repository to clone.

Syrinx supplies the voice runtime and the STT/TTS adapters. The reasoner — the thing that decides what to say — is your own agent framework, wired in through a bridge package. Pick one:

Terminal window
npm install @kuralle-syrinx/core @kuralle-syrinx/deepgram @kuralle-syrinx/cartesia \
@kuralle-syrinx/kuralle @kuralle-syrinx/aisdk @kuralle-agents/core @ai-sdk/openai
Terminal window
DEEPGRAM_API_KEY=...
OPENAI_API_KEY=...
CARTESIA_API_KEY=...
# Optional — Cartesia falls back to a default voice when this is unset.
CARTESIA_VOICE_ID=...

Wire a cascade — Deepgram STT → your reasoner → Cartesia TTS — with Deepgram owning turn detection.

Write it as a factory. A session is per-conversation, not per-process, so what you export is a function that builds a fresh one.

A Kuralle runtime brings agents, tools, flows, skills, and memory. fromKuralleRuntime turns one into a Syrinx Reasoner:

import { VoiceAgentSession } from '@kuralle-syrinx/core';
import { DeepgramSTTPlugin } from '@kuralle-syrinx/deepgram';
import { CartesiaTTSPlugin } from '@kuralle-syrinx/cartesia';
import { ReasoningBridge } from '@kuralle-syrinx/aisdk';
import { fromKuralleRuntime } from '@kuralle-syrinx/kuralle';
import { defineAgent, createRuntime, MemoryStore } from '@kuralle-agents/core';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const agent = defineAgent({
id: 'assistant',
model: openai('gpt-4.1-mini'),
instructions: 'You are a helpful voice assistant. Keep your replies short.',
});
const runtime = createRuntime({
agents: [agent],
defaultAgentId: 'assistant',
sessionStore: new MemoryStore(),
});
export function createVoiceAgent(): VoiceAgentSession {
const session = new VoiceAgentSession({
plugins: {
stt: { api_key: process.env.DEEPGRAM_API_KEY!, model: 'nova-3', sample_rate: 16000, emit_eos_on_final: true },
bridge: {},
tts: { api_key: process.env.CARTESIA_API_KEY! },
},
endpointingOwner: 'provider_stt',
});
session.registerPlugin('stt', new DeepgramSTTPlugin());
session.registerPlugin('bridge', new ReasoningBridge(
fromKuralleRuntime(runtime, { sessionId: 'quickstart' }),
));
session.registerPlugin('tts', new CartesiaTTSPlugin());
// Return it unstarted — the server below calls start(), once, per connection.
return session;
}

Give each conversation its own sessionId so the runtime keeps separate history and memory per caller.

That’s the whole agent: audio in becomes a transcript, the transcript becomes a reply, the reply becomes audio. Only the bridge slot changed between those two tabs — the STT and TTS wiring, the turn-taking, and everything downstream are identical. The same seam takes a Mastra agent, or a realtime speech-to-speech model instead of a cascade.

Register every plugin before the session starts, and start it exactly once: start() runs the plugin init chain that opens the provider sockets, and throws if called twice.

Serve the agent over the Syrinx WebSocket protocol. createVoiceWebSocketServer calls your factory once per connection and starts each session for you:

Terminal window
npm install @kuralle-syrinx/server-websocket
import { createVoiceWebSocketServer } from '@kuralle-syrinx/server-websocket';
import { createVoiceAgent } from './my-agent.js';
await createVoiceWebSocketServer({
port: 4173,
path: '/ws',
createSession: () => createVoiceAgent(),
});
console.log('agent listening on ws://localhost:4173/ws');

That is the whole server. It owns the audio wire — resampling, Opus on the downlink, frame pacing, heartbeats, and reconnect with ordered replay.

Now open Syrinx Studio and point it at your machine:

https://syrinx-studio.mithushancj.workers.dev/?ws=ws://localhost:4173/ws

Allow the microphone and start talking. The server owns turn detection, so you speak and stop naturally — there is no push-to-talk — and it yields when you interrupt it mid-sentence. Studio shows the live transcript, per-turn timings, and how each turn resolved.

A session is idle until something pushes audio frames at it. That something is a transport — the browser server above is one. Swapping it does not change the agent you just wrote:

  • A WAV file — a full turn with no server and no microphone, which is what you want in tests and CI. See Run it locally.
  • Telephony — the same agent answering a Twilio or Telnyx phone call.
  • Cloudflare Workers — the whole pipeline on the edge, one hibernatable Durable Object per call. See Deploy on Cloudflare.