Skip to main content

API

API Велеса живет внутри процесса veles gateway. Его используют Nerve, интеграции и служебные клиенты, которым нужен доступ к состоянию шлюза, сессиям, задачам, файлам рабочей области, личностям и вызовам моделей. Интерфейс делится на два транспорта:
  • HTTP-маршруты для простых операций, загрузки вложений и доски задач.
  • WebSocket JSON-RPC на /ws для интерактивного управления сессиями, чатом, файлами, личностями и секретами.

Адрес и авторизация

Хост, порт и токен задаются в gateway-разделе config.json:
{
  "gateway": {
    "host": "127.0.0.1",
    "port": 18790,
    "token": "..."
  }
}
Токен можно задать и через переменную окружения VELES_GATEWAY_TOKEN (она переопределяет значение из config.json, в том числе при запуске с уже существующим файлом конфигурации). Если токен не задан вообще, gateway не запускается, а любые запросы получают 401. Nerve не обязан хранить этот токен в своём окружении. Для входа пользователя Nerve вызывает до connect отдельный WebSocket-метод auth.login: Veles проверяет пароль, возвращает эффективный gateway.token только серверному процессу Nerve, а браузер получает только cookie-сессию Nerve. Если пароль Nerve ещё не задан, первый вход можно выполнить самим gateway.token; после сохранения пароля этот запасной вход отключается. HTTP-запросы, кроме GET /health, должны передавать токен в заголовке:
Authorization: Bearer <gateway.token>
При отсутствии или несовпадении токена защищённые HTTP-маршруты отвечают 401 Unauthorized с заголовком WWW-Authenticate: Bearer realm="veles-gateway". GET /health остаётся открытым для внешних проверок доступности. Сравнение токена выполняется в постоянном времени (compare_digest). WebSocket-клиент сначала подключается к /ws. Сразу после accept gateway присылает событие connect.challenge со случайным одноразовым nonce:
{
  "type": "event",
  "event": "connect.challenge",
  "payload": { "nonce": "kZ8s...generated" },
  "seq": 1
}
Затем клиент отправляет JSON-RPC запрос connect с токеном. Поддерживаются две формы параметров: вложенная auth.token (предпочтительно) и плоская token.
{
  "type": "req",
  "id": "connect-1",
  "method": "connect",
  "params": {
    "auth": {
      "token": "<gateway.token>"
    }
  }
}
До успешного connect любой другой метод возвращает ошибку -32001 Unauthorized, а входящие/исходящие события не доставляются клиенту. Исключение до connect составляют методы входа Nerve:
MethodНазначение
auth.statusВозвращает, задан ли пароль Nerve и разрешён ли первичный вход через gateway.token.
auth.loginПроверяет пароль Nerve или первичный gateway.token, затем возвращает gatewayToken только серверному клиенту.
Метод auth.password.set доступен только после обычного connect с токеном gateway и сохраняет новый пароль Nerve в зашифрованном хранилище Veles. Если зашифрованное хранилище пароля недоступно или не расшифровывается, auth.login не включает запасной вход через gateway.token и возвращает ошибку хранилища. Это нужно, чтобы потеря ключа шифрования не превращалась в обход уже заданного пароля. Успешный ответ:
{
  "type": "res",
  "id": "connect-1",
  "ok": true,
  "payload": {
    "protocol": 3,
    "server": "veles"
  }
}

Формат WebSocket RPC

Запросы имеют общий вид:
{
  "type": "req",
  "id": "request-id",
  "method": "sessions.history",
  "params": {}
}
Ответы:
{
  "type": "res",
  "id": "request-id",
  "ok": true,
  "payload": {}
}
Ошибки:
{
  "type": "res",
  "id": "request-id",
  "ok": false,
  "error": {
    "code": -32602,
    "message": "validation message"
  }
}
Gateway также отправляет события с type: "event". У события всегда есть поля event (имя), payload (данные) и seq (монотонный счётчик событий внутри текущего WebSocket-подключения для упорядочивания и восстановления после переподключения):
{
  "type": "event",
  "event": "chat",
  "payload": { "...": "..." },
  "seq": 42
}
Основные события: connect.challenge при подключении, chat для потока сообщений ассистента и пользователя, project для хода проекта и workspace.file.changed для обновления дерева файлов рабочей области в удалённом интерфейсе.

Коды ошибок

Ошибки возвращаются в поле error с числовым code (совместимым с JSON-RPC) и человекочитаемым message:
CodeКогда возникает
-32700Тело запроса не является валидным JSON.
-32601Неизвестный метод.
-32602Ошибка валидации параметров (ValueError в обработчике).
-32001Не пройдена авторизация: отсутствует или неверен токен.
-32000Внутренняя ошибка обработчика.

