Перейти к основному содержимому
БЛОГ

Agent Skills и SKILL.md: пишем, индексируем, проверяем

Напишите SKILL.md для Agent Skills на своём сайте, добавьте его в /.well-known/agent-skills/index.json и проверьте sha256-digest одним скриптом на curl.

Автор: IMozzОбновлено 2026-11-01
Agent Skills и SKILL.md для сайта — aiSiteReady

Agent Skill для сайта — это файл SKILL.md, который объясняет агенту, как пользоваться вашим сайтом или API, плюс запись в /.well-known/agent-skills/index.json, по которой агент находит файл и проверяет, что он не изменился. Формат файла взят с agentskills.io. Индекс обнаружения — отдельный черновик с пятью обязательными полями на навык, и проще всего незаметно ошибиться в поле digest: SHA-256 от точных байтов, которые вы отдаёте.

Навыки — последний слой стека готовности. Сначала агент добирается до ваших страниц и читает их, потом находит, что можно вызвать, потом узнаёт, как этим правильно пользоваться. Как сделать сайт доступным для вызова агентами рисует всю карту: MCP Server Card говорит, куда подключаться, каталог API перечисляет ваши API.

Эта статья — подробный разбор именно этого слоя. Сразу оговорка: формат SKILL.md поддерживают широко, а индекс для сайтов появился позже, поддержан слабее и всё ещё остаётся черновиком. Подтвердить я смог одного открытого клиента, который его читает.

Ключевые выводы

  • Навык — это SKILL.md с YAML front-matter: name (до 64 символов, строчные буквы, цифры, дефисы) и description (до 1 024 символов), где сказано, что навык делает и когда его применять (спецификация Agent Skills).
  • Обнаружение на сайтах — черновик, а не RFC IETF: Agent Skills Discovery 0.2.0, обновлён 12 марта 2026 года. Индекс перечисляет навыки с полями name, type, description, url и digest (cloudflare/agent-skills-discovery-rfc).
  • digest — это sha256: и хеш сырых байтов артефакта. Клиент ОБЯЗАН (MUST) его проверить и НЕ ВПРАВЕ использовать файл при несовпадении (тот же черновик).
  • Самый дешёвый способ избежать дрейфа — считать digest от тех же байтов, которые отдаёт маршрут. Так делает aiSiteReady, и я проверил результат снаружи через shasum.
  • Проверка agent_skills в aiSiteReady валидирует форму индекса и формат digest. SKILL.md она не скачивает, поэтому скрипт проверки ниже запускайте сами.

Что такое Agent Skill и чем он отличается от документации API?

Agent Skill — это папка с обязательным файлом SKILL.md (YAML front-matter плюс инструкции в Markdown) и необязательными каталогами scripts/, references/ и assets/ (спецификация Agent Skills). В отличие от документации API, которая говорит, что существует и где, навык говорит агенту, что делать. Формат изначально разработала Anthropic и выпустила как открытый стандарт; в списке клиентов на agentskills.io сейчас больше сорока поддерживающих его продуктов.

Масштабируется он за счёт постепенной загрузки. При старте агент читает только name и description каждого навыка — около 100 токенов на навык. Когда задача подходит, он загружает тело SKILL.md — до 5 000 токенов. Приложенные файлы грузятся, только если на них ссылаются инструкции (документация Anthropic).

Этим навык и отличается от документации API. OpenAPI и Server Card говорят агенту, что существует и где. Навык говорит, что делать: когда он применим, какие вызовы и в каком порядке делать и как поступать при сбое. Своего транспорта и эндпоинта он не объявляет.

Поле front-matterПравило
name (обязательно)До 64 символов; строчные буквы, цифры, дефисы; без дефиса в начале, в конце и без двойного; должно совпадать с именем родительской папки
description (обязательно)От 1 до 1 024 символов; говорит, что делает навык и когда его применять

Необязательные поля — license, compatibility, metadata и экспериментальное allowed-tools. Платформа Anthropic добавляет два правила: в обязательных полях не должно быть XML-тегов, а в имени — слов anthropic и claude. Поскольку имя должно совпадать с папкой, навык сайта scan-url естественным образом лежит по адресу /.well-known/agent-skills/scan-url/SKILL.md.

Как агенты находят навыки на сайте?

