Skip to content

Agent — склад і запуск

Агент — це конфіг + стан, а не контейнер

Найголовніше: агент — це рядок у Postgres, а не запущений процес. Він «завжди доступний», бо це дані — визначення + пам'ять + історія. У нього немає власного пода. Коли йому пишуть, ефемерний мозок (образ api, що запускається на один турн) «вдягає» ці дані, відпрацьовує один турн і зникає.

Agent (рядок у БД)  ──вантажиться на турн──▶  ефемерний мозок (api)  ──руки за потреби──▶  worker
   конфіг + стан                                orchestrator + LLM-loop                    (k8s Job)

З чого складається агент

Група слайсів agent (шар L5) — це агент і все, чим він володіє або користується:

частина (слайс)що вносить в агента
agentвизначення: soul, user, heartbeat, config, runtimeProfile, type, status
memoryвласна довгострокова пам'ять агента (вектори) — що він пам'ятає між чатами
knowledgeкурований RAG-корпус (LightRAG), яким він шукає — завантажені доки, за базами
chatісторія діалогу (треди, повідомлення) — поточний контекст
appзовнішні конектори (OAuth / куки), які агент використовує як тули (Google, X, …)
channelповерхні, на яких він говорить (telegram, slack, веб-віджет)
orchestratorмозок, який на кожен турн збирає все вище і ганяє цикл

memoryknowledge: пам'ять — це власна еволюційна пам'ять агента; знання — це курований корпус, який заливає користувач. Обидва годують турн, але це різні джерела.

Сутність Agent

prisma
model Agent {
  id               String   @id      // agent-{uuid}; у консьєржа виводиться з id команди
  teamId           String            // тенант
  externalId       String?           // id походження з пакета — ключ повторного імпорту
  ownerId          String            // учасник-власник
  name             String
  description      String?
  status           String   @default("active")     // active | disabled | archived
  type             String   @default("standard")   // standard | concierge — призначає лише сервер
  soul             String?           // SOUL.md      — персона, дослівно
  user             String?           // USER.md      — контекст про користувача, дослівно
  heartbeat        String?           // HEARTBEAT.md — текст автозапуску з пакета, дослівно
  config           Json     @default("{}")  // agent.config.json рантайму, дослівно
  runtimeProfile   String   @default("none")   // чи можна цьому агентові руки і скільки машини
  promptLogEnabled Boolean  @default(false)  // записувати зібрані промпти цього агента
  createdAt        DateTime @default(now())
  updatedAt        DateTime @updatedAt
}

Створити агента = вставити цей рядок. Він одразу придатний — без провіжинингу, без пода.

Дві колонки варто прочитати двічі, бо в обох навмисно вузьке значення за замовчуванням, і жоден реліз не має розширювати його наявному агентові: runtimeProfile це none (рук немає), а promptLogEnabledfalse (нічого не записується). Див. Руки і Що агент робив.

Три дослівні документи — це власний текст агента, а не його відображення. soul, user і heartbeat — ті самі файли, які несе пакет; вони їдуть в експорт байт у байт і так само повертаються.

Видалення рядка тепер забирає все за собою. Листування, нотатки, навички, секрети і конфігурація автозапуску йдуть каскадом одним оператором, тому видалення, обірване посередині, не видаляє нічого, а не лишає напівпорожню оболонку. Єдине, чого каскад не дістає, — вектори агента, у них за задумом немає зовнішнього ключа, і їх прибирають явно.

Чим агент є і що дозволяє тип

У кожного агента є тип. Тип — а не розсип булевих прапорців — відповідає на питання «що це таке?», і все інше випливає з нього. Сьогодні їх два:

типщо це
standardусе, що створює власник; значення за замовчуванням для будь-якого рядка
conciergeсистемний агент команди. Персона приходить з коду; його не можна перейменувати, вимкнути, заархівувати, видалити та експортувати, у нього немає секретів, навичок і автозапуску

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

Одна декларація, два споживачі. Що дозволяє тип, описано однією таблицею (AGENT_CAPABILITIES): чи редагується душа, звідки береться системний промпт, чи можна перейменувати / вимкнути / заархівувати / видалити, чи є секрети / автозапуск / навички, чи можна експортувати пакет і перезаписати агента імпортом, який у нього тулсет. Цю таблицю читає кожен шлях запису — оновлення, зміна статусу, видалення, імпортер та експортер пакета і тул консьєржа create_agent. Інтерфейс читає те саме значення: воно їде в кожній відповіді про агента полем capabilities, тож другої копії, якій є куди розійтися, у застосунку немає.