HTTP endpoints

MethodRouteНазначение
GET/healthОткрытая проверка доступности gateway, токен не нужен.
GET/statusОбщий статус gateway, модели, thinking, каналов, сессий и task storage. В config.models возвращаются настроенные модели с id, label и modalities.
POST/tools/invokeСовместимый HTTP-вызов отдельных gateway tools.
POST/attachments/uploadЗагрузка вложения в workspace с привязкой к x-session-key.
GET/POST/api/personalities/selectLLM-выбор лучшей Personality для пользовательского запроса.
GET/push/configПубличная (для браузера) часть конфигурации Firebase web push: configured, firebase, vapidKey.
POST/push/subscriptionsРегистрация FCM-токена устройства: {"token", "platform", "userAgent"}.
DELETE/push/subscriptionsУдаление подписки устройства: {"token"}.
POST/push/presenceОтметка видимости вкладки/PWA: {"clientId", "visible"} — гасит зеркалирование ответов в push на срок channels.push.presenceTtlSeconds, пока пользователь смотрит на приложение.
GET/api/tasksСписок задач с фильтрами status, priority, assignee, label, q, limit, offset.
POST/api/tasksСоздание задачи.
GET/api/tasks/configКонфигурация task board.
PUT/api/tasks/configОбновление конфигурации task board.
GET/api/tasks/proposalsСписок предложений ассистента для задач.
POST/api/tasks/proposalsСоздание предложения.
POST/api/tasks/proposals/{proposal_id}/approveПринять предложение.
POST/api/tasks/proposals/{proposal_id}/rejectОтклонить предложение.
GET/api/tasks/{task_id}Получить задачу.
PATCH/api/tasks/{task_id}Обновить задачу.
DELETE/api/tasks/{task_id}Удалить задачу.
POST/api/tasks/{task_id}/reorderПереместить задачу между колонками или позициями.
POST/api/tasks/{task_id}/executeЗапустить выполнение задачи в выбранной сессии.
POST/api/tasks/{task_id}/completeЗаписать результат выполнения задачи.
POST/api/tasks/{task_id}/approveПодтвердить выполненную задачу.
POST/api/tasks/{task_id}/rejectОтклонить выполненную задачу.
POST/api/tasks/{task_id}/abortПрервать выполнение задачи.

Выбор личности

Endpoint GET /api/personalities/select?query=... и POST /api/personalities/select используют модель по умолчанию, чтобы выбрать подходящую Personality по тексту запроса. Этот служебный выбор вызывается без reasoning/thinking effort, чтобы не тратить расширенное рассуждение на маршрутизацию. Промпт ограничивает выбор списком реально настроенных personalityId, а backend проверяет, что выбранный идентификатор существует. В ответе GET /status поле config.models содержит настроенные модели. Для каждой записи передаются id, label, modalities и, если он явно задан, список reasoningEfforts. Nerve использует этот список как приоритетный набор уровней рассуждения для модели. Nerve также проксирует этот contract через свой POST /api/personalities/select, чтобы окно нового диалога могло предложить режим Подобрать без прямого доступа браузера к gateway token. Тело POST:
{
  "query": "Нужно проверить PR и найти регрессии"
}
Ответ:
{
  "personalityId": "reviewer",
  "personality": {
    "personalityId": "reviewer",
    "id": "reviewer",
    "name": "Reviewer",
    "model": null,
    "thinkingLevel": "high",
    "isDefault": false,
    "rootSessionKey": "personality:reviewer:main"
  },
  "reason": "Запрос требует ревью кода и поиска рисков.",
  "confidence": 0.91,
  "model": "openrouter/...",
  "candidateCount": 3,
  "usage": {}
}
SOUL.md всех личностей передается модели для выбора, но не возвращается в поле personality.

Загрузка вложений

POST /attachments/upload принимает необработанное тело файла и заголовки:
HeaderЗначение
x-session-keyСессия, к которой относится вложение.
x-attachment-nameИмя файла.
x-attachment-mime-typeТип содержимого файла.
x-attachment-kindОпциональный тип: image, audio, voice, file.
Ответ содержит объект вложения с id, workspacePath, absolutePath, mimeType и другими полями. Nerve затем передает такие объекты в chat.send как attachmentRefs. Для attachmentRefs основным адресом файла считается workspacePath. Поле absolutePath может отсутствовать в запросе от Nerve: шлюз сам находит файл внутри рабочей области Велеса, проверяет, что он существует, и подставляет свой канонический полный путь. Если путь не удается разрешить в рабочей области Велеса, chat.send возвращает ошибку вместо тихой отправки сообщения без вложения.

