npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@absolutejs/voice

v0.0.22-beta.616

Published

Voice primitives and Elysia plugin for AbsoluteJS

Readme

@absolutejs/voice

@absolutejs/voice is the self-hosted voice operations layer for AbsoluteJS.

It gives your app the primitives hosted voice platforms usually keep behind their dashboards: browser voice sessions, phone-call routes, provider routing, assistant tools, handoffs, traces, evals, production-readiness checks, latency proof, storage adapters, and framework-native UI helpers.

Use it when you want Vapi/Retell/Bland-style voice-agent capability, but you want the orchestration, data, traces, storage, and UI to live inside the AbsoluteJS server you already operate.

What's new

0.0.22-beta.474 · Phase 5 runtime hookup — auto-wire monitor sockets to live sessions

The Phase 5 monitor primitive is now first-class in the voice runtime. Pass a monitor binding to voice({...}) and every session opened against the voice plugin auto-registers in the monitor registry, outbound TTS audio auto-fans-out to all listeners, and the close/superseded/session-switch paths deregister automatically. Supervisors connecting to the listen route get a live audio stream without any manual record.emit() calls.

import { Elysia } from "elysia";
import {
  createVoiceInMemoryMonitorRegistry,
  createVoiceLiveMonitorRoutes,
  createVoiceMonitorRuntimeBinding,
  voice,
} from "@absolutejs/voice";

const monitorRegistry = createVoiceInMemoryMonitorRegistry();

const app = new Elysia()
  .use(
    voice({
      path: "/voice/realtime",
      stt: deepgram({ apiKey: process.env.DEEPGRAM_API_KEY! }),
      tts: elevenlabs({ apiKey: process.env.ELEVENLABS_API_KEY! }),
      onTurn: async (session, turn, api) => {
        // your business logic
      },
      session: sessionStore,
      monitor: createVoiceMonitorRuntimeBinding(monitorRegistry, {
        audioFormat: { channels: 1, container: "raw", encoding: "pcm_s16le", sampleRateHz: 24_000 },
      }),
    }),
  )
  .use(
    createVoiceLiveMonitorRoutes({
      registry: monitorRegistry,
      authenticate: async ({ request }) => await verifySupervisorJWT(request),
    }),
  );

Surface additions:

  • createVoiceMonitorRuntimeBinding(registry, { audioFormat?, defaultSource? }) — returns a VoiceMonitorRuntimeBinding you pass to the voice plugin's new monitor option. Internally, each session open calls registerSession({ handle, sessionId }), audio fans out via emitAudio() on every binary socket.send, and the close/superseded/session-switch paths call deregister(reason).
  • VoicePluginConfig.monitor?: VoiceMonitorRuntimeBinding — new optional field on the main voice plugin config.
  • VoiceMonitorMutableRegistry.deregister(sessionId, reason?) — explicit deregister path so the runtime binding can tear down stale records on re-register without throwing. The register()-returned deregister fn now also accepts an optional reason.
  • The runtime binding is opt-in — if you don't pass monitor, voice behaves exactly as before. No overhead on the audio-send hot path beyond a single Map.get.

4 new tests cover the runtime binding's register/emit/deregister cycle, no-op-after-deregister, re-registration tear-down, and the audioFormat + defaultSource option threading. Full voice suite now 963 pass / 1 pre-existing fail.

0.0.22-beta.473 · Phase 5 — live listen + control monitor sockets (Vapi monitorPlan parity)

Two new WebSocket routes per session that mirror Vapi's monitorPlan.listenUrl + monitorPlan.controlUrl. Supervisors can subscribe to a live call's outbound audio and send control commands (transfer, hangup, escalate, voicemail, no-answer, plus caller-defined mute/say/inject) without touching the call itself.

import { Elysia } from "elysia";
import {
  buildVoiceMonitorPlan,
  createVoiceInMemoryMonitorRegistry,
  createVoiceLiveMonitorRoutes,
  createVoiceMonitorSession,
} from "@absolutejs/voice";

const registry = createVoiceInMemoryMonitorRegistry();

// In your runtime: when a session opens, register it so supervisors can listen.
const record = createVoiceMonitorSession({ handle, sessionId: handle.id });
const deregister = registry.register(record);
// When audio leaves the assistant, fan it out:
//   record.emit({ at: Date.now(), chunk, format, source: 'assistant' });
// On call end:
//   deregister();

const app = new Elysia()
  .use(
    createVoiceLiveMonitorRoutes({
      authenticate: async ({ sessionId, route, request }) =>
        await verifySupervisorJWT(request),
      controlHandlers: {
        say: async ({ message, session }) => {
          await yourTtsRuntime.sayInSession(session.sessionId, message.text);
          return { detail: `Said: ${message.text}`, ok: true, type: "say" };
        },
      },
      htmlPath: "/voice/monitor",
      registry,
    }),
  );

const plan = buildVoiceMonitorPlan({
  baseUrl: "wss://api.example.com",
  sessionId: handle.id,
});
// plan.listenUrl  → wss://api.example.com/api/voice/monitor/<sessionId>/listen
// plan.controlUrl → wss://api.example.com/api/voice/monitor/<sessionId>/control

Surface summary:

  • createVoiceInMemoryMonitorRegistry(){ register, get, list, emit, emitClose }. Voice's session runtime (or any caller) wires register() on open + the returned deregister on close; emit() fans out outbound audio frames; emitClose() notifies listeners that the call ended.
  • createVoiceLiveMonitorRoutes(options) — Elysia plugin that mounts two .ws() routes per session: :sessionId/listen (read-only outbound audio as binary frames) and :sessionId/control (JSON control messages). Default handlers map transfer/hangup/escalate/voicemail/no-answer onto VoiceSessionHandle verbs; mute/say/inject require caller-supplied handlers via controlHandlers.
  • buildVoiceMonitorPlan(input) — Vapi-shaped helper returning { listenUrl, controlUrl } for inclusion in your call-create API response.
  • Auth hookauthenticate?: ({ sessionId, route, request }) => Promise<boolean> runs at the start of both routes; rejected connections get a 4401 close.

This is a primitive: wiring voice's existing activeSessions runtime into the registry (so audio actually flows from a real call without manual record.emit() calls) is the next step and intentionally left out to keep this change additive. Until then, registries built by hand (e.g. from a custom telephony adapter where you control the outbound audio buffer) work out of the box.

0.0.22-beta.472 · Phase 6 — multilingual STT proof gate

runVoiceMultilingualProof(...) turns the voice-fixtures-multilingual corpus (FLEURS + BSC Catalan-Spanish code-switch + CoSHE Hindi-English code-switch) into a gateable readiness/proof artifact. Buyers evaluating Vapi-replacement can now run any combination of STT adapters against the multilingual corpus and assert per-language WER / pass-rate / term-recall budgets in CI.

import {
  buildVoiceMultilingualProofReadinessCheck,
  renderVoiceMultilingualProofMarkdown,
  runVoiceMultilingualProof,
} from "@absolutejs/voice";
import { deepgram } from "@absolutejs/voice-deepgram";
import { speechmatics } from "@absolutejs/voice-speechmatics";
import { soniox } from "@absolutejs/voice-soniox";

