본문으로 건너뛰기

Agent 엔진 개요

Elftia의 Agent 시스템은 멀티 엔진 디스패치 아키텍처를 기반으로 합니다. EngineDispatcher는 통합 요청 디스패처로 동작하며, EngineType에 따라 요청을 해당 엔진 구현으로 라우팅합니다. 각 엔진은 IEngine 인터페이스를 구현하고 서로 다른 백엔드 서비스를 캡슐화합니다.

아키텍처 다이어그램

graph TB
Router["AgentRouter / MagiService"] --> Dispatcher["EngineDispatcher"]

Dispatcher --> TinyElf["TinyElfEngine<br/>tinyelf"]
Dispatcher --> ClaudeSdk["ClaudeSdkEngine<br/>claude-sdk"]
Dispatcher --> CliRunner["CliRunnerEngine<br/>cli"]
Dispatcher --> Chat["ChatEngine<br/>chat"]
Dispatcher --> STChat["STChatEngine<br/>st-chat"]
Dispatcher --> Api["ApiEngine<br/>api"]

TinyElf --> TinyLoop["TinyElfAgentLoop<br/>Tool call loop"]
TinyElf --> ToolReg["ToolRegistry<br/>Tool registry"]
TinyElf --> LLMAdapter["TinyElfLLMAdapter<br/>LLM adapter"]

ClaudeSdk --> AgentSvc["AgentService<br/>Claude Agent SDK"]
ClaudeSdk --> Proxy["AgentProxyServer<br/>HTTP proxy"]

CliRunner --> Supervisor["ProcessSupervisor<br/>Process management"]

Chat --> Pipeline["ChatCompletionPipeline<br/>Chat pipeline"]

IEngine 인터페이스

모든 엔진은 IEngine 인터페이스를 구현해야 합니다.

interface IEngine {
readonly engineType: EngineType;
startSession(ctx: EngineSessionContext): Promise<void>;
resumeSession(ctx: EngineSessionContext): Promise<void>;
interrupt(dbSessionId: string): Promise<boolean>;
isActive(dbSessionId: string): boolean;
reloadMcpForAllActive?(): Promise<void>;
}
MethodDescription
startSession새 세션 시작(사용자 메시지 저장, 엔진 루프 실행)
resumeSession기존 세션에서 대화 계속 진행
interrupt활성 세션을 중단하고 성공 여부 반환
isActive세션이 실행 중인지 확인
reloadMcpForAllActive모든 활성 세션의 MCP 도구를 핫 리로드(선택 사항)

EngineSessionContext

모든 엔진은 동일한 세션 컨텍스트 구조를 공유합니다. 각 엔진은 필요한 필드를 사용합니다.

interface EngineSessionContext {
dbSessionId: string; // Database session ID
session: ChatSession; // Full session record
sender: WebContents; // Electron IPC sender
prompt: string; // User message
attachments?: Array<...>; // Multimodal attachments
providerId?: string; // LLM provider ID
model?: string; // Model ID (v89+ is bare SDK id)
/**
* v89+: CLI backend binding (from chat_sessions.cliBackend column).
* 'claude-code' goes through ClaudeSdkEngine + AgentProxyServer.passThrough;
* 'codex' / 'gemini-cli' goes through CliRunnerEngine.
*/
cliBackend?: 'claude-code' | 'codex' | 'gemini-cli' | null;
/**
* v89+: 1M-context preference (from chat_sessions.useExtendedContext column).
* Engines / AgentProxyServer / TransformerChainExecutor decide whether to
* inject 'context-1m-2025-08-07' into anthropic_beta based on this.
*/
useExtendedContext?: boolean;
engineConfig?: EngineConfig; // Engine-specific configuration
projectPath?: string; // Project path
agentId?: string; // Agent ID

// Chat engine specific
systemPrompt?: string;
parentId?: string | null;
thinkingLevel?: string;
searchOrchestration?: PipelineSearchConfig;
mcpConfig?: PipelineMcpConfig;

// Magi/Agent engine specific
providerEnv?: Record<string, string>;
onSessionEnd?: () => void;
isOfficialProvider?: boolean;
customAgentOptions?: { ... };
permissionMode?: string;
channelSource?: string;
channelId?: string;
channelSenderId?: string;
channelUserPermissions?: { canUseTool: boolean; requireConfirmation: boolean };
}

EngineDispatcher

EngineDispatcher는 엔진 등록과 디스패치의 핵심입니다.

class EngineDispatcher {
private engines = new Map<EngineType, IEngine>();

registerEngine(engine: IEngine): void;
getEngine(type: EngineType): IEngine;
hasEngine(type: EngineType): boolean;
dispatch(type: EngineType, ctx: EngineSessionContext): Promise<void>;
resume(type: EngineType, ctx: EngineSessionContext): Promise<void>;
interrupt(dbSessionId: string): Promise<boolean>;
reloadMcpForAllActive(): Promise<void>;
getEngineInfos(): EngineInfo[];
}