Они запрашивают один JSON-файл по well-known-пути. Механизм описан в черновике Agent Skills Discovery в GitHub-репозитории Cloudflare: издатель ОБЯЗАН (MUST) отдавать /.well-known/agent-skills/index.json со списком всех навыков (cloudflare/agent-skills-discovery-rfc). На 2 октября 2026 года это версия 0.2.0, опубликованная 17 января 2026 и обновлённая 12 марта 2026. Она опирается на RFC 8615, но не является документом IETF, а записи agent-skills в реестре well-known URI от IANA, обновлённом 16 сентября 2026 года, я не нашёл. Детали могут измениться.

Вот реальный индекс aiSiteReady, снятый с продакшена 2 октября 2026 года.

{
  "$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
  "skills": [
    {
      "name": "scan-url",
      "type": "skill-md",
      "description": "Submit a public URL and receive an Agent Readiness Score (0–100) with per-check evidence and prioritized, localized remediation guidance for AI-agent and AI-search readiness.",
      "url": "https://www.ai-siteready.com/.well-known/agent-skills/scan-url/SKILL.md",
      "digest": "sha256:1d7a33bcc768060bbba4328bec09637e6dcc043ada3c85f19f758879bde54ffb"
    }
  ]
}

$schema — непрозрачный идентификатор версии. Клиент должен предупредить о незнакомом значении и пропустить индекс, а при отсутствии $schema считать индекс старым форматом 0.1.0. Каждая запись в skills обязана содержать все пять полей.

Поле записиПравило из черновика
nameТе же правила, что и для имени в SKILL.md
typeskill-md для одного файла или archive для .tar.gz или .zip с SKILL.md в корне; неизвестные типы клиент пропускает
descriptionДо 1 024 символов; СЛЕДУЕТ (SHOULD) совпадать с описанием из front-matter
urlПолный URL, путь от корня сайта или путь, относительный к URL индекса
digestsha256: и 64 строчных hex-символа от сырых байтов артефакта

Строку $schema сравнивают, а не скачивают. Черновик говорит, что она не обязана открываться, и у хоста schemas.agentskills.io при моей проверке 2 октября 2026 года не было DNS-записи.

Четыре шага: агент запрашивает index.json, читает запись scan-url, скачивает SKILL.md и сверяет sha256-digest, затем следует инструкциям навыка.

Кто на самом деле читает индекс? Поддержка неравномерна. Открытый установщик skills от Vercel Labs реализует версию 0.2.0: npx skills add https://your-domain читает индекс, скачивает все перечисленные навыки, сверяет хеш каждого с digest и только потом даёт выбрать, что установить (исходник, разбор Evil Martians, август 2026). В остальных местах поддержка слабее: майский запрос 2026 года на загрузку навыков из well-known-URL в Claude Code закрыли через месяц как неактивный, так и не реализовав.

Как написать SKILL.md, которым агенты действительно пользуются?

Начните с description: это всё, что агент видит, прежде чем решить. Руководство Anthropic советует писать, что навык делает и когда его применять, от третьего лица и с конкретными ключевыми словами, и предостерегает от расплывчатых вроде «Helps with documents» (Skill authoring best practices).

Для сайта назовите ситуацию пользователя: «Looks up the status of an order by order number. Use when a user asks where an order is, whether it shipped, or when it will arrive».

Дальше в теле нужны пять разделов в таком порядке.

РазделЧто в нёмЗачем
When to useСитуации словами пользователяПодтверждает совпадение
How to invokeТочные эндпоинты, методы, параметры, пример запросаАгент не угадает ваш API
OutputФорма результатаНе даёт выдумывать поля
Errors and limitsКаждая ошибка, следующий шаг, лимитыНе даёт уйти в цикл повторов
Scope and safetyЧего навык не делает; никаких секретовТекст попадает прямо в контекст агента

Размеченный SKILL.md: name и description во front-matter, затем пять разделов тела — от When to use до Scope and safety.

Последняя строка важнее, чем кажется. SKILL.md попадает прямо в контекст агента, поэтому черновик предупреждает, что вредоносный файл может внедрить инструкции, а клиентам рекомендует грузить навыки только из доверенных (allowlist) источников (черновик, раздел Security considerations).

Пишите только о действиях вашего сайта, никогда не просите агента куда-либо отправлять данные пользователя и прямо говорите, что выходит за рамки навыка. Там же сказано, что клиентам НЕЛЬЗЯ (SHALL NOT) по умолчанию исполнять файлы из scripts/, так что навык для сайта не должен от скриптов зависеть. Берите один файл skill-md короче 500 строк (agentskills.io).

