How to Build Custom AI Assistants in AionUi by Creating Skills in the `skills/` Directory
You can build custom AI assistants in AionUi by creating a SKILL.md file inside a folder under the skills/ directory, adding YAML frontmatter with name and description, and enabling the skill either globally via assistantPresets.ts or per-conversation through the enabledSkills array.
AionUi implements a modular skill system that allows you to extend AI assistant capabilities without modifying core application code. By placing structured knowledge files in the skills/ directory, you create reusable capabilities that the system discovers, indexes, and injects into agent conversations on demand.
Understanding the Skill Loading Pipeline
The skill system follows a strict discovery and injection pipeline defined across several core files in the AionUi source code.
Skill Storage Locations
In src/process/initStorage.ts, the application defines the canonical paths for skill storage:
const STORAGE_PATH = { …, skills: 'skills' };
const getSkillsDir = () => path.join(cacheDir, STORAGE_PATH.skills);
const getBuiltinSkillsDir = () => path.join(getSkillsDir(), '_builtin');
getSkillsDir()returns the root directory for all custom skills (typically[cache]/skills/).getBuiltinSkillsDir()points toskills/_builtin/, which houses system skills that are always available.
Discovery and Indexing
The AcpSkillManager class in src/process/task/AcpSkillManager.ts handles skill discovery:
discoverBuiltinSkills()scans the_builtindirectory and loads all found skills automatically.discoverSkills(enabledSkills)scans the customskills/directory but only loads skills whose names appear in theenabledSkillsarray passed to the manager.
The manager looks for skills in three possible locations:
const builtinSkillFile = path.join(builtinSkillsDir, skillName, 'SKILL.md');
const skillDirFile = path.join(skillsDir, skillName, 'SKILL.md');
const skillFlatFile = path.join(skillsDir, `${skillName}.md`);
After discovery, buildSkillsIndexText() generates a lightweight index containing the name and description from each skill's frontmatter.
Injection into Conversations
In src/process/task/agentUtils.ts, the function prepareFirstMessageWithSkillsIndex() injects the skills index into the first message of a conversation. When the AI replies with a [LOAD_SKILL: name] token, AcpSkillManager.getSkill(name) lazily reads the full body of the matching SKILL.md file and provides it to the agent.
Creating a Custom Skill Step-by-Step
Follow these steps to build custom AI assistants by creating skills in the skills/ directory.
Step 1: Create the Directory Structure
Create a folder for your skill inside the skills/ directory. The folder name should match your skill identifier:
mkdir -p skills/my-custom-assistant
Alternatively, you can use a flat file structure: skills/my-custom-assistant.md.
Step 2: Write the SKILL.md with Frontmatter
Create a SKILL.md file inside your skill folder. Begin with YAML frontmatter defining the name and description fields:
---
name: my-custom-assistant
description: Provides specialized data analysis capabilities for CSV files.
---
# Custom Data Analysis Assistant
When activated, this skill enables advanced CSV parsing and statistical analysis.
## Capabilities
- Parse malformed CSV files
- Calculate moving averages
- Generate summary statistics
The name and description from the frontmatter appear in the skills index injected into the first message.
Step 3: Enable the Skill
Option A: Global Enable via Preset
Edit src/common/presets/assistantPresets.ts and add your skill name to the defaultEnabledSkills array of your chosen preset:
export const assistantPresets = [
{
id: 'cowork',
name: 'Cowork Assistant',
defaultEnabledSkills: ['skill-creator', 'pptx', 'docx', 'my-custom-assistant'],
// ... other fields
},
];
Option B: Per-Conversation Enable
Use the UI "Add Skills" button to add the skill name to the conversation's enabledSkills array, or set it programmatically via the API when creating an agent.
Step 4: Verify Loading
Start a new conversation with the assistant. The first message should contain an "Available Skills" section listing your new skill:
[Available Skills]
- my-custom-assistant: Provides specialized data analysis capabilities for CSV files.
Test the lazy loading by asking the assistant to [LOAD_SKILL: my-custom-assistant]. The agent should then receive the full body content defined in your SKILL.md.
Programmatic Skill Management
For advanced use cases, you can manage skills programmatically using the AionUi API.
Adding a Skill Programmatically
import { getSkillsDir } from '@/process/initStorage';
import { writeFileSync, mkdirSync } from 'fs';
import path from 'path';
// Create folder
const skillName = 'text-summarizer';
const skillFolder = path.join(getSkillsDir(), skillName);
mkdirSync(skillFolder, { recursive: true });
// Write SKILL.md
const skillFile = path.join(skillFolder, 'SKILL.md');
writeFileSync(
skillFile,
`---\nname: ${skillName}\ndescription: Summarises long text into a concise paragraph.\n---\n\n# Usage\nPaste any text and ask for a summary.\n`
);
Enabling a Skill for a Single Conversation
import { ProcessConfig } from '@/process/initStorage';
import type { AcpBackendConfig } from '@/types/acpTypes';
// Enable skill for a specific conversation
await ProcessConfig.set('acp.customAgents', [
{
id: 'custom-assistant',
name: 'My Custom Assistant',
enabled: true,
isPreset: false,
isBuiltin: false,
enabledSkills: ['text-summarizer'], // Enable the new skill
presetAgentType: 'gemini',
// other required fields …
},
]);
Loading a Skill Inside a Custom Agent
import { AcpSkillManager } from '@/process/task/AcpSkillManager';
async function loadAndShowSkill(name: string) {
const manager = AcpSkillManager.getInstance([name]); // ensure it’s discovered
await manager.discoverSkills([name]);
const skill = await manager.getSkill(name);
if (skill?.body) {
console.log(`Skill “${name}” body:\n`, skill.body);
}
}
loadAndShowSkill('markdown-table-formatter');
Key Files and Their Roles
| File | Role | Link |
|---|---|---|
src/process/initStorage.ts |
Defines getSkillsDir() and getBuiltinSkillsDir(); manages the user-writable skills folder and copies built-in skills. |
initStorage.ts |
src/process/task/AcpSkillManager.ts |
Core manager that discovers, indexes, and lazily loads skills. Implements discoverBuiltinSkills(), discoverSkills(), and getSkill(). |
AcpSkillManager.ts |
src/process/task/agentUtils.ts |
Injects the skills index into the first chat message via prepareFirstMessageWithSkillsIndex() and handles loadSkillsContent(). |
agentUtils.ts |
src/common/presets/assistantPresets.ts |
Defines assistant presets; contains defaultEnabledSkills array for globally enabling skills. |
assistantPresets.ts |
src/renderer/hooks/usePresetAssistantInfo.ts |
Reads enabledSkills from conversation extra data for backward-compatible session management. |
usePresetAssistantInfo.ts |
Summary
- Skills are modular knowledge units stored as
SKILL.mdfiles in theskills/directory, discovered byAcpSkillManager.ts. - Storage locations are defined in
initStorage.ts: custom skills live inskills/, while built-ins reside inskills/_builtin/. - Discovery requires enabling skills via
defaultEnabledSkillsin presets or theenabledSkillsarray per conversation. - Injection happens automatically:
agentUtils.tsadds a skills index to the first message, and lazy loading occurs when the AI requests[LOAD_SKILL: name]. - Frontmatter (
nameanddescription) inSKILL.mdfiles drives the indexing system.
Frequently Asked Questions
What file format should I use for skills?
Skills must be written as Markdown files named SKILL.md (for folder-based skills) or <skill-name>.md (for flat file skills). Each file must begin with YAML frontmatter containing name and description fields, followed by the skill body content. The AcpSkillManager.ts parser specifically looks for these frontmatter keys to build the skills index.
How do I enable a skill for all conversations?
To enable a skill globally, modify the defaultEnabledSkills array in src/common/presets/assistantPresets.ts. Add your skill's name (as defined in the frontmatter) to the array of the desired preset. For example, adding 'markdown-table-formatter' to the cowork preset's defaultEnabledSkills makes that skill available in every new conversation using that preset.
Can I load skills dynamically during a conversation?
Yes, skills support lazy loading via the [LOAD_SKILL: name] token. When the AI generates this token in a response, AcpSkillManager.getSkill(name) dynamically reads the full body of the specified SKILL.md file and injects it into the context. This allows assistants to pull in specialized knowledge only when needed, keeping initial context windows small while retaining access to extensive capabilities.
Where are built-in skills stored?
Built-in skills reside in the skills/_builtin/ directory, as defined by getBuiltinSkillsDir() in src/process/initStorage.ts. These skills are automatically copied to the user's skills folder on first run and are always loaded by discoverBuiltinSkills() in AcpSkillManager.ts, regardless of the enabledSkills configuration. User-created custom skills should be placed directly in the skills/ folder, not in the _builtin subdirectory.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →