Подключение организации: путь
Это сквозной маршрут партнёра от «хотим, чтобы у наших пользователей был агент» до работающего чата с инструментами. Здесь названы все экраны, ключи и вызовы в том порядке, в котором они нужны. Подробные контракты на соседних страницах: provisioning, серверный чат, идентичность.
Эталонная интеграция: Store Deputy. Одна команда Store Deputy = одна команда Agentfy, один подключённый магазин = один агент, каталог магазина = MCP-сервер. Inline-профили проверены также с изолированным локальным MCP; см. отчёт задачи.
Кто есть кто
| Термин | Что значит |
|---|---|
| Организация | Партнёр внутри Agentfy. У неё неизменяемые namespace и environment (test или production), операторы, ключи и одобренная политика. |
| Оператор | Прямой пользователь Agentfy, который управляет организацией: owner, admin, developer, billing, support. Операторы не видят команды клиентов и их беседы. |
| Администратор платформы | Сотрудник Agentfy. Активирует организации и утверждает политику организации в админке. |
| Внешний актор | Пользователь продукта партнёра. Agentfy знает его только по externalUserId внутри namespace; у него нет пароля и прямого входа. |
| Команда | Команда Agentfy, созданная под рабочее пространство партнёра (team.externalId). Ей принадлежат агенты, беседы и счета. |
| Профиль | Что такое агент: имя, инструкции, skill, модель, необязательный MCP-сервер и необязательные worker-инструменты. Передаётся с запросом и фиксируется в операции. |
| Операция | Один запрос provisioning и его автомат состояний: pending → ready, либо failed, либо retired. |
| MCP-сервер | Собственный HTTP-эндпоинт партнёра, который отдаёт агенту инструменты. Agentfy ходит к нему с per-agent ключом. |
Продукт партнёра Бэкенд партнёра Agentfy
──────────────── ─────────────── ───────
пользователь: «создать агента» → POST /provisionings (ключ provisioning) → операция, команда, агент
PUT …/configuration {ключ MCP} → tools/list на вашем MCP
← ready
пользователь открывает чат → POST /chat/conversations (ключ chat:*)
PUT …/context {grant} → доверенный MCP-контекст
POST …/messages → ран, SSE-события
→ вызовы инструментов на ваш MCPШаг 1. Создать организацию
В приложении Организации (/orgs) → Создать организацию. Создатель становится владельцем и подтверждает эту ответственность на форме.
namespaceиenvironmentпостоянны. Дляtestиproductionзаводите отдельные организации; у них нет общих команд, ключей и профилей.- Бесплатно. Оплата пока не подключена. Для создания, активации и подключения организации не нужны договор, договорный лимит, карта или адреса возврата. Использование не создаёт задолженности или будущих платёжных обязательств. Технические лимиты, утверждённые политики, права операторов и явная активация платформой сохраняются. Будущая оплата подключается отдельно и явно; бесплатный период не оплачивается задним числом.
- Операторов добавляют на том же экране. Членство оператора не связано с командами клиентов и не даёт платформенной роли.
Организация создаётся в статусе pending. Администратор платформы проверяет её в админке (/orgs) и активирует. Пока статус не active, ничего ниже не работает.
Утвердить политику организации
В Организации → ваша организация → Политика владелец или администратор задаёт разрешённые модели, MCP-хосты, доверенный контекст, worker-инструменты, сетевые назначения worker и максимум команд, затем сохраняет и отправляет черновик. Администратор платформы открывает очередь политик в разделе Организации в админке, проверяет diff и утверждает или отклоняет с причиной. Provisioning разрешает только утверждённая политика; пока новая версия на рассмотрении, действует предыдущая.
Шаг 2. Выпустить серверные ключи
Организации → Интеграция · Серверные ключи. Создайте один ключ и выберите разрешения сервера. Можно совмещать подготовку (provision:create, provision:read) и чат (chat:membership, chat:create, chat:context, chat:run, chat:read). Для отдельных сервисов можно выпускать отдельные ключи. Права чата и identity требуют владельца организации или администратора платформы и явного подтверждения; admin/developer могут выпускать ключи только с правами подготовки.
Секрет показывается один раз и хранится на сервере. По умолчанию ключ без срока действия; можно указать будущую дату без ограничения в 90 дней. Старые даты сохраняются. Замена сохраняет права и срок; старый ключ отключается сразу или после выбранного периода совместной работы (до 24 часов, не позднее его прежнего срока). Отзыв доступен всегда. Ограничения организации, namespace, окружения и статуса сохраняются.
Права identity зарезервированы: рабочих API выпуска и управления идентификацией пока нет. Старое поле kind необязательно, поддерживается для совместимости и не даёт прав.
Шаг 3. Передать профиль в запросе
Платформа один раз одобряет политику организации. Передавайте agent.profile в запросе создания: {name, instructions, skill: {name, description, body}, modelId?, mcp?: {url, requiredTools, trustedContext?}, worker?: {tools, egress?}}. Содержимое в рамках политики не требует отдельного одобрения. Без mcp агент только разговаривает.
requiredTools — обязательный минимум, а не список разрешённых инструментов: доступны все обнаруженные инструменты с учётом прав вызывающего и правил совпадающих имён. Каждый успешный PUT …/profile перечитывает неизменённое MCP-подключение после сохранения профиля; изменение подключения по-прежнему обрабатывает worker настройки. Изменение только requiredTools также запускает перечитывание, не останавливая работающего агента.
Для всех подключённых MCP-серверов, включая добавленные вручную, первый допустимый ход после MCP_TOOLS_REFRESH_SECONDS (по умолчанию 300 секунд) запускает фоновое обновление. Этот ход сохраняет прежний список; новые инструменты доступны со следующего хода после завершения обновления. У неактивного агента опроса нет. После ошибки повтор происходит через тот же интервал; сохранение профиля или кнопка обновления не ждут истечения срока. Фоновое обнаружение ограничено 5 секундами на HTTP-обмен и не задерживает начавший его ход.
Метаданные с переданным секретом и список без обязательного инструмента отклоняются. Последний успешный список и время его обнаружения сохраняются; lastError виден в настройках MCP агента и очищается после успешного обновления. Клиент не держит соединение между запросами и не подписывается на notifications/tools/list_changed; обновление обеспечивают сохранение профиля и проверка срока.
Модель должна входить в allowedModels, хост MCP — проходить проверку публичного адреса и входить в mcpHosts, если включён mcpHostsRestricted. Доверенный контекст требует trustedContextAllowed; инструменты и сеть воркера ограничены workerTools и workerEgress, а также потолком развёртывания. Лимиты символов: имя 120, инструкции 16 000, имя skill 64, описание 1 024, тело 32 000. MCP-инструменты: 1–64, worker-инструменты: 1–16, сетевые адреса: до 64.
agent.profile обязателен. Если modelId отсутствует, используется первая модель одобренной политики. Удалённые поля templateId и templateVersion, как и другие неизвестные поля, возвращают 422. Каталога сохранённого содержимого и выбора по умолчанию нет.
Во вкладке «Политика» флажок Все инструменты сохраняет workerTools: ["*"]: политика допускает все текущие и будущие инструменты воркера. Список отдельных инструментов при этом отключён. Агент по-прежнему запрашивает конкретные инструменты; сетевые адреса и пределы развёртывания проверяются отдельно.
Админка → Организации → Созданные агенты показывает журнал только для чтения: внешний ID, команду, субъект, статус, дату и раскрываемый зафиксированный профиль. Существующие снимки и одобренные политики не меняются. Обновление содержимого живого агента — отдельная задача AGNT2-477.
Шаг 4. Открыть свой MCP-сервер
Если агент должен что-то делать с вашими данными, вы поднимаете MCP-сервер по HTTP и вписываете его адрес в профиль.
- Адрес должен быть публичным HTTPS. Agentfy отказывает loopback, частным сетям и именам
.internal/.localраньше любых проверок (пол адресов) и резолвит имя, проверяя каждый адрес за ним. ЛокальноMCP_DEV_LOOPBACK=trueприNODE_ENV=devдопускаетlocalhostпо обычному HTTP. - Аутентификация: Agentfy шлёт
Authorization: Bearer <ключ>в каждом вызове. Ключ тот, что вы передадите на шаге 5: вы его выпускаете, вы его проверяете. - Обнаружение: при provisioning Agentfy вызывает
initialize,notifications/initializedиtools/list; каждое имя изrequiredToolsдолжно присутствовать, иначе операция падает сmcp_tools_missing. - Доверенный контекст (
trustedContext: true): в каждом вызове инструмента Agentfy добавляетX-Agentfy-Agent-Id,X-Agentfy-Thread-Id,X-Agentfy-Turn-Id,X-Agentfy-Tool-Call-Id,X-Agentfy-Initiated-By: humanи грант беседы, привязанный на шаге 6, какX-SD-Context. По ним ваш сервер решает, что этому агенту можно читать для этого человека прямо сейчас. - Ход с
done.stopReason: completedполучаетcompleted, даже после ошибок отдельных инструментов. Только завершение из-заtool_errorдаётfailed / TOOL_REFUSED;max_iterationsостаётсяlimited. Итог определяютrun_statusили polling запуска. Оба содержатtoolErrors: [{toolCallId, name, errorCode}]; без ошибок это пустой массив.errorCode— безопасныйTOOL_FAILED, без сырых аргументов и результатов. Строгий клиент может считать любой запуск с непустымtoolErrorsнеудачным.
Шаг 5. Provisioning: команда и её агент
Все вызовы идут с ключом provisioning и Idempotency-Key; повтор с тем же ключом и телом возвращает ту же операцию.
Первая команда рабочего пространства (bootstrap):
POST /v1/orgs/{orgId}/provisionings
Authorization: Bearer <ключ provisioning>
Idempotency-Key: workspace-77-v1
{
"subject": { "externalUserId": "person-42" },
"team": { "externalId": "workspace-77", "name": "Workspace 77" },
"agent": {
"externalId": "store-9",
"profile": {
"name": "Store assistant",
"instructions": "Help the authorized user with the store.",
"skill": {
"name": "store-help",
"description": "Store assistance",
"body": "Use the attached MCP for authorized store requests."
},
"modelId": "<allowed model>",
"mcp": {
"url": "https://mcp.example.com/mcp",
"requiredTools": [
"store_describe"
]
}
}
},
"payer": { "mode": "organization" }
}subject становится владельцем команды. Ответ (202, пока pending) содержит operationId, userId (id внешнего актора в Agentfy), teamId, agentId, status, step, reason, retryable, version и configurationVersion. Сохраните id операции, команды и агента: других ручек у вас нет.
Ещё один агент в существующей команде: POST /v1/orgs/{orgId}/teams/{teamId}/agents/provisionings с subject (он уже должен быть owner или admin этой команды, см. членства на шаге 6) и agent.
Передать MCP-ключ. С MCP-профилем операция останавливается на pending / configuration_required. Выпустите ключ, который примет ваш MCP-сервер, и передайте его:
PUT /v1/orgs/{orgId}/operations/{operationId}/configuration
{ "expectedVersion": 1, "mcp": { "credential": "<секрет, который проверяет ваш MCP>" } }Agentfy хранит его зашифрованным как секрет агента и никогда не возвращает. Ротация позже, это тот же вызов с текущим expectedVersion.
Дождаться готовности. Опрашивайте GET /v1/orgs/{orgId}/operations/{operationId}. Шаги идут profile → mcp_discovery → complete; status: ready значит, что агент активен со своим skill, MCP-сервером и обнаруженными инструментами.
reason | Что значит | Повтор |
|---|---|---|
configuration_required | ждёт ключ из PUT выше | после PUT |
policy_violation: <field> | профиль выходит за утверждённую политику организации (или утверждённой политики нет) | расширить и утвердить политику, затем повторить ту же операцию с текущим expectedVersion |
model_unavailable | модели профиля нет в каталоге или она выше плана | после правки настроек платформы |
egress_refused | адрес MCP в частной сети или вне платформенной политики MCP-хостов | нет, правьте профиль |
mcp_discovery_failed | ваш MCP не ответил на initialize/tools/list | POST …/retry { "expectedVersion" } |
mcp_tools_missing | в tools/list нет части requiredTools | после правки вашего сервера |
setup_profile_missing | операция создана до появления профилей | нет |
Нарушение политики возвращает HTTP 422 до создания ресурсов: code: policy_violation, message: policy_violation: <field>. Поля: policy, modelId, mcpHosts, mcp.url, trustedContextAllowed, workerTools, workerEgress. Исправьте профиль или получите одобрение изменения политики и отправьте запрос снова. Каталог моделей и план команды проверяются при подготовке.
Тот же agent.profile принимает ручка дополнительного агента; в ней нет team и payer. Без MCP не нужны конфигурация и обнаружение инструментов. С MCP передайте ключ ниже и дождитесь обнаружения.
Принятое содержимое замораживается в снимке операции и fingerprint запроса. Повтор сохраняет снимок, даже если политика изменилась. Другое содержимое с тем же ключом даёт IDEMPOTENCY_CONFLICT, с той же живой внешней резервацией — EXTERNAL_ID_CONFLICT. Изменение содержимого живого агента относится к отдельному процессу обновления на месте, а не к снятию с учёта. Снятие предназначено для намеренного удаления или замены.
Шаг 6. Чат через ваш бэкенд
Чат проверяет конкретные права chat:* для каждой операции; можно использовать тот же ключ, что и для подготовки. Интерфейс вашего продукта говорит с вашим бэкендом, бэкенд говорит с Agentfy. Код в браузере не видит ни ключа, ни гранта.
- Синхронизируйте членство при каждом изменении роли пользователя в вашем пространстве:
PUT /v1/orgs/{orgId}/chat/membershipsсexternalUserId,externalTeamId,role,active,sourceRevision,validUntil. Отозванные участники сразу теряют права чата и provisioning. - Откройте беседу на пользователя и агента:
POST /v1/orgs/{orgId}/chat/conversations{externalUserId, externalTeamId, agentId}. В ответе естьmcp: {serverId, destination}, если у агента есть MCP-сервер. - Привяжите доверенный контекст до первого сообщения (и снова до истечения): выпустите у себя короткоживущий грант для этого человека и беседы, затем
PUT …/conversations/{id}/contextсserverId,destination(ровно те, что вернула беседа),grant,expiresAt(не больше пяти минут вперёд) иexpectedVersion. Agentfy хранит грант зашифрованным и предъявляет его вашему MCP какX-SD-Context.GET …/contextчитает версию и срок без гранта. - Отправьте сообщение:
POST …/conversations/{id}/messages{externalUserId, externalTeamId, message}сIdempotency-Key→202 {runId, status: accepted}. - Читайте ран потоком:
GET …/runs/{runId}/events, это SSE с событиямиturn,text(дельты),tool_call,tool_result {ok},doneи итоговымrun_status {status, reason, toolErrors, timings}.Last-Event-IDвозобновляет поток.GET …/runs/{runId}даёт ту же истину без потока;GET …/messagesдаёт сохранённую историю.thinkingскрыт и не передаётся. Имена и поля JSON описаны в контракте событий. БезLast-Event-IDили с{runId}:0возвращается весь запуск;{runId}:Nвозвращает хвост после N, в том числе после завершения.
Ход с done.stopReason: completed получает completed, даже после ошибок отдельных инструментов. Только завершение из-за tool_error даёт failed / TOOL_REFUSED; max_iterations остаётся limited. Итог определяют run_status или polling запуска. Оба содержат toolErrors: [{toolCallId, name, errorCode}]; без ошибок это пустой массив. errorCode — безопасный TOOL_FAILED, без сырых аргументов и результатов. Строгий клиент может считать любой запуск с непустым toolErrors неудачным.
Шаг 7. Дать агенту руки: worker-инструменты
С worker.tools в профиле в рамках политики Agentfy по требованию поднимает агенту изолированный под воркера и предлагает перечисленные инструменты (exec выполняет команды в рабочей директории, file читает и пишет её, http и web_fetch ходят в сеть, browser* управляют браузером). Воркер останавливается через idleSeconds без вызовов.
Сетевые инструменты ограничены дважды: потолком инсталляции (RUNTIME_WORKER_EGRESS_ALLOWLIST) и собственным запросом агента. Пока профиль не несёт worker.egress (AGNT2-472), у партнёрских агентов запрос пуст: http и web_fetch отказывают любому адресу, exec при этом работает.
Политика организации также ограничивает сетевые назначения worker: запрошенные адреса должны укладываться и в неё, и в RUNTIME_WORKER_EGRESS_ALLOWLIST. Утверждение политики не отменяет проверку публичности адресов. Сужение политики ограничивает новый или повторный provisioning, но не отзывает уже готовых агентов.
Шаг 8. Обновить содержимое на месте или удалить агента
Обновить содержимое на месте
Используйте ключ provisioning и текущую version из GET …/operations/{operationId}. Передайте полный inline-профиль с теми же полями и проверкой политики, что в шаге 5. Пропущенные необязательные поля удаляются; без modelId выбирается первая разрешённая модель.
PUT /v1/orgs/{orgId}/operations/{operationId}/profile
Authorization: Bearer <provisioning-key>
Content-Type: application/json
{
"expectedVersion": 3,
"profile": {
"name": "Store assistant",
"instructions": "Use the revised store procedures.",
"skill": { "name": "store-guide", "description": "Store procedures", "body": "Follow the revised guide." }
}
}Инструкции, имя, навык, модель и права воркера меняются у того же агента. Обновление только содержимого сохраняет ready без discovery. Новый MCP URL или набор обязательных инструментов переводит операцию в pending; discovery использует сохранённый ключ и завершает её в ready либо failed с существующей причиной и признаком retryable. Добавление MCP без сохранённого ключа ожидает configuration. Исправьте удалённый endpoint и вызовите POST …/retry с актуальным expectedVersion; перед следующим PUT профиля операция должна вернуться в ready. Удаление MCP из профиля отключает привязку.
После изменения MCP URL дождитесь ready, затем заново привяжите контекст каждой существующей беседы до следующего сообщения. Гранты контекста привязаны к (serverId, destination) и не переносятся на новый URL. С ключом chat:* и теми же полями пользователя выполните GET …/conversations/{id}/context, чтобы получить текущую версию для нового адреса (0, если привязки ещё нет), затем PUT по тому же пути: expectedVersion из GET, прежний serverId, новый канонический destination, свежий грант, разрешённый для этого адреса, и expiresAt. Не используйте версию старого адреса. Поля запроса описаны в шаге 6.
Устаревшая версия возвращает 409 OPERATION_VERSION_CONFLICT, нарушение политики — 422. Обновляются только ready-операции; нужно право provision:create, даже если у ключа есть права чата. ID агента, команда, субъект и беседы сохраняются: текущий ход заканчивается с загруженными инструкциями, следующий получает новые. Повтор исходного POST использует исходный fingerprint. Admin → организация → Созданные агенты показывает все редакции, время и ключ отправителя.
Удалить агента
Для намеренного удаления или изменения externalId, команды либо субъекта снимите агента с учёта и создайте заново:
DELETE /v1/orgs/{orgId}/teams/{teamId}/agents/{externalAgentId}
Authorization: Bearer <ключ provisioning>204 архивирует агента, отзывает его секреты и MCP-привязку, закрывает беседы и ставит tombstone на резервацию; старая операция читается как retired. Тот же externalId можно провизионировать снова: он получит нового агента, новую операцию и выбранный вами профиль. Вызов идемпотентен; неизвестные id отвечают непрозрачным 404. Считайте 404 успехом только после того, как GET …/operations/{id} подтвердил retired.
Окружения и локальная разработка
- Каждый ключ принадлежит одному окружению.
PARTNER_API_ENVIRONMENTAPI (testилиproduction) должен совпадать; он не зависит отNODE_ENV. - Локальный Agentfy с
MCP_DEV_LOOPBACK=trueвapi/.env.devпринимает MCP-адресаhttp://localhost:…в профилях, обнаружении и контексте чата. ВнеNODE_ENV=devничего не меняется. - Публичный туннель к ноутбуку (serveo, cloudflared) годится для демо против размещённого Agentfy, но новый адрес можно передать в inline-профиле, если его хост разрешён политикой. Для разработки предпочитайте loopback-переключатель.
Что должен хранить ваш бэкенд
| Значение | Откуда |
|---|---|
| id организации, окружение, ключи с правами подготовки и чата | шаг 2, серверная конфигурация |
| профиль, переданный в запросе | шаг 3 |
на пространство: externalTeamId, teamId Agentfy | ответ bootstrap на шаге 5 |
на агента: externalAgentId, operationId, agentId, ваш MCP-ключ, configurationVersion | шаг 5 |
на пользователя: externalUserId, sourceRevision членства | шаг 6 |
на беседу: conversationId, serverId, destination, текущий грант и версия контекста | шаг 6 |
на сообщение: ключ идемпотентности и runId | шаг 6 |
Store Deputy хранит всё это в OrganizationWorkspace, OrganizationProvision, OrganizationChatMember и OrganizationConversation; его слайсы agent/orgProvision и agent/orgChat, рабочий образец всего пути.
Ограничение MCP-доменов необязательно: редактор новой политики отправляет mcpHostsRestricted: false, поэтому публичный MCP URL в профиле агента не требует отдельного домена в политике. В дополнительных настройках включите «Разрешать только указанные MCP-домены», чтобы проверять mcpHosts; включённое ограничение с пустым списком запрещает все MCP-подключения. Существующие политики и старые API-запросы без флага сохраняют ограничение, включая пустые списки. Режим применяется после отправки/одобрения политики. Остальные требования одобрения, проверка URL, защита частных адресов и сетевые ограничения платформы и воркера сохраняются.
Расход организации по ключу и локальное демо
В форме серверных ключей выпустите analytics:read (owner/admin или администратор платформы; developer не может выдать финансовый доступ). Scope можно добавить к provisioning-ключу либо выпустить отдельный ключ. GET /v1/orgs/{orgId}/analytics?period=7d|30d|month принимает его как Bearer и возвращает ту же агрегацию, что отчёт owner/admin по JWT. Только своя организация и окружение API: чужая организация — 404, нет scope — 403, истёкший/отозванный ключ или неверное окружение — 401. JWT без изменений; /analytics/live и /analytics/year доступны только по JWT.
priceSide: "cost" — сохранённая себестоимость поставщика, не списание кредитов и не перепродажная цена. costUsdPico — точная целочисленная строка в 10⁻¹² USD. unpricedEventsExcluded: true: неоценённые события исключены из суммы, а не оценены нулём; проверяйте unpricedEvents. usage: null у ответа тоже не означает ноль.
GET /v1/orgs/{orgId}/credentials/current показывает организацию, окружение и scope текущего действующего ключа без секрета. Работает и без analytics:read; не даёт прав человека.
Локальное демо /organization: подключение и диагностика scope → сохранённая команда → агент → разговор со стоимостью и частями хода → расход и сверка. Языки ru/en, светлая/тёмная тема, телефон; ключи остаются на сервере. Настройка — demo/README.md в репозитории.