Устройства
Устройство — это воркер, которого подняли не мы. Появилось в AGNT2-267.
Всё, что продукт до этого гонял по каналу инструментов, было подом: мы его создавали, мы давали ему пропуск, выписанный на одну сессию, и жил он несколько минут. Устройство — другой вид собеседника на том же самом канале: вещь, которой кто-то владеет, которая стоит в доме, включена месяцами и выключена неделю, и которая звонит нам сама.
Почему это вообще работает
Из-за первого структурного факта со страницы канала инструментов: воркер звонит наружу, и никто не звонит внутрь. Это писалось про под — и оказалось ровно тем свойством, которое нужно Raspberry Pi за домашним роутером. До него нельзя достучаться снаружи, и не нужно.
Это не новая архитектура. Это существующая, повёрнутая.
Что общего с сессией — а общего почти всё
Провод, целиком: субпротокол, обрамление JSON-RPC, рукопожатие MCP, ответ tools/list, соотнесение tools/call и коды закрытия. Всё это — канал инструментов без изменений, и один класс ведёт его для обоих видов собеседника. Кода протокола в слайсе устройства нет вовсе.
Чего не общего — и какова за это цена
Сессия определяется своими потолками: ttlSeconds, maxExecSeconds, окно простоя, которое читает жнец, семь состояний, шесть причин отказа, четыре двери разбора. У устройства нет ни одного из них, поэтому это своя таблица, а не сессия с девятью обнулёнными колонками.
Цена названа, а не оставлена на обнаружение:
- Теперь на одном канале два вида собеседника. Каждому будущему читателю группы
runtime/придётся спрашивать «сессия или устройство?» там, где раньше был один ответ, а тому, кто разбирает сокет, придётся смотреть в две таблицы вместо одной. - Четыре механизма к устройству не применяются. Каталог профилей (насколько крупный), жнец дедлайнов (когда умрёт), четыре двери разбора (как убирается) и шесть причин отказа (почему не стартовал) — всё это машинерия сессии. Читатель, полагающий, что у группы
runtime/один жизненный цикл, ошибётся.
Более дешёвый вариант проиграл по конкретной причине: сделать сессию бессрочной значит превратить девять её колонок в «null здесь означает, что строка не совсем сессия», и каждому читателю — жнецу, подметальщику, разбору, экрану состояния — пришлось бы выучить вторую форму строки. Одной необнулённой колонкой меньше — и жнец удаляет чью-то камеру за то, что она девяносто секунд молчала.
Строка — она же и учётная запись
publicId + secretHash, то же разделение, что у ключа встраивания: хранится не рабочий ключ.
Отличие от ключа встраивания — в том, что именно называет учётная запись. Там ключ называет команду, и у одной команды их может быть сколько угодно. Здесь ключ и есть личность устройства, один на строку — потому что отзыв должен останавливать одно устройство, и потому что канал обязан знать, какое именно устройство сейчас позвонило.
Ключ висит на команде, а не на агенте, поэтому удалённая команда не может оставить после себя работающую учётную запись.
Маршруты
GET /teams/:teamId/devices все устройства команды
GET /agents/:id/devices те, к которым этот агент вправе обращаться
POST /agents/:id/devices привязать
POST /agents/:id/devices/:deviceId/disconnect оборвать живое соединение
DELETE /agents/:id/devices/:deviceId отвязатьГде код
| само устройство, его канал, ключ | api/src/slices/runtime/device |
| привязка к агенту, набор инструментов, темп вызовов | api/src/slices/agent/device |
См. также Использование и Канал инструментов.