Из чего состоит
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не поднимется на плейсхолдерном секрете. ВнеdevelopmentJWT_SECRET, который не задан, всё ещё равен закоммиченному плейсхолдеру или короче 32 символов, останавливает старт и называет, что именно не так. Плейсхолдер, с которым приложение поднимается, — это плейсхолдер, который уедет в прод, а знаниеJWT_SECRETдаёт токен за любого пользователя.
Один транспорт в браузере, и что на нём едет
app и admin разговаривают с api через один клиент, построенный на штатном fetch. Второго пути нет: сгенерированный SDK, стримы и загрузка-выгрузка пакета идут одинаково, поэтому продление сессии и обработка ошибок действуют на каждый запрос, а не на половину. Когда транспортов было два, петлю refresh чинили на перехваченной половине, а на стримах её не было вовсе.
На этом единственном клиенте едут три поведения, и каждое стоит там потому, что его отсутствие однажды обошлось в измеримую цену:
- Один повтор на запрос, а не цепочка. Неустранимый
401раньше давал сотни пар запросов, пока не вмешивался рейт-лимитер. - Одно продление на пачку. Несколько параллельных
401продлевают сессию ровно один раз, и латчем служит сама сессия, а не флаг в модуле. - Повтор уходит со свежим токеном, а терминальный отказ чистит сессию и уводит на
/loginс причиной: человек, выброшенный на экран входа без объяснения, решает, что продукт сломан.
Дальше
- Как мы это строим — как устроен код внутри
api. - Docker-образы — как собирается и поставляется каждая часть.
- Слои-слайсы — восемь групп слайсов подробно.