Skip to main content

Architecture Overview

Project AIRI is built as a modern, modular monorepo designed to power AI-driven virtual characters across multiple platforms. This guide provides a comprehensive overview of the system architecture, technology stack, and component interactions.

System Architecture

AIRI follows a layered architecture that separates concerns between presentation (Stage), business logic (Core), and runtime services (Server).

Monorepo Structure

The project is organized as a pnpm workspace monorepo with clear separation of concerns:

Directory Layout

airi/
├── apps/               # Application entry points
│   ├── stage-web/      # Browser version
│   ├── stage-tamagotchi/  # Desktop (Electron)
│   ├── stage-pocket/   # Mobile (Capacitor)
│   └── server/         # Backend server
├── packages/           # Shared libraries and core modules
│   ├── stage-ui/       # Core business components & stores
│   ├── stage-shared/   # Shared stage logic
│   ├── ui/             # Primitive UI components
│   ├── server-runtime/ # WebSocket server runtime
│   ├── server-sdk/     # Client SDK for server
│   ├── server-shared/  # Shared server types
│   ├── plugin-sdk/     # Plugin development SDK
│   ├── plugin-protocol/# Plugin communication protocol
│   ├── i18n/           # Translations
│   └── ...             # Additional utilities
├── services/           # External service integrations
│   ├── discord-bot/
│   ├── telegram-bot/
│   ├── minecraft/
│   └── ...
├── plugins/            # Plugin implementations
│   ├── airi-plugin-web-extension/
│   ├── airi-plugin-homeassistant/
│   └── ...
└── crates/             # Legacy Rust/Tauri code

Workspace Organization

Application-level packages that users interact with directly:
  • stage-web: Browser-based web application (Vue 3 + Vite)
  • stage-tamagotchi: Desktop application (Electron + Vue 3)
  • stage-pocket: Mobile application (Capacitor + Vue 3)
  • server: Backend API server
  • component-calling: Component system examples

Technology Stack

Frontend Technologies

TechnologyPurposeUsed In
Vue 3UI frameworkAll Stage apps
TypeScriptType safetyEntire codebase
ViteBuild toolAll apps
PiniaState managementStage apps
VueUseVue composablesAll Vue code
UnoCSSUtility-first CSSAll frontends
Reka UIHeadless UI componentsUI primitives
Three.js3D graphicsVRM/3D rendering
VitestUnit testingAll packages

Backend & Runtime Technologies

TechnologyPurposeUsed In
Node.jsRuntime environmentAll servers
ElectronDesktop app frameworkstage-tamagotchi
CapacitorMobile app frameworkstage-pocket
H3HTTP serverserver-runtime
CrossWSWebSocket libraryserver-runtime
DuckDB WASMIn-browser databaseWeb/Desktop
Drizzle ORMDatabase ORMAll database code

Development Tools

ToolPurpose
pnpmPackage manager & workspaces
ESLintLinting & formatting
TurboMonorepo build orchestration
tsdownTypeScript bundler
nano-stagedGit hooks

Key Libraries

  • @xsai/generate-text: Text generation abstraction
  • @xsai/stream-text: Streaming text generation
  • @xsai/generate-speech: Speech synthesis
  • @xsai/stream-transcription: Streaming speech recognition
  • @huggingface/transformers: Transformers.js for in-browser ML
  • onnxruntime-web: ONNX model inference in browser
  • @moeru/eventa: Type-safe IPC/RPC framework
  • injeca: Dependency injection for services
  • crossws: WebSocket server/client
  • superjson: JSON serialization with type preservation
  • @tresjs/core: Three.js Vue integration
  • @tresjs/cientos: Three.js helper components
  • animejs: Animation library
  • @vueuse/motion: Vue animation composables
  • valibot: Schema validation
  • zod: Alternative schema validation
  • xstate: State machine library

Frontend Architecture

Stage Applications

The “Stage” represents the presentation layer where AIRI appears to users. All Stage applications share a common architecture:

Component Hierarchy

// Simplified component structure
stage-web/stage-tamagotchi/stage-pocket
  ├── Layouts (from @proj-airi/stage-layouts)
  │   ├── Default Layout
  │   └── Settings Layout
  ├── Pages (from @proj-airi/stage-pages + app-specific)
  │   ├── Main Stage View
  │   ├── Settings Pages
  │   └── Devtools Pages
  ├── Components (from @proj-airi/stage-ui)
  │   ├── Character Display (VRM/Live2D)
  │   ├── Chat Interface
  │   ├── Audio Controls
  │   └── Model Configuration
  └── Stores (from @proj-airi/stage-ui)
      ├── Character Store
      ├── Settings Store
      ├── Providers Store
      └── Analytics Store

Stage UI Package (packages/stage-ui/)

