Модель рантайму
Ця сторінка — потік: як повідомлення перетворюється на роботу, один турн від початку до кінця. Та сама історія простою мовою — Життя одного запиту; протокол на рівні дроту — на сторінках Worker. Тут — послідовність, режими й те, що переживає турн.
Турн одним поглядом
повідомлення
→ orchestrator зберегти повідомлення · завантажити агента · регідрувати контекст
→ enforcement гейт квот/лімітів — до будь-яких витрат
→ LLM-цикл один об'єднаний MCP-тулсет:
├─ без тул-колів → стримимо відповідь → зберігаємо → готово (без пода — більшість турнів)
├─ brain-тул → у процесі (memory · secret · cron · knowledge …)
└─ worker-тул → підняти worker-сесію (одного разу) → tools/call каналом тулів
→ wrap-up зберегти відповідь + summary · заемітити usage · відпуститиКрок за кроком
- Інтейк.
orchestratorзберігає повідомлення користувача (agent/chat), завантажує конфіг агента (agent) і регідрує робочий контекст із Postgres — хвіст вікна + біжучий summary (контекст-рушій, див. Коротку пам'ять). - Enforcement.
setting/enforcementперевіряє квоти/ліміти команди до будь-якої роботи. - Цикл.
llm.stream()крутиться в мозку з одним об'єднаним тулсетом: brain-MCP (in-process тули), worker-MCP (підключений лише поки жива worker-сесія) і connector-MCP (API магазину). У worker'а немає LLM — цикл ніколи не покидає мозок. - Тули не потрібні → стримимо відповідь, зберігаємо, готово. Под не створюється; це дешевий шлях, і більшість турнів іде ним.
- Потрібні руки — перший worker-тул-кол запускає провіжининг:
- перевикористати живу
AgentRuntimeSessionдля(agent, chat), якщо вона running/idle — черга торкається максимум раз на сесію, ніколи на тул-кол; - інакше — provision-задача в BullMQ; менеджер рантайму (
runtime/worker) обираєRuntimeProfile, збирає k8s Job, інжектить короткоживучі креди; - worker завантажується, дзвонить назад по WebSocket, MCP
initialize+tools/list— його тули входять у тулсет сесії (Deploy); - далі мозок ганяє
tools/callканалом тулів; прогрес і логи стримляться у фронтенд через Redis pub/sub (runtime/event).
- перевикористати живу
- Wrap-up. Результати зберігаються через
system/file; записуються відповідь асистента й оновлений summary; емітятьсяusageBillingEvents; мозок відпускає. Worker-сесія живе свій idle-інтервал — наступний турн її перевикористає — поки reaper її не знесе (Destroy).
Що завершує хід
Хід скасовує закриття сокета. Закрита вкладка, перезавантаження, вихід із застосунку, зникла мережа і власна кнопка «стоп» у чаті (вона обриває браузерний fetch, а отже закриває сокет) закінчуються однаково: скасування йде вниз по циклу — в інструмент, що виконується, і в сам виклик моделі, — тому хід припиняється за мілісекунди, а не палить токени для читача, який пішов.
SPA-навігація хід НЕ скасовує, і це зроблено навмисно. Перемикання вкладки картки, відкриття іншої сторінки, розмонтування компонента чату — браузер тримає запит живим, хід дораховується і в агента зберігається. Користувач, який перемкнув екран, нікуди не пішов; відповідь досі для нього, і вбити хід означало б викинути роботу, яку він ось-ось прочитає. «Скасовувати на розмонтуванні» — не відсутня половина скасування, а регрес, який тільки й чекає, щоб його написали.
Зупинений хід агента зберігає вже доставлений текст із позначкою «зупинено», тому тред після перезавантаження каже те саме, що казав екран. Хід, який завершився помилкою, не зберігає нічого.
Один хід на агента
В агента одночасно йде один хід — це тримає блокування в Redis:
- блокування спливає через 5 хвилин і, доки хід живий, продовжується до повних п'яти приблизно кожну третину цього строку, але не довше за стелю в 60 хвилин — так довгий хід не втрачає блокування, а загублений не може тримати агента вічно;
- блокування best-effort: недоступний Redis не має права ні затримати хід, ні впустити його;
- автозапуск його читає — зайнятий агент пропускається, а не переривається.
Стелі одного ходу
- 8 ітерацій циклу інструментів. Досягнута стеля зберігає частковий результат, а не викидає його.
- 3 виклики інструментів, що змінюють дані, за хід, у будь-якому ході — агентському не менше, ніж консьєржському. Бюджет ходу витрачає кожен інструмент, який пише: створення та архівація агента, переписування промпта автозапуску, зміна частоти, запис нотатки в пам'ять. Четверта спроба отримує відмову фразою, яку модель може прочитати, а не мовчання. Без цього одне невдале формулювання могло б зробити вісім змін в одній відповіді, бо цикл рахує звернення до моделі і гадки не має, які інструменти щось пишуть. Один виклик — одна зміна, навіть якщо він рухає одразу три поля: бюджет рахує зміни робочого простору, а не поля.
429від провайдера моделі виходить назовні як 429, а його недоступність — як 503, і ніколи як незрозуміла п'ятисотка: клієнт має розрізняти «повтори трохи згодом» і «у нас поломка».
Режими рантайму → примітиви k8s
Підслайс orchestrator/mode вирішує, скільки машини отримує турн:
Режим береться з колонки runtimeProfile самого агента, і за замовчуванням це none — агент, якому ніхто не видавав рук, до цієї таблиці не доходить зовсім. Усередині режиму нічого не піднімається, доки модель справді не викличе інструмент воркера; див. Руки.
| Режим | Для чого | Примітив | Idle |
|---|---|---|---|
| None | рук немає — значення за замовчуванням | без пода — лише LLM + пам'ять | n/a |
| Light | bash, скрипти, файли | k8s Job, restartPolicy: Never, ttlSecondsAfterFinished | 60–120s |
| Browser | використовується браузер-тул | worker Job із headless Chromium у поді (Playwright, --user-data-dir на тенанта) | поки браузер не простоює |
| Heavy | скрейпінг, великий compute | k8s Job на нодпулі workers, високі ліміти | 30–60s |
| Warm | premium | Light/Heavy живе довше за idle | 10–30 хв |
Коли сесія падає, запис каже яким саме способом
Сесія воркера падає шістьма різними способами, і кожен вказує на своє місце, куди йти дивитися, — тому рядок несе причину, а не одне слово failed:
| причина | що підозрювати |
|---|---|
dial_timeout | провіжининг — под не додзвонився зовсім |
handshake_timeout | образ або перепустка, яку йому видали |
ready_timeout | под піднявся, але так і не достартував |
session_mismatch | той, хто дзвонить, не та сесія, за яку себе видає |
channel_version | викатка — сторони говорять різними версіями |
heartbeat_lost | зомбі: був живий і перестав відповідати |
Розходження версій у цьому списку не випадкове. Сторони викочуються окремо, тому згодні вони будуть не завжди, — а рукостискання, яке не вміє сказати «я говорю іншою версією», відмовляє зависанням замість відмови, і це та поломка, яку розбирають годинами, а не секундами.
Що переживає турн
Мозок і worker зникають; це — ні:
| Стан | Живе в |
|---|---|
| повідомлення + біжучий summary | agent/chat (Postgres) — див. Коротку пам'ять |
| довготривала пам'ять і факти | agent/memory — див. Пам'ять |
рядки Task · AgentRuntimeSession | runtime (Postgres) — див. Схему БД |
| файли й артефакти | system/file → object storage |
| витрати | usage BillingEvents → billing |
Черга і масштабування
- BullMQ на Redis — провіжининг + scheduled-задачі; delayed-jobs заразом слугують idle-таймерами; pub/sub несе потік подій.
- Нативні k8s Jobs — образ worker'а несе Playwright + Chromium (браузер у поді, без окремого пулу);
ttlSecondsAfterFinishedдає scale-to-zero. Без KEDA/Knative для MVP. - Короткоживучі токени сесій — Core карбує JWT зі скоупом однієї
AgentRuntimeSession(user/auth); довгоживучі секрети в под не потрапляють (безпека).
Дивіться також
- Worker — Deploy / Use / Destroy — життєвий цикл сесії та MCP-канал тулів на рівні дроту.
- Коротка пам'ять — як контекст-рушій збирає промпт кожного турну.
- Схема БД — форми
TaskіAgentRuntimeSession. - Як це працює — та сама історія простою мовою.