Skip to main content

Main Process Design

The main process is the core of Elftia, responsible for all business logic, external API calls, database operations, and security controls. The entry file is packages/desktop/app/main/index.ts.

Service Module Overview

The main process contains 45+ service modules, organized by functional domain under packages/desktop/app/main/services/:

graph LR
subgraph "Core Infrastructure"
Core[core/]
Config[config/]
Settings[settings/]
end

subgraph "Auth & Accounts"
Auth[auth/]
Account[account/]
OAuth[oauth/]
end

subgraph "AI Engines"
Engine[engine/]
Completion[completion/]
LLM[llm/]
Prompt[prompt/]
end

subgraph "Agent Orchestration"
Magi[magi/]
Agent[agent/]
Plugin[plugin/]
end

subgraph "Messaging Channels"
Channel[channel/]
Security[security/]
end

subgraph "Media & Content"
Media[media/]
Project[project/]
Search[search/]
end

Core --> Engine
Core --> Channel
Engine --> Magi
LLM --> Completion
Completion --> Engine
Magi --> Agent
Channel --> Security

Service List by Domain

DomainServiceDescription
Core (core/)LoggerServiceWinston + daily-rotate-file logging
AppPathsApplication path resolution (userData, dbPath, configDir, etc.)
RuntimeServiceRuntime information (version, channel, platform)
CacheServiceGeneral-purpose cache (TTL expiry cleanup)
ConfigManagerConfig file management
SecurityServiceAES-256-GCM encryption service
CryptoServicePBKDF2 key derivation + encrypt/decrypt
PerformanceMonitorPerformance monitoring and alerting
ErrorHandlerGlobal error handling
PluginManagerPlugin lifecycle management
DatabaseOptimizerSQLite optimization (VACUUM, ANALYZE)
CommandServiceCommand execution service
Auth (auth/)AuthServiceIPC authentication token validation
AuthSessionServiceLogin session + Deep Link handling
AuthTokenStoreAuth token persistence
TokenRefreshServiceAutomatic token refresh
Account (account/)AccountServiceUser credential management
AccountTokensServiceOAuth token management
LimitsServiceMembership limit checks
LLM (llm/)LLMConfigServiceProvider/model configuration CRUD
TransformerServiceRequest format transformation chain
Completion (completion/)CompletionServiceLLM API call entry point
ApiKeyPoolServiceMulti-key round-robin + session affinity
Engine (engine/)EngineDispatcherEngine registration and routing
ApiEngineMedia API engine
ChatEngineGeneral chat engine
ClaudeSdkEngineClaude Agent SDK engine
TinyElfEngineBuilt-in Agent engine
CliRunnerEngineCLI subprocess engine
STChatEngineRP chat pipeline engine
Magi Orchestration (magi/)MagiServiceCore message-processing service (includes assembleMagiMcps which collects Clawia's MCP set via the services/capabilities/tools/mcp-builtin/ registry; buildTinyElfDirectMcpServers is the TinyElf entry point)
MagiSessionServiceSession persistence management
MagiWorkspaceServiceWorkspace directory management
MagiSdkOptionsBuilderPrompt building (V1-V4) + user MCP injection (setUserMcpServers / getMcpServers(allowedUserMcpNames) / getDirectMcpServers) + Channel probe; built-in MCP assembly migrated out to mcp-providers/ since Phase 5.9
AgentOrchestratorAgent dispatch and orchestration
AgentDiscoverySchedulable Agent roster
MessageRouterDual-mode message routing
ChannelMagiBridgeBridge from Channel to Magi
SessionEventBusSession event bus
SessionDispatcherImplCross-session dispatch implementation
SubagentRegistrySubagent state tracking
Built-in MCP Registry (mcp-providers/)McpProviderRegistry12 built-in providers + dynamic ScriptPlugin provider factory. All built-in MCP handlers run in-process; SDK calls directly / TinyElf registers as ITool / CLI accesses via the central BuiltinMcpHttpServer HTTP bridge. The DispatchServer / ChannelServer / VisionAssistServer HTTP bridges were removed in cleanup-legacy-mcp (2026-05-19)
BuiltinMcpHttpServerCentral HTTP MCP bridge (per-session scope); all in-process MCPs are exposed to the CLI engine through it
assembleMcpForSession(ctx)Unified assembly entry (SDK + TinyElf; returns McpAssembly containing sdkServers / tinyElfServers / promptFragments / allowedTools / cleanups)
applyAssembly.tsapplyAssemblyToSdkOptions / applyAssemblyToTinyElfConfig — merges McpAssembly into engine options
contextBuilders.tsbuildAssemblyContextFor{Clawia,Agent} — constructs AssemblyContext from the caller's perspective
registerBuiltinMcpProviders.tsOne-time boot assembly point (includes ScriptPlugin same-name alias handling)
builtin/<Name>Provider.tsOne Provider module per built-in MCP (12 static + dynamic ScriptPlugin factory)
Channel (channel/)ChannelPluginLoaderPlugin discovery and loading
ChannelPluginRegistryPlugin registration and instance management
ChannelMessageRouterMessage trigger routing
ChannelMarketplaceServicePlugin marketplace
Security (security/)ExecutionFirewallFile path firewall
GuardianAgentAI tool call review
PromptGuardianPrompt injection detection
RateLimiterRate limiting
InputSanitizerInput sanitization
UserPermissionServiceUser permission management
ChannelPermissionGateChannel permission confirmation
AuditLoggerSecurity audit log
Media (media/)ImageGenerationServiceImage generation
MusicGenerationServiceMusic generation
MediaStorageServiceMedia file storage
MediaResourceServiceContent-addressed resource storage
MediaConfigServiceMedia provider configuration
AsrService / TtsServiceSpeech recognition/synthesis
Project (project/)ProjectServiceProject CRUD
FileIndexServiceFile index service
GitServiceGit operations
Search (search/)WebSearchServiceUnified search entry point
JinaProvider / TavilyProvider / SearxngProviderSearch engine adapters
UI (ui/)ThemeServiceTheme management
WindowControlsServiceWindow controls
TrayServiceSystem tray
NotificationServiceDesktop notifications
Config (config/)ConfigStoreelectron-store + fs.watch hot-reload
Cron (cron/)CronServiceCron scheduled task dispatch
MCP (mcp/)McpServiceMCP server management
Plugins (plugin/)ScriptPluginLoaderScript plugin loading
ScriptPluginRegistryScript plugin registry
ScriptPluginBridgeManagerPlugin bridge management

IPC Router Architecture

All frontend-to-backend communication goes through the IPC Router layer; 68+ router modules are currently registered.

secureHandle Pattern

Each IPC channel is wrapped with secureHandle, enforcing authentication token validation:

{/* packages/desktop/app/main/ipc/safe-handle.ts */}
function secureHandle(
channel: string,
handler: (event: IpcMainInvokeEvent, params: unknown) => Promise<unknown>,
validateToken: (token: string) => boolean,
): void;

Flow:

sequenceDiagram
participant R as Renderer
participant P as Preload
participant S as secureHandle
participant H as Router Handler

R->>P: window.api.someMethod(params)
P->>S: ipcRenderer.invoke(channel, {token, ...params})
S->>S: validateToken(token)
alt token invalid
S-->>P: throw AuthError
else token valid
S->>H: handler(event, params)
H-->>S: result
S-->>P: result
P-->>R: result
end

Route Registration

All routes are centrally registered in registerAllRouters(). Each Router class extends BaseRouter and registers its channels in the register() method:

{/* Simplified Router structure */}
class SomeRouter extends BaseRouter {
register() {
secureHandle('domain:action', async (_event, params) => {
const validated = SomeSchema.parse(params);
return this.service.doSomething(validated);
}, this.validate);
}
}

Route Categories and Counts

Router Directory/FileChannel PrefixMain Channel Count
auth/auth:*5+
account/accounts:*, accountTokens:*8+
session/sessions:*, sessionOrganizer:*10+
chat/chatMessages:*, chatAssistants:*, chatControl:*, mediaSession:*15+
completion/completion:*5+
capabilities/llm/llmProviders:*, llmModels:*, apiKeys:*, transformers:*15+
project/projects:*, files:*, git:*10+
media/media:*, imageProviders:*, musicProviders:*, videoProviders:*, asr:*, tts:*20+
settings/settings:*, appPreferences:*8+
ui/theme:*, window:*6+
MagiRoutermagi:*10+
ChannelPluginRouterchannels:*8+
sillytavern/characterCards:*, worldInfo:*, regexScripts:*, groupChat:*, etc.25+
Othersmcp:*, webSearch:*, todo:*, tasks:*, notes:*, tags:*, cron:*, elfi:*, etc.30+

Worker Thread Pattern

Time-consuming I/O operations run in Worker Threads to avoid blocking the main thread.

DbClient — Database Worker

DbClient is the most critical Worker, encapsulating all interactions with the SQLite database:

sequenceDiagram
participant S as Service
participant C as DbClient
participant W as db.worker.ts
participant D as SQLite (WAL)

S->>C: db.someMethod(params)
C->>C: seq++, create Promise
C->>W: postMessage({id: seq, method, params})
W->>D: Execute SQL query
D-->>W: Result
W-->>C: postMessage({id: seq, result})
C->>C: resolve(pending[seq])
C-->>S: Promise<result>
{/* Simplified DbClient RPC pattern */}
class DbClient extends EventEmitter implements DbRpc {
private worker: Worker;
private seq = 0;
private pending = Map<number, {resolve, reject}>;

async send(method: string, params: unknown): Promise<unknown> {
const id = ++this.seq;
return new Promise((resolve, reject) => {
this.pending.set(id, { resolve, reject });
this.worker.postMessage({ id, method, params });
});
}

async ready(): Promise<void>;
}

Key design points:

  • Async RPC: Each call is assigned a unique sequence number; requests are sent via postMessage
  • Ready wait: await db.ready() ensures the Worker has finished initialization (schema creation) before use
  • Error forwarding: Worker errors bubble up through EventEmitter

Worker Thread List

Worker FilePurposeCommunication
db.worker.tsSQLite database read/write (100+ RPC methods)DbClient RPC
fileSearch.worker.tsFile search (fuzzy match)MessagePort
fileWatcher.worker.tsFilesystem watching (chokidar)MessagePort
mcp.worker.tsMCP server process managementMessagePort
diagnostics.worker.tsSystem diagnostic data collectionMessagePort
project.worker.tsProject file index buildingMessagePort
fileSnapshot.worker.tsFile snapshot diffingMessagePort

Key Infrastructure

LoggerService

Based on Winston, supports log level filtering and automatic log file rotation:

{/* Simplified LoggerService interface */}
class LoggerService {
info(message: string, meta?: Record<string, unknown>): void;
warn(message: string, err?: Error, meta?: Record<string, unknown>): void;
error(message: string, err?: unknown, meta?: Record<string, unknown>): void;
debug(message: string, meta?: Record<string, unknown>): void;
}
  • Log location: {userData}/logs/
  • Rotation policy: daily-rotate-file, retained for 14 days
  • Format: JSON + timestamp

SecurityService / CryptoService

{/* Core encryption service interface */}
class SecurityService {
encrypt(plaintext: string): string;
decrypt(ciphertext: string): string;
}

class CryptoService {
deriveKey(password: string, salt: Buffer): Buffer;
encrypt(data: string, key: Buffer): EncryptedData;
decrypt(data: EncryptedData, key: Buffer): string;
}

AppPaths

Centrally manages all application directory paths:

PropertyPathPurpose
userData{appData}/elftia/Application data root
dbPath{userData}/elftia.dbSQLite database
configDir{userData}/config/Config file directory
resourcesDir{userData}/resources/Media resources
diagnosticsDir{userData}/diagnostics/Diagnostic data
logsDir{userData}/logs/Log files
FileDescription
packages/desktop/app/main/index.tsMain process entry; all service creation and startup
packages/desktop/app/main/services/routers/index.tsRoute registration center, registerAllRouters()
packages/desktop/app/main/services/routers/BaseRouter.tsRouter base class
packages/desktop/app/main/ipc/safe-handle.tssecureHandle IPC security wrapper
packages/desktop/app/main/workers/DbClient.tsDatabase Worker client
packages/desktop/app/main/workers/db.worker.tsDatabase Worker implementation
packages/desktop/app/main/workers/types.tsWorker RPC type definitions
packages/desktop/app/main/services/infra/logger/LoggerService.tsLogging service
packages/desktop/app/main/services/platform/security/SecurityService.tsEncryption service
packages/desktop/app/main/services/infra/paths/paths.tsPath management
packages/desktop/app/main/services/agent-core/engine/EngineDispatcher.tsEngine dispatcher