Agent 엔진 확장 방법
이 문서는 세 가지 일반적인 확장 시나리오에 대한 실용 가이드를 제공합니다: 새로운 도구 추가, 새로운 엔진 추가, Agent 구성 파일 생성.
가이드 1: 새로운 도구 추가
TinyElf 엔진에 새로운 도구를 추가합니다.
단계
1. ITool 인터페이스 구현
packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/ 디렉터리에 새로운 파일을 생성합니다:
// MyNewTool.ts
import type { ITool } from './ToolInterface';
export class MyNewTool implements ITool {
readonly name = 'MyNewTool';
readonly description = '도구 기능 설명 (LLM이 이 설명을 읽고 사용 여부를 결정함)';
readonly parameters = {
type: 'object',
properties: {
input: {
type: 'string',
description: '입력 파라미터 설명',
},
optional_param: {
type: 'number',
description: '선택적 파라미터',
},
},
required: ['input'],
};
constructor(private workspacePath: string) {}
async execute(params: Record<string, unknown>): Promise<string> {
const input = String(params.input);
// 도구 로직 구현
return `실행 결과: ${input}`;
}
}
핵심 요구 사항:
name은 고유해야 함description은 LLM이 이 도구를 언제 사용해야 하는지 이해할 수 있도록 작성parameters는 표준 JSON Schema 형식 사용execute는 문자열 결과 반환 (50KB 초과 시 자동 절삭)- 오류 발생 시
Error로 시작하는 문자열 반환 또는 예외 발생
2. ToolRegistryBuilder에 등록
TinyElfToolRegistryBuilder.ts를 수정합니다:
import { MyNewTool } from './tools/MyNewTool';
export async function buildToolRegistry(...): Promise<ToolRegistryBuildResult> {
// ...기존 도구 등록 코드...
// 새로운 도구 등록
toolRegistry.register(new MyNewTool(projectPath));
// ...
}
3. 민감도 분류 결정
도구가 읽기 전용의 안전한 도구인 경우, TinyElfAgentLoop.ts의 안전 도구 집합에 추가합니다:
private static readonly SAFE_TOOLS = new Set([
'Read', 'ListDir', 'Glob', 'Grep',
'WebSearch', 'WebFetch',
'list_skills', 'read_skill',
'Notify', 'SessionsYield', 'SessionsHistory',
'MyNewTool', // 안전 집합에 추가
]);
추가하지 않으면 기본적으로 민감한 도구(사용자 확인 필요)로 간주됩니다.
4. 도구 상속 구성 (선택)
하위 Agent가 이 도구를 상속해야 하는 경우, TinyElfToolRegistryBuilder.ts에서:
const inheritableToolNames = new Set([
'list_skills', 'read_skill', 'slash_command',
'MyNewTool', // 상속 가능 집합에 추가
]);
수정 체크리스트
| 파일 | 작업 | 설명 |
|---|---|---|
tinyelf/tools/MyNewTool.ts | 생성 | 도구 구현 |
tinyelf/TinyElfToolRegistryBuilder.ts | 수정 | 도구 등록 |
tinyelf/TinyElfAgentLoop.ts | 수정 | 민감도 분류 (선택) |
tinyelf/TinyElfToolRegistryBuilder.ts | 수정 | 도구 상속 (선택) |
가이드 2: 새로운 엔진 추가
Elftia에 새로운 Agent 엔진 유형을 추가합니다.
단계
1. EngineType 추가
packages/desktop/app/shared/contracts/elftia-agent-types.ts에서:
export type EngineType =
| 'tinyelf'
| 'claude-sdk'
| 'cli'
| 'chat'
| 'st-chat'
| 'api'
| 'my-engine'; // 신규 추가
2. IEngine 인터페이스 구현
packages/desktop/app/main/services/agent-core/engine/ 디렉터리에 새로운 파일을 생성합니다:
// MyEngine.ts
import type { EngineType } from '@shared/contracts/elftia-agent-types';
import type { EngineSessionContext, IEngine } from './types';
export class MyEngine implements IEngine {
readonly engineType: EngineType = 'my-engine';
private activeSessions = new Map<string, { cancel: () => void }>();
async startSession(ctx: EngineSessionContext): Promise<void> {
const { dbSessionId, sender, prompt } = ctx;
// 1. 사용자 메시지 저장
// 2. IPC 이벤트 전송
sender.send('agent:event', {
type: 'userMessage',
sessionId: dbSessionId,
message: { id: '...', role: 'user', content: prompt },
});
// 3. 엔진 로직 실행 (보통 비동기)
const controller = new AbortController();
this.activeSessions.set(dbSessionId, {
cancel: () => controller.abort(),
});
try {
// ...엔진 핵심 로직...
sender.send('agent:event', {
type: 'complete',
sessionId: dbSessionId,
});
} finally {
this.activeSessions.delete(dbSessionId);
}
}
async resumeSession(ctx: EngineSessionContext): Promise<void> {
// startSession과 유사하지만 히스토리 로드
await this.startSession(ctx);
}
async interrupt(dbSessionId: string): Promise<boolean> {
const session = this.activeSessions.get(dbSessionId);
if (session) {
session.cancel();
this.activeSessions.delete(dbSessionId);
return true;
}
return false;
}
isActive(dbSessionId: string): boolean {
return this.activeSessions.has(dbSessionId);
}
}
3. EngineDispatcher에 등록
packages/desktop/app/main/index.ts에서:
import { MyEngine } from './services/agent-core/engine/MyEngine';
const dispatcher = new EngineDispatcher();
// ...기존 엔진 등록...
dispatcher.registerEngine(new MyEngine());
4. index.ts에서 내보내기
packages/desktop/app/main/services/agent-core/engine/index.ts에서:
export { MyEngine } from './MyEngine';
5. i18n 키 추가 (선택)
UI에서 엔진 이름을 표시해야 하는 경우, 국제화 키를 추가합니다:
{
"backendMyEngine": "My Engine",
"backendMyEngineDesc": "My custom engine description"
}
수정 체크리스트
| 파일 | 작업 | 설명 |
|---|---|---|
@shared/contracts/elftia-agent-types.ts | 수정 | EngineType 추가 |
services/agent-core/engine/MyEngine.ts | 생성 | 엔진 구현 |
services/agent-core/engine/index.ts | 수정 | 새 엔진 내보내기 |
main/index.ts | 수정 | EngineDispatcher에 등록 |
| i18n 파일 | 수정 | 표시 이름 추가 (선택) |
가이드 3: Agent 구성 생성
TinyElf 엔진에서 사용할 Agent 구성 파일을 생성합니다.
Agent 구성 형식
---
name: Agent 이름
description: Agent 설명
model: main
permissionMode: default
tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
skills:
- code-standards
---
시스템 프롬프트 본문 (Markdown 형식)
## 지시 사항
1. 첫 번째 단계
2. 두 번째 단계
AgentConfig 타입
interface AgentConfig {
name: string;
description: string;
tools?: string[]; // 도구 화이트리스트
model?: ModelAlias | string; // 모델 선택
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
skills?: string[]; // 스킬 목록
systemPrompt: string; // 본문 내용
}
type ModelAlias = 'main' | 'background' | 'inherit' | 'sonnet' | 'opus' | 'haiku';
모델 별칭 해석
function resolveModelAlias(
alias: string,
parentModel: string,
backgroundModel?: string,
): string;
| 별칭 | 해석 결과 |
|---|---|
main / inherit | parentModel 반환 |
background | backgroundModel 반환 (없으면 parentModel로 폴백) |
sonnet | parentModel의 opus/haiku를 sonnet으로 교체 |
opus | opus로 교체 |
haiku | haiku로 교체 |
| 기타 | 구체적인 모델 ID로 사용 |
Agent 탐색
AgentsLoader는 다음 위치에서 Agent 구성을 탐색합니다:
class AgentsLoader {
constructor(
projectPath: string, // .claude/agents/*.md
personalDir: string, // ~/.claude/agents/*.md
);
listAgents(): AgentInfo[];
loadAgent(name: string): AgentConfig | null;
}
탐색 우선순위: 프로젝트 > 개인 > 내장
Agent 사용 시나리오
spawn된 하위 Agent로 실행
spawn_agent(prompt: "Review this code", agent: "code-reviewer")
SpawnTool은 AgentsLoader.loadAgent()를 통해 Agent 구성을 로드하여 시스템 프롬프트, 도구 제한, 권한 모드를 설정합니다.
Agent 패널에서 선택
프론트엔드는 AgentsLoader.listAgents()를 통해 사용 가능한 모든 Agent를 나열하며, 사용자가 선택하면 agentId를 엔진에 전달합니다.
수정 체크리스트
| 파일 | 작업 | 설명 |
|---|---|---|
.claude/agents/<name>.md | 생성 | Agent 구성 파일 |
코드 변경은 필요하지 않습니다. 구성 파일을 올바른 경로에 배치하면 AgentsLoader가 자동으로 탐색합니다.
일반 참고 사항
테스트
모든 도구와 엔진에는 대응하는 테스트가 있어야 합니다:
- 도구 테스트:
tinyelf/tools/__tests__/ - 엔진 테스트:
agent-core/engine/__tests__/ - 보안 테스트:
platform/security/__tests__/
인덱스 업데이트
새로운 모듈을 추가한 후, .claude/skills/architecture-index/SKILL.md의 파일 인덱스를 업데이트합니다.
IPC 이벤트 규약
모든 엔진의 IPC 이벤트는 통일된 형식을 따라야 합니다:
sender.send('agent:event', {
type: string, // 이벤트 유형
sessionId: string, // 세션 ID
// ...이벤트별 페이로드
});
도구 명명 규약
| 도구 유형 | 명명 형식 | 예시 |
|---|---|---|
| 파일 시스템 | PascalCase | Read, Write, Edit |
| Shell | PascalCase | Bash |
| 기능형 | snake_case | spawn_agent, list_skills |
| MCP | mcp__<server>__<tool> | mcp__github__list_repos |
| 세션형 | PascalCase 프리픽스 | SessionsSpawn, SessionsList |
핵심 파일
| 파일 | 경로 | 설명 |
|---|---|---|
| ITool 인터페이스 | tinyelf/tools/ToolInterface.ts | 도구 기본 인터페이스 |
| IEngine 인터페이스 | agent-core/engine/types.ts | 엔진 기본 인터페이스 |
| EngineDispatcher | agent-core/engine/EngineDispatcher.ts | 엔진 레지스트리 센터 |
| ToolRegistryBuilder | tinyelf/TinyElfToolRegistryBuilder.ts | 도구 등록 빌드 |
| AgentsLoader | tinyelf/agents/AgentsLoader.ts | Agent 구성 로더 |
| SkillsLoader | tinyelf/skills/SkillsLoader.ts | 스킬 구성 로더 |
| EngineType 정의 | @shared/contracts/elftia-agent-types.ts | 타입 정의 |
| 메인 엔트리 | main/index.ts | 엔진 등록 |
모든 경로는 packages/desktop/app/main/services/를 기준으로 합니다.
관련 모듈
| 모듈 | 설명 | 참고 문서 |
|---|---|---|
| TinyElf Agent Loop | 도구 실행 루프 | TinyElf 심층 분석 |
| 도구 시스템 | 도구 등록 및 실행 | 도구 시스템 |
| 보안 레이어 | 3단계 보안 파이프라인 | 보안 레이어 |
| ClaudeSdkEngine | SDK 엔진 | ClaudeSdkEngine |
| CliRunnerEngine | CLI 엔진 | CliRunnerEngine |