Незнайоме значення типу з БД (рядок, записаний новішим релізом) читається як standard — це найменші права, а не найбільші.

Архівація — консьєржу, видалення — людині

Консьєрж уміє відправити агента в архів розмовою і повернути його звідти. Видалити він не може — жодним інструментом, під жодним прапорцем, і жоден невикористаний метод не чекає свого часу.

Межу проведено за зворотністю, а не за небезпекою взагалі: архівація це зміна статусу, і її розвертає один рух, а видалення забирає листування, нотатки, навички і секрети агента, і не повертає їх ніщо. Помилка моделі в першому випадку коштує одного кліку, у другому — всього, що в цього агента вклали.

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

Видалення живе в кабінеті, у вкладці General самого агента, і підтвердження називає агента на ім'я, а не питає «ви впевнені?»: людина має прочитати, що саме зникне.

Як турн «запускає» агента

Кроку «запустити агента» немає. Агент прокидається на турн:

1. повідомлення приходить у канал → ingress резолвить (agentId, sessionId), перевірка доступу
2. orchestrator (api, ефемерний) ЗБИРАЄ агента:
      • вантажить визначення Agent (soul, config, runtimeProfile, type)
      • піднімає контекст пам'яті (memory) + читає історію чату (chat)
      • резолвить прикріплені бази знань (knowledge) + креди конекторів (app, secrets)
      • enforcement-перевірка (setting) — чи може команда витрачати?
3. збирає промпт → ганяє LLM-loop (system/llm), стримить токени назовні
4. tool-аналіз:
      • без тулів → стримить відповідь напряму → зберігає → ГОТОВО  (без пода)
      • з тулами  → tasks (BullMQ) → worker (k8s Job) робить bash/fs/browser → результати назад
5. зберігає: повідомлення асистента (chat), апдейти пам'яті, usage-події
6. ефемерний мозок ВИХОДИТЬ — агент знову просто рядок у БД

Наступне повідомлення повторює це; якщо рантайм-сесія ще тепла, worker перевикористовується (без холодного старту).

Тули та зобов'язання

Інструменти агента діляться за місцем виконання:

  • У мозку (api): пам'ять, пошук по листуванню, секрети, поточний час, запит до знань. Под не потрібен.
  • У worker: усе, чому потрібне середовище — оболонка, файли, браузер або довільний вихід у мережу. Останнє — не зручність: запит, зроблений мозком, був би нашим же сервером, що йде туди, куди йому сказали, а мозок тримає базу і ключ шифрування.

Модель бачить один злитий список, інструменти воркера в ньому під власними іменами, а маршрутизацію робить хост. Ні префікса, ні другого списку — але інструмент воркера, ім'я якого збіглося з інструментом мозку, стає мовчки недосяжним: колізію виграє базовий набір. Це варто знати заздалегідь, а не виявити.

Руки — профіль виконання і грант інструментів

Два різні питання, і обидва відповідають «ні», доки хтось не скаже інакше.

Чи дозволені цьому агентові руки взагалі? runtimeProfile, колонка в агента — none · light · browser · heavy · warm, за замовчуванням none. Агент з none не чіпає ні черги, ні кластера, і жоден реліз не видає руки тому, хто їх не мав: міграція, що додала колонку, не заповнила нічого. У консьєржа та сама колонка на його власному рядку — одна на команду, — тому ввімкнути руки йому коштує команді одного воркера, а не одного на кожного агента.

Які з інструментів воркера можуть дістатися його сесіям? Грант, у config.tools агента. Це підмножина тих десяти імен, які поділ інструментів віддає рукам, і за замовчуванням він порожній — тобто воркер без інструментів, а не воркер з усіма. Ім'я поза цими десятьма відхиляється, а не ігнорується. Ім'я, якого не вміє образ, — інший випадок, і його не відхиляють: грант з browser у light-сесії просто не дає інструмента браузера, а чіпляється той список, який воркер повідомив у відповідь на готовність.