Вызов tools по HTTP

POST /tools/invoke — это HTTP-обёртка над частью операций gateway, удобная для клиентов без WebSocket-соединения. Тело запроса:
{
  "tool": "sessions_list",
  "args": {},
  "sessionKey": "web:default"
}
Ответ:
{
  "ok": true,
  "result": { "...": "..." }
}
Ошибки возвращаются как { "ok": false, "error": { "message": "..." } } (HTTP 400 для невалидного тела или ошибки аргументов). Поддерживаемые значения tool:
toolНазначение
sessions_listСписок сессий (аналог sessions.list).
sessions_historyИстория диалога по sessionKey/conversationId/sessionId.
session_statusИзменить model/thinkingLevel сессии.
sessions_spawnСоздать дочернюю сессию и отправить в неё задачу.
cronУправление расписаниями: list, add, update, delete, run, runs.
tasksОперации с доской задач (зеркало части /api/tasks).
memory_storeNo-op ({ "stored": false, "mode": "noop" }); память хранится навыком, а не gateway.
subagentsВозвращает пустой список (зарезервировано).
Для интерактивного управления чатом и сессиями предпочитайте WebSocket RPC — /tools/invoke рассчитан на скриптовые и служебные вызовы. При создании и обновлении задания cron сохраняет не только текст и расписание, но и поля запуска: sessionKey, personalityId, payload.model и payload.thinking. Если указан personalityId, но нет sessionKey, задание запускается в основной сессии этой личности. При выполнении Велес применяет сохраненные модель и уровень рассуждения к сессии задания перед обращением к модели.

WebSocket RPC methods

Статус

MethodНазначение
statusСтатус gateway и текущей или указанной сессии.
providers.usageПоказатели расхода и остатка для всех подключённых поставщиков, которые умеют их сообщать.
openrouter.balanceУстаревшее представление баланса OpenRouter для совместимости; новые клиенты используют providers.usage.
providers.usage возвращает поставщиков в порядке общего реестра. Поставщик без подключённых учётных данных или без обработчика расхода не попадает в ответ. Интерфейс не должен проверять конкретные имена поставщиков: подписи, полное значение, короткое значение для значка, доля шкалы и время сброса приходят вместе с показателем.
{
  "providers": [
    {
      "providerId": "openai_codex",
      "label": { "en": "OpenAI Codex", "ru": "OpenAI Codex" },
      "status": "ok",
      "lastUpdated": 1783764000000,
      "metrics": [
        {
          "id": "five_hour",
          "label": { "en": "5-hour limit", "ru": "Лимит на 5 часов" },
          "value": 72.5,
          "maximum": 100,
          "progress": 0.725,
          "displayValue": { "en": "72.5% remaining", "ru": "Осталось 72.5%" },
          "badgeValue": { "en": "72.5%", "ru": "72.5%" },
          "resetAt": 1783771200000,
          "defaultBadge": true
        }
      ]
    }
  ],
  "checkedAt": 1783764000000
}
status принимает ok, stale или unavailable. При временной ошибке Велес возвращает последнюю успешную запись со значением stale; если успешной записи ещё не было, поставщик возвращается как unavailable без показателей. Успешные ответы хранятся в памяти 30 секунд и удаляются при обновлении конфигурации или учётных данных. resetAt всегда задан в миллисекундах от начала эпохи.

Конфигурация и перезапуск

МетодНазначение
config.getВернуть путь к config.json, JSON-схему, версию файла, очищенное содержимое конфигурации и метаданные скрытых секретов.
config.saveПроверить и сохранить конфигурацию. При restart: true шлюз планирует собственный перезапуск после отправки ответа.
config.raw.getВернуть полный текст активного config.json без скрытия gateway.token и секретных значений.
config.raw.saveПроверить и сохранить полный текст активного config.json без перезапуска шлюза.
gateway.restartЗапланировать перезапуск процесса шлюза без изменения конфигурации.
config.get не возвращает gateway.token в значении и удаляет это поле из схемы для редактора. Остальные значения, которые являются целями секретов, возвращаются как маркеры вида { "$velesRedacted": true, "targetId": "..." }, если значение уже задано. config.save восстанавливает такие маркеры и скрытые поля из исходной конфигурации перед валидацией, поэтому интерфейс может сохранить остальные изменения, не раскрывая секреты. Методы config.raw.get и config.raw.save предназначены для полного текстового редактора конфига. Они показывают физическое содержимое файла, поэтому в ответе могут быть gateway.token и секреты, если они записаны в config.json открытым текстом. При сохранении шлюз проверяет, что текст является объектом JSON и проходит схему Велеса, поддерживает проверку expectedMtimeMs от устаревшей записи, сохраняет переданный текст как есть и не планирует перезапуск. После такой правки пользователь перезапускает шлюз вручную.