Вот front-matter и первый раздел собственного навыка scan-url из aiSiteReady — в том виде, как он отдаётся, без вводного абзаца.

---
name: scan-url
description: Scan a public http(s) URL for AI-agent and AI-search readiness and return an Agent Readiness Score (0-100) with per-check evidence and prioritized, localized remediation guidance across discoverability, content accessibility, bot governance, protocol/API discovery, and commerce.
license: Apache-2.0
---

# Agent Readiness Scan

## When to use

- A user asks whether a site is discoverable, parseable, or usable by AI agents and
  AI-search crawlers.
- You need a structured readiness report (robots/sitemap/llms.txt, structured data,
  bot governance, MCP/A2A/API discovery, commerce signals) for a URL.

Сверив его со своим же чек-листом, я нашёл три пробела:

  1. В описании сказано, что навык делает, но нет оборота «используйте, когда»: эта фраза лежит в теле, которое агент читает уже после решения.
  2. REST-вариант предлагает «poll the returned report path», но ответ 202 на POST /api/v1/scans содержит id и access_token, а не путь, и непубличный отчёт без токена не прочитать.
  3. Раздела с ошибками нет.

Вот что я бы добавил, взяв коды статусов из живого документа OpenAPI.

### Option B - REST API

1. `POST /api/v1/scans` with {"url": "https://example.com", "profile": "all", "locale": "en"}.
   The `202` response carries `id` and `access_token`.
2. Poll `GET /api/v1/scans/{id}` with the header `X-Scan-Access-Token: <access_token>`
   until `status` is `succeeded` or `failed`.
3. Read `GET /api/v1/scans/{id}/report.json` with the same header.

## Errors and limits

- `400` `invalid_scan_url`: the URL is not a public http(s) address. Ask for another one.
- `409` `idempotency_conflict`: the same `Idempotency-Key` was sent with a different body.
- `422` `validation_error` or `invalid_check_selection`: the request body is invalid. Fix it; do not resend it unchanged.
- `429` `rate_limited`: wait the number of seconds in `Retry-After`, then retry once.
- `503` `queue_unavailable`: try again later; do not loop.
- Only public pages are scanned: no logins, no remote JavaScript.

Если лимиты описаны в навыке, публикуйте их и заголовками: см. заголовки RateLimit-Policy для ИИ-агентов.

Как отдавать индекс и SKILL.md?

Как статические, публичные, кэшируемые файлы с честными медиатипами. Черновик конкретен в том, что сервер ОБЯЗАН (MUST) и что СЛЕДУЕТ (SHOULD) делать (черновик, раздел HTTP Considerations); в последнем столбце — то, что продакшен-маршруты aiSiteReady вернули 2 октября 2026 года.

РесурсТребованиеaiSiteReady
Медиатип индексаMUST быть application/jsonapplication/json; charset=utf-8
Медиатип SKILL.mdMUST быть text/markdown или text/plaintext/markdown; charset=utf-8
МетодыMUST поддерживать GET и HEADТолько GET на 2 октября 2026 года; оба — после исправления (см. ниже)
Отсутствующий навыкMUST вернуть 404404 с JSON-телом
КэшированиеSHOULD задать Cache-Controlpublic, max-age=3600
CORSSHOULD задать заголовки для браузерных потребителейНе задан
HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: public, max-age=3600

HTTP/2 200
content-type: text/markdown; charset=utf-8
cache-control: public, max-age=3600
vary: Accept-Encoding

Поле url может быть полным URL, путём от корня сайта (разрешается относительно origin индекса) или путём, относительным к каталогу индекса. aiSiteReady публикует полные URL, и у этого есть цена.

Генератор индекса берёт origin из NUXT_PUBLIC_SITE_URL, а если переменная не задана, откатывается к dev-origin localhost:5173. Сканер отклоняет такой URL — в localhost нет точки, а 5173 не порт 80 или 443, — поэтому продакшен-деплой, где про переменную забыли, публикует индекс, который проверка оценивает как PARTIAL. Пути от корня обошли бы проблему, и установщик, чей код я читал, разрешает их через new URL(entry.url, indexUrl), но проверяйте каждого клиента, который вам важен.

Для статического сайта фреймворк не нужен. Этот скрипт копирует каждый навык побайтово в публичную папку и пишет индекс по тем же байтам.

