Skip to main content
MIRA is a desktop application built on Electron with a React renderer and two Python-based reasoning engines. All components run locally on your machine.

Process model

┌──────────────────────────────────────────────────────────────────────┐
│  Electron Main Process (Node.js)                                     │
│                                                                      │
│  ┌─────────────────┐  ┌─────────────────┐  ┌──────────────────────┐  │
│  │   IPC Router    │  │   SQLite DB     │  │   Engine Manager     │  │
│  │   (ipcMain)     │  │   (mira.db)     │  │   NAE bridge / RLM   │  │
│  └────────┬────────┘  └─────────────────┘  └──────────┬───────────┘  │
│           │  contextBridge                             │  stdio/IPC  │
└───────────┼────────────────────────────────────────────┼─────────────┘
            │                                            │
            ▼                                            ▼
┌───────────────────────────────┐     ┌──────────────────────────────┐
│   Renderer Process            │     │   Python Subprocess(es)      │
│   React + Zustand             │     │   Bundled Python 3.11        │
│                               │     │                              │
│   ┌───────────────────────┐   │     │   ┌──────────┐ ┌─────────┐   │
│   │  Chat  │  Skills      │   │     │   │   NAE    │ │   RLM   │   │
│   │  Workflows  │  Eval   │   │     │   │  engine  │ │  engine │   │
│   └───────────────────────┘   │     │   └──────────┘ └─────────┘   │
└───────────────────────────────┘     └──────────────────────────────┘

Key architectural decisions

Electron multi-process isolation

Each window runs in a sandboxed renderer process. The contextBridge in the preload script exposes only a typed window.api surface — raw Node.js APIs are not accessible from renderer code.

Python subprocess engines

Both reasoning engines are Python programs launched as child processes. They communicate with the Electron main process via bridge scripts (nae-bridge.py, rlm-bridge.py) using a JSON-Lines protocol (newline-delimited JSON) over stdin/stdout. Each JSON object written to stdout by the bridge script is one message; each JSON object sent on stdin is one command. This isolates engine crashes from the UI process.

SQLite for local persistence

better-sqlite3 is used synchronously in the main process. The database lives in the OS-specific app data directory and stores sessions, skills, workflows, eval data, and non-secret settings.

OS encryption for secrets

Electron’s built-in safeStorage API encrypts all API keys and credentials. Encrypted blobs are stored in credentials.json in the OS-specific app data directory (not directly in the OS keychain as named entries). The underlying encryption uses the OS native mechanism — macOS Keychain, Windows DPAPI, or libsecret on Linux. The renderer process never receives raw key values — only masked versions for display.

Zustand for UI state

React state is managed with Zustand stores covering every major domain: chatStore, settingsStore, skillStore, workflowStore, evalStore, documentStore, mcpStore, activityStore, nativeAgentStore, rlmStore, and credentialStore. Each store communicates with the main process exclusively via the window.api IPC bridge.

Data flow for a chat message

User types message
  → Renderer dispatch → window.api.invoke('rlm:query' | 'nae:query', payload)
    → IPC main handler: validate, store message in SQLite
      → RLMManager / NAEManager: send JSON-Lines command to Python subprocess stdin
        → Python bridge: stream iterations/chunks back via stdout
          → Main process: emits 'rlm:iteration' / 'nae:chunk' IPC event
            → Renderer: Zustand store update → React re-render
              → Final answer: 'rlm:final' / 'nae:final' event → message persisted
Edit this page — Open a pull request