Три наслідки, які треба знати до того, як іти шукати поломку:

  • Звичайна розмова не піднімає нічого. На початку ходу не провіжиниться ніщо; запит іде в чергу лише тоді, коли модель справді тягнеться до інструмента воркера.
  • Одне доручення просить один раз. Скільки б інструментів модель не викликала за хід, воркер запитується один, а жива сесія з минулого ходу перевикористовується, а не замінюється.
  • Хід, що підняв воркер, не встигає ним скористатися. Набір інструментів ходу збирається до першого звернення до моделі, а под відповідає на tools/list секундами пізніше — тому нові інструменти приїжджають у наступний хід. Це форма конвеєра, а не дефект.

Усе, що повернулося з інструмента воркера, — недовірений ввід, і він потрапляє в той самий контекст: текст завантаженої сторінки, вивід команди. Найважливіше це для консьєржа, бо саме він уміє ще й створювати та архівувати агентів.

Промпт збирається шарами

Промпт, під яким працює агент, — це не його душа. Він збирається наново на кожному ході з трьох шарів:

[ PLATFORM_BASE ]         що таке агент Agentfy + правила дому   (код, для всіх агентів)
[ TOOL_USE_GUIDANCE ]     як узагалі викликати інструменти — без схем конкретних тулів
[ soul || DEFAULT_SOUL ]  персона ЦЬОГО агента, дослівно

База живе в коді, тому продуктове правило діє для кожного агента, і його не треба передруковувати в кожну душу; душа лишається саме тією персоною, яку написав її автор.

Усе, що змінюється від ходу до ходу, сюди не потрапляє. Локальний час користувача додає преамбула ходу в шарі інструментів, а контракт NO_REPLY у тіка автозапуску — саме повідомлення тіка. Факт одного прогону, записаний у спільний промпт, протікає в усі інші ходи.

Клієнт надсилає разом із ходом свою IANA-таймзону, і преамбула повідомляє моделі локальний час користувача. Хід, у якого придатної таймзони немає, — тік автозапуску, виклик з API, який її не надіслав, — просто працює в UTC.

Автозапуск