엔진 등록

엔진은 packages/desktop/app/main/index.ts에서 등록됩니다.

const dispatcher = new EngineDispatcher();
dispatcher.registerEngine(new TinyElfEngine(...));
dispatcher.registerEngine(new ClaudeSdkEngine(...));
dispatcher.registerEngine(new CliRunnerEngine(getProcessSupervisor(), db, logger));
dispatcher.registerEngine(new ChatEngine(...));
dispatcher.registerEngine(new STChatEngine(...));
dispatcher.registerEngine(new ApiEngine(...));

중단 메커니즘

interrupt() 메서드는 모든 엔진을 순회하면서 대상 세션을 소유한 엔진을 찾고 해당 세션을 중단합니다.

async interrupt(dbSessionId: string): Promise<boolean> {
for (const engine of this.engines.values()) {
if (engine.isActive(dbSessionId)) {
return engine.interrupt(dbSessionId);
}
}
return false;
}

엔진 타입 레지스트리

EngineTypeClassBackend ServiceActive Session Tracking
tinyelfTinyElfEngine내장 Agent 루프Map<string, ActiveTinyElfSession>
claude-sdkClaudeSdkEngineAgentService (SDK)AgentService 내부 관리
cliCliRunnerEngineProcessSupervisorMap<string, ActiveCliSession>
chatChatEngineChatCompletionPipeline내부 Map
st-chatSTChatEngineCompletionService내부 Map
apiApiEngineCompletionService상태 없음

활성 세션 추적

TinyElf 엔진은 ActiveTinyElfSession을 사용해 각 활성 세션의 상태를 추적합니다.

interface ActiveTinyElfSession {
dbSessionId: string;
projectPath: string;
abortController: AbortController;
parentMessageId?: string;
lastAssistantMessageId?: string;
directMcpConnections?: Array<...>;
hookExecutor?: HookExecutor;
sdkDualWrite?: {
sdkSessionId: string;
lastUuid: string;
seqNum: number;
};
subagentManager?: SubagentManager;
}

IPC 이벤트

엔진은 sender.send('agent:event', ...)를 통해 렌더러 프로세스에 이벤트를 보냅니다.

Event TypePayloadDescription
init{ sessionId, systemInfo }세션 초기화
sessionCreated{ sessionId }세션 생성 완료
userMessage{ sessionId, message }사용자 메시지 저장됨
streamDelta{ sessionId, delta }스트리밍 텍스트/사고 델타
toolCallStart{ sessionId, block }도구 호출 시작
toolCallEnd{ sessionId, block, status }도구 호출 완료
assistantMessage{ sessionId, message }Assistant 메시지 저장됨
permissionRequest{ sessionId, requestId, toolName, input }권한 확인 요청
result{ sessionId, stats }실행 통계 결과
complete{ sessionId }세션 완료
error{ sessionId, error }오류 이벤트
retry{ sessionId, retry }API 재시도 알림
processing{ sessionId, message }처리 상태

주요 파일

FilePathDescription
IEngine interfaceservices/agent-core/engine/types.ts엔진 인터페이스와 컨텍스트 정의
EngineDispatcherservices/agent-core/engine/EngineDispatcher.ts엔진 등록과 디스패치
Module entryservices/agent-core/engine/index.ts모든 엔진 내보내기
TinyElfEngineservices/agent-core/engine/tinyelf/TinyElfEngine.tsTinyElf 엔진 구현
ClaudeSdkEngineservices/agent-core/engine/ClaudeSdkEngine.tsClaude SDK 엔진
CliRunnerEngineservices/agent-core/engine/cli/CliRunnerEngine.tsCLI 엔진
ChatEngineservices/agent-core/engine/ChatEngine.tsChat 엔진
STChatEngineservices/agent-core/engine/STChatEngine.ts단일 턴 Chat 엔진
ApiEngineservices/agent-core/engine/ApiEngine.tsAPI 엔진
EngineType definition@shared/contracts/elftia-agent-types.ts타입 정의

모든 경로는 packages/desktop/app/main/을 기준으로 합니다.

확장 지점

  • 새 엔진 추가: IEngine 인터페이스 구현 + EngineDispatcher에 등록 + EngineType 추가
  • 새 IPC 이벤트 추가: 엔진 콜백에서 sender.send()를 통해 전송
  • MCP 핫 리로드: reloadMcpForAllActive() 메서드 구현

관련 모듈

ModulePathRelationship
AgentRouterservices/routers/AgentRouter.tsIPC 호출을 수신하고 디스패치
MagiServiceservices/agent-core/magi/MagiService.ts상위 수준 오케스트레이션, 엔진 선택
CompletionServiceservices/capabilities/llm/completion/CompletionService.tsLLM API 호출
AgentServiceservices/agent-core/agent/AgentService.tsClaude SDK 세션 관리