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 / 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 の使用シナリオ
スポーンされる子 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, // event type
sessionId: string, // session ID
// ...event-specific payload
});
ツールの命名規約
| ツールの種類 | 命名形式 | 例 |
|---|---|---|
| ファイルシステム | PascalCase | Read, Write, Edit |
| シェル | 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 |