Уведомления
AgentCore хранит in-app уведомления на каждого пользователя и стримит новые события через per-user WebSocket-канал. Уведомления нужны для работы, которая требует внимания пользователя за пределами текущего чата: approvals, упоминания, сгенерированные документы, операционные алерты.
Модель данных
Уведомления хранятся в таблице notifications:
| Поле | Назначение |
|---|---|
userId | Получатель. Любые операции list/read/delete ограничены этим пользователем. |
type | Тип события, задаётся продюсером: approval_pending, mention, document_ready и т.п. |
title | Короткий заголовок для ленты. |
body | Опциональный более длинный текст. |
entityType / entityId | Опциональная цель для ссылки: approval, сообщение, документ, генерация. |
readAt | null, пока пользователь не отметил как прочитанное. |
createdAt | Ключ сортировки для ленты и retention-задач. |
Индексы (userId, readAt) и (userId, createdAt) делают unread count, пагинацию и retention-сканы дешёвыми.
Типы уведомлений
Бэкенд хранит type как строку — можно добавлять новых продюсеров без миграции БД. Текущие и планируемые типы:
| Тип | Продюсер | Сущность |
|---|---|---|
approval_pending | Маршруты agent runner и approvals, когда нужен HITL или эскалация. | approval |
mention | Коллаборативные фичи, когда пользователя напрямую упоминают в треде или документе. | message, comment или document |
document_ready | Генерация документов и ingestion, когда результат готов. | document или document_generation |
system | Операционные уведомления, не привязанные к бизнес-сущности. | Опционально |
Запись уведомления — это долговременная запись. WebSocket-доставка — только realtime-транспорт; клиенты после переподключения всё равно должны запрашивать REST inbox.
REST API
Все маршруты требуют JWT и работают только с текущим пользователем.
| Операция | Эндпоинт | Примечания |
|---|---|---|
| Список | GET /api/v1/notifications?limit=20&offset=0 | Возвращает items, pagination, unreadCount. |
| Только непрочитанные | GET /api/v1/notifications?unreadOnly=true | Использует readAt IS NULL. |
| Отметить одно прочитанным | PATCH /api/v1/notifications/:id/read | Идемпотентно; уже прочитанные строки возвращаются без изменений. |
| Отметить всё прочитанным | POST /api/v1/notifications/read-all | Обновляет все непрочитанные строки текущего пользователя. |
| Удалить одно | DELETE /api/v1/notifications/:id | Удаляет только строки, принадлежащие текущему пользователю. |
Route layer всегда проверяет и id, и userId — угадать чужой id и прочитать или удалить чужое уведомление не получится.
WebSocket-трансляция
Realtime-канал — /ws/notifications.
Клиенты аутентифицируются одним из двух способов:
- подключиться с
?token=<jwt>; - подключиться и прислать первым сообщением
{ "action": "auth", "token": "<jwt>" }.
Если аутентификация не пройдена за 5 секунд — сокет закрывается с кодом 4001.
После аутентификации сокет подписывается на in-process EventEmitter по ключу userId. Продюсеры сначала создают строку в БД, потом вызывают emitNotification(). WebSocket отправляет:
{
"type": "notification.created",
"payload": {
"id": "clx...",
"userId": "clu...",
"type": "approval_pending",
"title": "Approval required",
"body": "A generated reply needs review",
"entityType": "approval",
"entityId": "clp...",
"readAt": null,
"createdAt": "2026-04-16T10:15:00.000Z"
}
}
Emitter живёт внутри процесса, поэтому при горизонтальном масштабировании нужен общий pub/sub-слой, чтобы несколько реплик API могли транслировать подписанным сокетам на разных подах. Redis pub/sub естественно подходит — Redis уже нужен для BullMQ.
Read state
Статус прочтения вычисляется из readAt:
- уведомление считается непрочитанным, если
readAtравноnull; - отметка «прочитано» выставляет
readAtв текущее серверное время; read-allобновляет все непрочитанные уведомления текущего пользователя;- удаление строки убирает её из inbox и из подсчёта unread.
Клиенты должны оптимистично обновлять локальный read state, но при этом ресинхронизироваться через GET /notifications после переподключения или упавших write-запросов.
Retention-политика
В request-path нет автоматической чистки. Рекомендованная production-политика — плановая задача очистки:
- непрочитанные уведомления хранить, пока не прочитали или не удалили явно;
- прочитанные хранить 90 дней;
- строки
approval_pendingиdocument_readyхранить дольше, если политика аудита требует, чтобы связанный approval или документ оставался обнаруживаемым; - удалять небольшими батчами, упорядоченными по
createdAt, чтобы избежать длинных блокировок таблицы.
Задача очистки не должна удалять связанную бизнес-сущность. Она удаляет только inbox-указатель.
Пользовательские настройки
Настройки уведомлений моделируются на стороне клиента/продукта как direct или digest доставка:
| Настройка | Поведение |
|---|---|
direct | Немедленно показать событие из WebSocket и сохранить строку в inbox. |
digest | Оставить строку в inbox, не прерывать UI, включить в плановый дайджест. |
muted | Сохранить строки, важные для аудита, но не показывать в проактивном UI. |
Пока настройки не хранятся на стороне сервера, клиенты должны держать локальные предпочтения по каждому type уведомления, а канонический unread count всё равно брать с сервера.
Чеклист для продюсера
При добавлении нового продюсера:
- создайте строку
Notificationв том же логическом потоке, что и исходное событие; - укажите
entityTypeиentityId, если пользователь должен куда-то перейти; - вызовите
emitNotification(notification)после коммита строки; - добавьте или расширьте тесты REST inbox и WebSocket-трансляции;
- задокументируйте, по умолчанию это direct или digest событие.