Docent
Reference

Presence and voice

The fifteen presences an owner can choose, how the SDK loads one, the ten live voices, and when the guide is allowed to open a microphone.

The character your users see and what it sounds like are both chosen in the Console, per environment, so staging can try one before production does.

The presences

Fifteen, and the difference between them is worth knowing before you choose.

NameSlugKind
Dotthe defaultdrawn
Adadocent-guiderendered, animated
Dot, rendereddot-renderedrendered
Penguinpenguin-conciergerendered
Owlowl-docentrendered
Foxfox-scoutrendered
Corgicorgi-usherrendered
Catcat-curatorrendered
Otterotter-hostrendered
Bearbear-librarianrendered
Raccoonraccoon-archivistrendered
Luna mothlunamoth-guiderendered
Axolotlaxolotl-greeterrendered
Snailsnail-lamplighterrendered
Molemole-tunnellerrendered

Dot is drawn, in SVG and CSS. It tints to your brand, its pupils track the cursor, and its arm points at any angle.

The other fourteen are pre-rendered frames. Fixed colours, a facing side rather than a bearing, and no pupil tracking. Neither is better; they are different trades, and the Console's cards say which one each is making.

Loading one

The owner's choice arrives with the session mint, as a slug. Your job is one prop:

import { PRESENCE_LOADERS } from '@usedocent/mascot/library/loaders'

;<Docent
  // ...
  loadPresence={(slug) => PRESENCE_LOADERS[slug]?.() ?? Promise.reject(new Error(slug))}
/>

It is injected rather than chosen by name inside the SDK so that an app showing one presence does not ship the bytes of the other fourteen. PRESENCE_LOADERS is a chunk per presence and none is fetched until a session names one.

Omit loadPresence and every session gets Dot, whatever the Console says. A presence that fails to load is a mascot that stays Dot, not a session that never starts, and the failure arrives as an error event with the code presence_failed.

You can also override the mascot outright with the presence prop, which takes palette and mascot among other things. That wins over the owner's choice, so use it only when your app has a reason to.

Voice

Voice is off unless you ask for it:

voiceWhat happens
'off' (default)Nothing opens a microphone. Text mode is a full peer and works on its own.
'tap'Voice starts on the user's own first tap of the presence, and never before it.

Even with 'tap', the first attempt shows a consent prompt in the caption panel rather than opening anything, and the browser's own microphone permission comes after that. Declining is not an error: the guide keeps working by typing. The consent is remembered per app and per user, so it is asked once rather than on every reload.

The microphone closes after each turn, and a visible indicator is shown while it is open. The overlay's mute control reaches the real call rather than only the mascot's face.

loadAudio is what makes any of this possible. Omit it and startVoice() rejects with voice_unavailable:

loadAudio={(vendor) =>
  vendor === 'openai' ? import('@usedocent/sdk-audio-live') : import('@usedocent/sdk-audio')
}

The session grant names the vendor, so only the chunk that session was granted is ever fetched. Both are dynamic imports, so neither is in your bundle until somebody taps.

The live voices

A live session speaks in one of ten, chosen per environment in Console, Voice. The default is marin.

marin (default)cedaralloy
ashballadcoral
echosageshimmer
verse

marin and cedar are the two the vendor's own documentation recommends. The Console previews each one so you can hear it before you save.

The Telnyx catalogue is the other tab on that page and is far larger. It is filtered: voices whose synthesis would leave the vendor's own infrastructure are not offered, because that is what keeps the retention promise on the privacy page true.

The two are stored separately, because the two vendors share no voice names.

On this page