Чат

MethodНазначение
chat.sendОтправить сообщение в сессию. Поддерживает sessionKey, conversationId, message, attachmentRefs, idempotencyKey, deliver, executionMode и clientLocale. Вложенные изображения берутся из рабочей области Велеса, уменьшаются до ограниченного размера для запроса к модели и повторно добавляются в ближайшую историю, если пользователь продолжает обсуждать уже отправленные изображения. Если выбранная настроенная модель не содержит image или vision в agents.defaults.models[].modalities, Велес не передает данные изображения провайдеру и оставляет модели только текстовое описание вложения.
chat.abortОстановить активный ответ в сессии.
chat.historyПолучить историю сообщений по sessionKey, conversationId или sessionId.
executionMode принимает direct или project; если поле не передано, используется direct. Режим project разрешён только для первого сообщения пустого нового диалога. clientLocale принимает en или ru и задаёт запасной язык вопросов и материалов, если язык исходного сообщения определить не удалось. Для проекта шлюз сохраняет исходное сообщение, структурированные ссылки на вложения, текущую модель и уже приведённый к каноническому виду уровень рассуждений. Служебные значения auto, default и off не передаются поставщику как reasoning_effort; устаревшее значение uhigh преобразуется в xhigh. Связь с созданным проектом приходит в событии project, сводке сессии и ответе projects.get. После создания проекта последующий chat.send с executionMode: "direct" не отклоняется. Велес находит активный проект по стабильному идентификатору диалога, добавляет в системный контекст его текущее состояние и подключает к этому ходу только средства project_get_state, project_submit_answers, project_approve_plan, project_revise_plan, project_continue и project_cancel. Они не регистрируются в общем реестре и не передаются модели в обычном диалоге. Изменяющие средства применяют те же проверки состояния, номера редакции и идемпотентности, что и явные вызовы projects.*. События ответа ассистента приходят через WebSocket event chat. Поле state определяет фазу:
stateЗначение
startedАссистент начал новый ответ (новый runId).
deltaПромежуточный фрагмент ответа или обновление прогресса инструментов.
finalФинальное сообщение. Приходит и для сообщений ассистента, и для входящих сообщений пользователя из других каналов (role: "user").
abortedОтвет остановлен через chat.abort.
Поля payload события chat:
ПолеОписание
sessionKey, sourceSessionKeyКлюч сессии события. Для изолированного диалога sessionKey может иметь вид personality:<id>:conv:<conversationId>, а sourceSessionKey остается корневым ключом personality:<id>:main.
stateФаза ответа (см. выше).
runIdИдентификатор текущего ответа; стабилен для всех событий одного ответа.
seqПорядковый номер события внутри текущего WebSocket-подключения (для упорядочивания/дедупликации).
totalTokens, contextTokenstotalTokens показывает текущую оценку занятого контекста сессии. contextTokens берётся из текущего agents.defaults.contextWindowTokens, а не из сохранённых метаданных диалога. Для пустого нового диалога totalTokens равен 0.
conversationId, conversationKey, rootSessionKeyПривязка к корневому диалогу Nerve. Поля приходят и для обычного корневого ключа, и для изолированного ключа personality:<id>:conv:<conversationId>.
toolEvents, toolHintПрогресс инструментов и флаг активности инструмента.
messageОбъект сообщения: role, content, timestamp (ms), а также toolEvents, toolHint, attachments при наличии. Присутствует для delta/final.
error, errorMessageТекст ошибки, если ответ завершился ошибкой.
Клиент группирует события по runId, применяет delta поверх накопленного текста и фиксирует ответ на final. Событие workspace.file.changed приходит, когда шлюз замечает создание, изменение, удаление или переименование файла в рабочей области. Оно нужно удалённому Nerve, который не может сам наблюдать файловую систему шлюза. Событие не хранится в постоянной очереди: если подключение интерфейса уже закрывается, шлюз прекращает пересылку для этого подключения, а после переподключения интерфейс заново запрашивает дерево файлов. Поля payload события workspace.file.changed:
ПолеОписание
pathОтносительный путь внутри рабочей области, с / как разделителем.
personalityIdИдентификатор личности для совместимости с текущим обработчиком интерфейса. Файлы рабочей области остаются общими; это поле не означает отдельный файловый корень.
Если один диалог личности уже выполняется, а Nerve создает, активирует или отправляет сообщение в другой диалог той же личности, gateway может использовать отдельный ключ выполнения personality:<id>:conv:<conversationId>. Такой ключ нужен только для живого выполнения: сохраненная история, conversationKey и rootSessionKey остаются привязаны к корневому диалогу личности, а sessions.list не показывает изолированный ключ как отдельную строку.

