Skip to content

Устройства

Устройство — это воркер, которого подняли не мы. Появилось в 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

См. также Использование и Канал инструментов.