Skip to content

Модель рантайму

Ця сторінка — потік: як повідомлення перетворюється на роботу, один турн від початку до кінця. Та сама історія простою мовою — Життя одного запиту; протокол на рівні дроту — на сторінках Worker. Тут — послідовність, режими й те, що переживає турн.

Турн одним поглядом

повідомлення
  → orchestrator   зберегти повідомлення · завантажити агента · регідрувати контекст
  → enforcement    гейт квот/лімітів — до будь-яких витрат
  → LLM-цикл       один об'єднаний MCP-тулсет:
       ├─ без тул-колів  → стримимо відповідь → зберігаємо → готово  (без пода — більшість турнів)
       ├─ brain-тул      → у процесі     (memory · secret · cron · knowledge …)
       └─ worker-тул     → підняти worker-сесію (одного разу) → tools/call каналом тулів
  → wrap-up        зберегти відповідь + summary · заемітити usage · відпустити

Крок за кроком

  1. Інтейк. orchestrator зберігає повідомлення користувача (agent/chat), завантажує конфіг агента (agent) і регідрує робочий контекст із Postgres — хвіст вікна + біжучий summary (контекст-рушій, див. Коротку пам'ять).
  2. Enforcement. setting/enforcement перевіряє квоти/ліміти команди до будь-якої роботи.
  3. Цикл. llm.stream() крутиться в мозку з одним об'єднаним тулсетом: brain-MCP (in-process тули), worker-MCP (підключений лише поки жива worker-сесія) і connector-MCP (API магазину). У worker'а немає LLM — цикл ніколи не покидає мозок.
  4. Тули не потрібні → стримимо відповідь, зберігаємо, готово. Под не створюється; це дешевий шлях, і більшість турнів іде ним.
  5. Потрібні руки — перший 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).
  6. Wrap-up. Результати зберігаються через system/file; записуються відповідь асистента й оновлений summary; емітяться usage BillingEvents; мозок відпускає. 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
Lightbash, скрипти, файлиk8s Job, restartPolicy: Never, ttlSecondsAfterFinished60–120s
Browserвикористовується браузер-тулworker Job із headless Chromium у поді (Playwright, --user-data-dir на тенанта)поки браузер не простоює
Heavyскрейпінг, великий computek8s Job на нодпулі workers, високі ліміти30–60s
WarmpremiumLight/Heavy живе довше за idle10–30 хв

Коли сесія падає, запис каже яким саме способом

Сесія воркера падає шістьма різними способами, і кожен вказує на своє місце, куди йти дивитися, — тому рядок несе причину, а не одне слово failed:

причинащо підозрювати
dial_timeoutпровіжининг — под не додзвонився зовсім
handshake_timeoutобраз або перепустка, яку йому видали
ready_timeoutпод піднявся, але так і не достартував
session_mismatchтой, хто дзвонить, не та сесія, за яку себе видає
channel_versionвикатка — сторони говорять різними версіями
heartbeat_lostзомбі: був живий і перестав відповідати

Розходження версій у цьому списку не випадкове. Сторони викочуються окремо, тому згодні вони будуть не завжди, — а рукостискання, яке не вміє сказати «я говорю іншою версією», відмовляє зависанням замість відмови, і це та поломка, яку розбирають годинами, а не секундами.

Що переживає турн

Мозок і worker зникають; це — ні:

СтанЖиве в
повідомлення + біжучий summaryagent/chat (Postgres) — див. Коротку пам'ять
довготривала пам'ять і фактиagent/memory — див. Пам'ять
рядки Task · AgentRuntimeSessionruntime (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); довгоживучі секрети в под не потрапляють (безпека).

Дивіться також