Проекты

Режим Проект ведёт сложную задачу через уточнение требований, утверждение плана, обязательное исследование, последовательное выполнение и итоговый документ. Состояние принадлежит Велесу и связано с одним диалогом; Nerve показывает сохранённые сообщения и канонический снимок, а также отправляет решения пользователя. Основным идентификатором связи служит conversationId. Технический ключ выполнения может меняться между корневой сессией личности и изолированной сессией диалога, поэтому уведомления о состоянии, отмена и итоговое сообщение всегда разрешают текущую сессию заново по идентификатору диалога. Сохранённый при создании проекта sessionKey нельзя использовать как неизменный адрес. Методы:
МетодПараметрыНазначение
projects.getНеобязательный projectId либо идентификатор диалога: sessionKey/key или conversationIdВернуть канонический снимок связанного проекта либо null, если проект не найден.
projects.submitAnswersprojectId, answers, outputPath; необязательные expectedRevision, idempotencyKeyСохранить ответы анкеты и точную относительную папку результатов, затем начать подготовку плана.
projects.planDecisionprojectId, decision; необязательные guidance, expectedRevision, idempotencyKeyУтвердить план (decision: "approve") либо запросить новую редакцию (decision: "revise"). Для revise поле guidance обязательно.
projects.resumeprojectId; необязательные guidance, expectedRevision, idempotencyKeyПродолжить приостановленную или незавершённую часть работы в новом контексте без очистки сохранённого состояния. Пояснение пользователя передаётся в guidance.
projects.cancelprojectId; необязательные expectedRevision, idempotencyKeyОтменить проект без удаления уже созданных материалов.
Элемент answers имеет вид { "questionId": "...", "optionId": "..." } для подготовленного варианта или { "questionId": "...", "customText": "..." } для собственного ответа. optionId и customText взаимоисключающие. Требуется ровно один допустимый ответ на каждый обязательный вопрос. expectedRevision включает оптимистическую защиту от устаревшего интерфейса: если номер не совпадает с текущим снимком, действие отклоняется, а клиент получает актуальное состояние через projects.get. idempotencyKey защищает от повторного применения одного решения после сетевого повтора. Ответ изменяющего метода содержит snapshot; поля content и started передают краткий результат действия и признак запуска фонового этапа, когда они применимы.

Снимок и состояния

Канонический снимок содержит как минимум:
ПолеОписание
projectId, sessionKey, conversationIdСвязь проекта с диалогом.
revisionМонотонный номер сохранённой редакции состояния.
status, phaseОбщее состояние и текущая фаза.
questions, answersАнкета и принятые ответы. Каждый вопрос содержит ровно три элемента options и разрешает собственный ответ.
suggestedOutputPath, outputPathПредложенная и подтверждённая папки материалов.
planPath, stepsПуть к плану и упорядоченные этапы с состояниями и материалами.
artifacts, finalPathСписок готовых материалов и путь к итоговому документу.
currentStepId, currentStepIndexТекущий этап, если выполняется работа.
errorБезопасное описание ошибки: code, message, phase, stepId, attempt, retryable, detailsPath.
updatedAtВремя последнего сохранённого изменения.
Поддерживаются состояния preparing_questions, awaiting_answers, planning, awaiting_plan_approval, running, paused, failed, completed и cancelled. В сводках sessions.list, sessions.history и sessions.get связь с проектом отражают поля executionMode, projectId и projectStatus; по ним Nerve находит проект после перезагрузки. Служебное состояние хранится отдельно от пользовательских материалов под .veles/project-runs/<projectId>/. Снимок состояния записывается атомарно, переходы дополнительно фиксируются в журнале, а подробности попыток очищаются от секретов. Новая редакция сохраняется до отправки соответствующего события.

Специальные сообщения проекта в чате