const report = await runVoiceMultilingualProof({
  adapters: [
    { adapter: deepgram({ apiKey, model: "nova-3" }), adapterId: "deepgram-nova3" },
    { adapter: speechmatics({ apiKey, region: "eu2" }), adapterId: "speechmatics-enhanced" },
    { adapter: soniox({ apiKey, enableLanguageIdentification: true }), adapterId: "soniox" },
  ],
  defaultThresholds: { maxAverageWordErrorRate: 0.30, minPassRate: 0.7 },
  perLanguage: [
    { language: "ca-es", label: "Catalan-Spanish code-switch", maxAverageWordErrorRate: 0.45 },
    { language: "hi-en", label: "Hindi-English code-switch", maxAverageWordErrorRate: 0.50 },
  ],
});

const readiness = buildVoiceMultilingualProofReadinessCheck(report, {
  baseHref: "/voice/multilingual-proof",
});
// drop `readiness` into your VoiceProductionReadinessReport.checks array

await Bun.write("docs/multilingual-proof.md", renderVoiceMultilingualProofMarkdown(report));

Highlights:

  • Loads fixtures from VOICE_FIXTURE_DIR (or any caller-supplied directory list / pre-loaded VoiceTestFixture[]), filters by an optional predicate, and runs each adapter through the existing runSTTAdapterBenchmark harness — no new STT plumbing required.
  • Buckets fixture results by fixture.language and applies per-language thresholds (maxAverageWordErrorRate, minAverageWordAccuracyRate, minPassRate, minTermRecall) layered over caller-provided defaultThresholds.
  • Returns a structured report (adapters[].languageReports[] with metrics + failures, plus per-adapter and global passes flags) plus a Markdown renderer for human review and a VoiceProductionReadinessCheck-shaped helper for drop-in readiness wiring.
  • Pairs naturally with every STT adapter shipped in voice-adapters (deepgram, assemblyai, azure streaming, speechmatics, gladia, soniox, google-speech streaming + buffered, openai-whisper buffered).

0.0.22-beta.471 · Vapi parity — fromVapiAssistantConfig adapter

Mechanical migration from a Vapi Assistant JSON to a voice assistant. Pass the JSON dump (or a typed subset), provide a modelFactory that maps Vapi's model.provider+model.model to a voice VoiceAgentModel, and get back { assistant, tools, routeHints, unsupported }.

import {
  createOpenAIVoiceAssistantModel,
  fromVapiAssistantConfig,
  type VapiAssistantConfig,
} from "@absolutejs/voice";

const config: VapiAssistantConfig = JSON.parse(vapiDump);

const { assistant, tools, routeHints, unsupported } = fromVapiAssistantConfig(config, {
  assistantId: "support",
  fetch,
  knowledgeBase: { collection: ragCollection },
  modelFactory: ({ provider, model, temperature }) => {
    if (provider === "openai") {
      return createOpenAIVoiceAssistantModel({
        apiKey: process.env.OPENAI_API_KEY!,
        model: model ?? "gpt-4.1-mini",
        temperature,
      });
    }
    throw new Error(`Unsupported provider: ${provider}`);
  },
  variableResolver: (path, { context }) => {
    if (path === "customer.name") return context.customerName;
    return undefined;
  },
  dtmfSendFactory: () => ({ args, api }) =>
    api.transfer({ target: "ivr", metadata: { digits: args.digits } }),
});

console.log("Migrated", tools.length, "tools;", unsupported.length, "fields need manual review:");
for (const reason of unsupported) console.log(`  - ${reason.field}: ${reason.detail}`);

