Skip to main content

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:

  • name must be unique
  • description should help LLM understand when to use this tool
  • parameters use standard JSON Schema format
  • execute returns string result (over 50KB will be auto-truncated)
  • On error, return string starting with Error or 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

FileActionDescription
tinyelf/tools/MyNewTool.tsCreateTool implementation
tinyelf/TinyElfToolRegistryBuilder.tsModifyRegister tool
tinyelf/TinyElfAgentLoop.tsModifySensitivity classification (optional)
tinyelf/TinyElfToolRegistryBuilder.tsModifyTool 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

FileActionDescription
@shared/contracts/elftia-agent-types.tsModifyAdd EngineType
services/agent-core/engine/MyEngine.tsCreateEngine implementation
services/agent-core/engine/index.tsModifyExport new engine
main/index.tsModifyRegister to EngineDispatcher
i18n filesModifyAdd 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;
AliasResolution
main / inheritReturn parentModel
backgroundReturn backgroundModel (fallback to parentModel)
sonnetReplace opus/haiku in parentModel with sonnet
opusReplace with opus
haikuReplace with haiku
OtherUse 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

FileActionDescription
.claude/agents/<name>.mdCreateAgent 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 typeNaming formatExample
File systemPascalCaseRead, Write, Edit
ShellPascalCaseBash
Functionalsnake_casespawn_agent, list_skills
MCPmcp__<server>__<tool>mcp__github__list_repos
SessionPascalCase prefixSessionsSpawn, SessionsList

Key Files

FilePathDescription
ITool interfacetinyelf/tools/ToolInterface.tsTool base interface
IEngine interfaceagent-core/engine/types.tsEngine base interface
EngineDispatcheragent-core/engine/EngineDispatcher.tsEngine registry center
ToolRegistryBuildertinyelf/TinyElfToolRegistryBuilder.tsTool registration build
AgentsLoadertinyelf/agents/AgentsLoader.tsAgent configuration loader
SkillsLoadertinyelf/skills/SkillsLoader.tsSkill configuration loader
EngineType definition@shared/contracts/elftia-agent-types.tsType definition
Main entrymain/index.tsEngine registration

All paths relative to packages/desktop/app/main/services/.

ModuleDescriptionReference
TinyElf Agent LoopTool execution loopTinyElf Deep Dive
Tool SystemTool registration and executionTool System
Security LayersThree-layer security pipelineSecurity Layers
ClaudeSdkEngineSDK engineClaudeSdkEngine
CliRunnerEngineCLI engineCliRunnerEngine