Documentation

LiveKit

@mnemix-ai/livekit-kit is a real, published integration kit for LiveKit Agents: direction-safe SIP caller-identity resolution, a single Northsun context-fetch call for voice.Agent({ instructions }), and session-end memory write-back via ctx.addShutdownCallback(). This is a Northsun-built, Northsun-maintained kit against LiveKit's documented SIP participant and job-context contract — not a native LiveKit feature or a certified LiveKit integration.

Why this kit looks different from the webhook kits

Every other Northsun voice kit (Twilio, ElevenLabs, Retell, Vapi, Bland) is built around a webhook: the vendor's platform POSTs to an HTTP endpoint you control. LiveKit Agents has no such thing. A LiveKit Agent is a worker process that connects outward to the LiveKit server (ctx.connect()), authenticated at the SDK/transport level — there is no vendor-signed inbound request for this kit to receive or verify, and no signature.ts in this kit for that reason. Your own worker code is always the one that calls Northsun, for both inbound and outbound telephony — fetchNorthsunContext() is the single entry point for both directions.

Install

npm install @mnemix-ai/livekit-kit @mnemix-ai/client @livekit/agents

Verified in a clean directory: @mnemix-ai/livekit-kit@0.1.0 and @mnemix-ai/client@0.2.2 install together with @livekit/agents, exit 0, and import clean.

@mnemix-ai/* is today's published scope. If your integration will outlive a possible future move to @northsun-ai/*, pin the version above rather than tracking latest.

Set your key

MNEMIX_KEY=sk_live_...
MNEMIX_BASE_URL=https://mcp.mnemix.ai   # optional; this is the default

MNEMIX_KEY is your Northsun API key — see Installation for how to get one. Set it as a platform secret in whatever process manager runs your LiveKit worker, never as a plaintext environment value in a repo or client bundle. LiveKit connectivity itself (LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET) is handled entirely by the LiveKit SDK — this kit never touches those.

Wire it into your worker

import { fetchNorthsunContext, handleLiveKitSessionEnd, mapChatHistoryToTranscript } from "@mnemix-ai/livekit-kit";

// Inside your agent's entry function, after ctx.connect() + ctx.waitForParticipant():
const northsun = await fetchNorthsunContext(
  { mnemix },
  { attributes: participant.attributes, session_id: ctx.room.name },
);

const session = new voice.AgentSession({ /* stt/llm/tts */ });
await session.start({ agent: new voice.Agent({ instructions: northsun.instructions }), room: ctx.room });

ctx.addShutdownCallback(async () => {
  await handleLiveKitSessionEnd(
    { mnemix },
    {
      attributes: participant.attributes,
      session_id: ctx.room.name,
      transcript: mapChatHistoryToTranscript(session.history.items),
      contextAuditId: northsun.mnemix.context_audit_id ?? undefined,
    },
  );
});

See packages/livekit-kit/examples/agent.ts for a full worker entry function, and packages/livekit-kit/examples/trigger-outbound-call.ts for placing an outbound SIP call.

Direction-safe caller identity

LiveKit's SIP participant attributes name the customer's phone number the same way in both call directions:

AttributeInboundOutbound
sip.phoneNumberthe caller (customer)the number dialed by the SIP participant (customer)
sip.trunkPhoneNumberthe DID dialed into (your own)the trunk's own originating number (your own)

sip.phoneNumber names the customer in both directions, so resolveLiveKitCallerIdentity() doesn't need a direction branch to pick the right field — it still computes direction explicitly (via sip.ruleID), because verifying what a field means for the direction you're in is the point, not memorizing one vendor's quirk. This design exists directly in response to a real bug class: an earlier sibling kit shipped a HIGH-severity defect from reading Twilio's From/To unconditionally, which enriched the tenant's own number on every outbound call. packages/livekit-kit/test/identity.test.ts carries a direct regression test asserting phoneNumber is never the trunk's own number, in either direction.

Resolution states — read this before you read known: false