Анкета, утверждение плана и ошибка передаются как обычные окончательные сообщения помощника в событии chat. Дополнительное поле payload.message.projectInteraction сохраняется вместе с сообщением и без изменений возвращается через chat.history. Поэтому Nerve размещает действие в хронологии диалога, а не восстанавливает отдельную панель только из события project. Общие поля взаимодействия:
ПолеОписание
idСтабильный идентификатор взаимодействия. Повторная доставка одной редакции обновляет то же сообщение. Каждая редакция плана получает идентификатор <projectId>:plan:<planRevision> и отдельное место в хронологии; ошибка получает отдельный идентификатор попытки.
kindquestionnaire, plan или error.
projectId, revisionПроект и редакция канонического состояния, к которой относится сообщение.
statusСостояние проекта в момент публикации сообщения.
Для questionnaire передаются questions, suggestedOutputPath и outputPath; для plantitle, summary, steps и planPath; для error — объект error из снимка. Клиент не должен применять сообщение с меньшей revision поверх уже показанного сообщения с тем же id. Новая редакция плана добавляется в конец диалога, а действия остаются доступны только на последней карточке плана. Если специальное сообщение пропущено, Nerve получает снимок через projects.get и временно строит из него только текущее действие, требующее ответа пользователя. Работа исполнителя использует существующий поток чата: пояснения приходят в chat со состоянием delta, а вызовы и результаты средств — в toolEvents по тому же контракту, что и в обычном режиме. Полностью завершённые пары «вызов — результат» дополнительно записываются в историю как контрольные точки, поэтому уже показанные результаты не исчезают после аварийного перезапуска и в истории не остаётся незакрытый вызов прерванного средства. После этапа контрольные точки текущей попытки заменяются полным ходом: сообщения помощника, вызовы и результаты средств сохраняются в обычной истории диалога, а окончательный вывод этапа приходит как обычное сообщение помощника. У такого окончательного события поле payload.historyRefresh равно true: клиент сразу объединяет сохранённый ход с живой лентой, чтобы промежуточные пояснения не исчезли после очистки потокового буфера. Для итоговой сборки в историю попадает ход использования средств, а полное содержимое сохраняется в final.md и заменяется в чате краткой ссылкой.

Событие project

Поле payload события project имеет одну из двух форм:
{
  "kind": "snapshot",
  "projectId": "project-id",
  "revision": 7,
  "snapshot": { "projectId": "project-id", "revision": 7, "status": "running" }
}
{
  "kind": "progress",
  "projectId": "project-id",
  "revision": 7,
  "progress": { "message": "...", "stepId": "step-02", "toolName": "..." }
}
snapshot является уведомлением о новой сохранённой редакции. progress — промежуточный, объединяемый интерфейсом ход текущей редакции; он не изменяет каноническое состояние. Оба вида событий могут быть пропущены при разрыве соединения и не являются постоянной очередью. После переподключения, пропуска редакции или смены диалога Nerve обязан вызвать projects.get, а не восстанавливать проект только из событий.

Папка и последовательность работы