Агент може виконати хід, коли з ним ніхто не розмовляє. Тік — це повноцінний хід (тули, пам'ять, витрата), а не дешевий пінг, і це єдиний спосіб для агента витрачати гроші без користувача за екраном.

  • Інтервал 10..1440 хвилин, за замовчуванням 60. Увімкнення автозапуску вимагає непорожнього промпта. Підлога існує тому, що тік — це повноцінний хід моделі, а поки немає обліку споживання, інтервал і є єдиним, що обмежує витрату. Її оголошено один раз і перевіряють на шляху запису, а не лише на HTTP-межі, тому жоден інший письменник під неї не пролізе; уже збережені рядки нижче підлоги піднімаються, кожен із рядком у лозі. Знизити підлогу можна змінною оточення, призначеною лише для розробки: перевірка ціною в десять хвилин очікування перестає виконуватися і починає здогадуватися.
  • Один глобальний обхідник, а не таймер на агента. Єдиний повторюваний джоб на Redis прокидається раз на 60 с на весь парк api й вибирає ті heartbeat'и, яким час, — тому тік спрацьовує рівно один раз за будь-якої кількості інстансів, і ніщо не можна запланувати точніше за період обходу.
  • Тік ніколи не перериває хід. Зайнятий агент пропускається і ловиться наступним обходом; за раз в агента йде один хід (див. Один хід на агента).
  • lastRunAt — це запланований слот, а не годинник обхідника, і heartbeat вважається дозрілим у межах половини періоду обходу до слота. Лише разом ці два правила не дають сітці дрейфувати і не дають подвійного спрацювання.
  • Мовчання не пише нічого. Тік, уся відповідь якого — NO_REPLY, не зберігає взагалі нічого. Синтетичне повідомлення-тригер теж не пишеться: діючий тік зберігає лише відповідь асистента та його кроки-тули з позначкою origin=heartbeat.
  • Agent.heartbeat — це не рушій. Це дослівний HEARTBEAT.md, привезений імпортованим пакетом. Агент може нести цей текст із вимкненим автозапуском і мати увімкнений автозапуск без жодного тексту — індикатор, побудований на полі, брехав би в обидва боки. Стан рушія їде окремо, полем autostart на агенті.
  • Заархівованого або вимкненого агента не будять ніколи. Обхідник ставить те саме питання, яке миттю пізніше поставить мозок: чи активний агент? Тік, випущений до будь-якого іншого статусу, забирає блокування ходу, рухає годинник і отримує відмову — і так вічно, не виробляючи нічого. Повернення агента з архіву знову робить його видимим для обхідника, і розклад при цьому не зачеплено.
  • У concierge автозапуску немає зовсім: його тип цього не дає.

Що агент може міняти у своєму автозапуску

Агент править свій автозапуск зсередини ходу. Межу проведено не за тим, яке поле назване, а за тим, хто почав хід:

У ХОДІ, ЯКИЙ ВІДКРИЛА ЛЮДИНА   текст  +  інтервал  +  ввімкненість
У ВЛАСНОМУ ТІКУ АГЕНТА         лише текст
НІХТО І НІКОЛИ                 нижче підлоги, вище стелі

Небезпечна була не сама правка, а петля. Агент, який може переналаштувати собі частоту на кожному пробудженні, вирішує, як часто йому вирішувати, і людини немає в жодній точці цього. Коли прохання приходить у розмові, людина присутня: вона читає відповідь і бачить нове значення на екрані Heartbeat тієї ж секунди — це і є підтвердження, без черги, яку комусь треба розбирати. Хід без явної позначки читається як тік, а не як людина.

Усе, що пише агент, іде через той самий сервіс, що й запис людини, тому підлога в 10 хвилин діє і тут, і другого шляху запису не існує. Вимкнути себе можна, але агент зобов'язаний сказати про це вголос у відповіді: агент, який пішов у тишу мовчки, — це стан, якого ніхто не замовляв, і ззовні він видно лише знятою галочкою на екрані.

Прокинутися частіше на час

Агент, якому доручили справу, що потребує нагляду, може підняти собі частоту на обмежений строк і повернутися до звичайної, щоб нікому не довелося про це пам'ятати.

  • Прискорена частота зберігається поруч зі звичайною, а не поверх неї, разом з моментом, коли вона перестає діяти. Що в силі зараз, обчислюється за годинником на кожному читанні.
  • Тому поверненню не потрібен виконавець: нічого не планується і нічого не запам'ятовується, і воно переживає перезапуск api з тієї самої причини — процес, який не працює, не може не зробити того, чого нікому робити не треба.
  • Не більше 60 хвилин на одне прискорення і не більше 3 прискорень за 24 години. Прискорення — це «поки я з цим розбираюся», а не нове умовчання; постійна зміна частоти — звичайна правка вище.
  • Підлога все одно діє. Прискорення теж не може піти нижче 10 хвилин. «Нагадай за хвилину» — рівно те, що підлога забороняє, і інструмент в обхід означав би скасування рішення, а не його виконання.

Інструкції, які виконуються один раз

Рядок у тексті автозапуску, позначений як разовий, викреслюється після того, як виконаний, тому наступний тік його не повторює. Регулярна інструкція не викреслюється. Коли йде останній разовий рядок, агент повертається до звичайної частоти — і лишається ввімкненим: агент, який вимкнув себе, перестав би підмітатися взагалі, і ззовні ніщо не сказало б чому.

Два «життєві цикли» — не плутати

  • Життєвий цикл агента = стан конфігу (status: activedisabledarchived). Постійний, у БД. Це «чи існує агент / чи увімкнений він».
  • Життєвий цикл рантайм-сесії = виконання (AgentRuntimeSession: pending → running → idle → stopped). Ефемерний, живе лише поки триває задача. Див. Worker.

Агент може бути active місяцями, не маючи рантайм-сесій більшість часу — у цьому весь сенс.

Канали — як повідомлення доходять до агента

channel прив'язує агента до поверхні (telegram / slack / веб-віджет). Вхідні повідомлення потрапляють в ingress, який резолвить цільового агента + сесію і віддає турн orchestrator. Той самий агент (один рядок у БД) може бути прив'язаний до кількох каналів одночасно.

Де живе код

  • Визначення і домен: api → група agent (agent · memory · knowledge · chat · app · channel · orchestrator).
  • Мозок: agent/orchestrator крутиться всередині образу api, ефемерно, на кожен турн.
  • Руки: верхньорівневий застосунок worker для важких тулів.
  • Рушій знань: окремий сервіс LightRAG.