Skip to content

Импорт и экспорт агента

Как агент переезжает между папкой .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Как
1SOUL.mdagent.soulсохранить тело дословно (то же имя, без переименования)
1agent.config.jsonagent.config (дословно)хранится как есть; runtimeProfile / лимиты / accessStrategy читаются из него (runtimeProfile дефолтится, если нет)
1USER.md, HEARTBEAT.mdagent.user, agent.heartbeatсохранить дословно (интервал heartbeat берётся из config)
2skills/<n>/SKILL.mdстроки skill (+ ассеты → system/file)парс frontmatter + тело
3MEMORY.md, memory/*.mdmemory (curated + daily[date])тот же текст; daily по ключу agentId+date
3data/memory.sqliteперестраивается, не импортируется (re-embed из markdown)
4data/secrets/<uid>.jsonsecret (только именаpendingKeys)AGNT2-74: пакет несёт ИМЕНА ключей, никогда значения; значения из старого файла выбрасываются, остаются только имена
5data/access.json + accessStrategyaccess (ACL + стратегия)channel-user-id → user Agentfy.ai
6data/sessions/<chan>:<uid>.jsonlchat (тред + сообщения)события → сообщения в порядке ts
7data/usage.jsonsystem/usage (опц. baseline)обычно пропускаем (forward-looking)
7browser-state/*.jsonapp/account (опц.)чувствительное/эфемерное; часто пропускаем

Маппинг событий сессии → chat

Каждая строка JSONL { id, type, ts, data }:

type события
usermessage(role=user, text, from)
assistantmessage(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.soulSOUL.md (тело как есть)
agent.config (дословно)agent.config.json
agent.user · agent.heartbeatUSER.md · HEARTBEAT.md
memory curated · dailyMEMORY.md · memory/YYYY-MM-DD.md
skill (+ ассеты)skills/<name>/SKILL.md
secret (только имена, без расшифровки)data/secrets/{shared|agent|user-<userId>}.json
accessdata/access.json
chat threads → событияdata/sessions/<chan>:<user>.jsonl
system/usage (опц.)data/usage.json
куки app/accountbrowser-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.