// node build-skills-index.mjs https://example.com
// Читает skills/<name>/SKILL.md, копирует каждый файл побайтово в public/
// и пишет индекс, digest которого считается по этим же байтам.
import { createHash } from "node:crypto";
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";

const origin = (process.argv[2] ?? "").replace(/\/$/, "");
if (!origin.startsWith("https://")) throw new Error("usage: node build-skills-index.mjs https://example.com");

const out = "public/.well-known/agent-skills";
const skills = [];
const names = readdirSync("skills", { withFileTypes: true })
  .filter((d) => d.isDirectory()) // пропускаем случайные файлы вроде .DS_Store
  .map((d) => d.name)
  .sort();
for (const name of names) {
  const bytes = readFileSync(join("skills", name, "SKILL.md")); // сырые байты, без перекодирования
  const front = /^\uFEFF?---\r?\n([\s\S]*?)\r?\n---/.exec(bytes.toString("utf8"))?.[1] ?? "";
  const field = (key) =>
    new RegExp(`^${key}:\\s*(.+)$`, "m").exec(front)?.[1].trim().replace(/^(["'])(.*)\1$/, "$2");
  if (field("name") !== name) throw new Error(`${name}: front-matter name must equal the folder name`);
  const description = field("description");
  if (!description || /^[>|]/.test(description)) throw new Error(`${name}: front-matter needs a one-line description`);

  mkdirSync(join(out, name), { recursive: true });
  writeFileSync(join(out, name, "SKILL.md"), bytes); // отдаём ровно то, что хешировали
  skills.push({
    name,
    type: "skill-md",
    description, // тот же текст, что в front-matter: черновик говорит, что он SHOULD совпадать
    url: `${origin}/.well-known/agent-skills/${name}/SKILL.md`,
    digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
  });
}

const schema = "https://schemas.agentskills.io/discovery/0.2.0/schema.json";
writeFileSync(join(out, "index.json"), `${JSON.stringify({ $schema: schema, skills }, null, 2)}\n`);
console.log(`wrote ${skills.length} skill(s) to ${out}`);

Проверьте content-type, который ваш хостинг отдаёт для .md-файлов. В стандартном nginx нет MIME-типа для md, и он отдаёт application/octet-stream, нарушая MUST из черновика; добавьте types { text/markdown md; } в блок http (в блоке server директива заменяет всю таблицу типов, включая JSON). И убедитесь, что отсутствующий навык даёт 404: одностраничное приложение, которое на любой неизвестный путь отвечает своей HTML-оболочкой с кодом 200, с радостью «отдаст» несуществующий SKILL.md.

Как не допустить расхождения digest с отдаваемым файлом?

Считайте его от тех же байтов, которые отдаёте, и на том же шаге. Digest — это sha256: и хеш сырых байтов артефакта, а клиент ОБЯЗАН (MUST) проверить его после загрузки и НЕ ВПРАВЕ использовать файл при несовпадении (черновик, раздел Integrity). Установщик Vercel Labs делает ровно это: fetchArtifactSkillByEntry хеширует скачанные байты и возвращает null, если результат отличается от entry.digest, а fetchAllSkills отфильтровывает такие null. Навык молча исчезает из списка, а ошибки о digest вы не увидите.

Так что дрейф — тихая поломка: правите SKILL.md, забываете пересобрать индекс — и клиенты, соблюдающие спецификацию, перестают использовать навык, а вы не увидите ни одной ошибки. Хешируйте распакованное тело, а не gzip-передачу. Всё, что переписывает байты уже после хеширования, например конвертация переводов строк или прокси, вставляющий футер, тоже ломает совпадение.

aiSiteReady устраняет проблему структурно. Текст навыка живёт в одной TypeScript-константе. Маршрут возвращает эту константу, а digest считается от неё при загрузке модуля. Второй копии, которую можно отредактировать отдельно, просто нет.

// константа навыка и её digest (сокращено)
import { createHash } from "node:crypto";

export const SCAN_URL_SKILL_MD = `---
name: scan-url
description: Scan a public http(s) URL …
---
…`;

export const SCAN_URL_SKILL_DIGEST = `sha256:${createHash("sha256")
  .update(SCAN_URL_SKILL_MD, "utf8")
  .digest("hex")}`;
// обработчик маршрута SKILL.md (сокращено)
export default defineReadOnlyHandler((event) => { // одинаково для GET и HEAD; 405 на прочие методы
  setResponseHeader(event, "Content-Type", "text/markdown; charset=utf-8");
  return SCAN_URL_SKILL_MD; // ровно та константа, от которой посчитан digest
});

Я проверил это снаружи 2 октября 2026 года. Продакшен-файл весит 2 476 байт, а shasum -a 256 печатает 1d7a33bc…bde54ffb — digest из индекса. Тело навыка при этом относительное к origin: в нём пути вроде /api/mcp/, а не абсолютные URL, поэтому байты, а с ними и digest, одинаковы на любом домене деплоя.

Проверка digest: SKILL.md (2 476 байт) совпадает с индексом и загружается; после правки в одну строку хеш меняется, и клиент файл отклоняет.

Если хостить навыки там, где вы ничего не контролируете, например по сырым ссылкам GitHub, эта гарантия теряется: правки в апстриме меняют байты под уже опубликованным digest. Правило Evil Martians — писать где угодно, а публиковать из места, которое вы контролируете (Evil Martians).

Как проверить индекс Agent Skills?

Проверяйте три вещи: индекс парсится и имеет верную форму, каждый url отдаёт правильный медиатип, а каждый digest совпадает с байтами. Ручной вариант — три команды curl.

# 1. заголовки и тело индекса
curl -sSi https://example.com/.well-known/agent-skills/index.json | head -20
# 2. все навыки и заявленные digest
curl -sS https://example.com/.well-known/agent-skills/index.json | jq -r '.skills[] | [.name, .digest] | @tsv'
# 3. реальный хеш отдаваемого навыка, чтобы сравнить на глаз
curl -sS https://example.com/.well-known/agent-skills/scan-url/SKILL.md | shasum -a 256

Для CI всё это делает один скрипт. Ему нужны curl, jq и shasum (на Linux без него подставьте sha256sum), и при любой ошибке он завершается с ненулевым кодом.

#!/usr/bin/env bash
# usage: ./check-skills.sh https://example.com
set -euo pipefail
ORIGIN="${1:?usage: check-skills.sh https://example.com}"
ORIGIN="${ORIGIN%/}"
INDEX="$ORIGIN/.well-known/agent-skills/index.json"
SCHEMA="https://schemas.agentskills.io/discovery/0.2.0/schema.json"
bad=0
ok()   { printf 'ok    %s\n' "$*"; }
fail() { printf 'FAIL  %s\n' "$*"; bad=1; }
tmp="$(mktemp)"; trap 'rm -f "$tmp"' EXIT

# 1. Индекс: статус, медиатип, поддержка HEAD, $schema
meta="$(curl -sS -o "$tmp" -w '%{http_code} %{content_type}' "$INDEX")"
[[ "$meta" == 200\ application/json* ]] && ok "index: GET 200, ${meta#* }" || fail "index: GET -> $meta"
[[ "$(curl -sS -I -o /dev/null -w '%{http_code}' "$INDEX")" == 200 ]] \
  && ok "index: HEAD 200" || fail "index: HEAD is not 200 (the draft says servers MUST support HEAD)"
body="$(cat "$tmp")"
jq -e --arg s "$SCHEMA" '."$schema" == $s' <<<"$body" >/dev/null \
  && ok "index: \$schema is the 0.2.0 URI" || fail "index: missing or unexpected \$schema"
jq -e '.skills | type == "array" and length > 0' <<<"$body" >/dev/null 2>&1 \
  && ok "index: skills[] has entries" || fail "index: skills[] is missing or empty"

# 2. Каждая запись: поля, URL, медиатип, name во front-matter и digest
while read -r entry; do
  name="$(jq -r '.name // ""' <<<"$entry")"; url="$(jq -r '.url // ""' <<<"$entry")"
  want="$(jq -r '.digest // ""' <<<"$entry")"
  type="$(jq -r '.type // ""' <<<"$entry")"
  jq -e '.description | type == "string" and length > 0 and length <= 1024' <<<"$entry" >/dev/null \
    || fail "$name: description is missing or longer than 1,024 characters"
  [[ "$type" == skill-md ]] || { fail "$name: type is '$type'; this script verifies skill-md entries only"; continue; }
  [[ "$name" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ && ${#name} -le 64 ]] || fail "$name: invalid name"
  [[ "$want" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "$name: digest is not sha256:<64 lowercase hex>"
  case "$url" in https://*|http://*) ;; /*) url="$ORIGIN$url" ;; *) url="$ORIGIN/.well-known/agent-skills/$url" ;; esac
  [[ "$url" == https://* && "$url" != *localhost* ]] || { fail "$name: url is not a public https URL ($url)"; continue; }

  meta="$(curl -sS -o "$tmp" -w '%{http_code} %{content_type}' "$url")"
  [[ "$meta" == 200\ text/markdown* || "$meta" == 200\ text/plain* ]] \
    && ok "$name: GET 200, ${meta#* }" || fail "$name: GET -> $meta"
  got="sha256:$(shasum -a 256 "$tmp" | cut -d' ' -f1)"
  [[ "$got" == "$want" ]] && ok "$name: digest matches the served bytes" \
    || fail "$name: digest drift - index says $want, served bytes hash to $got"
  head -n 10 "$tmp" | tr -d '\r' | grep -qx "name: $name" \
    && ok "$name: front-matter name matches" || fail "$name: front-matter name differs from the index"
done < <(jq -c '.skills[]?' <<<"$body" 2>/dev/null)

exit "$bad"

Запущенный на продакшен-сайте aiSiteReady 2 октября 2026 года, он вывел вот это.

ok    index: GET 200, application/json; charset=utf-8
FAIL  index: HEAD is not 200 (the draft says servers MUST support HEAD)
ok    index: $schema is the 0.2.0 URI
ok    index: skills[] has entries
ok    scan-url: GET 200, text/markdown; charset=utf-8
ok    scan-url: digest matches the served bytes
ok    scan-url: front-matter name matches

Эта ошибка была настоящей, и она была нашей. На 2 октября 2026 года наши well-known-маршруты были обработчиками только для GET, и запрос HEAD к ним возвращал 404, тогда как черновик требует поддерживать оба метода. Клиент, который перед загрузкой шлёт HEAD, неверно оценил бы наш сайт. Исправление оказалось небольшим: файлы маршрутов лишились суффикса .get, и один обработчик теперь отвечает на GET и HEAD с одинаковыми статусом и заголовками, так что эта строка выглядит как ok index: HEAD 200. Заметил я ошибку только потому, что написал этот скрипт, — так что запустите свой, прежде чем доверять собственной настройке.

Какие ошибки ломают индекс Agent Skills?

Чаще всего индекс ломают дрейф digest, расплывчатый description и localhost в url, и почти все такие поломки тихие: клиент пропускает навык или сканер понижает оценку проверки. Вот что я бы проверил в первую очередь.

ОшибкаЧто происходитКак исправить
Дрейф digestКлиенты, соблюдающие спецификацию, отказываются от навыка, а вы ничего не видитеСчитайте digest от отдаваемых байтов
Расплывчатый descriptionАгент никогда не выберет навыкСкажите, что он делает, и «Use when»
Навык, требующий логинаАгент застревает на первом шагеОпишите, как получить доступ, или не публикуйте
localhost или dev-порт в urlНаша проверка отклоняет URL (имя без точки или порт не 80/443) и даёт PARTIAL; staging-хост проверку проходит, но отправляет агентов на stagingЗадайте публичный origin или используйте пути от корня
HTML-оболочка с кодом 200 вместо отсутствующего файлаКлиент без проверки загрузит HTML как инструкцииВозвращайте 404
Нет $schema или выдуманныйУстановщик, чей код я читал, считает отсутствующий старым 0.1.0, а незнакомый пропускает; без него наша проверка даёт PARTIAL, а любую непустую строку, даже выдуманную, принимаетСкопируйте URI 0.2.0 точно
Старая форма 0.1.0 или путь /.well-known/skills/Наша проверка смотрит только новый путьПерейдите на agent-skills/ с type, url, digest
Описание в индексе отличается от front-matterНарушает SHOULD; агент видит две версииГенерируйте оба из одного источника

Последняя строка — ещё одно признание. Наш индекс берёт описание из A2A Agent Card, а в front-matter у SKILL.md стоит другая, более длинная фраза. Обе точны, но черновик говорит, что им СЛЕДУЕТ (SHOULD) совпадать. Скрипт сборки выше обходит проблему: он читает описание прямо из front-matter.

Как aiSiteReady проверяет индекс Agent Skills?

Проверка agent_skills в aiSiteReady делает один запрос к /.well-known/agent-skills/index.json и валидирует его по Agent Skills Discovery 0.2.0: структуру, пять полей и формат digest, но не сам отдаваемый SKILL.md. Остальное покрывает ваш скрипт. Она входит в категорию Protocol/API рядом с проверками MCP Server Card и каталога API, а аудит за 30 минут показывает, какое место она занимает в полном аудите.

СтатусУсловие
PASS$schema объявлен, content type — JSON, и есть хотя бы одна запись с валидными name, type, description, url и digest
PARTIALВалидные записи есть, но нет $schema или индекс отдан с не-JSON типом вроде text/plain; записи есть, но ни одна не валидна; skills отсутствует или пуст; либо JSON не парсится
UNKNOWNИндекс не удалось безопасно загрузить; 403 или 429 считается блокировкой, а не вердиктом
FAILИндекса нет: 404 или другой код ошибки, кроме 403/429, либо HTML-страница (оболочка SPA или JSON с типом text/html) вместо JSON

Четыре ограничения, которые стоит знать:

  • Проверка делает один запрос и никогда не скачивает SKILL.md, поэтому валидирует формат digest, но не поймает дрейф; для этого и нужен ваш скрипт.
  • Она смотрит только well-known-путь, без поиска через заголовок Link.
  • Категория Protocol/API выключена в профиле Контентный сайт, так что проверка учитывается только в профиле по умолчанию Все проверки и в профилях API/приложение и Интернет-магазин.
  • URL записей должны быть абсолютными http(s)-URL (или разрешаться в такой относительно URL индекса) с доменным именем, содержащим точку, и портом 80 или 443. Проверка их не резолвит и не скачивает, поэтому http://localhost:5173/… не проходит, а staging-хост или приватный IP пройдут.

Сколько весит категория Protocol/API (там она называется «Протоколы») в каждом профиле, показано на странице методологии.

Запустите бесплатное сканирование, чтобы увидеть, находит ли агент ваш индекс навыков, какие записи проходят проверку и что исправить в первую очередь — на английском, украинском или русском. А полный набор слоёв начинается со статьи о том, что значит готовность к ИИ-агентам.

Если коротко: навык — это SKILL.md, где сказано, когда его применять и что именно вызывать; индекс — пять полей на запись; а digest нужно считать от тех байтов, которые вы отдаёте. Пишите описание как триггер, генерируйте индекс из файлов и запускайте скрипт проверки на каждом релизе.

У IMozz 20 лет в разработке ПО, а последний год он строит продукты с помощью LLM. Он развивает aiSiteReady, сканер только для чтения, который проверяет, может ли ИИ-агент прочитать и вызвать сайт. Сканер серверно рендерит собственный контент как рабочий пример.

Частые вопросы

Является ли /.well-known/agent-skills/index.json официальным стандартом?
Пока нет. Он описан в черновике Agent Skills Discovery версии 0.2.0, который лежит в GitHub-репозитории Cloudflare и в последний раз обновлялся в марте 2026 года. Черновик опирается на well-known URI из RFC 8615, но не является RFC IETF, а записи agent-skills в реестре IANA я не нашёл. Сам формат SKILL.md — отдельный открытый стандарт на agentskills.io.
Что должно быть в front-matter у SKILL.md?
Два поля: name и description. Имя — до 64 символов: строчные латинские буквы, цифры и дефисы, без дефиса в начале, в конце и без двойного дефиса; оно должно совпадать с именем папки. Описание — от 1 до 1 024 символов, и в нём нужно сказать и что делает навык, и когда его применять: именно по описанию агент сопоставляет задачу с навыком, прежде чем что-либо загрузить.
Как вычисляется digest в индексе навыков?
Это SHA-256 от сырых байтов артефакта в виде sha256: и 64 строчных hex-символов. Для записи skill-md это ровно тот файл SKILL.md, который отдаёт url. Клиент обязан проверить digest после загрузки и не вправе использовать файл при несовпадении. В командной строке достаточно скачать url через curl и передать тело в shasum -a 256.
Читают ли ИИ-агенты мой индекс навыков уже сегодня?
Некоторые читают, но поддержка неравномерна. Открытый установщик skills от Vercel Labs читает индекс и проверяет каждый digest, а agentskills.io перечисляет больше сорока продуктов с поддержкой формата SKILL.md. Многие агенты well-known-индекс вообще не запрашивают. Публикуйте его как дешёвый слой обнаружения, а не как источник трафика, и сделайте SKILL.md полезным сам по себе.