Startup Phases
Elftia's main process startup flow is divided into 13 phases, each with clearly defined responsibilities and dependencies. All phase code is located in the createServices() function within packages/desktop/app/main/index.ts.
Startup Phase Overview
flowchart TB
P0[Phase 0: Protocol Registration + Logger]
P1[Phase 1: Core Services]
P2[Phase 2: Feature Services]
P25[Phase 2.5: Package Loading]
P26[Phase 2.6: Package Sync]
P27[Phase 2.7: Package Management Service]
PS1[Phase S1: Magi Dispatch]
PS2[Phase S2: MagiService]
PS25[Phase S2.5: CronService]
PC1[Phase C1: Channel Plugins]
PS3[Phase S3: ChannelMagiBridge]
P3[Phase 3: IPC Route Registration]
PT[Phase T1-T3: Todo/Task/Notes]
PW1[Phase W1: Window Creation]
PW2[Phase W2: WebSocket/Protocol]
P0 --> P1 --> P2 --> P25 --> P26 --> P27
P27 --> PS1 --> PS2 --> PS25
PS25 --> PC1 --> PS3
PS3 --> P3 --> PT --> PW1 --> PW2
Phase 0: Protocol Registration + Logger
Executes before app.whenReady(). Registers custom protocols and the core logging service.
flowchart LR
A[Register elftia:// protocol] --> B[Register privileged schemes]
B --> C[wallpaper://]
B --> D[media://]
B --> E[resource://]
| Step | Action | Description |
|---|---|---|
| 1 | app.setAsDefaultProtocolClient('elftia') | Register Deep Link protocol |
| 2 | protocol.registerSchemesAsPrivileged() | Register wallpaper/media/resource privileged protocols |
| 3 | new LoggerService() | Create logging service (Winston + daily-rotate) |
| 4 | setIpcLogger(logger) | Set IPC layer logger |
| 5 | new ErrorHandler(logger) | Create global error handler |
| 6 | setIpcErrorHandler(errorHandler) | Set IPC layer error handler |
| 7 | new AutoUpdateService(logger) | Create auto-update service |
| 8 | new DownloadService(logger) | Create download service |
| 9 | new WebUpdateService(logger, downloadManager) | Create web update service |
Key files: packages/desktop/app/main/services/infra/logger/LoggerService.ts, packages/desktop/app/main/services/infra/error/ErrorHandler.ts
Phase 1: Core Services
Creates the foundational services required for the application to run; these services have no external dependencies.
flowchart LR
RT[RuntimeService] --> AP[AppPaths]
AP --> DB[DbClient + ready]
DB --> SS[SettingsService]
SS --> CS[ConfigStore]
CS --> Migrate[Config Migration]
Migrate --> Auth[Auth Service Chain]
Auth --> WC[WindowControlsService]
| Step | Service | Dependencies | Description |
|---|---|---|---|
| 1 | RuntimeService | None | Runtime information (version, platform, channel) |
| 2 | AppPaths | None | Path resolution |
| 3 | DbClient | AppPaths | Database Worker creation |
| 4 | await db.ready() | DbClient | Blocking wait until Worker initialization completes |
| 5 | initDatabase() | db.ready | Drizzle ORM + WAL + migrate |
| 6 | SettingsService | AppPaths | Application settings |
| 7 | ConfigStore | AppPaths | electron-store config file |
| 8 | migrateSettingsToConfigStore() | Settings, ConfigStore | SQLite -> config.json migration |
| 9 | settings.setConfigStore(configStore) | - | Delegate pattern connection |
| 10 | configStore.startWatching() | - | fs.watch config hot-reload |
| 11 | AccountService | AppPaths | User credentials |
| 12 | AuthTokenStore | AccountService | Token storage |
| 13 | AuthSessionService | Runtime, AuthStore, AuthApi | Auth session |
| 14 | AppPreferencesService | ConfigStore | Application preferences |
| 15 | WindowControlsService | Settings | Window controls |
Key point: await db.ready() is the blocking point that ensures the SQLite Worker has finished creating the schema before proceeding. This prevents the SQLITE_BUSY error that occurs when two connections write simultaneously on first startup.
Phase 2: Feature Services
Creates lightweight feature services; heavy initialization is deferred until use.
| Service | Dependencies | Description |
|---|---|---|
ConfigManager | Logger, AppPaths | Config file management |
SecurityService | Logger | AES-256-GCM encryption |
PerformanceMonitor | Logger | Performance monitoring |
CacheService | Logger | General-purpose cache |
DatabaseOptimizer | Logger, DbClient | SQLite optimization |
NotificationService | Logger | Desktop notifications |
PluginManager | Logger | Plugin lifecycle |
ProjectService | DbClient, AccountService | Project management |
DiagnosticsService | AppPaths, ProjectService | Diagnostics service |
ChatService | Logger | Chat service |
McpService | Logger | MCP server management |
LLMConfigService | AppPaths, DbClient | LLM configuration |
CompletionService | AppPaths, LLMConfigService | LLM completion |
ApiKeyPoolService | DB, CompletionService | Multi-key round-robin |
ElfiService | ConfigStore, LLMConfig, Completion | Elfi assistant |
MediaConfigService | AppPaths, DbClient | Media configuration |
ImageGenerationService | MediaConfig | Image generation |
ThemeService | Settings, AuthSession | Theme management |
AgentService | DbClient, Settings, Logger | Agent service |
TransformerService | - | Request transformation |
WebSearchService | - | Web search |
EngineDispatcher + engine registration | Each engine's dependencies | Engine dispatch |
Engine Registration Order
{/* Engine registration */}
engineDispatcher.registerEngine(new ApiEngine(...));
engineDispatcher.registerEngine(new ChatEngine(...));
engineDispatcher.registerEngine(new ClaudeSdkEngine(...));
engineDispatcher.registerEngine(new CliRunnerEngine(...));
engineDispatcher.registerEngine(new TinyElfEngine(...));
engineDispatcher.registerEngine(new STChatEngine(...));
Phase 2.5: Package Loading
Loads Agent packages, Persona packages, and Script plugins from disk into memory.
flowchart TB
subgraph "Phase 2.5a: Agent Packages"
AL[AgentPackageLoader] --> BP[Load builtin packages]
BP --> UP[Load user packages]
UP --> AR[AgentPackageRegistry]
end
subgraph "Phase 2.5b: Persona Packages"
PL[PersonaPackageLoader] --> BPP[Load builtin packages]
BPP --> UPP[Load user packages]
UPP --> PR[PersonaPackageRegistry]
end
subgraph "Phase 2.5c: Script Plugins"
SL[ScriptPluginLoader] --> BSP[Load builtin plugins]
BSP --> USP[Load user plugins]
USP --> SR[ScriptPluginRegistry]
end
Load paths (dual-directory pattern):
| Package Type | Builtin Path (packaged) | Builtin Path (dev) | User Path |
|---|---|---|---|
| Agent | resources/agent-packages/ | ../../agent-packages/ | {userData}/agent-packages/ |
| Persona | resources/persona-packages/ | ../../persona-packages/ | {userData}/persona-packages/ |
| Script | resources/script-plugins/ | ../../script-plugins/ | {userData}/script-plugins/ |
Phase 2.6-2.7: Package Sync and Management
| Phase | Action | Description |
|---|---|---|
| 2.6 | PackageSyncService.syncAll() | Syncs in-memory package definitions to the database (upsert FK anchors + persona data) |
| 2.7 | new AgentPackageService(...) | Creates the package management service, supporting install/uninstall |
Phase S1: Magi Dispatch
Creates services related to Agent orchestration and dispatch.
flowchart TB
AGM[AgentManagementService] --> EB[SessionEventBus]
EB --> AD[AgentDiscovery]
AD --> AO[AgentOrchestrator]
AO --> MR[MessageRouter]
MR --> SDI[SessionDispatcherImpl]
AO -.->|setEngineDispatcher| ED[EngineDispatcher]
AO -.->|setDbProvider| DB[DbClient]
SDI -.->|injected into| TE[TinyElfEngine]
| Step | Service | Description |
|---|---|---|
| 1 | AgentManagementService | Agent CRUD + package registry queries |
| 2 | InternalSessionEventBus | Session event publish/subscribe |
| 3 | AgentDiscovery | Schedulable Agent roster (discovered from DB + package registry) |
| 4 | AgentOrchestrator | Agent dispatch and orchestration; API dispatch handler |
| 5 | MessageRouter | Dual-mode message routing (direct / via Orchestrator) |
| 6 | SessionDispatcherImpl | Cross-session dispatch implementation |
| 7 | Injected into TinyElfEngine | tinyElfEngine.setSessionDispatcher(sessionDispatcher) |
| 8 | SubagentRegistry.setDb(db) | Subagent registry write-through persistence |
Historical note: The
DispatchServerHTTP dispatch server was removed incleanup-legacy-mcp(2026-05-19). Thedispatchtool is now assembled in-process viaMagiDispatchProvider+MagiDispatchHandlersthrough the unified registry; handlers callAgentOrchestrator/AgentDiscoverydirectly without going through HTTP. CLI subprocesses access it through the centralservices/capabilities/tools/mcp-builtin/BuiltinMcpHttpServer.ts.
Phase S2: MagiService
Creates the core message-processing service and injects all dependencies.
{/* MagiService dependency injection chain */}
const magiService = new MagiService(
magiWorkspace, magiSession, magiOptionsBuilder,
magiOrchestrator, magiMessageRouter, magiEventBus,
llmConfig, logger, configStore,
);
// Deferred injection (avoids circular dependencies)
magiService.setAgentService(agent);
magiService.setCompletionService(completion);
magiService.setAsrService(new AsrService(asrConfig));
magiService.setEngineDispatcher(engineDispatcher);
magiService.setDbProvider(db);
magiService.setMcpListProvider(() => mcp.list());
magiService.setDeveloperModeProvider(() => ...);
MCP hot-reload: When the MCP server list changes, it automatically syncs to the Magi workspace and hot-reloads active sessions:
mcp.on('servers:changed', () => {
magiService.syncMcpServersToWorkspace();
magiService.notifyMcpChanged();
});
Phase S2.5: CronService
Creates the Cron scheduled task service:
| Injection | Purpose |
|---|---|
setNotificationService(notification) | Desktop notifications |
setNotificationChecker(...) | Check notification preferences |
setAgentExecutor(...) | Execute scheduled tasks via MagiService |
setStatusBroadcaster(...) | Broadcast task status to the frontend |
Phase C1: Channel Plugins
Discovers, loads, and registers Channel plugins.
flowchart TB
CL[ChannelPluginLoader] --> Discover[discover plugins]
Discover --> Load[load each plugin]
Load --> CR[ChannelPluginRegistry.registerPlugin]
CR --> CM[ChannelMarketplaceService<br/>async fetch remote manifest]
subgraph "Security Service Wiring"
UPS[UserPermissionService] --> CMR[ChannelMessageRouter]
CPG[ChannelPermissionGate] --> CMR
AL[AuditLogger] --> TE[TinyElfEngine]
RL[RateLimiter] --> CMR
IS[InputSanitizer] --> CMR
PG[PromptGuardian] --> CMR
end
CR --> CMR
Load paths (dual-directory pattern):
| Source | Path (packaged) | Path (dev) |
|---|---|---|
| Bundled (read-only) | resources/bundled-channel-plugins/ | elftia-channels/dist-plugins/ |
| Marketplace (writable) | {userData}/channel-plugins/ | same |
Security service wiring order:
UserPermissionService-> ChannelMessageRouter (user permission check)ChannelPermissionGate-> ChannelMessageRouter + TinyElfEngine (permission confirmation)AuditLogger-> TinyElfEngine (audit logging)RateLimiter-> ChannelMessageRouter (rate limiting)InputSanitizer-> ChannelMessageRouter (input sanitization)PromptGuardian-> ChannelMessageRouter (prompt injection detection)
Phase S3: ChannelMagiBridge
Bridges Channel events to MagiService:
const channelMagiBridge = new ChannelMagiBridge(
channelRegistry,
channelMessageRouter,
magiService,
logger,
);
Historical note: The
ChannelServerHTTP server was removed incleanup-legacy-mcp(2026-05-19). Thechannel_send/channel_listand other tools are now assembled in-process viaMagiChannelProvider+MagiChannelHandlersthrough the unified registry; handlers callChannelPluginRegistry/ChannelMessageRouterdirectly.
Phase 3: IPC Route Registration
All IPC handlers are registered before window creation.
const router = new Router(/* 60+ service dependencies */);
const { skillHubDownloadService } = router.register();
registerAllRouters() internally creates and registers 68+ Router instances:
| Router Class | Channel Prefix |
|---|---|
AuthRouter | auth:* |
ProjectRouter | projects:* |
SessionRouter | sessions:* |
CompletionRouter | completion:* |
LLMRouter | llmProviders:*, llmModels:* |
MediaRouter | media:* |
MagiRouter | magi:* |
ChannelPluginRouter | channels:* |
SecurityRouter | security:* |
TodoRouter | todo:* |
TaskRouter | tasks:* |
NoteRouter | notes:* |
SubagentRouter | subagents:* |
ElfiRouter | elfi:* |
EnvironmentRouter | env:* |
| ... and 50+ other routers | ... |
Phase T1-T3: Todo/Task/Notes
flowchart LR
T1[Phase T1: TodoService] --> T1a[initialize — load TODO.md]
T2[Phase T2: TaskService] --> T2a[initialize — scan tasks]
T3[Phase T3: NoteFileService + NoteWatcher] --> T3a[ensureVaultDir]
T3a --> T3b[scanAllNotes + reindex]
T3b --> T3c[DB notes migration check]
| Phase | Service | Action |
|---|---|---|
| T1 | TodoService | Initialize the Markdown to-do system |
| T2 | TaskService | Initialize filesystem + SQLite task management |
| T3 | NoteFileService | Ensure vault directory exists |
| T3 | NoteWatcher | Filesystem watcher |
| T3 | Notes migration | One-time migration from DB to .md files |
| T3 | Startup reindex | Scan all notes and update DB index |
Phase W1: Window Creation
Creates the BrowserWindow after all services and IPC routes are registered:
mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: preloadPath,
},
});
Phase W2: WebSocket/Protocol Handling
Post-window-creation initialization:
| Step | Action |
|---|---|
| 1 | Register wallpaper:// protocol handler |
| 2 | Register media:// protocol handler |
| 3 | Register resource:// protocol handler |
| 4 | Load renderer URL (dev server or file://) |
| 5 | Process pending Deep Links |
| 6 | Restore Channel instances (autoConnect) |
| 7 | Content Security Policy configuration |
| 8 | Navigation interception (isAllowedNavigation) |
Startup Timing
createServices() records timing at key phases:
[INFO] Core services initialized { duration: ~50ms }
[INFO] Feature services created { duration: ~200ms }
[INFO] Agent packages loaded { builtin: N, user: M }
[INFO] Persona packages loaded { builtin: N, user: M }
[INFO] Script plugins loaded { builtin: N, user: M }
[INFO] Magi dispatch services created (Phase S1)
[INFO] MagiService created (Phase S2)
[INFO] ChannelMagiBridge created (Phase S3)
[INFO] IPC handlers registered { duration: ~500ms }
Related Files
| File | Description |
|---|---|
packages/desktop/app/main/index.ts | Main process entry; createServices() function |
packages/desktop/app/main/services/routers/index.ts | registerAllRouters() route registration |
packages/desktop/app/main/db/index.ts | initDatabase() database initialization |
packages/desktop/app/main/workers/DbClient.ts | Database Worker client |
packages/desktop/app/main/services/infra/logger/LoggerService.ts | Logging service |
packages/desktop/app/main/services/infra/paths/paths.ts | AppPaths path management |
packages/desktop/app/main/services/platform/config/store/ConfigStore.ts | Config store + fs.watch |
packages/desktop/app/main/services/agent-core/agent/AgentPackageLoader.ts | Agent package loader |
packages/desktop/app/main/services/agent-core/agent/PersonaPackageLoader.ts | Persona package loader |
packages/desktop/app/main/services/capabilities/tools/script-plugin/ScriptPluginLoader.ts | Script plugin loader |
packages/desktop/app/main/services/capabilities/integrations/channel/ChannelPluginLoader.ts | Channel plugin loader |
packages/desktop/app/main/services/agent-core/magi/MagiService.ts | Magi core service |
packages/desktop/app/main/services/agent-core/magi/AgentOrchestrator.ts | Agent dispatch orchestrator |
packages/desktop/app/main/services/platform/cron/CronService.ts | Scheduled task service |
packages/desktop/app/main/services/content/workspace/todo/TodoService.ts | To-do service |
packages/desktop/app/main/services/content/workspace/tasks/TaskService.ts | Task management service |
packages/desktop/app/main/services/content/workspace/notes/NoteFileService.ts | Note file service |