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 to skills/_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 _builtin directory and loads all found skills automatically.
  • discoverSkills(enabledSkills) scans the custom skills/ directory but only loads skills whose names appear in the enabledSkills array 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.md files in the skills/ directory, discovered by AcpSkillManager.ts.
  • Storage locations are defined in initStorage.ts: custom skills live in skills/, while built-ins reside in skills/_builtin/.
  • Discovery requires enabling skills via defaultEnabledSkills in presets or the enabledSkills array per conversation.
  • Injection happens automatically: agentUtils.ts adds a skills index to the first message, and lazy loading occurs when the AI requests [LOAD_SKILL: name].
  • Frontmatter (name and description) in SKILL.md files 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →