Newer
Older
navi-1 / docs / skills.md

Skills and protocols

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.

Skills vs protocols vs manuals

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).

Document format

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: "Проверить статус; если результат уже есть — отметить шаг."
---

Контекст: порядок зависимостей, известные ловушки, что занимает время.

Fields

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.

Storage

Four 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
);
CREATE TABLE protocol_runs (
    id TEXT PRIMARY KEY,
    protocol_id TEXT, protocol_name TEXT, protocol_version INTEGER,
    session_id TEXT, user_id TEXT,
    status TEXT,        -- 'running' | 'done' | 'failed' | 'cancelled'
    steps JSONB, post_checks JSONB,      -- snapshot at start
    report TEXT, reason TEXT,
    started_at TIMESTAMPTZ, finished_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX idx_protocol_runs_one_active
    ON protocol_runs (session_id) WHERE status = 'running';
  • Ownership: personal rows have 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.
  • Uniqueness: (owner_id, kind, name) — the same name may exist once personally and once shared without colliding.
  • Versioning: every create and update appends to 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.

REST 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".

Proposals

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.

Runs

Loading a protocol only shows its text. Executing one is the protocol tool:

op what it does
start opens a run and splices the steps into the todo plan after the agent's current position
finish closes the run as done; requires a report and refuses while any step of the run is open
cancel closes it as cancelled with a reason; still-open run steps become skipped
fail closes it as failed with a reason; a run with a failed step can only end this way
status the active run and the state of its steps

A run row snapshots the protocol's version, steps and post_checks at start, so editing the catalog entry mid-run cannot change what the running agent owes the user; the log still says which version was executed. One run per session is running at a time (a partial unique index enforces it).

The steps are ordinary todo tasks, tagged with source="protocol", protocol_run_id and a stable step_id; insert_steps keeps the agent's own progress and statuses intact. Marking one done still goes through todo(op="update") and still needs a validation.

While a run is running, every model turn gets a system reminder with the protocol, its steps and the way to close it (navi/core/agent.py, next to the todo reminder). This is what survives compression: a run started ten turns ago is otherwise invisible to the model once the todo lines lose their context. The final-turn intercept also fires when a run is open even though all its steps are closed — a bare-text turn cannot leave it running.

The log is a row per run with report/reason; there is no separate UI yet — protocol(op="status") and the table are the current view.

Agent tools

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.

protocol

start, finish, cancel, fail, status — the run machinery described above:

{"op": "start", "id": "9c4d2b7a1e6f8035"}
{"op": "finish", "report": "<what was done, each post-check and its result>"}
{"op": "fail", "reason": "<which step, why, what state things are in>"}

Both tools are available to every profile that runs user work — assistant, designer_3d, developer, discuss, modeler_3d, navi_code and server_admin. Two exceptions: dispatcher is a single-turn internal router with max_iterations: 1 and no tools at all, and sub-agents get neither, because a protocol belongs to the session's plan, not to a delegated run.

Settings tab

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.

Not implemented yet

  • Hive synchronization of shared entries (schema is ready: content_hash, version).
  • A run-history view in the webclient (protocol_runs is written, but the tab shows the catalog only).