Skip to main content

Architecture

Charivo keeps a strict package boundary between orchestration, managers, browser runtimes, and server providers. The rest of this guide walks through that split and how to navigate the repo.

Dependency Direction​

The dependency flow is intentionally simple:

App
-> @charivo/core
-> modality packages
-> browser implementations via subpath exports
-> optional server providers behind API routes

In practice:

  • @charivo/core owns the Charivo orchestrator, shared types, and event bus
  • modality packages own stateful feature logic
  • browser runtime packages live on subpaths such as @charivo/tts/remote or @charivo/realtime/openai
  • server providers live on @charivo/server/* subpaths, which keeps credential use and the realtime session bootstrap on the server side — see Server Providers for why most of them are implemented in the modality packages

Lower layers should not take on orchestration concerns from higher layers.

Package Roles​

Core​

  • @charivo/core: orchestrator, shared contracts, event bus

Manager Packages​

  • @charivo/llm: conversation lifecycle and message orchestration
  • @charivo/tts: synthesis flow and lip-sync coordination
  • @charivo/stt: recording and transcription lifecycle
  • @charivo/realtime: realtime session state, tools, and in-place session updates
  • @charivo/render: renderer lifecycle, mouse tracking, and visual bridge logic

Each manager wraps a runtime implementation behind a stable manager-facing API.

Avatar Tool Extensions​

  • @charivo/avatar (optional): catalog-constrained avatar expression/motion/gaze tools, instructions, and a result projector, built on @charivo/core's neutral ToolRegistration/ToolResultProjector contracts. It bridges tool results into both @charivo/realtime and @charivo/llm, adjacent to but separate from either manager package — see Avatar Control. (Formerly published as @charivo/realtime-avatar.)

Public API Contract​

Charivo's public surface is factory-first: components are consumed through interfaces, not concrete classes.

  • Pluggable managers, clients, players, transcribers, and renderers are created via create* factories (e.g. createRemoteLLMClient, createOpenAITTSPlayer) and consumed through their interfaces (LLMManager, TTSPlayer, RenderManager, ...). Concrete implementation classes such as RemoteLLMClient, OpenAITTSPlayer, ConsoleRenderer, Live2DRendererImpl, and the *Manager implementations are never exported from a public entry point.
  • Charivo is the single top-level orchestrator: it owns the instance lifecycle — wiring managers together via attach*, holding the event bus, and owning dispose(). createCharivo(options) is the factory-first way in, attaching the managers you supply and applying the character last; it returns an ordinary instance, so late attach* calls work on it just as they do on a hand-constructed one. The class itself remains exported — a partial exception to the rule above — because it predates the factory and direct construction stays supported, not because the factory is limited to up-front wiring.
  • CharivoError and its subclasses (CharivoStateError, CharivoTimeoutError, CharivoTransportError, CharivoProviderError, CharivoDisposeError) are a second intentional exception: an error taxonomy to check with isCharivoError/error.code, not a constructible component.
  • The server provider classes (OpenAILLMProvider, OpenClawLLMProvider, GeminiLLMProvider, OpenAITTSProvider, GeminiTTSProvider, OpenAISTTProvider, GeminiSTTProvider, OpenAIRealtimeProvider, GeminiRealtimeProvider) are a third intentional exception, exported as concrete classes alongside their create*Provider factories: consumers rely on instanceof checks and on provider methods outside the narrow core interface (e.g. OpenAITTSProvider.setModel, absent from TTSProvider) — a contract packages/server/__tests__/barrel.test.ts pins. Separately, and not a "Node-only" restriction, the factories are also callable directly from a browser via dangerouslyAllowBrowser, letting a local app or test skip standing up a server. @charivo/server/* exports all nine for server use; the two realtime providers are implemented directly there instead of in a modality package (see Server Providers below).
  • Subclassing Charivo is unsupported; extend it through composition (attach managers, listen to events) rather than inheritance.
  • The concrete event bus implementation is internal; CharivoEventBus and CharivoEventEmitter are the contract other code depends on.

Browser Runtime Packages​

For most apps, start with remote packages and only use browser-direct packages when you explicitly want local development shortcuts or zero-server behavior.

LLM​

  • @charivo/llm/remote
  • @charivo/llm/openai — also exports the createOpenAILLMProvider server-side provider
  • @charivo/llm/openclaw — also exports the createOpenClawLLMProvider server-side provider
  • @charivo/llm/gemini — also exports the createGeminiLLMProvider server-side provider
  • @charivo/llm/stub

TTS​

  • @charivo/tts/remote
  • @charivo/tts/openai — also exports the createOpenAITTSProvider server-side provider
  • @charivo/tts/gemini — also exports the createGeminiTTSProvider server-side provider
  • @charivo/tts/web

STT​

  • @charivo/stt/remote
  • @charivo/stt/openai — also exports the createOpenAISTTProvider server-side provider
  • @charivo/stt/gemini — also exports the createGeminiSTTProvider server-side provider
  • @charivo/stt/web
  • @charivo/stt/openai-realtime — streaming transcriber; the app supplies the bootstrap function that owns credentials
  • @charivo/stt/gemini-live — streaming transcriber over the Gemini Live websocket; the app supplies the bootstrap function that owns credentials

Realtime​

  • @charivo/realtime/remote
  • @charivo/realtime/openai-agents
  • @charivo/realtime/openai
  • @charivo/realtime/gemini

Rendering​

  • @charivo/render-live2d
  • @charivo/render/stub

Server Providers​

Provider packages belong behind your own API routes:

  • @charivo/server/openai
  • @charivo/server/openclaw
  • @charivo/server/gemini

The LLM/TTS/STT providers under these subpaths are implemented in the matching modality package (@charivo/llm/openai, @charivo/llm/openclaw, @charivo/llm/gemini, @charivo/tts/openai, @charivo/tts/gemini, @charivo/stt/openai, @charivo/stt/gemini) and re-exported here; only the realtime providers — OpenAI (ephemeral client-secret minting) and Gemini Live (single-use ephemeral tokens carrying the whole session config) — are implemented directly in @charivo/server.

That placement is deliberate, not an inverted layering: each of those modality subpaths also ships a dev/testing browser client that wraps the same provider instead of duplicating an HTTP client, so the provider has to live where that client can reuse it — which is why @charivo/server/* is mostly re-exports. Fresh reviews have misread this twice; it has been examined and upheld both times, so treat it as settled unless the dev-client path itself is being removed.

Event Wiring​

Charivo intentionally keeps two event contracts:

  • RenderManager uses setEventBus(...)
  • TTSManager, STTManager, RealtimeManager, and LLMManager use setEventEmitter(...) (optional on LLMManager; wired only when it is attached via Charivo.attachLLM(...))

This is deliberate.

  • RenderManager subscribes to upstream events such as tts:lipsync:update, avatar:expression, avatar:motion, and avatar:gaze
  • TTS, STT, realtime, and LLM managers mainly publish events back into core — for LLMManager that means avatar:*-style events from tool result projectors, not conversation events

Do not normalize these contracts unless the public manager API is being redesigned.

For production browser apps:

  • LLM: @charivo/llm + @charivo/llm/remote + @charivo/server/openai or @charivo/server/gemini
  • TTS: @charivo/tts + @charivo/tts/remote + @charivo/server/openai or @charivo/server/gemini
  • STT: @charivo/stt + @charivo/stt/remote + @charivo/server/openai or @charivo/server/gemini
  • Realtime: @charivo/realtime + @charivo/realtime/remote + @charivo/server/openai or @charivo/server/gemini

Direct browser vendor packages are mainly for development, demos, and testing.

Browser-native packages are a separate option:

  • @charivo/tts/web
  • @charivo/stt/web

Use them when you want browser-only speech features and can accept browser support differences.

Repository Layout​

packages/
core/
avatar/
llm/
tts/
stt/
realtime/
render/
render-live2d/
render-iki/
server/
examples/
web/
companion/
iki-test/
docs/
guide/
history/
images/
docs-site/
scripts/
  • root README.md: project overview and top-level entry points
  • packages/: library packages, publishable except private development adapters such as @charivo/render-iki
  • examples/: the two documented reference apps — web puts the LLM/TTS/STT client styles side by side, companion is realtime-only with browser-local character memory — plus iki-test, a private harness for the iki renderer adapter
  • docs-site/: the site that publishes docs/guide/
  • docs/guide/: integration guides and package selection help
  • docs/history/: migrations, upgrade notes, and implementation history
  • scripts/: repo tooling

TypeScript Module Resolution​

Charivo's public subpath imports require a module resolution mode that honors package exports. Use one of:

  • moduleResolution: "bundler"
  • moduleResolution: "node16"
  • moduleResolution: "nodenext"