The catalog behind the agent's methodologies: reusable skills (how to approach a kind of task) and protocols (a strict, pre-agreed order of actions). One registry, one document format, one place in settings.
| manual | skill | protocol | |
|---|---|---|---|
| answers | "how does this tool work" | "how do I approach this" | "what exactly to do, in what order" |
| written by | developers | the agent, freely | the agent proposes, the user accepts |
| strictness | reference | advice, adapt as needed | commitment; deviation is an error to report |
| storage | manuals/*.md in the repo |
catalog row | catalog row |
A skill is injected as instructions and nothing more — no run, no report. A protocol has an explicit start, mandatory checks, and an explicit finish with a report (the run machinery builds on the todo plan; see todo-store.md).
Markdown with a YAML front matter block:
--- kind: protocol name: Корректное выключение инфраструктуры description: Останавливает сервисы в порядке зависимостей и проверяет диск. area: infrastructure tags: [power, shutdown] triggers: - "пользователь просит выключить сервер" steps: - "Остановить приложения" - "Остановить базы данных" - "Синхронизировать и отмонтировать диски" preconditions: - "Все пользователи предупреждены" post_checks: - "Прикладных юнитов не осталось" on_deviation: "Не продолжать; вернуться к предыдущему шагу и доложить." on_blocked: "Остановить процесс и доложить, что именно не дало завершить." on_already_done: "Проверить статус; если результат уже есть — отметить шаг." --- Контекст: порядок зависимостей, известные ловушки, что занимает время.
| Field | skill | protocol | |||
|---|---|---|---|---|---|
kind |
required | required | |||
name |
required, unique per owner | required, unique per owner | |||
description |
recommended | recommended | |||
triggers |
optional list — when to use it | optional list — when the protocol applies | |||
tags |
optional | optional | |||
area |
home \ |
infrastructure \ |
workflows \ |
general (default) |
same |
steps |
— | required | |||
preconditions |
— | optional list | |||
post_checks |
— | required — how each step's result is verified | |||
on_deviation |
— | required — the run drifted from the plan | |||
on_blocked |
— | required — the protocol cannot be completed | |||
on_already_done |
— | required — a step's result already exists | |||
| body | required — the instructions | the context around the steps |
Validation is strict on purpose: a protocol without its three escape hatches cannot be executed honestly when reality differs from the plan, so the store refuses it with a message naming the missing keys.
Unknown front matter keys are preserved in the JSONB copy, so extra metadata (owner_team, reviewed_by) survives a round trip through the editor.
Two tables (PostgreSQL, lazy DDL on first use):
CREATE TABLE skills (
id TEXT PRIMARY KEY, kind TEXT, name TEXT, description TEXT,
triggers TEXT[], tags TEXT[], area TEXT,
visibility TEXT, owner_id TEXT, -- 'personal' | 'shared'
content TEXT, frontmatter JSONB,
version INTEGER, content_hash TEXT,
created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, updated_by TEXT
);
CREATE TABLE skill_versions (
id BIGSERIAL PRIMARY KEY, skill_id TEXT REFERENCES skills(id) ON DELETE CASCADE,
version INTEGER, content TEXT, frontmatter JSONB, content_hash TEXT,
edited_by TEXT, reason TEXT, created_at TIMESTAMPTZ,
UNIQUE (skill_id, version)
);
CREATE TABLE skill_proposals (
id TEXT PRIMARY KEY, owner_id TEXT, session_id TEXT, kind TEXT, name TEXT,
content TEXT, frontmatter JSONB, replaces_id TEXT,
status TEXT, -- 'pending' | 'accepted' | 'rejected'
skill_id TEXT, error TEXT,
created_at TIMESTAMPTZ, decided_at TIMESTAMPTZ, decided_by TEXT
);
owner_id = <user>; shared rows have owner_id = '' and are writable only by an admin. An admin creates both; a regular user creates personal entries only.(owner_id, kind, name) — the same name may exist once personally and once shared without colliding.skill_versions; version on the row is the latest. History keeps edited_by and reason.content_hash is the SHA-256 of the document. It is the only hive-synchronization hook committed so far; the sync service itself (instance registry in the hive) is future work.SkillStore.list(user_id, kind?, query?, tag?, area?, limit) returns what the user may see (own + shared), personal first, then most recently updated. query matches name, description, triggers and tags. The agent's skills tool uses the same query; the settings tab uses the HTTP API.
All endpoints require a logged-in user.
| Method | Path | Purpose |
|---|---|---|
GET |
/skills?kind=&q=&tag=&area=&limit= |
catalog for the current user |
GET |
/skills/{id} |
one entry with the full document |
POST |
/skills |
create; body {content, visibility?, reason?} |
PUT |
/skills/{id} |
edit; body {content, reason?, visibility?} |
DELETE |
/skills/{id} |
remove (history cascades) |
GET |
/skills/{id}/versions?limit= |
edit history, newest first |
GET |
/skills/proposals/{id} |
a protocol proposal (owner or admin) |
visibility: shared on create or change requires role == "admin".
A protocol is never created by the agent directly. skills(op="propose") writes a row to skill_proposals (owner, session, document, optional replaces_id) and returns a card; the catalog row appears only when the user clicks Добавить.
The decision is handled by the server, not by the model: the browser sends {"type": "skill_proposal", "proposal_id": "...", "action": "accept" | "reject"} over the WebSocket, SkillStore.decide_proposal writes (or does not write) the catalog row, and the answer comes back as skill_proposal_decided. Accepting saves the stored document — the client cannot swap the text — and names the clicking user as the author of the new version. A proposal the store then refuses (a duplicate name, say) stays pending with the reason on the row, and the card shows it.
Rejecting leaves the catalog untouched. Either way the agent learns the outcome as a hidden user message, the same way a navi_ui form submission reaches it, so it can continue the conversation with the fact in hand.
Pending proposals do not show up in search or skills(op="load") — a document the user has not agreed to must never reach another turn as a methodology.
skills — search, load, create, update, propose:
{"op": "search", "query": "выключение", "area": "infrastructure"}
{"op": "load", "id": "3f2a9c1d8e4b7a60"}
{"op": "create", "content": "---\nkind: skill\n..."}
{"op": "update", "id": "3f2a9c1d8e4b7a60", "content": "<document>"}
{"op": "propose", "content": "<protocol document>", "replaces_id": "9c4d2b7a1e6f8035"}
load renders a compact document: for a protocol, preconditions, numbered steps, post-checks and the three escape hatches; for a skill, the instructions. create and update accept skills only — a protocol document sent there is refused with the reason, and update of a protocol points at propose with replaces_id. The tool is available to assistant, developer, server_admin and discuss.
Settings → Скилы и протоколы: the catalog list with a search box and kind/ area filters, a document editor that shows the server's validation error verbatim, create from a skill or protocol template, delete, and the edit history. An admin can also move an entry between personal and shared; for everyone else a shared entry opens read-only.
protocol_start / protocol_finish / protocol_cancel / protocol_fail and run logging.content_hash, version).