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.
HTTP-запросы должны передавать токен в заголовке:
Authorization: Bearer <gateway.token>
При отсутствии или несовпадении токена HTTP-маршруты отвечают 401 Unauthorized с заголовком WWW-Authenticate: Bearer realm="veles-gateway". Сравнение токена выполняется в постоянном времени (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, а входящие/исходящие события не доставляются клиенту.
Успешный ответ:
{
"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 (монотонный счётчик событий gateway для упорядочивания и восстановления после переподключения):
{
"type": "event",
"event": "chat",
"payload": { "...": "..." },
"seq": 42
}
Основные события: connect.challenge при подключении и chat для потока сообщений ассистента и пользователя.
Коды ошибок
Ошибки возвращаются в поле error с числовым code (совместимым с JSON-RPC) и человекочитаемым message:
| Code | Когда возникает |
|---|
-32700 | Тело запроса не является валидным JSON. |
-32601 | Неизвестный метод. |
-32602 | Ошибка валидации параметров (ValueError в обработчике). |
-32001 | Не пройдена авторизация: отсутствует или неверен токен. |
-32000 | Внутренняя ошибка обработчика. |
HTTP endpoints
| Method | Route | Назначение |
|---|
GET | /health | Проверка доступности gateway. |
GET | /status | Общий статус gateway, модели, thinking, каналов, сессий и task storage. |
POST | /tools/invoke | Совместимый HTTP-вызов отдельных gateway tools. |
POST | /attachments/upload | Загрузка вложения в workspace с привязкой к x-session-key. |
GET/POST | /api/personalities/select | LLM-выбор лучшей Personality для пользовательского запроса. |
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 проверяет, что выбранный идентификатор существует.
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 принимает raw body файла и заголовки:
| Header | Значение |
|---|
x-session-key | Сессия, к которой относится вложение. |
x-attachment-name | Имя файла. |
x-attachment-mime-type | MIME type. |
x-attachment-kind | Опциональный тип: image, audio, voice, file. |
Ответ содержит объект вложения с id, workspacePath, absolutePath, mimeType и другими полями. Nerve затем передает такие объекты в chat.send как attachmentRefs.
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_store | No-op ({ "stored": false, "mode": "noop" }); память хранится навыком, а не gateway. |
subagents | Возвращает пустой список (зарезервировано). |
Для интерактивного управления чатом и сессиями предпочитайте WebSocket RPC — /tools/invoke рассчитан на скриптовые и служебные вызовы.
WebSocket RPC methods
Статус
| Method | Назначение |
|---|
status | Статус gateway и текущей или указанной сессии. |
openrouter.balance | Баланс OpenRouter, если настроен OpenRouter API key. |
Чат
| Method | Назначение |
|---|
chat.send | Отправить сообщение в сессию. Поддерживает sessionKey, conversationId, message, attachmentRefs, idempotencyKey, deliver. |
chat.abort | Остановить активный ответ в сессии. |
chat.history | Получить историю сообщений по sessionKey, conversationId или sessionId. |
События ответа ассистента приходят через WebSocket event chat. Поле state определяет фазу:
state | Значение |
|---|
started | Ассистент начал новый ответ (новый runId). |
delta | Промежуточный фрагмент ответа или обновление прогресса инструментов. |
final | Финальное сообщение. Приходит и для сообщений ассистента, и для входящих сообщений пользователя из других каналов (role: "user"). |
aborted | Ответ остановлен через chat.abort. |
Поля payload события chat:
| Поле | Описание |
|---|
sessionKey, sourceSessionKey | Ключ сессии события. |
state | Фаза ответа (см. выше). |
runId | Идентификатор текущего ответа; стабилен для всех событий одного ответа. |
seq | Порядковый номер события внутри сессии (для упорядочивания/дедупликации). |
totalTokens, contextTokens | Счётчики токенов сессии, если доступны. |
conversationId, conversationKey, rootSessionKey | Привязка к корневому диалогу Nerve (только для root-сессий). |
toolEvents, toolHint | Прогресс инструментов и флаг активности инструмента. |
message | Объект сообщения: role, content, timestamp (ms), а также toolEvents, toolHint, attachments при наличии. Присутствует для delta/final. |
error, errorMessage | Текст ошибки, если ответ завершился ошибкой. |
Клиент группирует события по runId, применяет delta поверх накопленного текста и фиксирует ответ на final.
Сессии
| Method | Назначение |
|---|
sessions.list | Активные и технические сессии. |
sessions.history | История корневых диалогов Nerve. |
sessions.create | Создать новый корневой диалог. |
sessions.activate | Активировать сохраненный корневой диалог. |
sessions.get | Получить summary сессии или диалога. |
sessions.patch | Обновить label, model, thinkingLevel или parentId. |
sessions.reset | Очистить live session. |
sessions.delete | Удалить или скрыть сессию/диалог. |
Ключи корневых сессий Personality имеют вид personality:<id>:main.
Личности
| Method | Назначение |
|---|
personalities.list | Список Personality из workspace. |
personalities.select | LLM-выбор лучшей 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.get | Legacy-чтение workspace-файла по имени. |
personalities.files.set | Legacy-запись workspace-файла по имени. |
personalities.files.tree | Дерево файлов workspace. |
personalities.files.read | Чтение файла по workspace path. |
personalities.files.write | Запись файла по workspace path. |
personalities.files.downloadInfo | Метаданные для скачивания файла. |
personalities.files.downloadChunk | Чтение чанка файла. |
personalities.files.delete | Удаление файла. |
Файловые методы принимают personalityId, но workspace сейчас общий: Personality не создает отдельный workspace. Исключение - собственные файлы Personality внутри personalities/<id>/.
Навыки
| 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. |
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 задаёт их порядок.
Совместимость с 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).