Імпорт та експорт агента
Як агент переїжджає між текою .agent/ окремого рантайму і Postgres Agentfy.ai — в обидва боки. Це адаптер agent/package; той самий код обслуговує materialize/sync воркера. Див. також Сумісність з .agent.
Імпорт — .agent/ → Agentfy.ai DB
importAgent(dir, { teamId, agentId? }). Runtime-агент одно-тенантний, Agentfy.ai — мультитенантний, тому імпорту потрібен цільовий teamId (і опц. agentId для оновлення). Усе — ідемпотентний upsert за натуральними ключами, по секціях у транзакції.
Порядок (батьки раніше)
team (дано) → agent → { skill, memory, secret, access } → chatЩо куди
| Крок | Джерело (.agent/) | → Agentfy.ai | Як |
|---|---|---|---|
| 1 | SOUL.md | agent.soul | зберегти тіло дослівно (те саме ім'я, без перейменування) |
| 1 | agent.config.json | agent.config (дослівно) | зберігається як є; runtimeProfile / ліміти / accessStrategy читаються з нього (runtimeProfile дефолтиться, якщо немає) |
| 1 | USER.md, HEARTBEAT.md | agent.user, agent.heartbeat | зберегти дослівно (інтервал heartbeat береться з config) |
| 2 | skills/<n>/SKILL.md | рядки skill (+ ассети → system/file) | парс frontmatter + тіло |
| 3 | MEMORY.md, memory/*.md | memory (curated + daily[date]) | той самий текст; daily за ключем agentId+date |
| 3 | data/memory.sqlite | — | перебудовується, не імпортується (re-embed з markdown) |
| 4 | data/secrets/<uid>.json | secret (лише імена → pendingKeys) | AGNT2-74: пакет несе ІМЕНА ключів, ніколи значення; значення зі старого файлу відкидаються, лишаються тільки імена |
| 5 | data/access.json + accessStrategy | access (ACL + стратегія) | channel-user-id → user Agentfy.ai |
| 6 | data/sessions/<chan>:<uid>.jsonl | chat (тред + повідомлення) | події → повідомлення в порядку ts |
| 7 | data/usage.json | system/usage (опц. baseline) | зазвичай пропускаємо (forward-looking) |
| 7 | browser-state/*.json | app/account (опц.) | чутливе/ефемерне; часто пропускаємо |
Маппінг подій сесії → chat
Кожен рядок JSONL { id, type, ts, data }:
type події | → |
|---|---|
user | message(role=user, text, from) |
assistant | message(role=assistant, text) |
tool_call | запис виклику (name, params, toolUseId) |
tool_result | результат (toolUseId, result) |
summary | маркер компакта/саммарі |
Оригінальний event.id зберігається як externalId → повторний імпорт ідемпотентний (без дублів).
Наскрізні правила трансформації
- ID: runtime-UUID →
externalId; Agentfy.ai генерить свій{slice}-uuid. - Час: epoch ms →
DateTime. - Секрети: лише імена (AGNT2-74). Пакет ніколи не несе значення креда, тому імпорт оголошує імена (
AgentSecret.pendingKeys), а значення вводяться руками на місці призначення. Доки цього не зроблено, діалог імпорту та екран секретів називають відсутні. - Похідні індекси (
memory.sqlite, OpenSearch, pgvector) не переносяться — перебудовуються з канонічного markdown/json/jsonl.
Експорт — Agentfy.ai DB → .agent/
exportAgent(agentId, { userScope? }) — зворотна проєкція в теку .agent/ (tarball або директорія). Round-trip працює, бо обидві сторони говорять одним форматом пакета.
| Джерело (Agentfy.ai) | → .agent/ |
|---|---|
agent.soul | SOUL.md (тіло як є) |
agent.config (дослівно) | agent.config.json |
agent.user · agent.heartbeat | USER.md · HEARTBEAT.md |
memory curated · daily | MEMORY.md · memory/YYYY-MM-DD.md |
skill (+ ассети) | skills/<name>/SKILL.md |
secret (лише імена, без розшифрування) | data/secrets/{shared|agent|user-<userId>}.json |
access | data/access.json |
chat threads → події | data/sessions/<chan>:<user>.jsonl |
system/usage (опц.) | data/usage.json |
куки app/account | browser-state/<profile>.json |
data/memory.sqliteне пишемо — рантайм перебудує FTS з markdown на старті.- Результат можна
pull-нути рантаймом (через S3) або запустити напряму — агент «переїхав».
Межі та за що пакет відхиляється
Імпорт матеріалізує весь архів у пам'яті до того, як з нього щось прочитано, тому стеля — це властивість завантаження, а не агента, який у ньому описаний:
| завантаження | 25 МБ |
| записів | 20 000, включно з каталогами |
| сумарно в розпакованому вигляді | 128 МБ — перевіряється двічі: проти того, що оголошує директорія (до розпакування бодай одного байта), і проти того, що розпакувальник справді виробляє |
| стиснення | лише STORE і DEFLATE. bzip2/lzma відхиляються як непідтриманий метод, і це інше повідомлення, ніж «це не zip» |
У кожної відмови є код, а не фраза. Причина їде у відповіді про помилку полем details.refusal, а застосунок тримає по одному тексту на код. Причина, додана на сервері, не може мовчки впасти в загальний хвіст — код без тексту червонить тест.
Імпорт — усе або нічого. Відхилений імпорт лишає нуль рядків: напівзібраного агента в списку не буде. Значення, яких не тримає база — байт NUL, непарний сурогат, відмітка часу поза діапазоном timestamptz, — відхиляються на межі з назвою поля, а не доїжджають до Postgres і повертаються п'ятисоткою. (NUL — не ворожий ввід: ditto на теці в macOS кладе в архів сайдкари AppleDouble.)
Ніщо в пакеті не може назвати шлях за межами .agent/ — у жодну зі сторін. Запис, який ліг би поза текою, відхиляється на імпорті; і жодне значення з пакета — userId, ім'я навички, дата пам'яті — не може вивести ім'я запису назовні на експорті.
Типу агента в маніфесті немає (див. Склад). concierge не можна ні експортувати, ні перезаписати завантаженням; закриті обидві сторони, бо імпортер знаходить наявного агента за externalId.
Пакет приїжджає завантаженням або зі сховища, прив'язаного до орендаря, — але ніколи шляхом на сервері. Джерела «прочитай ось цей каталог» у HTTP-контракті немає: шлях від викликача дозволив би будь-якому автентифікованому учаснику змусити сервер обійти й вичитати будь-який каталог, до якого дотягується процес.
Архів пишеться так, щоб його читали чужі розпакувальники і щоб він був стабільним. Імена записів несуть UNIX-маркер платформи, тому не-ASCII ім'я навички розпаковується штатними засобами macOS і Linux вірно, а не сміттям; відмітки часу записів фіксовані, тому два експорти незмінного агента — ті самі байти.
Ідемпотентність ключується парою (teamId, externalId). Той самий пакет, імпортований двічі в одну команду, оновлює того самого агента; імпортований у дві різні команди дає двох незалежних агентів, у яких просто спільний origin id.
Round-trip
- Чисто round-trip:
SOUL/agent.config/MEMORY/skills/access(текст + JSON) та імена секретів разом зі scope. - Значення секретів — навмисний виняток (AGNT2-74): вони ніколи не потрапляють у пакет, тому переїзд завжди закінчується словами «введіть ці N кредів».
- Sessions: без втрат, якщо маппінг типів подій повний (user/assistant/tool_call/tool_result/summary).
- Індекси: похідні всюди → регенеруються з кожного боку, у пакеті не передаються.
Де запускається
- CLI / admin-ендпоінт:
agentfy import ./.agent --team <teamId>·agentfy export <agentId> -o ./out. - Воркер (той самий адаптер): materialize = export (hydrate
.agent/на поді перед турном); delta-sync = import (синк дельт після турна). Перевикористовує diff/S3-синк рантайму. - Код:
api→ адаптерagent/package.