メインコンテンツまでスキップ

Agent エンジンの拡張方法

本記事では、新しいツールの追加・新しいエンジンの追加・Agent 設定ファイルの作成という 3 つの一般的な拡張シナリオについて、実践的なガイドを提供します。

ガイド 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 = 'Tool functionality description (LLM reads this to decide whether to use)';
readonly parameters = {
type: 'object',
properties: {
input: {
type: 'string',
description: 'Description of input parameter',
},
optional_param: {
type: 'number',
description: 'Optional parameter',
},
},
required: ['input'],
};

constructor(private workspacePath: string) {}

async execute(params: Record<string, unknown>): Promise<string> {
const input = String(params.input);
// Implement tool logic
return `Execution result: ${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> {
// ...existing tool registration code...

// Register new tool
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', // Add to safe set
]);

追加しない場合、デフォルトで機密ツール(ユーザーの確認が必要)として扱われます。

4. ツール継承の設定(オプション)

子 Agent がこのツールを継承する必要がある場合、TinyElfToolRegistryBuilder.ts で設定します:

const inheritableToolNames = new Set([
'list_skills', 'read_skill', 'slash_command',
'MyNewTool', // Add to inheritable set
]);

変更チェックリスト

ファイル操作説明
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'; // New

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. Save user message
// 2. Send IPC event
sender.send('agent:event', {
type: 'userMessage',
sessionId: dbSessionId,
message: { id: '...', role: 'user', content: prompt },
});

// 3. Execute engine logic (usually async)
const controller = new AbortController();
this.activeSessions.set(dbSessionId, {
cancel: () => controller.abort(),
});

try {
// ...engine core logic...

sender.send('agent:event', {
type: 'complete',
sessionId: dbSessionId,
});
} finally {
this.activeSessions.delete(dbSessionId);
}
}

async resumeSession(ctx: EngineSessionContext): Promise<void> {
// Similar to startSession, but load history
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();
// ...existing engine registration...
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 Name
description: Agent Description
model: main
permissionMode: default
tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
skills:
- code-standards
---

System prompt body (Markdown format)

## Instructions

1. First step
2. Second step

AgentConfig の型

interface AgentConfig {
name: string;
description: string;
tools?: string[]; // Tool whitelist
model?: ModelAlias | string; // Model selection
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
skills?: string[]; // Skill list
systemPrompt: string; // Body content
}

type ModelAlias = 'main' | 'background' | 'inherit' | 'sonnet' | 'opus' | 'haiku';

モデルエイリアスの解決

function resolveModelAlias(
alias: string,
parentModel: string,
backgroundModel?: string,
): string;
エイリアス解決結果
main / inheritparentModel を返す
backgroundbackgroundModel を返す(存在しない場合は parentModel にフォールバック)
sonnetparentModel 内の opus/haikusonnet に置き換える
opusopus に置き換える
haikuhaiku に置き換える
その他具体的なモデル 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 の使用シナリオ

スポーンされる子 Agent として

spawn_agent(prompt: "Review this code", agent: "code-reviewer")

SpawnToolAgentsLoader.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, // event type
sessionId: string, // session ID
// ...event-specific payload
});

ツールの命名規約

ツールの種類命名形式
ファイルシステムPascalCaseRead, Write, Edit
シェルPascalCaseBash
機能ツールsnake_casespawn_agent, list_skills
MCPmcp__<server>__<tool>mcp__github__list_repos
セッションPascalCase プレフィックスSessionsSpawn, SessionsList

主要ファイル

ファイルパス説明
ITool インターフェースtinyelf/tools/ToolInterface.tsツールの基底インターフェース
IEngine インターフェースagent-core/engine/types.tsエンジンの基底インターフェース
EngineDispatcheragent-core/engine/EngineDispatcher.tsエンジンのレジストリセンター
ToolRegistryBuildertinyelf/TinyElfToolRegistryBuilder.tsツール登録のビルド
AgentsLoadertinyelf/agents/AgentsLoader.tsAgent 設定のローダー
SkillsLoadertinyelf/skills/SkillsLoader.tsスキル設定のローダー
EngineType 定義@shared/contracts/elftia-agent-types.ts型定義
メインエントリmain/index.tsエンジンの登録

すべてのパスは packages/desktop/app/main/services/ からの相対パスです。

関連モジュール

モジュール説明参考
TinyElf Agent Loopツール実行ループTinyElf 詳解
ツールシステムツールの登録と実行ツールシステム
セキュリティレイヤー3 層のセキュリティパイプラインセキュリティレイヤー
ClaudeSdkEngineSDK エンジンClaudeSdkEngine
CliRunnerEngineCLI エンジンCliRunnerEngine