Skip to main content

Examples Companion

examples/companion is the second shipped reference app. Where Examples Web puts the client styles side by side for comparison, the companion app is shaped like a product: a voice-first stage, a companion you pick and then keep, and a relationship that persists across sessions.

You choose a companion from a small catalog before meeting her, and the choice locks until you start over. Memory is partitioned by the selected character, so each companion keeps her own isolated memory namespace.

Live demo: charivo-companion.vercel.app

What Makes It Different

It is a realtime-only stack. There is no LLM, TTS, or STT manager anywhere in the app — the OpenAI Realtime API handles audio in and out directly, so the package list is:

@charivo/core
@charivo/realtime + @charivo/avatar
@charivo/render + @charivo/render-live2d
@charivo/server (one route)

One consequence worth noting: the character's speech drives lip-sync straight from the realtime audio stream rather than through a TTS player.

API Routes

The only server route is the realtime bootstrap:

  • POST /api/realtime Uses createOpenAIRealtimeProvider from @charivo/server/openai to mint a session bootstrap for @charivo/realtime/remote. It does not forward the client's session config: after checking that transport and session are present and that session.provider is "openai", it rebuilds the config with the model, output-token cap, and transcription model pinned server side, and rejects instructions or tools past its size caps.

Session length is capped in the browser at 90 seconds in production (15 minutes in development) for the same reason: once the bootstrap returns, audio flows between the browser and OpenAI directly and the server can no longer end it.

Everything else runs in the browser.

Browser-Local Memory

The memory engine is pure TypeScript running in the browser against a localStorage-backed store — there is no server datastore. On session start the app composes instruction blocks (persona, memory facts, relationship state, situational date/time) and passes them to startSession({ instructions }). During the session it captures turns and promotes them back into the store at checkpoints and on session end, so the relationship carries into the next visit.

Because the store is per-browser, memory never leaves the visitor's own machine — which is what makes the demo deployable without a database. The scope is the browser profile and origin, not a person: the app uses a fixed local user id, so anyone sharing that profile shares the memory. It also means memory does not sync across devices, and clearing site data resets it.

None of that is a statement about deployment safety: /api/realtime mints sessions with your server's OpenAI key and has no authentication or rate limiting, so any public deployment still needs its own abuse controls. A real product would add auth and a user-scoped server datastore.

One limitation worth knowing before you read the code: the MVP's fact extractor is a no-op and session summaries are always null. Promotion still advances the session record and the relationship state (session count, rapport, last seen), but it does not yet mine content facts from the conversation — those are managed by hand through the Settings panel until a real extractor lands.

The memory pipeline lives under examples/companion/src/memory/ and is unit tested there. Read the app README for the full read/inject and write/promote flow — it is the source of truth for the memory design, and this guide deliberately does not restate it.

Avatar Control And Gaze

Motions and gaze are driven by the model through @charivo/avatar (createAvatarControlTools, createAvatarResultProjector, buildAvatarControlInstructions), bridged back into the render manager over the shared Charivo event bus.

The app also ships optional webcam face tracking, off by default, that runs on-device and feeds the avatar's gaze. It is a useful reference for the gaze arbitration model described in Rendering — Gaze Drivers.

Files To Read

Run It

From the repository root, with OPENAI_API_KEY set in examples/companion/.env.local:

pnpm install
pnpm build
pnpm --filter ./examples/companion dev

Then open http://localhost:3001.

When To Use It

Use examples/companion when you want:

  • a realtime-only integration with no TTS/STT stack
  • a reference for driving avatar motion and gaze from model tool calls
  • a worked example of persisting character memory across sessions
  • an app shell shaped like a product rather than a settings panel

Use Examples Web instead when you want to compare client styles (remote vs browser-direct vs browser-native) or need LLM/TTS/STT wiring.