This document explains how Joanium is assembled, what runs in each process, and how the main surfaces connect to providers, tools, integrations, and local state.
Joanium is a local-first Electron desktop app composed from discoverable workspace packages. It is not organized around one large app file or one manual registry. The boot layer discovers package contributions, builds the runtime context, starts engines, registers IPC, then lets the renderer discover and mount pages.
The architecture is built around five composition points:
- Workspace package discovery from the root
package.json - Feature manifests loaded by
FeatureRegistry - Engine metadata loaded from
*Engine.js - IPC modules loaded from
*IPC.js - Page manifests loaded from
Page.js
| Layer | Main location | Responsibility |
|---|---|---|
| Electron entry | App.js |
App lifecycle, runtime directories, first window, auto-updates, boot handoff |
| Main process | Packages/Main |
Discovery, engine assembly, services, IPC, paths, window creation |
| Platform features | Packages/Features |
AI calls, agents, automations, channels, connectors, MCP, skills |
| Capability packages | Packages/Capabilities |
External integrations, free connectors, chat tools, automation hooks, prompt context |
| Renderer shell | Packages/Renderer |
Page discovery, sidebar, modals, navigation, channel/agent gateways |
| Renderer pages | Packages/Pages |
Chat, setup, automations, agents, skills, personas, marketplace, events, usage |
| Shared system | Packages/System |
Contracts, prompt assembly, state, utility helpers |
| Local state | Config, Data, Memories, Skills, Personas, Instructions |
User-owned and runtime-owned local files |
Startup begins in App.js.
- Electron flags are set, including HTTP/2 disablement and the app user agent.
- External navigation is guarded so external URLs open in the system browser.
- Runtime directories are created under the current state root.
- A first window is created immediately. It loads setup on first run and chat otherwise.
- Content libraries are initialized.
- Personal memory files are initialized.
boot()fromPackages/Main/Boot.jsruns discovery and engine assembly.- Engines are started.
- Existing windows receive attached services and a
backend-readyevent. - The system prompt is warmed in the background.
- MCP auto-connect runs in the background.
- Shutdown stops engines before the app quits.
Packages/Main/Boot.js is the main assembly point.
It performs these steps:
- Loads feature manifests from
FEATURE_DISCOVERY_ROOTS - Discovers engines from
ENGINE_DISCOVERY_ROOTS - Builds feature and engine storage handles with
createFeatureStorageMap - Creates the base boot context containing paths, feature registry, storage, user service, and prompt invalidation
- Instantiates engines in dependency order using their
needsandprovides - Sets feature registry base context so features can access connectors, storage, paths, and events
- Runs feature lifecycle hooks such as
onBoot - Discovers and registers IPC modules, injecting requested services and engines
- Returns the runtime context used by
App.js
Discovery starts in Packages/Main/Core/WorkspacePackages.js.
The root package.json declares workspace globs:
["Core/Electron", "Packages/*", "Packages/Capabilities/*", "Packages/Features/*"]Each workspace package can declare a joanium.discovery section:
{
"joanium": {
"discovery": {
"features": ["./Core"],
"engines": ["./Core"],
"ipc": ["./IPC"],
"pages": ["."],
"services": ["./Services"]
}
}
}DiscoveryManifest.js collects existing roots for each discovery kind. The rest
of the app consumes those frozen root lists.
Packages/Capabilities/Core/FeatureRegistry.js loads all discovered
Feature.js files, validates feature IDs, topologically sorts features by
dependsOn, and indexes what each feature contributes.
A feature can contribute:
- Service connectors for setup and settings
- Free connectors that are enabled by default or require only optional keys
- Chat tools exposed to the renderer-side agent loop
- Automation data sources
- Automation output types
- Automation instruction templates
- Feature-owned pages
- Prompt context sections
- Lifecycle hooks
- Storage descriptors
- Main-process methods invoked through
feature:invoke
This is why one integration package can show up in many product surfaces at once. For example, GitHub contributes connector setup, chat tools, prompt context, automation data sources, and automation outputs from one feature package.
Engines are long-lived runtime systems discovered from files ending in
Engine.js. Engine metadata is normalized through defineEngine.
Current engines:
| Engine | Provides | Storage key | Main responsibility |
|---|---|---|---|
connectors |
connectorEngine |
connectors |
Connector credential state and enabled/free connector state |
automation |
automationEngine |
automations |
Scheduled job execution, data collection, AI calls, outputs, history |
agents |
agentsEngine |
agenticAgents |
Scheduled autonomous prompts, renderer dispatch, run history |
channels |
channelEngine |
channels, channelMessages |
Telegram, WhatsApp, Discord, and Slack polling/replies |
Engines may depend on context values using needs. EngineAssembly.js
instantiates them only when their dependencies are available.
Main-process IPC modules are discovered from *IPC.js files. Each module exports
register(...), and optionally ipcMeta.needs to request dependencies from the
boot context.
Core/Electron/Bridge/Preload.js exposes two renderer bridges:
window.electronAPIfor generic IPC calls, sends, event listeners, PTY events, browser preview events, and update eventswindow.featureAPIfor feature boot payloads, feature method invocation, and feature event subscriptions
The renderer should not import main-process services directly. Use IPC.
The renderer starts in Packages/Renderer/Application/Main.js.
It:
- Loads feature boot payloads
- Registers feature-contributed pages
- Discovers built-in pages from the main process
- Builds the page map and sidebar navigation
- Initializes settings, about, library, and projects modals
- Starts channel and scheduled-agent gateways
- Opens a fresh chat
- Handles keyboard shortcuts
PagesManifest.js merges built-in pages and feature pages, then sorts them by
page order.
| Page | Purpose |
|---|---|
| Chat | Main AI workspace, model switching, project context, tools, files, history |
| Setup | First-run profile and provider setup |
| Automations | Scheduled data-driven jobs |
| Agents | Scheduled autonomous prompts |
| Skills | Local skill library controls |
| Marketplace | Remote skills and personas browser/install flow |
| Personas | Persona activation and library management |
| Events | Background execution history |
| Usage | Local usage analytics |
The chat page is the main orchestration surface and is reused by scheduled agents and channel replies.
The main loop in Packages/Pages/Chat/Features/Core/Agent.js:
- Resolves selected provider/model and fallback candidates
- Loads enabled skills and matches relevant skills by trigger text
- Loads workspace/project summary when a project is active
- Builds a prompt from system instructions, persona, skills, workspace state, connected service context, memory policy, and tool policy
- Loads available tools from built-ins, feature-contributed tools, and MCP servers
- Filters tools by connected connector state and active workspace state
- Streams model responses
- Handles single and parallel tool calls
- Supports tool category expansion through
request_tool_categories - Supports model failover and rate-limit backoff
- Guards potentially irreversible browser actions with confirmation
- Tracks usage and returns final text to the UI
Packages/Features/AI/index.js handles provider-specific request formatting and
stream parsing.
Current provider catalogs live in Config/Models:
- Anthropic
- OpenAI
- OpenRouter
- Mistral
- NVIDIA
- DeepSeek
- MiniMax
- Groq
- xAI
- Cohere
- Together
- Perplexity
- Cerebras
- Ollama
- LM Studio
Anthropic, Google, and OpenAI-compatible providers have separate message/tool formatters. Ollama and LM Studio use local OpenAI-compatible endpoints and can discover local models at runtime.
Capability packages live under Packages/Capabilities.
Current service connectors:
- Cloudflare
- Figma
- GitHub
- GitLab
- Google Workspace and its service extensions
- HubSpot
- Jira
- Linear
- Netlify
- Notion
- Sentry
- Spotify
- Stripe
- Supabase
- Vercel
Google Workspace is a root feature. Calendar, Contacts, Docs, Drive, Forms,
Gmail, Photos, Sheets, Slides, Tasks, and YouTube extend it through service
extensions and dependsOn: ["google-workspace"].
Free connectors are defined in Packages/Capabilities/FreeConnectors/Feature.js.
Some require no key, while NASA, FRED, OpenWeatherMap, and Unsplash require API
keys.
Packages/Main/Core/Paths.js controls state and bundled resource roots.
- Development state root: repo root
- Packaged state root: Electron
app.getPath("userData") - Development bundled root: repo root
- Packaged bundled root:
process.resourcesPath
See Data-And-Persistence.md before changing state paths, seed libraries, storage descriptors, or prompt-related data.
- Development mode writes runtime state into the repo root.
- Chat orchestration changes affect chat, scheduled agents, channels, tools, MCP, memory, and usage tracking.
- Feature storage keys must be globally unique across features and engines.
- Discovery relies on naming conventions:
Feature.js,*Engine.js,*IPC.js,Page.js,*Service.js. - Connector-specific behavior can span setup UI, connector state, prompt context, chat tools, and automation hooks.
- Minification is in-place during production builds. Keep source readable in Git and verify build scripts carefully.