This guide is the architectural and operational reference for Project AIRI — an open-source, self-hostable AI companion ("cyber living being" / AI VTuber) built by @moeru-ai. It is written for the people who run, deploy, extend, and contribute to AIRI: operators, backend engineers, frontend/desktop developers, plugin authors, and architects.
AIRI is not a single application. It is a large pnpm monorepo that ships several products sharing one core: a browser app, an Electron desktop companion, a Capacitor mobile app, a cloud backend, a fleet of chat-platform bot services, and a plugin ecosystem. The most important mental model for working on AIRI is therefore which surface am I in, and which shared package owns this logic?
Note
This guide documents commit
19e5020(release linev0.10.2). AIRI evolves quickly; treat version-specific details (dependency versions, file paths) as a snapshot. The always-current version lives at moeru-ai/airi.
Table of Contents
- Executive Summary
- System Architecture
- Monorepo Layout
- The Three Client Surfaces
- Shared Packages: the Stage Stack
- The Cloud Backend (
apps/server) - The Server Channel & SDK
- The Plugin System
- Bot Services
- Rendering, Audio & Memory Subsystems
- Requirements & Toolchain
- Development Workflow
- Building & Deployment
- Configuration Reference
- Security Analysis
- Observability & Operations
- Code Quality & Conventions
- Testing Strategy
- Troubleshooting (Operators & Developers)
- Potential Improvements
- Quick Reference
1. Executive Summary
| Aspect | Detail |
|---|---|
| What it is | Self-hostable AI companion platform — animated character (Live2D/VRM) driven by user-supplied LLM/TTS/STT/vision providers. |
| Repository | moeru-ai/airi, ~3,900 files, monorepo. |
| License | MIT. |
| Primary language | TypeScript (Vue 3 SFCs), with Kotlin/Swift (mobile shells) and GDScript (experimental Godot stage). |
| Package manager | pnpm (workspaces + catalog), via Corepack. |
| Build orchestration | Turborepo (turbo.json), Vite / electron-vite / VitePress, tsdown. |
| Client surfaces | Web (stage-web), Desktop (stage-tamagotchi, Electron), Mobile (stage-pocket, Capacitor). |
| Backend | apps/server — Hono + Drizzle/Postgres + Redis + better-auth + Stripe; deployed on Railway. |
| AI integration | xsai multi-provider client; 40+ LLM providers; local inference via candle (CUDA/Metal). |
| Key cross-cutting libs | @moeru/eventa (IPC/RPC), injeca (DI), valibot (validation), UnoCSS + reka-ui (UI), @moeru/std (utilities). |
Architectural thesis. AIRI is "web-first but not web-limited." Everything is built on web platform APIs (WebGPU, WebAudio, Web Workers, WebAssembly, WebSocket) so a single codebase runs in browsers, Electron, and mobile webviews. The desktop surface then escapes the browser sandbox to add native GPU inference, OS-level screen capture, and raw TCP — capabilities (Discord voice, Minecraft, local models) that browsers forbid.
Operational thesis. Two very different things can be "deployed":
- The client apps are shipped as static web bundles / signed desktop installers / mobile packages. They hold user config locally and need no server to function with cloud AI providers.
- The cloud backend (
apps/server) is an optional, separately deployed service that powers accounts, the hosted web experience, billing ("Flux"), and an LLM gateway. Self-hosters who only want the companion do not need it.
2. System Architecture
┌─────────────────────────────────────────────┐
│ AI PROVIDERS (BYO key) │
│ OpenAI · Anthropic · Gemini · DeepSeek · │
│ Ollama/vLLM (local) · ElevenLabs · Azure · │
│ Kokoro (local TTS) · image-gen services │
└───────────────▲─────────────────────────────┘
│ xsai (multi-provider client)
│
┌──────────────┐ ┌──────────────┐ │ ┌──────────────────────────┐
│ stage-web │ │stage-tamagotchi│ │ │ stage-pocket │
│ (browser) │ │ (Electron) │ │ │ (Capacitor iOS/Android)│
│ │ │ main │ renderer│ │ │ │
└──────┬───────┘ └───┬──────┬─────┘ │ └────────────┬─────────────┘
│ │ │ │ │
│ packages/stage-ui (shared components, stores, composables) │
│ packages/stage-shared · stage-ui-{live2d,spine,three} · ui │
└───────────────┴──────┴────────┴─────────────────┘
│ WebSocket "server channel"
│ (@proj-airi/server-sdk ⇄ server-runtime)
┌────────────────┼─────────────────────────┐
│ │ │
┌─────────▼───────┐ ┌─────▼──────────┐ ┌─────────▼──────────┐
│ services/* │ │ plugins/* │ │ apps/server │
│ discord/telegram│ │ claude-code │ │ Hono + Drizzle │
│ minecraft/x/... │ │ home-assistant │ │ Postgres + Redis │
│ computer-use-mcp│ │ web-extension │ │ better-auth+Stripe│
└─────────────────┘ └────────────────┘ └────────────────────┘
Data-flow summary.
- The user configures providers and a character in a client; that config lives locally (Pinia stores + browser storage / app data dir).
- The client calls AI providers directly via
xsaifor chat, voice, and vision. - Optional integrations (bots, plugins, other surfaces) connect to a running AIRI instance over the WebSocket server channel (
server-runtime⇄server-sdk). - The optional cloud backend provides accounts, the hosted web app's persistence, billing, and a server-side LLM gateway. Postgres is the single source of truth for account/billing state; Redis is cache/KV/pub-sub only.
3. Monorepo Layout
AIRI uses pnpm workspaces. The top-level directories define the architecture.
| Directory | Contains | Role |
|---|---|---|
apps/ |
Deployable applications | The products you ship/run. |
packages/ |
Shared libraries | Reusable code consumed by apps, services, and plugins. |
services/ |
Bot/backend services | Chat-platform integrations and the computer-use bridge. |
plugins/ |
First-party plugins | Optional capability extensions loaded by the plugin host. |
integrations/ |
External-tool integrations | VS Code extension + host plugin. |
engines/ |
Rendering engines | Experimental stage-tamagotchi-godot (GDScript + VRM/MToon). |
docs/ |
VitePress documentation site | Public docs + multilingual content + blog/DevLogs. |
crates/ |
Legacy Rust (Tauri) | The old desktop shell; superseded by Electron. |
nix/, patches/, scripts/, bucket/ |
Tooling | Nix packaging, pnpm patches, build scripts, Scoop manifest. |
Key apps/
| App | Package name | Description |
|---|---|---|
apps/stage-web |
@proj-airi/stage-web |
Browser app served at airi.moeru.ai. |
apps/stage-tamagotchi |
@proj-airi/stage-tamagotchi |
Electron desktop companion (main + renderer + preload). |
apps/stage-pocket |
@proj-airi/stage-pocket |
Capacitor mobile app (iOS Swift / Android Kotlin shells). |
apps/server |
@proj-airi/server |
Cloud backend (accounts, billing, gateway). |
apps/ui-admin |
@proj-airi/ui-admin |
Admin SPA for the backend. |
apps/ui-server-auth |
@proj-airi/ui-server-auth |
Auth/login SPA served by the backend. |
apps/component-calling |
— | Component-calling playground (e.g. weather widget demo). |
Key packages/
stage-ui (the heart — shared components, stores, composables), stage-shared, stage-pages, stage-ui-live2d, stage-ui-spine, stage-ui-three, stage-layouts, ui (reka-ui primitives), i18n, audio / audio-pipelines-transcribe / pipelines-audio, core-agent, core-character, memory-pgvector, plugin-sdk / plugin-protocol / plugin-sdk-tamagotchi, server-runtime / server-sdk / server-shared / server-schema / server-sdk-shared, model-driver-lipsync / model-driver-mediapipe, electron-eventa / electron-vueuse / electron-screen-capture, cap-vite, airi-screenshot, stream-kit, unocss-preset-fonts, plus several font packages.
Note
Many sub-projects spun out into the @proj-airi organization (the
xsaiLLM client, DuckDB-WASM drivers, RAG/memory experiments, Live2D utilities). When a capability seems to "come from nowhere," it is often one of these external packages.
4. The Three Client Surfaces
All three surfaces are Vue 3 + Vite + TypeScript + Pinia + VueUse + UnoCSS, sharing packages/stage-ui. They differ in their host runtime and entry points.
4.1 Web (apps/stage-web)
- Pure browser SPA; Vue Router; router config and build via
vite.config.ts. - Pages under
src/pages; settings layout atsrc/layouts/settings.vue. - PWA-installable; the canonical hosted deployment.
- Backend is optional and WIP for the self-host case; the hosted version uses
apps/server.
4.2 Desktop (apps/stage-tamagotchi, Electron)
The most architecturally involved surface. Built with electron-vite (electron.vite.config.ts) and split into the standard Electron process model:
| Process | Path | Responsibility |
|---|---|---|
| Main | src/main/ |
App lifecycle, windows, tray, native services. Composition root at src/main/index.ts (uses injeca DI). |
| Preload | src/preload/ |
Safe bridge exposing main APIs to the renderer. |
| Renderer | src/renderer/ |
The Vue UI (pages, stores, components, devtools). |
| Shared | src/shared/ |
Eventa IPC/RPC contracts shared across processes. |
Notable main-process services live under src/main/services/airi/: a local HTTP server (with auth, static-asset serving, server-manager), an MCP servers manager, the plugin host, onboarding, channel-server and godot-stage bridges, and widget/provider services (e.g. image-gen providers ComfyUI / Nano Banana / Replicate). Each window (main, chat, settings, dashboard, caption, desktop-overlay, inlay, about, onboarding, widgets, devtools, beat-sync, notice) has its own module under src/main/windows/ with an Electron RPC channel.
Important
The desktop app runs a local HTTP/WebSocket server to host the server channel and serve assets. This is the primary attack surface to reason about when binding it to anything other than localhost. See Security.
4.3 Mobile (apps/stage-pocket, Capacitor)
- Vue 3 + Capacitor; native iOS (Swift,
ios/App) and Android (Kotlin,android/app) shells. - Build glue lives in
packages/cap-vite(a Vite wrapper that drives Capacitor sync; configurable viaCAP_VITE_*andVITE_CAP_SYNC_IOS_AFTER_BUILDenv vars). - Connects to a desktop "server channel" over (secure) WebSocket for shared-brain use.
5. Shared Packages: the Stage Stack
packages/stage-ui is where most product behavior actually lives — both stage-web and stage-tamagotchi are thin shells over it.
Area (under stage-ui/src) |
What it owns |
|---|---|
stores/providers.ts, stores/providers/ |
Standardized provider definitions (the 40+ LLM/TTS/STT services and their config shape). |
stores/modules/ |
AIRI orchestration modules — the building blocks behind hearing, vision, etc. |
stores/settings/, stores/character/, stores/chat/, stores/ai/models/ |
Pinia state for settings, the active character, chat sessions, and model catalogs. |
composables/ |
Business-oriented Vue composables (audio, vision, etc.). |
components/, components/scenarios/ |
UI components; scenarios/ holds page/use-case-specific pieces (chat, connection, providers, onboarding dialogs…). |
libs/providers/providers/* |
Per-provider client adapters (anthropic, openai, ollama, bedrock, …). |
services/ |
Front-end service clients (e.g. characters.ts, inference-service-providers.ts) that talk to the backend. |
libs/workers/ |
Web Worker code (e.g. AutomaticSpeechRecognitionPipeline for client-side STT). |
Supporting packages:
packages/ui— standardized primitives (inputs, buttons, layout) built on reka-ui; minimal business logic. Its API is documented indocs/ai/context/ui-components.md.packages/stage-shared— logic shared across stage-ui, stage-ui-three, and the apps (auth, beat-sync, versioned local-storage composables, global shortcuts, WebGPU helpers).packages/stage-pages— shared page bases (settings, devtools).packages/i18n— all translations (managed via Crowdin); never scatter i18n into apps.
6. The Cloud Backend (apps/server)
apps/server is the hosted backend behind airi.moeru.ai. Self-hosters of the companion do not need it, but operators of a hosted instance do. Its own engineering notes live in apps/server/docs/ai-context/.
6.1 Stack and assembly
- Framework: Hono (HTTP + WebSocket).
- DI:
injeca. The single application-assembly entry point isapps/server/src/app.ts. - Persistence: Postgres via Drizzle ORM (
drizzle.config.ts, schemas undersrc/schemas/**, migrations bundled via an unplugin transform in@proj-airi/server-schema). Postgres is the sole source of truth for all balance and billing state. - Cache / pub-sub: Redis (KV, caching, and cross-instance WebSocket broadcast). The billing path does not use Redis Streams.
- Auth:
better-authwith an OIDC provider, email auth (Resend), and a role-basedadminplugin (ban/unban, balance adjustment). - Billing: Stripe, with a "Flux" credit ledger. Stripe Product/Price is the single source of truth for top-up pricing; the ledger enforces idempotency.
The route layer is deliberately thin; business logic lives in src/services/. The server runs as a single api role — there are no background loops; admin grants, Stripe webhooks, and event dispatch all run synchronously in the request thread.
6.2 HTTP / WebSocket surface
Routes are mounted under src/routes/ and include: auth, openai/v1/* (an OpenAI-compatible gateway: chat-completions, speech-catalog, speech-generation), providers, characters, chats, chat-ws (chat WebSocket), audio-speech-ws, flux, stripe (webhooks), voice-packs, and admin/* (config router, flux-grants, users, voice-packs).
| Method | Path | Purpose |
|---|---|---|
GET |
/ws/chat |
Chat sync WebSocket. |
GET |
/api/v1/audio/speech/ws |
Streaming speech WebSocket. |
POST |
/openai/v1/chat/completions |
OpenAI-compatible chat gateway (forwards to a configured upstream, meters Flux). |
Note
The server's "LLM capability" is not local inference — it forwards to a configured gateway and debits Flux by usage / fallback rate. WebSocket is used only for chat sync; cross-instance broadcast relies on Redis pub/sub.
6.3 Flux billing model
"Flux" is the platform credit. Key properties operators must understand:
- Balances and the credit ledger are Postgres truth, with Stripe idempotency on top-ups.
- Sub-services (TTS/STT/embeddings) meter against a debt ledger ("flux-meter").
- An admin grant path issues Flux synchronously (no batch table, no background worker), guarded by a role-based
adminGuardand an optionalidempotencyKey. - The
ai-contextnotes document a historical unbilled-usage exploit and its fix — relevant when auditing the billing path.
7. The Server Channel & SDK
The "server channel" is AIRI's internal real-time bus connecting surfaces, services, and plugins.
| Package | Role |
|---|---|
packages/server-runtime |
The WebSocket server implementation (/ws), event routing, config. |
packages/server-sdk |
The client (Client class) that connects to the runtime: connect/auth/reconnect, status tracking, typed messages. |
packages/server-shared |
Shared types, including the WebSocket message contracts. |
packages/server-sdk-shared, packages/server-schema |
Shared SDK helpers and DB schema (used by the cloud backend). |
The desktop app embeds a server-runtime instance; bot services and the mobile app use server-sdk to connect to it. The SDK surfaces explicit error states operators will see in logs: Client is closed, Client is not connected, Connection timed out after <n>ms, Connection aborted, Received invalid websocket message, and Authentication failed.
8. The Plugin System
AIRI's plugin system (packages/plugin-sdk, packages/plugin-protocol, packages/plugin-sdk-tamagotchi) lets third parties extend the companion. It is the most security-relevant extension point.
8.1 Model
- A plugin is described by a
plugin.airi.jsonmanifest (ManifestV1) that declares the protocol permissions it needs (e.g. invokecapabilities:wait, invokeresources:providers:list-providers, read the provider resource). - Plugins run inside a plugin host with runtime backends for
node,web, and shared services (plugin-sdk/src/plugin-host/runtimes/). The host exposes a client API (apis.providers.listProviders(), kits, tools, resources, bindings). - A set of registry services govern the lifecycle:
BindingsRegistryService,DependencyService,KitRegistryService,PermissionService,ResourceService,PluginSessionService,ToolRegistryService.
8.2 Permission enforcement
PermissionService (plugin-sdk/src/plugin-host/runtimes/shared/services/permissions.ts) is the gate. It throws on misuse — e.g. Cannot declare permissions for unknown plugin / Cannot grant permissions to unknown plugin — and a plugin's manifest must declare every protocol capability it invokes. Sessions advance through phases (loaded → ready) and expose their granted capability list.
Warning
Plugins execute code with the host's privileges. Treat third-party plugins like any code you run: review the manifest's declared permissions, and only load plugins from trusted sources. The Plugin Host Inspector (
/devtools/plugin-host) is the tool for validating a plugin's lifecycle and capabilities before relying on it.
8.3 First-party plugins
airi-plugin-claude-code (with its own CLI), airi-plugin-homeassistant, airi-plugin-bilibili-laplace, airi-plugin-web-extension, and airi-plugin-game-chess. The VS Code integration (integrations/vscode/) ships both a host plugin (airi-plugin-vscode) and the published extension (vscode-airi).
9. Bot Services
Services under services/ connect AIRI to chat platforms and the host computer. They are run independently (not bundled in the installer), configured by copying .env → .env.local, and started with a pnpm workspace filter.
| Service | Package | Notes |
|---|---|---|
| Discord | @proj-airi/discord-bot |
Joins servers + voice channels; uses Opus decoding + an audio monitor. |
| Telegram | @proj-airi/telegram-bot |
Requires Postgres (docker compose up -d); Drizzle migrations via db:generate + db:push; embeddings stored (pgvector). |
| Minecraft | @proj-airi/minecraft-bot |
Mineflayer-based agent with a cognitive layer (perception/action/skills) and a debug web UI. |
| Twitter/X | services/twitter-services |
Browser automation (Browserbase/Playwright) to read/post tweets; fully env-var configured. |
| Satori | @proj-airi/satori-bot |
Connects via the Satori chat protocol. |
| Computer-use | services/computer-use-mcp |
Desktop-grounding MCP: observes the screen and drives real OS-level input (macOS CGEvent), with a read-only Chrome DOM bridge extension. |
Generic run pattern:
cd services/<name>
cp .env .env.local # edit credentials
pnpm -F @proj-airi/<name> start
Caution
services/computer-use-mcpcan control the user's machine (mouse, keyboard) via real OS input events. Run it only on machines where you intend AIRI to have that control, and understand the blast radius before enabling it.
10. Rendering, Audio & Memory Subsystems
10.1 Character rendering
| Package | Format | Engine |
|---|---|---|
stage-ui-live2d |
Live2D .moc3 |
Live2D Cubism; loads models from ZIP (live2d-zip-loader.ts), OPFS caching (opfs-loader.ts). |
stage-ui-three |
VRM (3D) | Three.js + @pixiv/three-vrm; VRM composables, animations, MToon. |
stage-ui-spine |
Spine | Spine runtime; loads .skel/.json + .atlas from ZIP (spine-zip-loader.ts). |
engines/stage-tamagotchi-godot |
VRM/MToon | Experimental Godot renderer (GDScript). |
All renderers provide auto-blink, idle eye movement, and look-at. Lip-sync is handled by model-driver-lipsync; face/pose tracking by model-driver-mediapipe.
10.2 Audio pipelines
packages/audio, packages/pipelines-audio (LLM-streaming control + parsers), and packages/audio-pipelines-transcribe implement the voice loop: capture → VAD (voice-activity detection web worker) → STT → LLM → TTS → playback, with beat-sync visualization (stage-shared/beat-sync).
10.3 Memory
packages/memory-pgvector plus in-browser databases (DuckDB WASM and pglite, exposed through @proj-airi/drizzle-duckdb-wasm) keep conversation memory local to the device. A long-term "Memory Alaya" system is WIP.
11. Requirements & Toolchain
11.1 Development prerequisites
| Tool | Version | Notes |
|---|---|---|
| Git | any recent | — |
| Node.js | 23+ | Server Dockerfile uses Node 24. |
| Corepack | bundled with Node | Enables pnpm. |
| pnpm | latest (via Corepack) | corepack enable && corepack prepare pnpm@latest --activate. |
| Rust toolchain | stable | Only needed for crates/ (legacy Tauri) or native desktop bits. |
Platform extras:
Windows
Install Visual Studio with the Windows SDK and C++ build tools; install git, nodejs, and rustup (with rust-msvc) via Scoop.
macOS
brew install git node, then enable pnpm via Corepack.
Linux (desktop dev)
sudo apt install libssl-dev libglib2.0-dev libgtk-3-dev \
libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
NixOS
Electron needs shared libraries outside standard paths — use the FHS shell from flake.nix:
nix develop .#fhs
pnpm dev:tamagotchi
11.2 Runtime dependencies (cloud backend)
Postgres (with pgvector), Redis, and outbound access to your LLM gateway, Stripe, Resend, and (optionally) an OTLP endpoint and/or Langfuse.
12. Development Workflow
12.1 Bootstrapping
corepack enable
pnpm install
# optional, only for Rust/native work:
cargo fetch
12.2 Running each surface
| Command | Starts |
|---|---|
pnpm dev |
Web (stage-web) dev server (the default). |
pnpm dev:tamagotchi |
Desktop (Electron) dev. |
pnpm dev:pocket:ios --target <DEVICE_OR_SIM> |
Mobile dev on iOS (list devices with pnpm exec cap run ios --list). |
pnpm dev:docs |
VitePress documentation site. |
Tip
Installing
@antfu/nilets you usenr <script>regardless of package manager. The repo recommends it throughout its docs.
12.3 Scoping work with workspace filters
Use pnpm filters to target one workspace:
pnpm -F @proj-airi/stage-tamagotchi typecheck # tsc + vue-tsc
pnpm -F @proj-airi/stage-tamagotchi build # typecheck + electron-vite build
pnpm exec vitest run path/to/file.test.ts # one test file
pnpm -F @proj-airi/stage-tamagotchi exec vitest run
12.4 Before committing
pnpm lint && pnpm typecheck # formatting is via ESLint; lint:fix applies it
Branch naming: username/feat/short-name; Conventional Commits (e.g. feat(stage-ui): add reconnect backoff); gitmoji is prohibited; rebase pulls. Each packages/ and apps/ entry should carry a structured README.md (what it does, how/when to use it, when not to).
13. Building & Deployment
13.1 Client builds
- Web:
pnpm -F @proj-airi/stage-web buildproduces a static bundle; deploy to any static host / CDN. The hosted version is served by the backend. - Desktop:
pnpm -F @proj-airi/stage-tamagotchi buildruns typecheck + electron-vite build; packaging produces signed installers per platform. Distribution channels: GitHub releases, winget (MoeruAI.AIRI), Scoop (bucket/), Homebrew Cask. - Mobile: Vite build + Capacitor sync (
packages/cap-vite), then Xcode/Android Studio packaging.
13.2 Auto-updates (desktop)
Built on electron-updater. Channels: stable, beta, alpha, nightly, selectable via AIRI_UPDATE_CHANNEL; UPDATE_SERVER_URL overrides the feed (used by the local test harness in apps/stage-tamagotchi/scripts/update-test/). Verify via About → Check for updates, and inspect updater diagnostics under Settings → System → Developer.
13.3 Cloud backend deployment (Railway)
The backend ships as a Dockerfile (apps/server/production/railway/Dockerfile) and a railway.toml:
[build]
builder = "DOCKERFILE"
dockerfilePath = "/apps/server/production/railway/Dockerfile"
[deploy]
startCommand = "pnpm -F @proj-airi/server start"
Build stages: build the auth + admin UIs, then build server-schema, server-sdk-shared, and the server. OpenTelemetry is preloaded via tsx --import ./instrumentation.ts before any app module so the http/pg/ioredis instrumentation hooks install first.
Important
The server's Dockerfile contains a hard guard that fails the build if
server-schema'sdist/index.mjsstill references avirtual:migration specifier (an intermittent rolldown/tsdown transform failure). If a server build fails with that error, it caught a real packaging bug — do not bypass it.
13.4 Self-hosting observability stack (optional)
apps/server/docker-compose.otel.yml brings up an OpenTelemetry Collector, Prometheus, Loki, Tempo, and Grafana for local/self-hosted observability (Grafana on host port 3001).
14. Configuration Reference
There are two distinct configuration worlds; conflating them is the most common source of confusion.
14.1 Client app configuration (no env vars)
The web, desktop, and mobile apps are configured entirely through the in-app Settings UI, persisted in Pinia stores + local storage (and, on desktop, the app data folder). There is no .env for end users. See the User Guide settings reference for every pane.
14.2 Cloud backend & services (env vars)
Environment variables apply to apps/server and the services/* bots, loaded from .env.local (the backend uses dotenvx).
Backend / observability:
| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection (Drizzle). |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP endpoint; enables Grafana-pipeline traces/metrics/logs. |
OTEL_EXPORTER_OTLP_HEADERS |
key=value,key2=value2 auth headers for OTLP. |
OTEL_SERVICE_NAME / OTEL_SERVICE_NAMESPACE |
Service identity (default server / airi). |
OTEL_TRACES_SAMPLING_RATIO |
Head-based trace sampling (0–1, default 1). |
OTEL_SEMCONV_STABILITY_OPT_IN |
Defaults to http (stable HTTP semconv). |
RAILWAY_REPLICA_ID / SERVER_INSTANCE_ID |
Per-replica metric identity (avoid series churn). |
LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_BASE_URL |
Independent LLM-native tracing export. |
NODE_ENV |
Deployment environment label. |
Additional backend integrations (from the ai-context docs): Stripe keys (billing), Resend (email auth), and better-auth/OIDC client config.
Twitter/X service (services/twitter-services):
| Variable | Purpose |
|---|---|
TWITTER_API_KEY / TWITTER_API_SECRET |
Twitter API credentials. |
TWITTER_ACCESS_TOKEN / TWITTER_ACCESS_TOKEN_SECRET |
OAuth tokens. |
BROWSER_HEADLESS, BROWSER_USER_AGENT, BROWSER_VIEWPORT_WIDTH/HEIGHT, BROWSER_TIMEOUT, BROWSER_REQUEST_TIMEOUT, BROWSER_REQUEST_RETRIES |
Browser automation tuning. |
BROWSERBASE_API_KEY |
Browserbase remote browser. |
AIRI_URL / AIRI_TOKEN / ENABLE_AIRI |
Connect back to an AIRI server channel. |
MCP_PORT / ENABLE_MCP |
MCP server exposure. |
CONCURRENCY / CONFIG_PATH |
Worker concurrency / config file path. |
Computer-use service (services/computer-use-mcp): COMPUTER_USE_EXECUTOR, COMPUTER_USE_REMOTE_SSH_{HOST,USER,PORT}, COMPUTER_USE_REMOTE_RUNNER_COMMAND, COMPUTER_USE_REMOTE_DISPLAY_SIZE, COMPUTER_USE_REMOTE_OBSERVATION_{BASE_URL,TOKEN}, COMPUTER_USE_ALLOWED_BOUNDS, COMPUTER_USE_BROWSER_DOM_BRIDGE_{PORT,TIMEOUT_MS}.
Build-time (packages/cap-vite, mobile): CAP_VITE_BASE_CONFIG, CAP_VITE_CAP_ARGS_JSON, CAP_VITE_CONFIG_LOADER, CAP_VITE_ROOT, VITE_SKIP_MKCERT, VITE_CAP_SYNC_IOS_AFTER_BUILD.
Note
Several of the env keys above were extracted from
*.config.test.tsand*.types.tsfiles — they are the real, code-referenced variable names, but always confirm the canonical set against each service's.envtemplate before deploying.
15. Security Analysis
15.1 Client trust model
- API keys and personal config are stored locally (browser storage / desktop app data dir), not on a central server (for the self-hosted/local case). This is a privacy strength but also means the device is the security boundary — anyone with access to the machine can read stored credentials.
- The desktop app runs a local HTTP/WebSocket server (
src/main/services/airi/http-server/) with its own auth layer. Binding it beyondlocalhostexposes that surface to the network; the secure-WebSocket mode exists for the device-to-device (Pocket↔desktop) case and should be used deliberately. - Screen capture / vision is opt-in and gated behind an explicit developer toggle plus OS-level permission grants. Treat it as sensitive: AIRI sees whatever you let it capture, and frames may be sent to a cloud vision model.
15.2 Plugin & service privilege
- Plugins declare permissions in
plugin.airi.jsonand are gated byPermissionService, but a loaded plugin runs with host privileges. The manifest is an audit surface, not a sandbox guarantee — vet plugins before loading. computer-use-mcpcontrols real OS input. Its bundled Chrome extension is deliberately read-only (no DOM mutation, noeval, no external network, nochrome.scripting) — but the executor itself performs real clicks/keystrokes via CGEvent.- Bot services hold platform credentials (Discord/Telegram/Twitter tokens) in
.env.local. Keep those files out of version control and off shared machines.
15.3 Cloud backend
- better-auth provides OIDC, email auth, and a role-based admin plugin; ban/unban and balance changes are admin-gated (
adminGuard), with hot-path enforcement viaresolveRequestAuth+ a userinfo guard so OIDC JWTs take effect immediately. - Billing integrity: Postgres is the source of truth; Stripe top-ups are idempotent; admin Flux grants accept an
idempotencyKey. The team has already remediated one unbilled-usage exploit (documented inai-context) — audit the metering path when changing it. - Observability isolation: the Langfuse exporter runs on an isolated tracer provider so prompt/completion text is not leaked into the Grafana/Tempo OTLP stream — preserve that separation if you touch instrumentation.
15.4 Error surface (operational signals)
The codebase throws named errors operators should monitor: server-SDK connection errors (Authentication failed, Connection timed out), plugin permission errors (Cannot grant permissions to unknown plugin), model-loader errors (Textures not found, Expected exactly one moc file), and provider/character service failures (Failed to fetch providers, Failed to create character). An ApiError class (apps/server/src/utils/error.ts) standardizes backend error responses.
16. Observability & Operations
The backend has a mature OpenTelemetry setup (apps/server/instrumentation.ts):
- Traces fan out to two independent pipelines: OTLP → Tempo (Grafana), and (optionally) Langfuse for LLM-native generation spans. Langfuse uses an isolated provider with
AlwaysOnSamplerso generations are captured regardless of head-sampling. - Metrics & logs are OTLP-only (15s export interval), instrumenting HTTP (via
@hono/otelfor inbound,HttpInstrumentationfor outbound), Postgres (pg), Redis (ioredis), Node runtime, andundici(fetch). - Self-hosted stack:
docker-compose.otel.yml(Collector + Prometheus 7d retention + Loki + Tempo + Grafana). - Graceful shutdown: SIGTERM/SIGINT flush both SDK and Langfuse exporters before exit.
Operator notes from ai-context: metric ownership is split (Grafana for system metrics, PostHog for product events, Postgres for truth); set SERVER_INSTANCE_ID/RAILWAY_REPLICA_ID per replica to avoid metric series churn; grep logs for [otel-preload] to confirm the instrumentation preload ran.
17. Code Quality & Conventions
AIRI ships an unusually detailed AGENTS.md that governs contributions. Highlights:
- IPC via
@moeru/eventaonly; centralize contracts (e.g.apps/stage-tamagotchi/src/shared). - DI via
injeca, only at real external boundaries (DB, model runtime, queue, cache, FS, network, clock, env, feature gates) — not for internal helpers. - Validation via
valibot, schemas kept near consumers. - Error handling via
@moeru/std(errorMessageFrom(error)instead of manualinstanceof Errorchecks). - Styling via UnoCSS (preferred over Tailwind); primitives from
@proj-airi/ui(reka-ui); grouped class arrays over long inline strings; Iconify icon sets, not bespoke SVGs. - Module design: prefer deep modules over shallow ones; don't split by execution order; don't extract one-call helpers just to name a step; favor functional patterns, classes only for runtime/browser APIs or stateful domain boundaries.
- No backward-compatibility guards — refactor forward instead, with refactor docs.
- Utilities: prefer
es-toolkit; never add general-purpose utility libs without explicit maintainer sign-off. - JSDoc required on public/exported APIs (with
Use when/Expects/Returns); CLI/runner entry points require ASCII call-stack diagrams; normalizers require before/after examples. - File names camelCase; mark follow-ups/concerns/hacks with
// TODO:/// REVIEW:/// NOTICE:. - Documented past fixes live in
docs/solutions/(YAML frontmatter:module,tags,problem_type).
18. Testing Strategy
- Framework: Vitest, per project; keep runs targeted (
pnpm exec vitest run <file>). Rootvitest.config.tsregisters the projects. - Discipline: reproduce a bug with a failing test before patching; name regression tests with the tracker id (
Issue #<number>or the Linear key) and link the report in a comment above the test; include aROOT CAUSE:block. - Mocking: mock IPC/services with
vi.fn/vi.mock; do not rely on a real Electron runtime; prefer Vitest browser mode for DOM/Web Platform APIs over hard-mocking globals. Do not use mocks/as unknown as/dynamic imports to mask real import-boundary problems — fix the boundary. - Coverage: the cloud backend enforces 100% lines/functions/branches/statements (
apps/server/vitest.config.ts). Other packages grow coverage progressively. - Desktop updater has a dedicated local matrix harness (
scripts/update-test/) exercising lane × runtime-feed × bundle-version combinations.
Note
Reported confidence for this analysis was 71/100, with gaps concentrated in test-coverage breadth and the very large number of shallow leaf folders — expected for a monorepo of this size. The architecture-bearing modules are well covered.
19. Troubleshooting (Operators & Developers)
Error: ERR_UNSUPPORTED_ESM_URL_SCHEME / virtual: specifier in server-schema
- Cause: The
unplugin-drizzle-orm-migrationstransform intermittently no-ops (observed with tsdown 0.21.9 + rolldown 1.0.0-rc.16), shipping adist/index.mjsthat re-exports a literalvirtual:import. - Solution: The Railway Dockerfile already forces a clean rebuild and asserts the transform ran. Reproduce locally with
rm -rf packages/server-schema/dist && pnpm -F @proj-airi/server-schema run build. - Prevention: Don't cache
server-schema/distacross builds; keep the build-time guard in place.
Error: Electron won't start on Linux/NixOS (missing shared libraries)
- Cause: Electron needs WebKitGTK/GTK libraries absent from standard paths.
- Solution: Install the
libwebkit2gtk-4.1/libgtk-3/libglib2.0/libssl/libjavascriptcoregtk-4.1dev packages; on NixOS usenix develop .#fhsthenpnpm dev:tamagotchi. - Prevention: Document the FHS shell for NixOS contributors.
Error: Telegram bot fails to start
- Cause: Missing Postgres or un-run migrations.
- Solution:
docker compose up -dinservices/telegram-bot, thenpnpm -F @proj-airi/telegram-bot db:generate && db:push. ConfirmEMBEDDING_DIMENSIONis supported (the code throwsUnsupported embedding dimension). - Prevention: Provision the DB before first run.
Error: Authentication failed / Received invalid websocket message from server-sdk
- Cause: Bad/missing server-channel token, version skew between SDK and runtime, or connecting to the wrong address.
- Solution: Verify
AIRI_URL/AIRI_TOKEN, ensure the desktop server channel is running and reachable, and that SDK/runtime versions match (monorepoworkspace:pins). - Prevention: Pin versions via the workspace; surface connection status in service logs.
Issue: Metrics series churn / undercounting after each deploy
- Cause: No stable per-replica
service.instance.id; falls back torandomUUID(). - Solution: Set
SERVER_INSTANCE_ID(or rely onRAILWAY_REPLICA_ID). - Prevention: Make instance id part of the deploy template.
Issue: A plugin loads but has no capabilities / can't call an API
- Cause: The plugin's
plugin.airi.jsondidn't declare the required protocol permission, soPermissionServiceblocks the call. - Solution: Add the needed permissions to the manifest; verify via Devtools → Plugin Host Inspector that the session reaches
readyand lists the capability. - Prevention: Treat the manifest's permission list as the contract; test plugins in the inspector first.
20. Potential Improvements
Observations for maintainers, grounded in the source and the project's own notes:
- Provider-integration drift. Several provider config screens lag behind upstream API changes; the in-app advice is "use OpenAI-compatible." A conformance test matrix against each provider adapter in
stage-ui/src/libs/providers/providers/*would catch regressions earlier. services/computer-use-mcp/srcis a large module (41 files, ~360 KB). It is the heaviest service by far; splitting its session/state/transcript/workflow concerns and growing test coverage would reduce risk for a feature that drives real OS input.- Two desktop renderers in flight. The legacy
crates/(Tauri) shell and the experimentalengines/stage-tamagotchi-godotboth coexist with the active Electron app. A clear deprecation/EOL note in-repo would prevent contributor confusion. - Memory is partly WIP. Short-term and long-term memory ("Memory Alaya") are unreleased; the in-browser DB path (DuckDB WASM /
pglite) is the only shipping memory. Document the supported-vs-planned boundary in-product. - Community manual gaps. The user-facing manual has many "editor hasn't tested / details omitted" sections (hearing, artistry, VRM tuning). These are the highest-value areas for first-party documentation.
- i18n via AI translation. Non-Chinese manuals are AI-translated with light correction; a glossary/terminology lock in Crowdin would improve consistency for UI strings.
21. Quick Reference
21.1 Dev commands
| Command | Purpose |
|---|---|
corepack enable && pnpm install |
Bootstrap. |
pnpm dev |
Web dev server. |
pnpm dev:tamagotchi |
Desktop (Electron) dev. |
pnpm dev:pocket:ios --target <id> |
Mobile (iOS) dev. |
pnpm dev:docs |
Docs site. |
pnpm lint && pnpm typecheck |
Pre-commit checks. |
pnpm -F <pkg> build |
Build one workspace. |
pnpm exec vitest run <file> |
Run one test file. |
npx bumpp --no-commit --no-tag |
Bump monorepo version. |
21.2 Service start pattern
cd services/<name> && cp .env .env.local # edit credentials
pnpm -F @proj-airi/<name> start
21.3 Tech stack at a glance
| Layer | Technology |
|---|---|
| UI | Vue 3, Vue Router, Pinia, VueUse, UnoCSS, reka-ui |
| Desktop | Electron, electron-vite, electron-updater |
| Mobile | Capacitor (Swift/Kotlin), cap-vite |
| Build | pnpm workspaces + catalog, Turborepo, Vite, tsdown, VitePress |
| AI | xsai (40+ providers), candle (local CUDA/Metal), Kokoro TTS, transformers.js |
| Rendering | Live2D Cubism, Three.js + three-vrm, Spine, (experimental) Godot |
| Backend | Hono, Drizzle + Postgres (pgvector), Redis, better-auth, Stripe, Resend |
| Cross-cutting | @moeru/eventa (IPC), injeca (DI), valibot (validation), @moeru/std |
| Observability | OpenTelemetry, Prometheus, Loki, Tempo, Grafana, Langfuse, PostHog |
| Data (client) | DuckDB WASM, pglite, OPFS |
21.4 Map of the repo
| Path | Owns |
|---|---|
apps/stage-web · stage-tamagotchi · stage-pocket |
Client surfaces |
apps/server · ui-admin · ui-server-auth |
Cloud backend + its UIs |
packages/stage-ui (+ stage-shared, ui, stage-pages) |
Shared product logic |
packages/server-runtime · server-sdk · server-shared |
Server channel |
packages/plugin-sdk · plugin-protocol |
Plugin system |
packages/stage-ui-{live2d,spine,three} · model-driver-* |
Rendering |
packages/audio* · pipelines-audio |
Voice loop |
packages/memory-pgvector |
Memory |
services/* |
Bots + computer-use |
plugins/* · integrations/vscode |
Plugins + editor integration |
docs/ |
VitePress site |
crates/ |
Legacy Tauri (deprecated) |
21.5 Links
- Repository: https://github.com/moeru-ai/airi
- Docs site: https://airi.moeru.ai/docs/en/
- Sub-project org: https://github.com/proj-airi
xsaiLLM client: https://github.com/moeru-ai/xsai- Discord: https://discord.gg/TgQ3Cu2F7A