How to Extend Agent Engine
This article provides practical guides for three common extension scenarios: adding new tools, adding new engines, and creating Agent configuration files.
Guide 1: Adding New Tools
Add a new tool to the TinyElf engine.
Steps
1. Implement ITool Interface
Create a new file in 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}`;
}
}
Key requirements:
namemust be uniquedescriptionshould help LLM understand when to use this toolparametersuse standard JSON Schema formatexecutereturns string result (over 50KB will be auto-truncated)- On error, return string starting with
Erroror throw exception
2. Register in ToolRegistryBuilder
Edit 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. Determine Sensitivity Classification
If the tool is a read-only safe tool, add it to the safe tools set in 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
]);
If not added, defaults to sensitive tool (requires user confirmation).
4. Configure Tool Inheritance (optional)
If child Agents should inherit this tool, in TinyElfToolRegistryBuilder.ts:
const inheritableToolNames = new Set([
'list_skills', 'read_skill', 'slash_command',
'MyNewTool', // Add to inheritable set
]);
Modification Checklist
| File | Action | Description |
|---|---|---|
tinyelf/tools/MyNewTool.ts | Create | Tool implementation |
tinyelf/TinyElfToolRegistryBuilder.ts | Modify | Register tool |
tinyelf/TinyElfAgentLoop.ts | Modify | Sensitivity classification (optional) |
tinyelf/TinyElfToolRegistryBuilder.ts | Modify | Tool inheritance (optional) |
Guide 2: Adding New Engine
Add a new Agent engine type to Elftia.
Steps
1. Add EngineType
In packages/desktop/app/shared/contracts/elftia-agent-types.ts:
export type EngineType =
| 'tinyelf'
| 'claude-sdk'
| 'cli'
| 'chat'
| 'st-chat'
| 'api'
| 'my-engine'; // New
2. Implement IEngine Interface
Create a new file in 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. Register to EngineDispatcher
In 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. Export in index.ts
In packages/desktop/app/main/services/agent-core/engine/index.ts:
export { MyEngine } from './MyEngine';
5. Add i18n keys (optional)
If you need to display the engine name in UI, add internationalization keys:
{
"backendMyEngine": "My Engine",
"backendMyEngineDesc": "My custom engine description"
}
Modification Checklist
| File | Action | Description |
|---|---|---|
@shared/contracts/elftia-agent-types.ts | Modify | Add EngineType |
services/agent-core/engine/MyEngine.ts | Create | Engine implementation |
services/agent-core/engine/index.ts | Modify | Export new engine |
main/index.ts | Modify | Register to EngineDispatcher |
| i18n files | Modify | Add display name (optional) |
Guide 3: Creating Agent Configuration
Create an Agent configuration file for use in the TinyElf engine.
Agent Configuration Format
---
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 Type
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';
Model Alias Resolution
function resolveModelAlias(
alias: string,
parentModel: string,
backgroundModel?: string,
): string;
| Alias | Resolution |
|---|---|
main / inherit | Return parentModel |
background | Return backgroundModel (fallback to parentModel) |
sonnet | Replace opus/haiku in parentModel with sonnet |
opus | Replace with opus |
haiku | Replace with haiku |
| Other | Use as concrete model ID |
Agent Discovery
AgentsLoader discovers Agent configurations from:
class AgentsLoader {
constructor(
projectPath: string, // .claude/agents/*.md
personalDir: string, // ~/.claude/agents/*.md
);
listAgents(): AgentInfo[];
loadAgent(name: string): AgentConfig | null;
}
Discovery priority: Project > Personal > Built-in
Agent Use Cases
As a spawned sub-Agent
spawn_agent(prompt: "Review this code", agent: "code-reviewer")
SpawnTool loads Agent configuration via AgentsLoader.loadAgent(), setting system prompt, tool restrictions, and permission mode.
Selection in Agent panel
Frontend lists all available Agents via AgentsLoader.listAgents(), user selects and passes agentId to engine.
Modification Checklist
| File | Action | Description |
|---|---|---|
.claude/agents/<name>.md | Create | Agent configuration file |
No code changes needed. Once the configuration file is placed in the correct path, AgentsLoader will auto-discover it.
General Notes
Testing
All tools and engines should have corresponding tests:
- Tool tests:
tinyelf/tools/__tests__/ - Engine tests:
agent-core/engine/__tests__/ - Security tests:
platform/security/__tests__/
Index Update
After adding new modules, update the file index in .claude/skills/architecture-index/SKILL.md.
IPC Event Convention
All engines' IPC events must follow unified format:
sender.send('agent:event', {
type: string, // event type
sessionId: string, // session ID
// ...event-specific payload
});
Tool Naming Convention
| Tool type | Naming format | Example |
|---|---|---|
| File system | PascalCase | Read, Write, Edit |
| Shell | PascalCase | Bash |
| Functional | snake_case | spawn_agent, list_skills |
| MCP | mcp__<server>__<tool> | mcp__github__list_repos |
| Session | PascalCase prefix | SessionsSpawn, SessionsList |
Key Files
| File | Path | Description |
|---|---|---|
| ITool interface | tinyelf/tools/ToolInterface.ts | Tool base interface |
| IEngine interface | agent-core/engine/types.ts | Engine base interface |
| EngineDispatcher | agent-core/engine/EngineDispatcher.ts | Engine registry center |
| ToolRegistryBuilder | tinyelf/TinyElfToolRegistryBuilder.ts | Tool registration build |
| AgentsLoader | tinyelf/agents/AgentsLoader.ts | Agent configuration loader |
| SkillsLoader | tinyelf/skills/SkillsLoader.ts | Skill configuration loader |
| EngineType definition | @shared/contracts/elftia-agent-types.ts | Type definition |
| Main entry | main/index.ts | Engine registration |
All paths relative to packages/desktop/app/main/services/.
Related Modules
| Module | Description | Reference |
|---|---|---|
| TinyElf Agent Loop | Tool execution loop | TinyElf Deep Dive |
| Tool System | Tool registration and execution | Tool System |
| Security Layers | Three-layer security pipeline | Security Layers |
| ClaudeSdkEngine | SDK engine | ClaudeSdkEngine |
| CliRunnerEngine | CLI engine | CliRunnerEngine |