Skip to content

Из чего состоит

Agentfy.ai 2.0 поставляется как четыре вещи, которые можно задеплоить. Это вся система — нет runner и нет отдельного runtime-приложения, потому что эфемерный агент и есть api.

ЧастьПростыми словамиРоль
apiМозг + центр управленияCore и эфемерный мозг агента — одна кодовая база/образ. Содержит все 8 слоёв слайсов, включая мозг (agent/orchestrator) и плейн управления воркерами (группа слайсов runtime). «Рантайм» = образ api, запущенный эфемерно.
appСайт клиентаКабинет клиента (Nuxt).
adminПанель управленияАдмин-панель (Nuxt).
workerРукиПесочница по требованию, выполняющая обязательства агента (bash, файлы, браузер, тяжёлые/долгие задачи), которые мозг не может сам. Stateful на задачу; гаснет при простое.

Три из них (api, app, admin) — обычные постоянно работающие веб-сервисы. worker — исключение: он появляется только когда есть задача, потом завершается.

Кабинет на свежей учётной записи: встроенный агент-консьерж, готовый настроить первого агента

В свежей учётной записи ровно один агент — встроенный Консьерж, которого создаёт продукт, а не человек. Это единственный агент без собственных Soul, Heartbeat, секретов, навыков, сайта, MCP и подключений: его работа — настроить остальных.

Список агентов команды в кабинете

Почему нет отдельного «runner»

«Эфемерный рантайм», который все себе представляют, — это просто образ api, запущенный по требованию. Запуск той же кодовой базы на один ход даёт нам мозг — поэтому мы не держим второй runner-образ с собственным циклом агента. workerединственный действительно отдельный исполняемый файл, потому что ему нужен другой, тяжёлый образ: настоящая ОС с bash + Chromium.

Граница api ↔ worker

Это самая важная линия:

  • api = мозг + менеджер. Цикл LLM, оркестрация и прямые (без инструментов) ответы, плюс плейн, управляющий воркерами (группа слайсов runtime: очередь, жизненный цикл сессии, k8s, события), и сохранение выводов через system/file.
  • worker = руки. Контейнер с bash / файлами / Chromium (Playwright). Stateful на задачу (живая сессия браузера, рабочая директория). Не держит долгоживущих секретов — получает короткоживущий session-токен от api (user/auth), который истекает вместе с сессией.

Одно имя у одной вещи

«Песочница» и «воркер» раньше называли одно и то же в разных половинах кода. Больше нет: везде worker — каталог, образ, пропуск, эти страницы, слайсы и объекты, которые видно в kubectl. Два имени у одного понятия — это то, из-за чего читатель перестаёт понимать, одна перед ним вещь или две.

Слово делят два слайса, и это намеренно, а не недоделано: runtime/worker — половина, которая поднимает и гасит воркеры, а agent/worker — половина, которая решает, что ходу нужны руки. На проводе слова «песочница» не было никогда, поэтому контракт от всего этого не изменился.

Отброшенное имя один раз вернулось — через три недели, в коде тех, кто эту врезку не читал. Поэтому теперь это дело гейта, а не договорённости: make naming отвергает его где угодно в дереве, в имени файла так же, как в предложении, а scripts/naming-check.mjs — реестр отброшенных имён и единственное место, где отброшенное написание ещё выписано целиком.

Конфигурация, которая отказывает, а не расходится

  • У адреса API ровно один источник. app и admin читают его из nuxt'овского runtimeConfig.public.apiBase, который наполняет NUXT_PUBLIC_API_BASE (по умолчанию http://localhost:3333). Больше его не задаёт никто: import.meta.env не доезжает до браузерного бандла, поэтому положенное туда значение читается как undefined, и молча выигрывает захардкоженный запасной адрес — причём только на тех машинах, где адрес отличается от дефолтного.
  • api не поднимется на плейсхолдерном секрете. Вне development JWT_SECRET, который не задан, всё ещё равен закоммиченному плейсхолдеру или короче 32 символов, останавливает старт и называет, что именно не так. Плейсхолдер, с которым приложение поднимается, — это плейсхолдер, который уедет в прод, а знание JWT_SECRET даёт токен за любого пользователя.

Один транспорт в браузере, и что на нём едет

app и admin разговаривают с api через один клиент, построенный на штатном fetch. Второго пути нет: сгенерированный SDK, стримы и загрузка-выгрузка пакета идут одинаково, поэтому продление сессии и обработка ошибок действуют на каждый запрос, а не на половину. Когда транспортов было два, петлю refresh чинили на перехваченной половине, а на стримах её не было вовсе.

На этом единственном клиенте едут три поведения, и каждое стоит там потому, что его отсутствие однажды обошлось в измеримую цену:

  • Один повтор на запрос, а не цепочка. Неустранимый 401 раньше давал сотни пар запросов, пока не вмешивался рейт-лимитер.
  • Одно продление на пачку. Несколько параллельных 401 продлевают сессию ровно один раз, и латчем служит сама сессия, а не флаг в модуле.
  • Повтор уходит со свежим токеном, а терминальный отказ чистит сессию и уводит на /loginс причиной: человек, выброшенный на экран входа без объяснения, решает, что продукт сломан.

Дальше