JS / TypeScript SDK reference
Server-side TypeScript SDKs for LiveKit Agents and custom Node voice workers. MemoAir memory runs in the Node worker next to STT, LLM, TTS, and VAD. Browser clients join the LiveKit room; they do not run the MemoAir memory runtime.
Install
# Voice workernpm install memoair-voice memoair-livekit @livekit/agents # Optional partner/backend provisioning APInpm install memoair-adminmemoair-livekit depends on memoair-voice. Install both when you also use the raw client for indexing, smoke tests, or custom framework code.
Provision projects from code
Create one MemoAir project per customer and one agent per customer-facing voice flow before starting the LiveKit worker. This is a server-side admin operation using your account-scoped memoair_pk_* key, not a browser call.
import { MemoAirAdmin } from "memoair-admin" const admin = new MemoAirAdmin({ apiKey: process.env.MEMOAIR_API_KEY!, // memoair_pk_*}) // 1. Create one project per customer.const project = await admin.createProject({ name: "Customer A", slug: "customer-a", description: "Provisioned from onboarding flow",}) // 2. Create a voice agent inside that project.const agent = await admin.createAgent({ name: "Outbound Sales Agent", projectId: project.id, agentType: "voice",}) // 3. Store these IDs in your own DB. The LiveKit worker uses them later.console.log({ memoairProjectId: project.id, memoairAgentId: agent.id,})Backend endpoints: POST /v1/projects and POST /v1/projects/{projectId}/agents. The API key's org is inferred server-side and remains the tenancy boundary; a key from org A cannot create agents in org B or attach agents to org B projects.
MemoAirVoiceClient
High-level Node client from memoair-voice. Construct it once at process boot. Per-call userId or user={ id } routes each turn to the correct local runtime.
import { MemoAirVoiceClient } from "memoair-voice"Constructor
const client = new MemoAirVoiceClient({ apiKey: process.env.MEMOAIR_API_KEY!, projectId: process.env.MEMOAIR_PROJECT_ID!, agentId: process.env.MEMOAIR_AGENT_ID!,})Parameters
apiKeystringRequiredMemoAir account API key (memoair_pk_...). Used for cloud index calls and passed to every spawned local runtime for bootstrap/sync.
projectIdstringRequiredMemoAir project/workspace ID. Selects the org index, profile, permanent lane, and runtime storage partition.
agentIdstringRequiredMemoAir agent ID. Sent as X-Agent-Id on cloud calls so dashboard traces and agent configuration stay scoped.
searchMemory(query, options)
Local recall across profile, working, permanent, and org lanes. Returns a composed contextText string plus structured hits and trace timings.
const result = await client.searchMemory("where do I live?", { user: { id: "caller_42", name: "Alex" }, lanes: ["profile", "working", "permanent", "org"], timeoutMs: 250,}) console.log(result.contextText)console.log(result.trace.totalMs)Parameters
user / userId{ id, name?, metadata? } | stringRequiredEnd-user identity. Same ID across calls means the same per-user working/permanent memory lane.
lanesstring[]OptionalSubset of ["profile", "working", "permanent", "org"]. Defaults to all lanes.
intentstringOptionalDefault: "answer_current_user"Advisory hint forwarded to the local composer. The default is correct for most voice turns.
topKRecord<string, number>OptionalPer-lane top-k override, for example { permanent: 6, org: 4, working: 4 }.
timeoutMsnumberOptionalDefault: 250Per-turn memory deadline. Recall is local; late lanes are dropped rather than blocking the voice path.
saveResponse(options)
Persist a completed user/assistant turn. The local runtime appends immediately and syncs to cloud asynchronously.
await client.saveResponse({ userText: "I live in Bengaluru.", assistantText: "Got it.", userId: "caller_42", metadata: { framework: "livekit" },})createIndex(name, documents, options)
Cloud-side build-or-append for the project org index. Use this for small structured text seeds and use the dashboard for PDFs, website imports, or larger corpora.
await client.createIndex( "agent-memory", [ { id: "return-policy", text: "Returns are allowed within 30 days with a receipt.", metadata: { kind: "faq", topic: "returns" }, }, ], { userId: "index-builder" },)Parameters
namestringRequiredStable org index name. First call creates the index; later calls append or replace documents by ID.
documentsArray<{ id: string; text: string; metadata?: object }>RequiredPlain text documents. Keep this SDK path for small code-driven seeds; use the dashboard for PDFs, site imports, or large batches.
userIdstringOptionalProject-key callers should stamp a stable user ID such as index-builder. Org-only clients stamp the org sentinel automatically.
MemoAirLiveKitAgent
Drop-in @livekit/agents voice.Agent subclass from memoair-livekit. It opens a pinned runtime session on enter, searches before each reply, injects recall as a system message, and saves each assistant turn when you call registerSessionEvents(session).
import { MemoAirLiveKitAgent } from "memoair-livekit"const agent = new MemoAirLiveKitAgent({ apiKey: process.env.MEMOAIR_API_KEY!, projectId: process.env.MEMOAIR_PROJECT_ID!, agentId: process.env.MEMOAIR_AGENT_ID!, userId: participantIdentity, instructions: "You are a helpful voice assistant. Relevant memories are injected " + "as a system message before each reply.",}) const session = new voice.AgentSession({ vad: ctx.proc.userData.vad as silero.VAD, stt: new deepgram.STT({ model: "nova-2", language: "multi" }), llm: new openai.LLM({ model: "gpt-4o-mini" }), tts: new openai.TTS({ model: "gpt-4o-mini-tts", voice: "shimmer" }),}) agent.registerSessionEvents(session)ctx.addShutdownCallback(() => agent.onExit())await session.start({ agent, room: ctx.room })Server-side worker, not browser runtime
Put memoair-livekit inside the TypeScript LiveKit Agent worker. Do not import it in a Next.js client component or browser participant. The browser owns mic/playback/UI; the worker owns STT, LLM, TTS, VAD, and MemoAir memory.
Result and document shapes
type IndexDocument = { id: string text: string metadata?: Record<string, unknown>} type SearchResult = { contextText: string profile: Record<string, unknown> | null working: Record<string, unknown>[] permanent: Record<string, unknown>[] org: Record<string, unknown>[] sources: Record<string, unknown>[] trace: Record<string, unknown>} type IndexBuildResult = { indexName: string chunkCount: number version: number | null}