Если пользователь не выбрал папку, Велес предлагает docs/<YYYY-MM-DD>-<slug>--<id8>. outputPath — точный относительный путь внутри рабочей области. Запрещены абсолютные пути, выход через .., скрытые пути, символические ссылки за пределы рабочей области, корень рабочей области и служебные каталоги .veles, sessions, memory, tasks. Существующая папка с посторонними файлами допустима, но совпадение с любым путём, которым управляет проект, отклоняется до записи. Проект не перезаписывает и не удаляет посторонние файлы. Велес создаёт в папке brief.md, plan.md, research.md, дополняемый progress.md, отчёты steps/NN-<slug>.md, безопасные отчёты ошибок failures/*.md и итоговый final.md. План содержит не более десяти выполняемых этапов; первым всегда идёт исследование. Выполнение начинается только после утверждения. Если исследование изменило определения оставшихся этапов, новый план снова требует утверждения; иначе работа продолжается без лишней остановки. Каждый этап запускается строго после предыдущего, в отдельном свежем контексте и без возможности создавать вложенных исполнителей. Он получает обычные инструкции личности и рабочей области, исходное описание, ответы, утверждённый план, ограниченное резюме исследования, перечень готовых материалов и краткую передачу только от предыдущего этапа. Реестр средств создаётся заново для каждого этапа как снимок доступных средств основного цикла: поэтому поиск по памяти и документации, разбор документов и уже подключённые средства MCP доступны без отдельного списка, но отсутствующие средства не объявляются модели. Из снимка исключаются ask_user, message, spawn, model и cron; итоговая обработка дополнительно получает только средства с известным режимом чтения. Ошибка отдельного вызова возвращается модели с предложением выбрать обходной путь и сама по себе не завершает этап. Полный ход этапа виден и сохраняется в истории диалога, но в новый контекст следующего исполнителя передаётся только ограниченное резюме предыдущего результата. После всех этапов отдельная итоговая обработка собирает final.md и краткое сообщение диалога. Ошибка этапа останавливает последующие этапы и сохраняет отчёт попытки. projects.resume означает продолжение, а не очистку или откат: Велес не удаляет файлы, материалы, изменения или сохранённый ход прерванной попытки. Новый контекст запускается только для незавершённого этапа, проверяет фактическое состояние, отчёт об ошибке и прошлый ход диалога, после чего продолжает с последнего подтверждённого состояния. Завершённые этапы автоматически не повторяются. projects.cancel отменяет ожидающий или приостановленный проект, а chat.abort также отменяет принятый запрос режима «Проект», в том числе до создания постоянного состояния проекта. sessions.delete сначала отменяет незавершённый или ещё ожидающий обработки проект по conversationId и только затем скрывает или удаляет историю; папка материалов не удаляется. После перезапуска шлюза состояния ожидания ответов и утверждения остаются без изменения. Подготовка вопросов или плана и активное выполнение переводятся в paused с ошибкой gateway_restarted и требуют явного projects.resume: изменяющий файлы этап никогда не повторяется автоматически после перезапуска. При запуске Велес также сверяет метаданные диалога с каждым сохранённым проектом, включая завершённые и отменённые, чтобы сбой между записью состояния и уведомлением не оставил устаревший признак работы.

Сессии

MethodНазначение
sessions.listАктивные и технические сессии.
sessions.historyИстория корневых диалогов Nerve.
sessions.createСоздать новый корневой диалог.
sessions.activateАктивировать сохраненный корневой диалог.
sessions.getПолучить summary сессии или диалога.
sessions.patchОбновить label, model, thinkingLevel или parentId.
sessions.deleteУдалить или скрыть сессию/диалог.
sessions.patch может принимать model и thinkingLevel вместе. Nerve использует такое атомарное обновление при смене модели. Служебное значение thinkingLevel: "auto" сохраняется в метаданных сессии, но не передаётся поставщику как уровень рассуждения: модель применяет собственное поведение по умолчанию. Ключи корневых сессий Personality имеют вид personality:<id>:main. Когда корневая сессия личности занята другим ответом, sessions.create создает новую запись истории без очистки занятой live-сессии, а sessions.activate загружает выбранный диалог в изолированный ключ personality:<id>:conv:<conversationId>. Если корневая сессия свободна, сохраняется прежнее поведение: активный диалог работает прямо под personality:<id>:main. В ответах sessions.list, sessions.history и sessions.get поле contextTokens всегда отражает текущее значение agents.defaults.contextWindowTokens. Сохранённые метаданные старых диалогов не должны переопределять этот лимит после изменения настройки или перезапуска шлюза. Для диалога с проектом те же ответы содержат executionMode: "project", projectId и projectStatus.

Личности

MethodНазначение
personalities.listСписок Personality из workspace.
personalities.selectLLM-выбор лучшей Personality для запроса.
personalities.getПолучить одну Personality.
personalities.createСоздать Personality и root session.
personalities.patchОбновить имя, SOUL.md, модель или thinkingLevel.
personalities.deleteУдалить не-main Personality и связанную root-history.

Файлы workspace

MethodНазначение
personalities.files.listСписок top-level файлов workspace.
personalities.files.getLegacy-чтение workspace-файла по имени.
personalities.files.setLegacy-запись workspace-файла по имени.
personalities.files.treeДерево файлов workspace. Необязательный includeHidden: true включает папки с точкой; по умолчанию они скрыты. Правила показа скрытых файлов не меняются.
personalities.files.readЧтение файла по workspace path.
personalities.files.writeЗапись файла по workspace path.
personalities.files.downloadInfoМетаданные для скачивания файла.
personalities.files.downloadChunkЧтение чанка файла.
personalities.files.deleteУдаление файла.
Файловые методы работают с общей рабочей областью Велеса. personalityId не разделяет пользовательские файлы по разным корням: личность меняет промпт, навыки и историю, но не создает отдельное файловое пространство. Исключение - собственные служебные файлы личности внутри personalities/<id>/. У personalities.files.tree есть ограниченный одноуровневый режим для каталогов с большим числом файлов. Чтобы включить его, передайте положительное целое maxEntries вместе с depth: 1. Значения больше 2000 шлюз уменьшает до 2000; сочетание maxEntries с другой глубиной отклоняется как неверный запрос. Ограничение относится к числу просмотренных записей каталога, поэтому скрытые и исключённые пути тоже расходуют этот предел. Шлюз просматривает не более ещё одной записи сверх предела, только чтобы определить наличие продолжения, и не запускает рекурсивный обход. includeHidden действует как в обычном, так и в ограниченном режиме. Он не отменяет обычные запреты на выход за пределы рабочей области и служебные исключения. В ограниченном режиме ответ содержит следующие поля:
  • entries — прочитанные файлы и каталоги текущего уровня;
  • truncatedtrue, если предел был достигнут и часть каталога не просмотрена;
  • missingtrue, если запрошенный каталог отсутствует;
  • unavailabletrue, если путь не является каталогом или сам каталог нельзя прочитать полностью;
  • errors — пути относительно рабочей области, для которых не удалось прочитать сведения. Частичная ошибка отдельного файла не удаляет успешно прочитанные записи из ответа.
Если maxEntries не указан, сохраняется прежний ответ с entries и рекурсивной глубиной от 1 до 5; дополнительные поля состояния в него не добавляются. Для personalities.files.write можно передать expectedMtimeMs. Если файл был изменен после чтения, шлюз возвращает ошибку конфликта и не перезаписывает более свежую версию. Это поле нужно для операций «прочитал-изменил-записал», например для правки памяти через Nerve. При записи существующего файла с несколькими жесткими ссылками personalities.files.write сохраняет тот же файловый узел. Это важно для файлов, которые пользователь связал жесткой ссылкой, например workspace/config.json -> config.json: после сохранения через Nerve обе ссылки продолжают указывать на один файл. Для новых файлов, обычных файлов без дополнительных жестких ссылок и символических ссылок шлюз использует запись через временный файл и замену пути.

Навыки

MethodНазначение
personalities.skills.catalogЭффективный каталог навыков для Personality с учетом overlay порядка: personality, workspace, built-in.

Секреты

MethodНазначение
secrets.catalogКаталог secret targets и статусов.
secrets.setСохранить секрет.
secrets.deleteУдалить секрет.
secrets.refreshПеречитать runtime secrets и hot-swap provider/tool config.
secrets.oauth.startНачать OAuth flow.
secrets.oauth.statusПроверить OAuth flow.
secrets.oauth.completeЗавершить OAuth flow.
secrets.oauth.deleteУдалить OAuth profile.
MethodНазначение
auth.password.setСохранить пароль Nerve в зашифрованном хранилище Veles.

LLM

MethodНазначение
llm.chatНебольшой authenticated LLM chat request для control API callers. Поддерживает messages, tools, model, maxTokens, temperature, toolChoice.
llm.chat предназначен для control flows, а не для обычных пользовательских сессий. Для пользовательского чата используйте chat.send.

Пример жизненного цикла WebSocket-клиента

Минимальный сценарий «подключиться и отправить сообщение»:
1. Открыть WebSocket на ws://<host>:<port>/ws
2. Получить event "connect.challenge" с nonce
3. Отправить req "connect" с { auth: { token } }
   → res ok: { protocol: 3, server: "veles" }
4. (опц.) Отправить req "status" → текущая модель, thinking, активная сессия
5. Отправить req "chat.send" с { sessionKey, message }
   → res ok: { runId, status: "started" }
6. Принимать event "chat":
     state "started" → начался ответ runId
     state "delta"   → дописать payload.message.content
     state "final"   → ответ готов; зафиксировать сообщение
7. (опц.) req "chat.abort" с { sessionKey } для остановки
runId и seq позволяют клиенту переподключиться и восстановить состояние без дублирования сообщений: события одного ответа разделяют runId, а seq задаёт их порядок внутри текущего WebSocket-подключения. После переподключения счётчик начинается заново, поэтому клиент не должен сравнивать seq между разными WebSocket-соединениями.

Совместимость с Nerve

Nerve поверх этого API имеет собственные HTTP routes, но состояние задач, Personality, файлов, сессий и чата остается на стороне Veles gateway. Если добавляется новая backend-возможность для Nerve, сначала стоит определить gateway RPC или HTTP contract здесь, а затем проксировать его на стороне Nerve.
Канонический источник истины — код gateway: HTTP-маршруты в veles/api/routes.py, диспетчер RPC в veles/api/ws_handler.py, схема gateway в veles/config/schema.py. При изменении контрактов обновляйте эту страницу (см. инструкцию в AGENTS.md).