fetchNorthsunContext() never throws. Its result carries mnemix.resolution, one of three states:

  • "ok" — recall was attempted and Northsun answered. known: false here is usually the ordinary "new caller" case.
  • "identity_unresolved" — this kit could not determine an E.164 phone number for the participant at all (missing/malformed SIP attributes, or a non-SIP session with no override). Northsun was never called. This is a LiveKit-side configuration problem, not a statement about the caller, and is logged at error level precisely so it doesn't get silently read as "new caller."
  • "recall_failed" — an E.164 number was resolved, but the recall call itself errored or timed out.

handleLiveKitSessionEnd() refuses ({ ok: false }, Northsun never called) rather than writing an unattributable call when either the phone number or the session ID cannot be resolved — logged loudly, not silently defaulted.

What you get back

  • fetchNorthsunContext() returns .variables (the same 13-field shape every sibling kit returns, including the memory_status tri-state), .instructions (ready to pass directly as new voice.Agent({ instructions }) — named to match LiveKit's own field, not the sibling kits' system_prompt), and .mnemix (trace ID, resolution state, direction, timing).
  • handleLiveKitSessionEnd() returns { ok: boolean; error?: string }.
  • resolveLiveKitCallerIdentity(attributes) returns the raw identity-resolution result if you want to make your own routing decisions before calling Northsun.
  • mapChatHistoryToTranscript(session.history.items) converts LiveKit's ChatContext items into the transcript shape handleLiveKitSessionEnd expects.

memory_status is a tri-state, because memory_facts alone cannot tell "this caller genuinely has no governed facts" apart from "the facts read failed and we could not check":

  • "ok" — the read ran and answered. memory_facts is a real measurement, possibly empty.
  • "unavailable" — the read did not complete (server-side budget or error, or recall itself failed, timed out, or was never reached). memory_facts is [] here too, but that [] is not a measurement. buildInstructions() renders an explicit instruction telling the agent not to state or imply the caller has no facts on file, instead of staying silent — silence would let the agent infer "nothing on file" on its own, which is the exact false statement this field exists to prevent.
  • "off" — your tenant deployment has memory-unification reads disabled. Feature off, not a failure; rendered the same as "ok" with an empty list.

Current limitation — read this before you branch on "ok". The tri-state is only as precise as what the recall API puts on the wire, and the server-side change that reports a dropped governed read distinctly is not yet released. Until it is:

  • "ok" under-warns. If the governed-facts read times out or errors for a caller who already has call history, the API still returns an empty list, and it is indistinguishable from a caller who genuinely has no stored facts. The kit reports "ok" and the prompt stays silent.
  • "unavailable" can over-warn. The kit infers it when the caller's interaction count is also unreadable, which is deliberate but is not proof the facts read itself failed. Do not diagnose a facts outage from this field alone.

The direction of both errors is deliberate: the kit would rather warn you about a record it did read than stay silent about one it did not. Treat "unavailable" as "do not assert this caller has nothing on file" rather than as a precise diagnostic, and do not treat "ok" as proof of a measurement until the server change ships. This note will be removed when it does.

This kit does not build a voice.Agent for you, does not choose your STT/LLM/TTS providers, and does not decide your call outcome — it hands you enriched context and writes back what you tell it, nothing more. See What recall gives you back for the full field reference.

No vendor SDK at runtime

@livekit/agents / @livekit/rtc-node are devDependencies only — used to verify this kit's hardcoded assumptions (ParticipantKind.SIP, ChatMessage's shape) against the real, installed packages, and in the examples. The kit's own runtime code takes plain Record<string, string> attributes and duck-typed chat items; it never imports @livekit/agents itself, so it has no exposure to that SDK's own churn.

Verify it

Place or receive a call through your deployed LiveKit worker. Your worker logs should show one fetchNorthsunContext() call after ctx.waitForParticipant(), and one handleLiveKitSessionEnd() call from the shutdown callback. See Hangup, and what happens next for what happens immediately after that call.

Next steps