Jesse A. Green / notebook

Project AIRI Admin & Developer Guide

Generated June 2026 from moeru-ai/airi at 19e5020

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 line v0.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

  1. Executive Summary
  2. System Architecture
  3. Monorepo Layout
  4. The Three Client Surfaces
  5. Shared Packages: the Stage Stack
  6. The Cloud Backend (apps/server)
  7. The Server Channel & SDK
  8. The Plugin System
  9. Bot Services
  10. Rendering, Audio & Memory Subsystems
  11. Requirements & Toolchain
  12. Development Workflow
  13. Building & Deployment
  14. Configuration Reference
  15. Security Analysis
  16. Observability & Operations
  17. Code Quality & Conventions
  18. Testing Strategy
  19. Troubleshooting (Operators & Developers)
  20. Potential Improvements
  21. 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":

  1. 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.
  2. 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 xsai for 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 xsai LLM 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 at src/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 via CAP_VITE_* and VITE_CAP_SYNC_IOS_AFTER_BUILD env 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 in docs/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 is apps/server/src/app.ts.
  • Persistence: Postgres via Drizzle ORM (drizzle.config.ts, schemas under src/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-auth with an OIDC provider, email auth (Resend), and a role-based admin plugin (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 adminGuard and an optional idempotencyKey.
  • The ai-context notes 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.json manifest (ManifestV1) that declares the protocol permissions it needs (e.g. invoke capabilities:wait, invoke resources: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-mcp can 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/ni lets you use nr <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 build produces a static bundle; deploy to any static host / CDN. The hosted version is served by the backend.
  • Desktop: pnpm -F @proj-airi/stage-tamagotchi build runs 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's dist/index.mjs still references a virtual: 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.ts and *.types.ts files — they are the real, code-referenced variable names, but always confirm the canonical set against each service's .env template 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 beyond localhost exposes 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.json and are gated by PermissionService, but a loaded plugin runs with host privileges. The manifest is an audit surface, not a sandbox guarantee — vet plugins before loading.
  • computer-use-mcp controls real OS input. Its bundled Chrome extension is deliberately read-only (no DOM mutation, no eval, no external network, no chrome.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 via resolveRequestAuth + 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 in ai-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 AlwaysOnSampler so generations are captured regardless of head-sampling.
  • Metrics & logs are OTLP-only (15s export interval), instrumenting HTTP (via @hono/otel for inbound, HttpInstrumentation for outbound), Postgres (pg), Redis (ioredis), Node runtime, and undici (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/eventa only; 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 manual instanceof Error checks).
  • 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>). Root vitest.config.ts registers 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 a ROOT 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-migrations transform intermittently no-ops (observed with tsdown 0.21.9 + rolldown 1.0.0-rc.16), shipping a dist/index.mjs that re-exports a literal virtual: 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/dist across 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.1 dev packages; on NixOS use nix develop .#fhs then pnpm 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 -d in services/telegram-bot, then pnpm -F @proj-airi/telegram-bot db:generate && db:push. Confirm EMBEDDING_DIMENSION is supported (the code throws Unsupported 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 (monorepo workspace: 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 to randomUUID().
  • Solution: Set SERVER_INSTANCE_ID (or rely on RAILWAY_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.json didn't declare the required protocol permission, so PermissionService blocks the call.
  • Solution: Add the needed permissions to the manifest; verify via Devtools → Plugin Host Inspector that the session reaches ready and 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/src is 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 experimental engines/stage-tamagotchi-godot both 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)