The heart of the Stage system, containing: Directory Structure:
stage-ui/src/
├── components/          # Business components
│   ├── scenarios/       # Page-specific components
│   └── scenes/          # Scene components
├── composables/         # Business Vue composables
├── stores/              # Pinia stores
│   ├── providers/       # LLM provider configurations
│   └── modules/         # AIRI orchestration modules
├── constants/           # Constants and configurations
├── libs/                # Utility libraries
├── types/               # TypeScript types
├── utils/               # Helper utilities
└── workers/             # Web Workers (e.g., VAD)
Key Responsibilities:
  • Character state management
  • Provider configurations (OpenAI, Claude, etc.)
  • Audio input/output handling
  • Speech recognition (VAD)
  • UI business logic
  • Animation controllers

UI Primitives (packages/ui/)

Standardized UI primitives built on reka-ui:
  • Form components (Input, Textarea, Select, etc.)
  • Layout components (Card, Dialog, Sheet, etc.)
  • Feedback components (Toast, Alert, etc.)
  • Minimal business logic - pure presentation

Styling System

AIRI uses UnoCSS (not Tailwind) for styling. Prefer UnoCSS over Tailwind CSS throughout the codebase.
Best Practices:
<!-- ✅ Good: Grouped class arrays -->
<template>
  <div :class="[
    'px-2 py-1',
    'flex items-center',
    'bg-white/50 dark:bg-black/50'
  ]">
    Content
  </div>
</template>

<!-- ❌ Bad: Long inline classes -->
<template>
  <div class="px-2 py-1 flex items-center bg-white/50 dark:bg-black/50">
    Content
  </div>
</template>
Configuration:
  • Main config: uno.config.ts at root
  • Custom shortcuts and rules defined in config
  • Theme customization via UnoCSS presets

Routing

Router configuration in vite.config.ts:
import VueRouter from 'unplugin-vue-router/vite'

export default defineConfig({
  plugins: [
    VueRouter({
      routesFolder: 'src/pages',
      extensions: ['.vue', '.md'],
    })
  ]
})
Pages directory: apps/stage-web/src/pages/ Devtools: apps/stage-web/src/pages/devtools/
Route Layouts: Settings and devtools routes use layout metadata:
<route lang="yaml">
meta:
  layout: settings
</route>

Backend Architecture

Server Runtime (packages/server-runtime/)

The server runtime provides a WebSocket-based communication layer for connecting various AIRI components.

Architecture

// Simplified server architecture
Server Runtime (H3 + CrossWS)
  ├── WebSocket Handler (/ws)
  │   ├── Authentication Layer
  │   ├── Peer Registry
  │   ├── Module Announcement
  │   └── Event Routing
  ├── Heartbeat Manager
  └── Middleware System

Key Concepts

Peers: Connected WebSocket clients (UI, plugins, services) Modules: Named components that announce themselves Events: Messages with metadata, routing info, and data Routing: Event forwarding with destination filtering

Event Types

// Core event types
interface WebSocketEvent {
  type: string
  data: Record<string, unknown>
  metadata?: {
    event: { id: string, parentId?: string }
    source: MetadataEventSource
  }
  route?: {
    destinations?: Destination[]
    bypass?: boolean
  }
}

// Common event types:
'module:authenticate'           // Authenticate with token
'module:announce'               // Announce module presence
'module:configure'              // Configure a module
'transport:connection:heartbeat' // Heartbeat ping/pong
'registry:modules:sync'         // Sync module registry
'ui:configure'                  // UI sends config to module

Running the Server

# Development mode
pnpm -F @proj-airi/server-runtime dev

# Production
pnpm -F @proj-airi/server-runtime start

# Environment variables
SERVER_INSTANCE_ID=<id>         # Custom instance ID
AUTHENTICATION_TOKEN=<token>    # Require authentication
LOG_LEVEL=log|debug|warn|error  # Logging level
LOG_FORMAT=pretty|json          # Log format

Server SDK (packages/server-sdk/)

Client SDK for connecting to the server runtime:
import { Client } from '@proj-airi/server-sdk'

const client = new Client({ 
  name: 'my-plugin',
  url: 'ws://localhost:3000/ws'
})

// Connect and announce
await client.connect()

// Send events
client.send({
  type: 'custom:event',
  data: { message: 'Hello!' }
})

// Listen for events
client.on('custom:event', (event) => {
  console.log('Received:', event.data)
})

Data Flow

User Interaction Flow

Plugin Communication Flow

IPC & Communication

Eventa Framework

AIRI uses @moeru/eventa for type-safe, framework-agnostic IPC/RPC:
import { defineContract, createClient, createServer } from '@moeru/eventa'