Mappings:

  • model.provider + model.model + temperature → your modelFactory (you control which provider adapter to use; the adapter doesn't bake in API keys).
  • messages[].role === "system" (or systemPrompt) → assistant system field; {{var}} bare expansions auto-compile to a function-form prompt that resolves built-ins ({{now}}, {{date}}, {{time}}) and your variableResolver for everything else. LiquidJS filters are reported in unsupported.
  • tools[].type → named-tool factories from beta.470: endCallcreateVoiceEndCallTool, transferCallcreateVoiceTransferCallTool (numbers/SIP URIs auto-mapped), voicemailcreateVoiceVoicemailDetectionTool, apiRequest / function (with server.url) → createVoiceApiRequestTool, query (or top-level knowledgeBaseId) → createVoiceRAGTool if you pass a knowledgeBase option. dtmf requires a dtmfSendFactory so it can reach your telephony adapter. function tools without server.url are forwarded to your optional customToolFactory.
  • voice.* (TTS) + transcriber.* (STT) + firstMessage / firstMessageMode / silenceTimeoutSeconds / maxDurationSeconds / endCall* / recordingEnabled / compliancePlan.* / backgroundSound / voicemailMessagerouteHints you pass into your voice() plugin / carrier route at mount time.
  • monitorPlan, startSpeakingPlan, stopSpeakingPlan, voicemailDetection.provider, model.toolIds, firstMessageMode → reported in unsupported with concrete migration instructions.

0.0.22-beta.470 · Vapi parity — named tool catalog

Five named-tool factories that mirror Vapi's built-in tools[].type entries 1:1. Drop them straight into createVoiceAssistant({ tools: [...] }) to get the same buyer ergonomics without writing the wiring yourself.

import {
  createVoiceAssistant,
  createVoiceApiRequestTool,
  createVoiceDTMFTool,
  createVoiceEndCallTool,
  createVoiceTransferCallTool,
  createVoiceVoicemailDetectionTool,
} from "@absolutejs/voice";

const tools = [
  createVoiceEndCallTool({ farewell: "Thanks for calling, goodbye." }),
  createVoiceTransferCallTool({
    destinations: [
      { id: "billing", target: "+15551234567", message: "Connecting you to billing." },
      { id: "supervisor", target: "sip:supervisor@pbx", metadata: { queue: "vip" } },
    ],
  }),
  createVoiceDTMFTool({
    send: async ({ args, api }) => api.transfer({ target: "ivr", metadata: { digits: args.digits } }),
  }),
  createVoiceVoicemailDetectionTool({ completeAfterMarking: true }),
  createVoiceApiRequestTool({
    description: "Look up a customer record",
    name: "lookupCustomer",
    method: "GET",
    url: "https://api.example.com/customers",
    buildQuery: ({ args }) => ({ email: String(args.email) }),
    parameters: { type: "object", properties: { email: { type: "string" } }, required: ["email"] },
  }),
];

Per-tool highlights:

  • createVoiceEndCallTool — calls api.complete(result) with an optional resolved result. JSON-schema { reason?: string }. Custom farewell (static or ({args, context, session}) => string).
  • createVoiceTransferCallTool — multi-destination router, exposes an enum of destination ids to the LLM; calls api.transfer({ target, metadata, reason, result }) with the matching destination. Pairs with createVoiceTwilioRedirectHandoffAdapter for live carrier transfer.
  • createVoiceDTMFTool — caller provides a send hook (typically forwarding to your telephony adapter); validates digits against allowedDigits and maxDigits. Default allowed set 0123456789*#.
  • createVoiceVoicemailDetectionTool — LLM-callable companion to ML-based voicemail detection; calls api.markVoicemail({ metadata }) and (by default) api.complete(). Use when the model hears a beep / "leave a message" prompt.
  • createVoiceApiRequestTool — generic HTTP-call tool with buildBody / buildQuery / buildHeaders / parseResponse hooks. Supports DI of fetch for tests.

0.0.22-beta.469 · Vapi parity — RAG-as-a-tool helper

createVoiceRAGTool(collection, options?) wraps any @absolutejs/rag RAGCollection (or any object that satisfies VoiceRAGCollectionLike) into a VoiceAgentTool shaped like Vapi's built-in query / knowledgeBaseId flow.

import { createInMemoryRAGStore, createRAGCollection } from "@absolutejs/rag";
import { createVoiceAssistant, createVoiceRAGTool } from "@absolutejs/voice";

const collection = createRAGCollection(
  createInMemoryRAGStore(),
  { embedding: openaiEmbeddings("text-embedding-3-small") },
);

const knowledgeBase = createVoiceRAGTool(collection, {
  allowedFilterKeys: ["productLine"],
  fixedFilter: ({ context }) => ({ tenantId: context.tenantId }),
  maxChunkChars: 320,
  scoreThreshold: 0.65,
});

const assistant = createVoiceAssistant({
  id: "support",
  model: openaiVoiceAssistantModel,
  tools: [knowledgeBase],
  system: "You are a support agent. Always cite the knowledge base.",
});

Highlights:

  • Default tool name searchKnowledgeBase matches the conventional Vapi knowledge-base tool name.
  • Generated JSON-schema parameters expose query (required), topK (clamped to maxTopK), and an optional filter object whose keys are gated by allowedFilterKeys.
  • fixedFilter can be a static record or a (context) => filter function (use for tenant scoping, locale, etc.). Fixed values always win over LLM-supplied filters.
  • Default formatter returns numbered citations (1. <title> (score 0.910): <chunk>); override via formatResult or resultToMessage.
  • No hard dependency on @absolutejs/ragVoiceRAGCollectionLike is structurally compatible with RAGCollection, so install rag (or any duck-typed retrieval backend) as a peer.

0.0.22-beta.464 → 468 · Media pipeline issue codes across product surfaces

@absolutejs/voice now projects compact @absolutejs/media reports into every buyer-facing voice surface so media health is gateable, linkable, and visible without buying a hosted dashboard.

Proof pack (beta.464–465)summarizeVoiceMediaPipelineReport(report, { artifacts }) returns a compact VoiceMediaPipelineProofSummary with aggregated issueCodes and per-section status. writeVoiceMediaPipelineArtifacts({ dir, report, hrefBase }) persists media-quality.{json,md}, media-transport.{json,md}, and media-processor-graph.{json,md} into a proof-pack run directory and returns href links to wire into the summary. Together these shrink the mediaPipelineCalibrationAssertion summary in voice proof packs from ~35 KB to ~1.7 KB (95% reduction) and trim the total proof JSON from ~170 KB to ~114 KB.

Production readiness (beta.464)buildVoiceMediaPipelineReadinessChecks(report, { baseHref, label }) returns drop-in VoiceProductionReadinessCheck[] entries (overall, media quality, transport, processor graph, interruption). Spread them into additionalChecks for granular gating alongside the existing aggregate "Media pipeline quality" check.

Operations record (beta.466–467)VoiceOperationsRecord.mediaPipeline is a new optional VoiceOperationsRecordMediaPipelineSummary (status, qualityStatus, transportStatus, processorGraphStatus, issueCodes, jitterMs, frames, surface). Populate it by passing mediaPipeline: VoiceMediaPipelineReport to buildVoiceOperationsRecord, or pass mediaPipeline (a report or sessionId-keyed resolver) to createVoiceOperationsRecordRoutes so every /api/voice-operations/:sessionId response carries it automatically.

Failure replay (beta.466)buildVoiceFailureReplay reads record.mediaPipeline and adds media.pipelineIssueCodes + media.pipelineStatus to the report, appends "Media pipeline issue: <code>" entries to summary.issues, and demotes failure-replay status to failed/degraded when the pipeline is fail/warn. The Markdown incident artifact gets a new "Media Pipeline" section.

Incident timeline (beta.466)VoiceIncidentTimelineOptions.extraEvents accepts any custom event source. Feed buildVoiceMediaPipelineIncidentEvents(report, { now, source, category }) into it to surface media-pipeline issues as category: "monitor" entries in /api/voice/incident-timeline alongside operational and failure-replay events.

Ops record renderers (beta.468)renderVoiceOperationsRecordIncidentMarkdown adds a one-line Media pipeline summary and an issue-code section. renderVoiceOperationsRecordHTML adds a nav anchor, a summary-grid card, and a #media-pipeline section listing surface, per-section status, frame/jitter, and codes.

Requires @absolutejs/[email protected]+. The voice bundle externalizes @absolutejs/media to keep browser bundles parseable; install media as a peer dependency.

Why AbsoluteJS Voice

  • Self-hosted by default: your app owns sessions, traces, reviews, tasks, handoffs, retention, and provider keys.
  • Provider-neutral: use Deepgram, AssemblyAI, OpenAI, Anthropic, Gemini, ElevenLabs-style TTS, or your own adapters without rewriting app workflow code.
  • Browser and phone surfaces: mount browser WebSocket voice routes plus Twilio, Telnyx, and Plivo telephony routes from the same package.
  • Production proof: ops status, ops recovery, production readiness, operations records, turn quality, turn latency, live browser p50/p95 latency, trace timelines, evals, fixtures, and contracts are package primitives.
  • Framework parity: React, Vue, Svelte, Angular, HTML, HTMX, and plain client entrypoints share the same core behavior.
  • No hosted platform tax: AbsoluteJS Voice does not add a mandatory per-minute orchestration fee between your app and your providers.

Start Here

Pick the path that matches what you are building:

  • Browser voice agent: mount voice(...), choose an STT adapter, and use the React/Vue/Svelte/Angular/HTML/HTMX client helpers for mic, transcript, reconnect, and status UI.
  • Phone voice agent: mount Twilio, Telnyx, or Plivo routes, normalize carrier outcomes, inspect carrier readiness, and persist call lifecycle traces.
  • Outbound campaigns: create self-hosted campaign queues, import CSV/JSON recipients, enforce rate limits/quiet hours/retry backoff, dry-run carrier dialers, and fail production readiness when campaign proof regresses.
  • Production readiness: mount the status and proof primitives you need, such as createVoiceOpsStatusRoutes(...), createVoiceProductionReadinessRoutes(...), quality routes, trace routes, eval routes, and smoke contracts.
  • Support/debug entrypoint: mount createVoiceOperationsRecordRoutes(...) so every problematic session has one call-log object linking traces, replay, provider events, tools, handoffs, audit, reviews, tasks, and delivery attempts.
  • Provider routing and fallback: use LLM/STT/TTS provider routers, provider health, provider simulation controls, and cost/latency-aware routing policies.
  • Evals and simulation: mount createVoiceSimulationSuiteRoutes(...) to run scenario fixtures, workflow contracts, tool contracts, outcome contracts, baseline comparisons, and saved benchmark artifacts before live traffic.

Buyer Paths

These are the primitive-first paths a Vapi-style buyer usually needs. Each path stays inside your AbsoluteJS app; the package gives you route handlers, stores, reports, hooks, composables, services, widgets, and contracts instead of a hosted dashboard.

| If you need | Start with | Add proof with | UI entrypoints | | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Web voice assistant | voice(...) or createVoiceAssistant(...) | trace timeline, turn quality, live latency, reconnect contract, operations record | React/Vue/Svelte/Angular voice stream helpers, HTML/HTMX/client helpers | | Phone voice assistant | createVoicePhoneAgent(...) | carrier matrix, setup instructions, phone smoke contract, production readiness, operations record | phone setup HTML/JSON, smoke HTML/JSON, framework status UI | | Multi-specialist support flow | createVoiceAgentSquad(...) | squad contract, handoff traces, context traces, operations record | Agent Squad status hooks/composables/services/widgets | | Business actions and tools | createVoiceAgentTool(...) plus agent tool runtime | tool contracts, audit events, integration events, outcome contracts | operations record, action center, contract routes | | Guardrails and policy checks | createVoiceGuardrailPolicy(...), createVoiceGuardrailRuntime(...), and createVoiceGuardrailRoutes(...) | live assistant/tool enforcement, blocking/warning decisions, redacted content, assistant.guardrail trace events | guardrail JSON/Markdown routes and operations record traces | | Provider routing and fallback | provider routers, health checks, simulation controls | provider contract matrix, provider-stage traces, latency SLO reports | provider contract hooks/composables/services/widgets | | Production operations | ops status, ops recovery, production readiness, delivery runtime | readiness gates, recovery report, incident Markdown, delivery queues | ops action center, delivery runtime UI, operations record | | Outbound campaigns | createVoiceCampaignRoutes(...) | recipient validation, consent/dedupe, carrier dry-run, campaign readiness | campaign routes and operations-record-linked attempt proof | | Simulation before launch | createVoiceSimulationSuiteRoutes(...) | scenarios, evals, tool contracts, outcome contracts, baselines | simulation-suite HTML/JSON and linked operations records | | Compliance controls | runtime storage, audit logger, data-control routes | retention dry-run, redacted audit export, zero-retention policy, deploy gate | data-control HTML/JSON and audit/export routes |

Capability Matrix

| Surface | Core package | Example/demo role | Not our lane | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | | Browser voice | WebSocket voice route, client stream/controller/store primitives, reconnect, barge-in, latency, framework helpers | Prove the same mic/transcript/status workflow across React, Vue, Svelte, Angular, HTML, and HTMX | Hosted iframe widget that owns app UX | | Telephony | Twilio, Telnyx, Plivo route bridges, phone-agent wrapper, setup instructions, smoke contracts, carrier outcomes | Show setup/smoke/proof surfaces and call lifecycle debugging | Buying/provisioning phone numbers for the user | | Agents and tools | assistant, agent, tools, squads, handoff/context policies, contracts, audit hooks | Demonstrate realistic support/sales/workflow flows | Dashboard-only visual bot builder | | Provider layer | OpenAI/Anthropic/Gemini model paths, STT/TTS adapter seams, routing, fallback, health, simulation | Show provider switching and health in UI | Reselling provider minutes or hiding provider accounts | | Observability | traces, timelines, replay, operations records, incident Markdown, ops recovery, readiness | Make every failing proof link to a call/session record | Vendor dashboard as source of truth | | Evals and simulation | fixtures, eval routes, simulation suite, workflow/tool/outcome contracts, baselines | Prove flows before live traffic | Opaque hosted test runner | | Data and compliance controls | file/SQLite/Postgres/S3 storage paths, redaction, retention, audit exports, guarded deletion | Show customer-owned storage and export proof | Legal certification or compliance attestation |

Proof Pack

Use this checklist when a buyer asks, "How do I know this replaces a hosted voice dashboard?" Each artifact is a route, report, contract, or export the app owns. The point is not screenshots; the point is reproducible proof that can live in CI, an internal admin page, or a customer-facing demo.

| Buyer question | Proof artifact | What it proves | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | Can I launch a browser voice agent quickly? | /voice, framework mic/transcript UI, /traces, /production-readiness | Browser voice route, live transcript, trace persistence, readiness gate | | Can I run phone agents without hosted orchestration? | /voice/phone/setup, /voice/phone/smoke-contract, carrier matrix JSON | Carrier setup instructions, webhook/stream URLs, smoke proof, lifecycle traces | | Can I debug a bad call like a hosted call log? | /voice-operations/:sessionId, /voice-operations/:sessionId/incident.md | Transcript, trace timeline, provider decisions, tools, handoffs, reviews, tasks, audit, deliveries | | Can I test before production traffic? | /voice/simulations, tool contracts, outcome contracts, workflow contracts | Scenario/eval proof, tool idempotency/retry proof, business outcome proof | | Can I prove provider fallback and latency? | provider contract matrix, provider status UI, /turn-latency, /live-latency | Provider choice, fallback behavior, server turn timing, browser p50/p95 timing | | Can operators intervene safely? | live-ops routes, action center, ops action audit routes, operations record | Pause/resume/takeover, injected instructions, operator action audit trail | | Can I run outbound campaigns? | /voice/campaigns, /voice/campaigns/observability, /api/voice/campaigns/readiness-proof | Recipient import evidence, consent/dedupe checks, scheduling policy, worker-safe attempts | | Can I handle post-call workflow? | createVoicePostCallAnalysisRoutes(...), reviews, tasks, integration events, outcome contracts, operations record | Extracted-field proof, task creation, webhook/sink delivery, matched session proof | | Can I keep data in my infrastructure? | /data-control, /data-control/audit.md, retention dry-run/apply routes | Customer-owned storage, redaction, audit export, guarded deletion, zero-retention planning | | Can I prove release readiness? | /production-readiness, /ops-recovery, delivery runtime, readiness profiles | Deploy gates for session health, audits, delivery queues, provider/campaign/phone proof |

For a demo, the fastest convincing path is:

  1. Start a browser or phone session.
  2. Open /voice-operations/:sessionId for the session.
  3. Open /production-readiness and follow any linked proof surface.
  4. Open /voice/simulations or the relevant tool/outcome contract route.
  5. Open /data-control to show where retention, redaction, and audit export live.

If those five surfaces are green and linked, the buyer can see the core difference from Vapi-style hosted orchestration: the operational proof lives inside the app, not in a vendor dashboard.

Post-Call Analysis Proof

Use createVoicePostCallAnalysisRoutes(...) when the hosted-platform feature you need is call analysis plus follow-up proof. It validates that required extracted fields exist, expected ops tasks were created, integration/webhook events delivered, and the report links back to /voice-operations/:sessionId.

import { createVoicePostCallAnalysisRoutes } from "@absolutejs/voice";

app.use(
  createVoicePostCallAnalysisRoutes({
    path: "/api/voice/post-call-analysis",
    operationRecordBasePath: "/voice-operations/:sessionId",
    reviews: runtime.reviews,
    tasks: runtime.tasks,
    integrationEvents: runtime.events,
    source: ({ reviewId, sessionId }) => ({
      reviewId,
      sessionId,
      // Use your own extractor output here, for example fields persisted from an LLM/tool result.
      extractedFields: loadExtractedPostCallFields(reviewId ?? sessionId),
    }),
    fields: [
      { path: "review.postCall.target", label: "customer target" },
      { path: "customerId" },
      { path: "category" },
    ],
    requiredTaskKinds: ["support-triage"],
    requireDeliveredIntegrationEvent: true,
  }),
);

Guardrails

Use createVoiceGuardrailRuntime(...) when you need code-owned live enforcement for what an agent may say, what tool payloads may contain, or which transcript content should warn/redact before downstream workflow. Use createVoiceGuardrailRoutes(...) beside it when you also want JSON/Markdown proof. The primitive does not force a moderation vendor or hosted dashboard; it emits assistant.guardrail trace events from the runtime and route surfaces.

import {
  createVoiceGuardrailRuntime,
  createVoiceGuardrailRoutes,
  voiceGuardrailPolicyPresets,
} from "@absolutejs/voice";

const guardrails = createVoiceGuardrailRuntime({
  blockResult: ({ decision }) => ({
    assistantText: "I need to route this to a human specialist.",
    escalate: {
      reason: `guardrail-blocked-${decision.stage}`,
    },
  }),
  policies: [voiceGuardrailPolicyPresets.supportSafeDefaults],
  trace: runtime.traces,
});

const assistant = createVoiceAssistant({
  guardrails: guardrails.assistantGuardrails,
  id: "support",
  model,
  tools: guardrails.wrapTools([lookupCustomerTool, createTicketTool]),
});

app.use(
  createVoiceGuardrailRoutes({
    path: "/api/voice/guardrails",
    policies: [voiceGuardrailPolicyPresets.supportSafeDefaults],
    trace: runtime.traces,
  }),
);

Use-Case Recipe: Support Triage

Use this path when you want a Vapi-style support assistant that can answer web or phone calls, look up customer context, route billing issues to a specialist, create follow-up work, and leave one debuggable call record. It is a recipe over primitives, not a support app kit.

The production shape is:

  1. Persist sessions, traces, reviews, tasks, integration events, and audit events in app-owned runtime storage.
  2. Define server-side tools with idempotency and contract proof.
  3. Compose support and billing specialists with createVoiceAgentSquad(...).
  4. Mount a browser route with voice(...) and optionally a phone route with createVoicePhoneAgent(...).
  5. Add outcome, readiness, simulation, audit, and operations-record routes so every failed proof links back to the call.
import { Elysia } from "elysia";
import {
  createVoiceAgent,
  createVoiceAgentSquad,
  createVoiceAgentTool,
  createVoiceFileRuntimeStorage,
  createVoiceOperationsRecordRoutes,
  createVoiceProductionReadinessRoutes,
  createVoiceSimulationSuiteRoutes,
  createVoiceToolContractRoutes,
  createVoiceToolRuntimeContractDefaults,
  resolveVoiceOutcomeRecipe,
  voice,
} from "@absolutejs/voice";
import { deepgram } from "@absolutejs/voice-deepgram";

const runtime = createVoiceFileRuntimeStorage({
  directory: ".voice-runtime/support-triage",
});

const lookupCustomer = createVoiceAgentTool({
  name: "lookup_customer",
  description: "Look up a customer and their open support state.",
  parameters: {
    type: "object",
    properties: {
      customerId: { type: "string" },
    },
    required: ["customerId"],
  },
  execute: async ({ args }) => ({
    customerId: args.customerId,
    plan: "business",
    openTickets: 1,
    status: "active",
  }),
});

const support = createVoiceAgent({
  id: "support",
  system:
    "Triage the caller, use tools for account context, and hand billing questions to billing.",
  tools: [lookupCustomer],
  trace: runtime.traces,
  model: {
    async generate({ messages, tools }) {
      const latest = messages.at(-1)?.content.toLowerCase() ?? "";
      if (latest.includes("billing")) {
        return {
          assistantText: "I am routing you to billing with the context so far.",
          handoff: { reason: "billing-request", targetAgentId: "billing" },
        };
      }

      return {
        assistantText: `I can help with that. I can also use ${tools.map((tool) => tool.name).join(", ")} when I need account context.`,
      };
    },
  },
});

const billing = createVoiceAgent({
  id: "billing",
  system: "Handle billing questions and escalate refund or cancellation risk.",
  trace: runtime.traces,
  model: {
    async generate() {
      return {
        assistantText:
          "I can help with billing. I have the handoff context from support.",
      };
    },
  },
});

const supportDesk = createVoiceAgentSquad({
  id: "support-desk",
  defaultAgentId: "support",
  agents: [support, billing],
  trace: runtime.traces,
  handoffPolicy: ({ handoff }) =>
    handoff.targetAgentId === "billing"
      ? {
          summary:
            "Billing specialist receives the support summary and current caller intent.",
          metadata: { queue: "billing" },
        }
      : {
          allow: false,
          reason: "Only billing handoffs are approved in this recipe.",
          escalate: { reason: "unsupported-specialist" },
        },
});

const toolContractDefinitions = [
  {
    id: "lookup-customer-contract",
    label: "Lookup customer returns support state",
    tool: lookupCustomer,
    cases: [
      {
        id: "active-business-customer",
        args: { customerId: "cus_123" },
        expect: {
          expectedResult: {
            customerId: "cus_123",
            openTickets: 1,
            plan: "business",
            status: "active",
          },
          expectStatus: "ok",
        },
      },
    ],
    defaultRuntime: createVoiceToolRuntimeContractDefaults(),
  },
];

const app = new Elysia()
  .use(
    voice({
      path: "/voice/support",
      preset: "reliability",
      session: runtime.session,
      stt: deepgram({
        apiKey: process.env.DEEPGRAM_API_KEY!,
        model: "flux-general-en",
      }),
      trace: runtime.traces,
      onTurn: supportDesk.onTurn,
      onComplete: async () => {},
      ops: {
        ...resolveVoiceOutcomeRecipe("support-triage", {
          assignee: "support-oncall",
          queue: "support-triage",
        }),
        events: runtime.events,
        reviews: runtime.reviews,
        tasks: runtime.tasks,
      },
    }),
  )
  .use(
    createVoiceToolContractRoutes({
      contracts: toolContractDefinitions,
      htmlPath: "/voice/support/tool-contracts",
      path: "/api/voice/support/tool-contracts",
    }),
  )
  .use(
    createVoiceSimulationSuiteRoutes({
      htmlPath: "/voice/support/simulations",
      path: "/api/voice/support/simulations",
      tools: toolContractDefinitions,
      operationsRecordHref: "/voice-operations/:sessionId",
    }),
  )
  .use(
    createVoiceOperationsRecordRoutes({
      htmlPath: "/voice-operations/:sessionId",
      path: "/api/voice-operations/:sessionId",
      store: runtime.traces,
      reviews: runtime.reviews,
      tasks: runtime.tasks,
      integrationEvents: runtime.events,
    }),
  )
  .use(
    createVoiceProductionReadinessRoutes({
      htmlPath: "/production-readiness",
      path: "/api/production-readiness",
      store: runtime.traces,
      links: {
        operationsRecords: "/voice-operations/:sessionId",
        simulations: "/voice/support/simulations",
      },
    }),
  );

The demo UI should show a mic button, transcript, current specialist badge, readiness link, simulation link, and operations-record link. Use the framework helpers for that surface instead of embedding a dashboard: VoiceAgentSquadStatus in React, useVoiceAgentSquadStatus(...) in Vue, createVoiceAgentSquadStatus(...) in Svelte, VoiceAgentSquadStatusService in Angular, or <absolute-voice-agent-squad-status> for HTML/HTMX.

This recipe covers the hosted-platform expectations that matter for support triage: assistant entrypoint, business tools, specialist handoff, post-call task creation, simulation proof, tool contract proof, production readiness, audit-compatible runtime storage, and a call-log replacement at /voice-operations/:sessionId.

Use-Case Recipe: Appointment Scheduling

Use this path when the assistant needs to check availability, book a slot, create a confirmation task, and prove the post-call workflow before production traffic. This is the self-hosted version of a hosted scheduling agent: your app owns the calendar tool, booking policy, storage, follow-up tasks, and call evidence.

The production shape is:

  1. Persist sessions, traces, reviews, tasks, integration events, and audit events in app-owned runtime storage.
  2. Define check_availability and book_appointment as server-side tools with deterministic tool contracts.
  3. Use resolveVoiceOutcomeRecipe('appointment-booking') so completed calls create appointment-confirmation work.
  4. Add an outcome contract that requires a completed session, review, task, and integration events.
  5. Mount simulation, outcome-contract, readiness, and operations-record routes so scheduling regressions fail before live calls.
import { Elysia } from "elysia";
import {
  createVoiceAgent,
  createVoiceAgentTool,
  createVoiceFileRuntimeStorage,
  createVoiceOperationsRecordRoutes,
  createVoiceOutcomeContractRoutes,
  createVoiceProductionReadinessRoutes,
  createVoiceSimulationSuiteRoutes,
  createVoiceToolContractRoutes,
  createVoiceToolRuntimeContractDefaults,
  resolveVoiceOutcomeRecipe,
  voice,
} from "@absolutejs/voice";
import { deepgram } from "@absolutejs/voice-deepgram";

const runtime = createVoiceFileRuntimeStorage({
  directory: ".voice-runtime/appointments",
});

const checkAvailability = createVoiceAgentTool({
  name: "check_availability",
  description: "Return open appointment slots for a service and date.",
  parameters: {
    type: "object",
    properties: {
      date: { type: "string" },
      service: { type: "string" },
    },
    required: ["date", "service"],
  },
  execute: async ({ args }) => ({
    date: args.date,
    service: args.service,
    slots: ["2026-05-04T15:00:00-04:00", "2026-05-04T16:30:00-04:00"],
  }),
});

const bookAppointment = createVoiceAgentTool({
  name: "book_appointment",
  description: "Book a confirmed appointment slot.",
  parameters: {
    type: "object",
    properties: {
      customerName: { type: "string" },
      phone: { type: "string" },
      service: { type: "string" },
      startsAt: { type: "string" },
    },
    required: ["customerName", "phone", "service", "startsAt"],
  },
  execute: async ({ args }) => ({
    appointmentId: `appt_${args.startsAt}`,
    customerName: args.customerName,
    phone: args.phone,
    service: args.service,
    startsAt: args.startsAt,
    status: "confirmed",
  }),
});

const scheduler = createVoiceAgent({
  id: "scheduler",
  system:
    "Collect caller details, check availability, book an appointment, and summarize the confirmation.",
  tools: [checkAvailability, bookAppointment],
  trace: runtime.traces,
  model: {
    async generate({ tools }) {
      return {
        assistantText: `I can check times and book the appointment. Available tools: ${tools.map((tool) => tool.name).join(", ")}`,
      };
    },
  },
});

const toolContractDefinitions = [
  {
    id: "check-availability-contract",
    label: "Availability returns bookable slots",
    tool: checkAvailability,
    cases: [
      {
        id: "consultation-slots",
        args: { date: "2026-05-04", service: "consultation" },
        expect: { expectStatus: "ok" },
      },
    ],
    defaultRuntime: createVoiceToolRuntimeContractDefaults(),
  },
  {
    id: "book-appointment-contract",
    label: "Booking returns a confirmed appointment",
    tool: bookAppointment,
    cases: [
      {
        id: "confirmed-consultation",
        args: {
          customerName: "Ada Lovelace",
          phone: "+15551234567",
          service: "consultation",
          startsAt: "2026-05-04T15:00:00-04:00",
        },
        expect: {
          expectedResult: {
            appointmentId: "appt_2026-05-04T15:00:00-04:00",
            customerName: "Ada Lovelace",
            phone: "+15551234567",
            service: "consultation",
            startsAt: "2026-05-04T15:00:00-04:00",
            status: "confirmed",
          },
          expectStatus: "ok",
        },
      },
    ],
    defaultRuntime: createVoiceToolRuntimeContractDefaults(),
  },
];

const outcomeContractDefinitions = [
  {
    id: "appointment-booked",
    label: "Completed appointment call produces follow-up work",
    expectedDisposition: "completed",
    minSessions: 1,
    minTasks: 1,
    requireIntegrationEvents: [
      "call.completed",
      "review.saved",
      "task.created",
    ],
    requireReview: true,
    requireTask: true,
  },
];

const app = new Elysia()
  .use(
    voice({
      path: "/voice/appointments",
      preset: "reliability",
      session: runtime.session,
      stt: deepgram({
        apiKey: process.env.DEEPGRAM_API_KEY!,
        model: "flux-general-en",
      }),
      trace: runtime.traces,
      onTurn: scheduler.onTurn,
      onComplete: async () => {},
      ops: {
        ...resolveVoiceOutcomeRecipe("appointment-booking", {
          assignee: "scheduling-oncall",
          queue: "appointments",
        }),
        events: runtime.events,
        reviews: runtime.reviews,
        tasks: runtime.tasks,
      },
    }),
  )
  .use(
    createVoiceToolContractRoutes({
      contracts: toolContractDefinitions,
      htmlPath: "/voice/appointments/tool-contracts",
      path: "/api/voice/appointments/tool-contracts",
    }),
  )
  .use(
    createVoiceOutcomeContractRoutes({
      contracts: outcomeContractDefinitions,
      events: runtime.events,
      htmlPath: "/voice/appointments/outcome-contracts",
      operationsRecordHref: "/voice-operations/:sessionId",
      path: "/api/voice/appointments/outcome-contracts",
      reviews: runtime.reviews,
      sessions: runtime.session,
      tasks: runtime.tasks,
    }),
  )
  .use(
    createVoiceSimulationSuiteRoutes({
      htmlPath: "/voice/appointments/simulations",
      operationsRecordHref: "/voice-operations/:sessionId",
      outcomes: {
        contracts: outcomeContractDefinitions,
        events: runtime.events,
        reviews: runtime.reviews,
        sessions: runtime.session,
        tasks: runtime.tasks,
      },
      path: "/api/voice/appointments/simulations",
      tools: toolContractDefinitions,
    }),
  )
  .use(
    createVoiceOperationsRecordRoutes({
      htmlPath: "/voice-operations/:sessionId",
      integrationEvents: runtime.events,
      path: "/api/voice-operations/:sessionId",
      reviews: runtime.reviews,
      store: runtime.traces,
      tasks: runtime.tasks,
    }),
  )
  .use(
    createVoiceProductionReadinessRoutes({
      htmlPath: "/production-readiness",
      links: {
        operationsRecords: "/voice-operations/:sessionId",
        simulations: "/voice/appointments/simulations",
      },
      path: "/api/production-readiness",
      store: runtime.traces,
    }),
  );

The UI should keep the scheduling flow simple: microphone, transcript, selected slot, booking status, confirmation task link, /voice/appointments/simulations, /voice/appointments/outcome-contracts, /production-readiness, and /voice-operations/:sessionId. If the booking or confirmation proof fails, the operator should start at the outcome contract and follow the linked operations record.

This recipe covers the hosted-platform expectations that matter for appointment scheduling: scheduling tools, deterministic tool proof, post-call confirmation work, outcome validation, simulation proof, production readiness, and one call-log replacement for debugging.

Use-Case Recipe: Campaign Outreach

Use this path when you need Retell/Bland-style outbound outreach without handing recipients, consent proof, attempt policy, carrier outcomes, or debugging records to a hosted campaign dashboard. The package gives you campaign primitives; your app decides who can upload recipients, when workers run, which carrier dials, and how campaign results sync back to your product.

The production shape is:

  1. Store campaigns in app-owned storage.
  2. Import recipients with consent checks, phone validation, dedupe, variables, and rejected-row evidence.
  3. Configure campaign policy for max attempts, concurrency, attempt windows, quiet hours, rate limits, and retry backoff.
  4. Use a carrier dialer or your own dialer function, then apply Twilio/Telnyx/Plivo webhook outcomes back to attempts.
  5. Expose campaign routes, observability, readiness proof, production readiness, and operations-record links for every attempted call.
import { Elysia } from "elysia";
import {
  createVoiceCampaignRoutes,
  createVoiceFileRuntimeStorage,
  createVoiceOperationsRecordRoutes,
  createVoiceProductionReadinessRoutes,
  createVoiceReadinessProfile,
  createVoiceSQLiteCampaignStore,
  runVoiceCampaignReadinessProof,
} from "@absolutejs/voice";

const runtime = createVoiceFileRuntimeStorage({
  directory: ".voice-runtime/campaign-outreach",
});

const campaigns = createVoiceSQLiteCampaignStore({
  path: ".voice-runtime/campaigns.sqlite",
});

const app = new Elysia()
  .use(
    createVoiceCampaignRoutes({
      htmlPath: "/voice/campaigns",
      operationsRecordHref: "/voice-operations/:sessionId",
      path: "/api/voice/campaigns",
      store: campaigns,
      title: "Renewal Outreach",
    }),
  )
  .use(
    createVoiceOperationsRecordRoutes({
      htmlPath: "/voice-operations/:sessionId",
      path: "/api/voice-operations/:sessionId",
      store: runtime.traces,
    }),
  )
  .use(
    createVoiceProductionReadinessRoutes({
      ...createVoiceReadinessProfile("phone-agent", {
        campaignReadiness: () =>
          runVoiceCampaignReadinessProof({
            store: campaigns,
          }),
        explain: true,
      }),
      links: {
        campaigns: "/voice/campaigns",
        operationsRecords: "/voice-operations/:sessionId",
      },
      store: runtime.traces,
    }),
  );

await fetch("/api/voice/campaigns", {
  body: JSON.stringify({
    maxAttempts: 3,
    maxConcurrentAttempts: 10,
    name: "Renewal outreach",
    schedule: {
      attemptWindow: { startHour: 9, endHour: 17 },
      quietHours: { startHour: 12, endHour: 13 },
      rateLimit: { maxAttempts: 60, windowMs: 60_000 },
      retryPolicy: { backoffMs: [5 * 60_000, 30 * 60_000] },
    },
  }),
  headers: { "content-type": "application/json" },
  method: "POST",
});

await fetch("/api/voice/campaigns/campaign-1/recipients/import", {
  body: JSON.stringify({
    csv: `id,name,phone,consent,segment
recipient-1,Ada,+15550001001,yes,trial
recipient-2,Grace,+15550001002,true,enterprise
recipient-3,Linus,not-a-phone,yes,partner
recipient-4,Barbara,+15550001004,no,trial`,
    metadataColumns: ["segment"],
    requireConsent: true,
    variableColumns: ["segment"],
  }),
  headers: { "content-type": "application/json" },
  method: "POST",
});

await fetch("/api/voice/campaigns/campaign-1/enqueue", {
  method: "POST",
});

Use /api/voice/campaigns/campaign-1/tick for manual workers or createVoiceCampaignWorkerLoop(...) when the app should continuously drain eligible recipients. The runtime enforces the campaign policy on each tick, so parallel workers do not double-start recipients and attempts respect quiet hours, rate limits, retry backoff, and max attempts.

For production carrier dialing, pass a dialer to createVoiceCampaignRoutes(...): createVoiceTwilioCampaignDialer(...), createVoiceTelnyxCampaignDialer(...), createVoicePlivoCampaignDialer(...), or a custom dialer that starts the call and returns an external call id. Run runVoiceCampaignDialerProof(...) before live traffic to dry-run carrier request metadata and webhook outcome application.

The UI should show /voice/campaigns, /voice/campaigns/observability, /api/voice/campaigns/readiness-proof, /production-readiness, and operations-record links for recent attempts. If a recipient failed, the operator should open the campaign attempt, follow /voice-operations/:sessionId, and see the same trace/review/task/audit context used by support and phone-agent flows.

This recipe covers the hosted-platform expectations that matter for campaign outreach: recipient import evidence, consent/dedupe checks, scheduling policy, worker-safe attempts, carrier dry-run proof, webhook outcome mapping, queue observability, readiness gating, and call-log replacement links.

Use-Case Recipe: Meeting Recorder

Use this path when the product needs a browser recorder for meetings, interviews, demos, or internal calls: capture microphone audio, persist transcripts and traces, generate a post-call review, expose a replayable operations record, and keep retention/export controls inside the app. This is not a hosted meeting bot; it is a set of recorder primitives your AbsoluteJS UI can own.

The production shape is:

  1. Mount a browser voice route with persistent session, trace, review, task, and audit-capable storage.
  2. Use framework stream/controller helpers for the microphone, transcript, reconnect state, and recording status.
  3. Persist a review artifact on completion with transcript, summary, latency, outcome, and recommended follow-up.
  4. Mount trace timelines, operations records, production readiness, and data-control routes.
  5. Gate release with the meeting-recorder readiness profile so reconnect, barge-in/interruption, provider routing, latency, and session-health proof stay visible.
import { Elysia } from "elysia";
import {
  createVoiceDataControlRoutes,
  createVoiceFileRuntimeStorage,
  createVoiceOperationsRecordRoutes,
  createVoiceProductionReadinessRoutes,
  createVoiceReadinessProfile,
  createVoiceTraceTimelineRoutes,
  voice,
  voiceComplianceRedactionDefaults,
} from "@absolutejs/voice";
import { deepgram } from "@absolutejs/voice-deepgram";

const runtime = createVoiceFileRuntimeStorage({
  directory: ".voice-runtime/meeting-recorder",
});

const app = new Elysia()
  .use(
    voice({
      path: "/voice/meeting-recorder",
      preset: "reliability",
      session: runtime.session,
      stt: deepgram({
        apiKey: process.env.DEEPGRAM_API_KEY!,
        model: "flux-general-en",
      }),
      trace: runtime.traces,
      async onTurn({ turn }) {
        return {
          assistantText: "",
          metadata: {
            recorder: true,
            transcript: turn.text,
          },
        };
      },
      onComplete: async () => {},
      ops: {
        events: runtime.events,
        reviews: runtime.reviews,
        tasks: runtime.tasks,
        buildReview: ({ session }) => ({
          errors: [],
          latencyBreakdown: [],
          notes: ["Generated by the self-hosted meeting recorder path."],
          postCall: {
            label: "Meeting summary",
            recommendedAction: "Review the transcript and share action items.",
            summary: "Review transcript, decisions, and follow-up owners.",
          },
          summary: {
            outcome: "completed",
            pass: true,
            turnCount: session.turns.length,
          },
          title: `Meeting recorder review for ${session.id}`,
          timeline: [],
          transcript: {
            actual: session.turns
              .map((turn) => turn.text)
              .filter(Boolean)
              .join("\n"),
          },
        }),
      },
    }),
  )
  .use(
    createVoiceTraceTimelineRoutes({
      htmlPath: "/traces",
      path: "/api/voice-traces",
      store: runtime.traces,
    }),
  )
  .use(
    createVoiceOperationsRecordRoutes({
      htmlPath: "/voice-operations/:sessionId",
      integrationEvents: runtime.events,
      path: "/api/voice-operations/:sessionId",
      reviews: runtime.reviews,
      store: runtime.traces,
      tasks: runtime.tasks,
    }),
  )
  .use(
    createVoiceDataControlRoutes({
      ...runtime,
      audit: runtime.audit,
      auditDeliveries: runtime.auditDeliveries,
      redact: voiceComplianceRedactionDefaults,
      traceDeliveries: runtime.traceDeliveries,
    }),
  )
  .use(
    createVoiceProductionReadinessRoutes({
      ...createVoiceReadinessProfile("meeting-recorder", {
        explain: true,
      }),
      links: {
        dataControl: "/data-control",
        operationsRecords: "/voice-operations/:sessionId",
        traces: "/traces",
      },
      store: runtime.traces,
    }),
  );

The UI should show a clear recording button, elapsed time, live transcript, reconnect state, recording status, a stop/finalize action, and links to /voice-operations/:sessionId, /traces, /production-readiness, and /data-control. Use useVoiceStream(...), useVoiceController(...), createVoiceStream(...), VoiceStreamService, or the HTML/HTMX client helpers depending on the framework; the route and trace/review stores stay the same.

For customer-facing exports, use the same redaction/export primitives as support workflows: render trace Markdown or audit Markdown with voiceComplianceRedactionDefaults, then deliver it through file, webhook, or S3 delivery runtimes. For sensitive recordings, start with a retention dry run through /data-control/retention/plan before applying deletion.

This recipe covers the hosted-platform expectations that matter for meeting recorders: browser mic capture, live transcript UI, reconnect visibility, post-call review, transcript/debug record, readiness proof, customer-owned storage, retention controls, and redacted export paths.

Use-Case Recipe: Compliance-Sensitive Calls

Use this path when the voice app handles sensitive customer support, healthcare-adjacent intake, financial workflows, internal investigations, or regulated customer data. AbsoluteJS Voice can provide self-hosted controls and evidence: customer-owned storage, provider-key ownership, redaction defaults, audit trails, guarded deletion, zero-retention policy helpers, redacted exports, and deploy gates. It does not certify the app for HIPAA, SOC 2, GDPR, or any other legal regime by itself.

The production shape is:

  1. Use customer-owned runtime storage, preferably Postgres for production records and S3/webhook delivery for exported evidence.
  2. Keep provider keys in the app owner environment, not in a hosted voice dashboard.
  3. Pass audit into agents/tools/squads so provider calls, tool executions, handoffs, retention runs, and operator actions are recorded.
  4. Mount data-control routes for redacted audit export, retention dry-runs, guarded deletion, zero-retention planning, and provider-key recommendations.
  5. Make audit evidence, recent retention-policy evidence, and audit/trace delivery health part of production readiness.
import { Elysia } from "elysia";
import {
  applyVoiceDataRetentionPolicy,
  buildVoiceDataRetentionPlan,
  createVoiceAuditLogger,
  createVoiceDataControlRoutes,
  createVoiceOperationsRecordRoutes,
  createVoicePostgresRuntimeStorage,
  createVoiceProductionReadinessRoutes,
  createVoiceZeroRetentionPolicy,
  exportVoiceAuditTrail,
  renderVoiceAuditMarkdown,
  voiceComplianceRedactionDefaults,
} from "@absolutejs/voice";

const runtime = createVoicePostgresRuntimeStorage({
  connectionString: process.env.DATABASE_URL!,
  schemaName: "voice_ops",
  tablePrefix: "sensitive",
});

const audit = createVoiceAuditLogger(runtime.audit);

const app = new Elysia()
  .use(
    createVoiceDataControlRoutes({
      ...runtime,
      audit: runtime.audit,
      auditDeliveries: runtime.auditDeliveries,
      path: "/data-control",
      redact: voiceComplianceRedactionDefaults,
      title: "Sensitive Voice Data Control",
      traceDeliveries: runtime.traceDeliveries,
    }),
  )
  .use(
    createVoiceOperationsRecordRoutes({
      audit: runtime.audit,
      htmlPath: "/voice-operations/:sessionId",
      integrationEvents: runtime.events,
      path: "/api/voice-operations/:sessionId",
      reviews: runtime.reviews,
      store: runtime.traces,
      tasks: runtime.tasks,
    }),
  )
  .use(
    createVoiceProductionReadinessRoutes({
      audit: {
        require: [
          { type: "provider.call" },
          { type: "operator.action" },
          { type: "retention.policy", maxAgeMs: 7 * 24 * 60 * 60 * 1000 },
        ],
        store: runtime.audit,
      },
      auditDeliveries: runtime.auditDeliveries,
      links: {
        audit: "/audit",
        auditDeliveries: "/audit/deliveries",
        dataControl: "/data-control",
        operationsRecords: "/voice-operations/:sessionId",
        traceDeliveries: "/traces/deliveries",
      },
      store: runtime.traces,
      traceDeliveries: runtime.traceDeliveries,
    }),
  );

await audit.operatorAction({
  action: "retention.policy.reviewed",
  actor: { id: "ops-admin", type: "operator" },
  outcome: "ok",
  resource: { id: "zero-retention-policy", type: "voice.retention" },
});

const zeroRetentionPolicy = createVoiceZeroRetentionPolicy({
  ...runtime,
  audit: runtime.audit,
  auditDeliveries: runtime.auditDeliveries,
  traceDeliveries: runtime.traceDeliveries,
});

const retentionPlan = await buildVoiceDataRetentionPlan(zeroRetentionPolicy);

if (retentionPlan.deletedCount > 0) {
  await applyVoiceDataRetentionPolicy({
    ...zeroRetentionPolicy,
    dryRun: false,
  });
}

const auditExport = await exportVoiceAuditTrail({
  redact: voiceComplianceRedactionDefaults,
  store: runtime.audit,
});

const redactedAuditMarkdown = renderVoiceAuditMarkdown(auditExport.events, {
  title: "Sensitive Voice Audit Export",
});

For the actual agent or squad, pass the same audit logger into createVoiceAgent(...), createVoiceAgentSquad(...), or createVoiceAssistant(...) with explicit auditProvider and auditModel labels. That makes provider usage and tool execution visible in /data-control/audit.md, /audit, readiness checks, and operations records.

The UI should expose /data-control, /data-control.json, /data-control/audit.md, /data-control/retention/plan, /production-readiness, and /voice-operations/:sessionId. Destructive retention application should remain a server-side operator action that first reviews the dry-run plan and then posts confirm: "apply-retention-policy".

This recipe covers the hosted-platform expectations that matter for compliance-sensitive voice apps: customer-owned records, provider-key ownership, redacted exports, audit evidence, guarded retention, zero-retention planning, deploy gates, and a clear boundary that the package supplies controls and proof artifacts, not legal certification.

How This Differs From Hosted Voice Platforms

Hosted voice-agent platforms are strongest when you want a managed dashboard, phone-number provisioning, hosted orchestration, and campaign tooling out of the box.

AbsoluteJS Voice is strongest when voice is part of your own product and you need code-owned primitives:

  • Your app stores the call data instead of a vendor dashboard being the source of truth.
  • Your app controls provider routing, fallback, retries, handoffs, and retention.
  • Your team can inspect and extend every primitive.
  • Your framework UI can render first-class voice state without iframe/dashboard handoffs.
  • Your production checks and evals can run in CI, smoke tests, or your own admin UI.

The goal is not to clone a hosted platform. The goal is to make AbsoluteJS the best place to build and operate self-hosted voice products.

Default Debug Path

Hosted platforms usually make the call log the center of debugging. AbsoluteJS Voice makes the operations record that center, while keeping the data and routes inside your app.

Mount `createVoiceOpera