Оформление
Внешние агенты
Как устроена работа платформы с внешними агентами: состав, модель данных, синхронизация событий, объявляемые агентом разделы и выдача модели.
Роль платформы
EVA выступает управляющим слоем для внешних автономных агентов. Сам агент это отдельный сервис, который живёт вне платформы и делает свою работу сам.
Это отдельная сущность, не связанная с таблицей агентов чата
Существующая таблица агентов чата не изменяется и в этой возможности не участвует. Связей между ними нет: ни внешнего ключа, ни общих полей. Совпадает только слово «агент».
Общая схема
Обращения идут в обе стороны. Платформа опрашивает агента и посылает ему команды. Агент отдаёт платформе поток своих событий и, если ему выдана модель, обращается за ответами модели через шлюз платформы.
Состав
| Составляющая | Где | Назначение |
|---|---|---|
| Клиент протокола | apps/api/src/integrations/managed-agents/eacp-client.ts | Все обращения к агенту, проверка адреса, разбор ошибок |
| Служба агентов | apps/api/src/services/managed-agents-service.ts | Реестр, шифрование токена, команды, запись событий |
| Синхронизация событий | apps/api/src/services/managed-agent-event-sync.ts | Постоянное чтение потока событий всех включённых агентов |
| Служба выдачи модели | apps/api/src/services/managed-agent-llm-service.ts | Служебная учётная запись, ключ, привязка модели |
| Адреса управления | apps/api/src/routes/managed-agents.ts | 30 адресов управления агентами |
| Адреса ручного разбора | apps/api/src/routes/attention.ts | 11 адресов работы со спорными случаями |
| Контракт | contracts/eacp/ | Описание протокола с зафиксированными хешами файлов |
Модель данных
Реестр агентов
managed_agents
| Поле | Назначение |
|---|---|
name | Отображаемое имя |
base_url | Адрес управляющего API, уникален |
auth_type | Способ авторизации, всегда bearer |
encrypted_token | Токен агента, шифрование AES-256-GCM |
enabled | Идёт ли синхронизация событий |
agent_type, instance_id, agent_version, protocol_version | Взято из описания, которое присылает агент |
manifest_json | Последнее полученное описание целиком |
status | Черновик, готов, деградация, отключён, ошибка |
last_ready_at, last_checked_at, last_error | Результат последней проверки |
Журнал событий
managed_agent_events это неизменяемый локальный журнал: номер события, идентификатор, источник, тип, серьёзность, время, предмет, идентификатор связи, данные и обязательная пометка о маскировании.
managed_agent_event_cursors хранит по каждому агенту номер последнего прочитанного события. Это позиция чтения потока.
Защита от повторов держится на двух уникальных ограничениях: на паре агент плюс номер события и отдельно на идентификаторе события.
Команды
managed_agent_commands это аудит: ключ идемпотентности, тип команды, кто послал, переданные данные, идентификатор операции у агента, состояние и текст ошибки.
Ручной разбор
eva_attention_items это проекция спорных случаев агента внутрь платформы. Все поля, которые могут содержать персональные данные, помечены как безопасные: заголовок, отправитель, тема. Есть поле версии для защиты от одновременной правки и поле назначенного исполнителя.
eva_attention_action_records аудит действий оператора: ключ идемпотентности, тип действия, контрольная сумма запроса, кто, какую версию ожидал, обязательная причина и результат.
eva_attention_audit_events отдельный журнал событий разбора.
managed_agent_attention_sync_states позиция чтения и результат последней синхронизации спорных случаев.
Объявляемые агентом разделы
managed_agent_ui_manifests кэш описания разделов: версия расширения, ревизия, метка для условного запроса, само описание, признак пригодности, текст ошибки разбора и список ухудшений.
managed_agent_ui_actions аудит нажатых кнопок в объявленных разделах.
Что именно пишется в аудит нажатий
Записываются раздел, действие, затронутые записи, кто нажал, имена переданных полей, ожидаемая версия, причина, состояние и краткая сводка ответа. Значения полей не записываются: это сделано ради персональных данных.
Выдача модели
managed_agent_llm_bindings какая модель выдана агенту, через какую служебную учётную запись, каким ключом, по какому адресу шлюза, с какими параметрами и с какой ревизией.
Изменения существующих таблиц
Это самое важное для понимания последствий.
| Изменение | Зачем |
|---|---|
| У пользователей появилось поле вида учётной записи, по умолчанию «человек» | Служебная учётная запись агента помечается как машинная. Она авторизуется только по ключу и никогда не входит по паролю |
| В журнале расхода ссылка на чат стала необязательной | Обращения агента к модели происходят вне любого чата |
| В журнале расхода появилась ссылка на агента | Расход относится на агента, а не на чат |
Вид записи расхода для таких обращений отличается от обычного обращения через шлюз.
Синхронизация событий
Это фоновый набор обработчиков внутри процесса API.
| Свойство | Значение |
|---|---|
| Проверка списка агентов | Каждые 15 секунд |
| Задержки при обрыве | 1, 2, 5, 10, 30 секунд, счётчик сбрасывается после успешного события |
| Что читается | Поток событий агента, начиная с сохранённого номера |
| Первичная загрузка | Один раз на обработчик, если агент объявляет проекцию спорных случаев, потому что события несут только изменения |
| Пропуск агента | Только если агент прямо объявил, что потока событий у него нет |
Таймер не удерживает процесс: при остановке платформы он не мешает завершению.
Немаскированные события отклоняются
Запись события выполняется в одной транзакции с продвижением позиции чтения. Перед записью проверяется пометка о маскировании, и событие без неё отклоняется с ошибкой. Персональные данные в центральную базу через этот путь не попадают.
Ошибка построения проекции не обрывает чтение потока: она только записывается в лог как предупреждение.
Объявляемые агентом разделы
Платформа не знает заранее, какие экраны нужны конкретному агенту. Агент присылает описание своих разделов, а платформа рисует их сама.
Как складывается состав разделов
| Раздел | Условие появления |
|---|---|
| Обзор | Безусловно |
| Задания | Безусловно |
| Объявленные агентом | Столько, сколько объявил, в заданном им порядке |
| Настройки | Агент отдаёт схему настроек и не объявил своей формы настроек |
| Реестр операций | Агент объявил такую возможность и не объявил своего раздела с этим именем |
| Ручной разбор | То же правило |
| Журнал | Агент ведёт историю событий |
Общих разделов только два
Без всяких условий добавляются лишь обзор и задания. Всё остальное, включая журнал, зависит от того, что агент сообщил о себе. Раздел, объявленный агентом, вытесняет платформенный вариант с тем же именем.
Порядок платформенных закреплён: обзор первым, задания вторыми, журнал последним.
Поддерживаемые виды разделов: таблица, карточка записи, форма, показатели, лента событий, произвольные данные, встроенная страница.
Внутри карточки записи бывают панели: произвольные данные, лента, файлы, пары ключ и значение, размеченный текст.
| Ограничение | Значение |
|---|---|
| Предел размера описания | 512 КБ, задан в коде |
| Кэш описания | Срок жизни задаётся переменной, по умолчанию 30 секунд |
| Условный запрос | Поддерживается по метке, повторный запрос без изменений возвращает признак «не изменилось» |
| Разбор описания | Не строгая проверка, а нормализация с ухудшением: неизвестный вид раздела превращается в произвольные данные, лишние поля отбрасываются |
Важное решение по безопасности
Агент не передаёт платформе адреса, по которым ей идти. Платформа сама собирает путь из идентификатора раздела. Это прямо отмечено в коде: маршрут выводится из идентификатора, поэтому агент не может подсунуть произвольный адрес.
Выдача модели агенту
Платформа создаёт для агента служебную учётную запись, ключ API и привязывает модель. Агенту сообщается адрес OpenAI-совместимого шлюза платформы.
Дальше агент обращается за ответами модели к платформе, а не к провайдеру напрямую. Ключи провайдеров остаются внутри платформы, а расход попадает в общий учёт.
Ключ можно перевыпустить, привязку проверить или удалить.
Права
| Объект | Действия |
|---|---|
| Внешние агенты | Чтение, Администрирование |
| Ручная обработка агентов | Девять независимых действий вместо обычного набора |
Действия ручной обработки: чтение, исправление полей, выбор кандидата, продолжение обработки, ручное завершение, назначение исполнителя, загрузка замены, просмотр персональных данных, подтверждение исхода.
Такое разделение позволяет выдать право на действие, не выдавая права видеть персональные данные.
Расхождение в текущей версии
API отдаёт данные по праву чтения, но маршрут экрана требует администрирования. Роль только для просмотра до экрана не доходит, хотя подсказка про такую роль в интерфейсе предусмотрена.
Переменные окружения
| Переменная | По умолчанию | Назначение |
|---|---|---|
EACP_TIMEOUT_MS | 15000 | Таймаут обычного обращения к агенту |
EACP_EXPORT_TIMEOUT_MS | 120000 | Таймаут выгрузки |
MANAGED_AGENT_UI_MANIFEST_TTL_SECONDS | 30 | Срок жизни кэша описания разделов |
MANAGED_AGENT_UI_EXPORT_MAX_BYTES | 33554432 | Предел размера выгрузки |
MANAGED_AGENT_UI_EMBED_ENABLED | false | Разрешение встроенных страниц агента |
MANAGED_AGENT_LLM_GATEWAY_URL | Адрес платформы плюс /v1 | Адрес шлюза для агента |
Настроек в базе эта возможность не добавляет: строк общих настроек платформы не появляется.