// Define contract
const contract = defineContract({
  greet: {
    input: z.object({ name: z.string() }),
    output: z.string()
  }
})

// Server (main process)
const server = createServer(contract, {
  greet: async ({ name }) => `Hello, ${name}!`
})

// Client (renderer process)
const client = createClient(contract, server)
const result = await client.greet({ name: 'World' })
Usage Locations:
  • Electron main ↔️ renderer: apps/stage-tamagotchi/src/shared
  • Plugin ↔️ host: packages/plugin-sdk
  • Service ↔️ service: Custom contracts

Dependency Injection

AIRI uses injeca for dependency injection:
import { createContainer, createToken } from 'injeca'

// Define token
const LoggerToken = createToken<Logger>('Logger')

// Create container
const container = createContainer()

// Register service
container.register(LoggerToken, () => new ConsoleLogger())

// Resolve service
const logger = container.resolve(LoggerToken)
Usage Pattern: See apps/stage-tamagotchi/src/main/index.ts for composition patterns.

Database & Persistence

DuckDB WASM

In-browser database for web and desktop:
import { drizzle } from '@proj-airi/drizzle-duckdb-wasm'
import { DuckDB } from '@proj-airi/duckdb-wasm'

// Initialize
const duckdb = await DuckDB.create()
const db = drizzle(duckdb)

// Query
const results = await db.select().from(users).where(eq(users.id, 1))

Storage Solutions

StorageUse CasePackages
DuckDB WASMStructured data (messages, analytics)@proj-airi/duckdb-wasm
LocalForageKey-value storagelocalforage
IndexedDBLarge binary dataidb-keyval
PostgreSQLProduction services (Telegram bot)drizzle-orm

Internationalization

Centralized translations in packages/i18n/:
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

// Use in templates
<p>{{ t('common.welcome') }}</p>

// Use in code
const message = t('errors.network')
Adding Translations:
  1. Add keys to packages/i18n/locales/
  2. Never scatter i18n across apps/packages
  3. Contribute to Crowdin for translations

Performance Considerations

Web Performance

Web Workers

VAD and heavy computations run in Web Workers to avoid blocking the main thread.

Code Splitting

Dynamic imports and route-based splitting reduce initial bundle size.

WebGPU

GPU acceleration for ML inference and 3D rendering where available.

WASM

DuckDB and ONNX Runtime use WebAssembly for near-native performance.

Desktop Performance

The Electron app (stage-tamagotchi) can use:
  • Native CUDA (NVIDIA)
  • Metal (Apple)
  • Native file system access
  • Native audio/video APIs

Mobile Performance

Capacitor (stage-pocket) bridges to native APIs:
  • Native camera/microphone
  • Push notifications
  • Background tasks
  • File system access

Build System

Turbo Orchestration

The monorepo uses Turbo for efficient builds:
# Build all packages
pnpm build

# Build specific workspace
pnpm -F @proj-airi/stage-web build

# Build with filters
turbo run build --filter="./packages/*"

Package Bundling

Packages use tsdown for bundling:
// tsdown.config.ts
import { defineConfig } from 'tsdown'

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm'],
  dts: true,
  clean: true,
})

Testing Strategy

Unit Tests (Vitest)

# Run all tests
pnpm test:run

# Run specific test file
pnpm exec vitest run apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.test.ts

# Run workspace tests
pnpm -F @proj-airi/stage-tamagotchi exec vitest run

Testing Patterns

import { describe, it, expect, vi } from 'vitest'

describe('MyComponent', () => {
  it('should render correctly', () => {
    // Mock dependencies
    const mockService = vi.fn()
    
    // Test logic
    expect(mockService).toHaveBeenCalled()
  })
})
Guidelines:
  • Mock IPC/services with vi.fn()/vi.mock()
  • Don’t rely on real Electron runtime
  • Keep tests targeted for speed
  • Use Vitest browser env where possible

Configuration Files

Key Configuration Locations

FilePurpose
package.jsonRoot workspace config
pnpm-workspace.yamlWorkspace definition
tsconfig.jsonBase TypeScript config
uno.config.tsUnoCSS configuration
eslint.config.jsESLint rules
vitest.config.tsVitest configuration
turbo.jsonTurbo pipeline config

Environment Variables

Common environment variables:
# Server Runtime
SERVER_INSTANCE_ID=<id>
AUTHENTICATION_TOKEN=<token>
LOG_LEVEL=log|debug|warn|error

# Development
VITE_DEV_SERVER_URL=http://localhost:5173
CAPACITOR_DEV_SERVER_URL=https://<ip>:5273

Next Steps

Plugin Development

Learn how to create plugins for AIRI

Contributing Guide

Set up your development environment

API Reference

Explore the API documentation

Examples

Check out example implementations

Build docs developers (and LLMs) love