В уроке 04 tools были захардкожены прямо в коде. Добавить новый — значит переписать агента.
Skill решает
эту проблему: это папка с файлом SKILL.md, которую агент подключает как плагин.
Стандарт agentskills.io — открытый формат,
чтобы разные агенты могли использовать одни и те же skills.
Ключевая идея — progressive disclosure (постепенное раскрытие). Агент не загружает все skills целиком. Вместо этого — три уровня:
name + description из frontmatterActivation (<5000 токенов): полный текст
SKILL.md с инструкциямиExecution (по запросу): файлы из
references/, скрипты, ассеты
Зачем? Если у агента 50 skills, загрузить все instructions — это 250K токенов. С progressive disclosure агент сначала видит список из 50 коротких описаний (~5K токенов), активирует 2–3 нужных, и только потом погружается в детали.
Шаг 1. Создаём skill — filesystem
Каждый skill — это папка. Внутри — SKILL.md с YAML-frontmatter и инструкциями
на естественном языке. Опционально — папка references/ для детальной документации.
mkdir -p skills/filesystem/references
--- name: filesystem description: Read, write, and list files. Use when the agent needs to work with the file system. metadata: author: ai-agents-book version: "1.0" --- # Filesystem Skill Ты умеешь работать с файлами: читать, записывать и просматривать директории. ## Доступные инструменты - **read_file** — прочитать содержимое файла по пути - **write_file** — записать содержимое в файл - **list_dir** — получить список файлов в директории ## Правила - Перед записью всегда проверяй, существует ли файл - Для больших директорий используй фильтрацию - Подробности по каждому инструменту — см. references/REFERENCE.md
Разберём frontmatter:
name— уникальный идентификатор skill'аdescription— короткое описание для Discovery-уровня. Агент читает его, чтобы решить: нужен ли этот skill?metadata— автор, версия, любые дополнительные поля
Тело после --- — это Activation-уровень: полные инструкции, которые агент получает в system prompt при активации skill'а.
# Filesystem Skill — Reference ## read_file - **Параметры:** `path` (string) — путь к файлу - **Возвращает:** содержимое файла в UTF-8 - **Ошибки:** если файл не найден — вернёт сообщение об ошибке ## write_file - **Параметры:** `path` (string), `content` (string) - **Возвращает:** подтверждение записи - **Ошибки:** если нет прав на запись ## list_dir - **Параметры:** `path` (string) — путь к директории - **Возвращает:** вывод `ls -la`
references/ — третий уровень disclosure. Агент заглядывает сюда
только когда ему нужны детали конкретного tool. Это экономит токены: основной
SKILL.md остаётся компактным.
tree skills/filesystem # skills/filesystem # ├── SKILL.md # └── references/ # └── REFERENCE.md
Шаг 2. Создаём второй skill — web-search
Два skill'а — минимум, чтобы увидеть модульность в действии. Обрати внимание на description:
формулировка "Use when..." помогает агенту понять, когда активировать этот skill.
mkdir -p skills/web-search
--- name: web-search description: Search the web for current information. Use when the agent needs up-to-date data, documentation, or facts beyond its training cutoff. metadata: author: ai-agents-book version: "1.0" --- # Web Search Skill You can search the web to find current information. ## Available Tools - **web_search** — perform a web search query and return top results ## Rules - Use concise, specific search queries - Prefer official sources (documentation, GitHub, Wikipedia) - Always cite the source of information in your response
ls skills/ # filesystem/ web-search/
Шаг 3. Пишем загрузчик skills
Теперь — код. Нам нужно программно загружать SKILL.md,
парсить frontmatter и отдавать агенту. Это реализация progressive disclosure в TypeScript:
// src/05-skills.ts
import OpenAI from "openai";
import { execSync } from "child_process";
import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
import { join } from "path";
import dotenv from "dotenv";
dotenv.config();
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY ?? "",
});
// === Интерфейс Skill ===
interface Skill {
name: string;
description: string; // Discovery-уровень: когда использовать
instructions: string; // Activation-уровень: полные инструкции
location: string; // путь к папке skill'а
tools: OpenAI.ChatCompletionTool[];
handlers: Record<string, (args: any) => string>;
}
// === Парсер YAML frontmatter ===
function parseFrontmatter(raw: string) {
const parts = raw.split("---");
if (parts.length < 3) {
throw new Error("Invalid SKILL.md: no frontmatter found");
}
const frontmatter = parts[1].trim();
const body = parts.slice(2).join("---").trim();
const meta: Record<string, string> = {};
for (const line of frontmatter.split("\n")) {
const idx = line.indexOf(":");
if (idx > 0 && !line.startsWith(" ")) {
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
}
}
return { name: meta.name, description: meta.description, body };
}
// === Discovery: сканируем папку, извлекаем name + description ===
function discoverSkills(dir: string) {
const entries = readdirSync(dir)
.filter(name => {
const fullPath = join(dir, name, "SKILL.md");
try { statSync(fullPath); return true; }
catch { return false; }
});
console.log(`\n📋 Discovery: найдено ${entries.length} skill(s)`);
return entries.map(name => {
const skillPath = join(dir, name);
const raw = readFileSync(join(skillPath, "SKILL.md"), "utf-8");
const { name: skillName, description } = parseFrontmatter(raw);
console.log(` • ${skillName}: ${description}`);
return { name: skillName, description, location: skillPath };
});
}
// === Activation: загружаем полный SKILL.md ===
function activateSkill(location: string) {
const raw = readFileSync(join(location, "SKILL.md"), "utf-8");
const { name, description, body } = parseFrontmatter(raw);
console.log(` ✅ ${name} activated (${body.length} chars)`);
return { name, description, instructions: body, location };
}
discoverSkills() — это Discovery-уровень: быстрый скан папки,
только name + description. activateSkill() — Activation: загружаем полные
инструкции. Два вызова — два уровня раскрытия.
Шаг 4. Собираем агента с skills
Для каждого skill создаём tools + handlers, потом передаём в createAgent().
System prompt формируется по стандарту agentskills.io — с XML-блоком <available_skills>:
// === Skill: filesystem — tools + handlers ===
function createFilesystemSkill(meta: ReturnType<typeof activateSkill>): Skill {
return {
...meta,
tools: [
{
type: "function",
function: {
name: "read_file",
description: "Прочитать файл",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "Путь к файлу" },
},
required: ["path"],
},
},
},
{
type: "function",
function: {
name: "write_file",
description: "Записать содержимое в файл",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "Путь к файлу" },
content: { type: "string", description: "Содержимое" },
},
required: ["path", "content"],
},
},
},
{
type: "function",
function: {
name: "list_dir",
description: "Список файлов в директории",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "Путь к директории" },
},
required: ["path"],
},
},
},
],
handlers: {
read_file: ({ path }) => {
try { return readFileSync(path, "utf-8"); }
catch (e: any) { return `Ошибка: ${e.message}`; }
},
write_file: ({ path, content }) => {
try { writeFileSync(path, content); return `Файл ${path} записан`; }
catch (e: any) { return `Ошибка: ${e.message}`; }
},
list_dir: ({ path }) => {
try { return execSync(`ls -la ${path}`, { encoding: "utf-8" }); }
catch (e: any) { return `Ошибка: ${e.message}`; }
},
},
};
}
// === Skill: web-search — tools + handler-заглушка ===
function createWebSearchSkill(meta: ReturnType<typeof activateSkill>): Skill {
return {
...meta,
tools: [
{
type: "function",
function: {
name: "web_search",
description: "Поиск в интернете",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "Поисковый запрос" },
},
required: ["query"],
},
},
},
],
handlers: {
web_search: ({ query }) =>
`[Заглушка] Результаты поиска по "${query}": ` +
`1. Official docs — https://example.com/docs\n` +
`2. Tutorial — https://example.com/tutorial`,
},
};
}
// === Создание агента из массива skills ===
function createAgent(skills: Skill[]) {
const allTools = skills.flatMap(s => s.tools);
const allHandlers = Object.assign({}, ...skills.map(s => s.handlers));
// System prompt по стандарту agentskills.io: XML-блок available_skills
const skillsXml = skills.map(s =>
` <skill name="${s.name}">\n${s.instructions}\n </skill>`
).join("\n");
const systemPrompt = `Ты полезный ассистент. Вот твои навыки:
<available_skills>
${skillsXml}
</available_skills>
Используй инструменты когда нужно. Отвечай на русском.`;
return async function run(userMessage: string) {
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
{ role: "user", content: userMessage },
];
let iteration = 0;
while (iteration < 10) {
iteration++;
console.log(`\n--- Итерация ${iteration} ---`);
const response = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4.5",
messages,
tools: allTools,
});
const message = response.choices[0].message;
messages.push(message);
if (!message.tool_calls?.length) {
console.log("\nОтвет:", message.content);
return message.content;
}
for (const toolCall of message.tool_calls) {
const name = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(`Tool: ${name}(${JSON.stringify(args)})`);
const result = allHandlers[name]?.(args)
?? `Tool "${name}" не найден`;
console.log(`→ ${result.slice(0, 150)}`);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: result,
});
}
}
};
}
// === main: Discovery → Activation → Execution ===
async function main() {
// 1. Discovery — сканируем все skills
const discovered = discoverSkills("./skills");
// 2. Activation — активируем нужные skills и создаём реализации
console.log("\n🔌 Activation:");
const fsMeta = activateSkill(discovered[0].location);
const wsMeta = activateSkill(discovered[1].location);
const skills = [
createFilesystemSkill(fsMeta),
createWebSearchSkill(wsMeta),
];
// 3. Execution — запускаем агента
console.log(`\n🚀 Agent создан с ${skills.length} skills: ${skills.map(s => s.name).join(", ")}`);
const agent = createAgent(skills);
await agent(
"Посмотри файлы проекта в текущей директории, " +
"прочитай package.json и создай README.md с кратким описанием проекта."
);
}
main();
npx tsx src/05-skills.ts
list_dir, read_file, write_file —
все из filesystem skill.
skills/*/ должен быть файл SKILL.md
с корректным YAML-frontmatter (три дефиса --- сверху и снизу).
Если discoverSkills() находит 0 skills — проверь путь ./skills.
createAgent([...skills]) — модульность. Хочешь добавить работу с API — создаёшь
папку с SKILL.md и подключаешь. Хочешь убрать web-search — просто не активируешь.
Progressive disclosure — экономия. Агент тратит ~100 токенов на Discovery каждого skill, а не ~5000 на полные инструкции. При 50 skills это разница между 5K и 250K токенов.
Стандарт agentskills.io — переносимость. Skill в формате
SKILL.md
можно переиспользовать между разными агентами и фреймворками. XML-блок <available_skills>
в system prompt — каноничный способ передать инструкции модели.