Сповіщення
AgentCore зберігає in-app сповіщення для кожного користувача та віддає нові події через персональний WebSocket-канал. Сповіщення потрібні для речей, що вимагають уваги поза поточним чатом: схвалення, згадки, згенеровані документи, операційні алерти.
Модель даних
Сповіщення зберігаються в таблиці notifications:
| Поле | Призначення |
|---|---|
userId | Одержувач. Усі операції списку, читання та видалення ведуться для цього користувача. |
type | Тип події, що задає продюсер, наприклад approval_pending, mention або document_ready. |
title | Короткий заголовок для inbox. |
body | Опційний довший текст. |
entityType / entityId | Опціональне посилання на таргет: approval, message, document або generation. |
readAt | null, поки користувач не позначить сповіщення прочитаним. |
createdAt | Ключ сортування для inbox та задач ретеншну. |
Індекси (userId, readAt) і (userId, createdAt) роблять дешевими підрахунок непрочитаних, пагінацію та скани ретеншну.
Типи сповіщень
Сервер тримає type як рядок, щоб нових продюсерів можна було додавати без міграції БД. Поточні та заплановані типи:
| Тип | Продюсер | Subject |
|---|---|---|
approval_pending | Agent runner і approval-маршрути, коли потрібен або ескалований HITL-ревʼю. | approval |
mention | Колаборативні фічі, коли користувача згадують у треді або документі. | message, comment або document |
document_ready | Генерація документів і ingestion знань, коли результат готовий. | document або document_generation |
system | Операційні повідомлення, не привʼязані до бізнес-сутності. | Опційно |
Запис сповіщення — довготривалий. Доставка через WebSocket — це транспорт реального часу; клієнти все одно мають перезапитувати REST-inbox після реконекту.
REST API
Усі маршрути вимагають JWT-автентифікації і працюють тільки для поточного користувача.
| Операція | Ендпоінт | Примітки |
|---|---|---|
| Список inbox | 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-бродкастинг
Live-доставка йде через /ws/notifications.
Клієнти автентифікуються одним із двох способів:
- підключитися з
?token=<jwt>; або - підключитися і відправити
{ "action": "auth", "token": "<jwt>" }першим повідомленням.
Якщо автентифікація не завершується за пʼять секунд — сокет закривається з кодом 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 локальний для процесу, для горизонтального масштабування треба додати shared pub/sub layer, перш ніж кілька API-реплік зможуть бродкастити у сокети на різних подах. Redis pub/sub — природний варіант, бо Redis уже потрібен для BullMQ.
Стан прочитання
Стан непрочитаного визначається полем readAt.
- Сповіщення непрочитане, коли
readAt—null. - Позначення прочитаним виставляє
readAtу поточний серверний час. read-allоновлює всі непрочитані сповіщення поточного користувача.- Видалення рядка прибирає його з inbox і з майбутніх підрахунків непрочитаних.
Клієнти можуть оптимістично оновлювати локальний стан читання, але мають ресинхронізуватись із GET /notifications після реконекту або невдалих write-запитів.
Політика ретеншну
У request-path немає автоматичного очищення. Рекомендована продакшн-політика — запланований cleanup-джоб:
- тримати непрочитані доти, доки їх не прочитають або явно не видалять;
- тримати прочитані 90 днів;
- тримати
approval_pendingіdocument_readyдовше, якщо compliance вимагає видимості повʼязаного схвалення чи документа; - видаляти невеликими батчами з сортуванням по
createdAt, щоб уникнути довгих table locks.
Cleanup-джоб ніколи не має видаляти повʼязану бізнес-сутність. Видаляється тільки inbox-покажчик.
Преференції tenant
Налаштування сповіщень моделюються на рівні tenant/product як direct або digest-доставка:
| Преференція | Поведінка |
|---|---|
direct | Негайно показати WebSocket-подію і зберегти рядок в inbox. |
digest | Зберегти рядок в inbox, приглушити негайне UI-переривання, включити у запланований summary. |
muted | Зберегти рядки, важливі для аудиту, але не показувати у проактивному UI. |
Поки преференції не зберігаються серверно, клієнти мають тримати локальні налаштування на type сповіщення й усе одно брати канонічний unread-count із сервера.
Чекліст продюсера
При додаванні нового продюсера:
- створіть
Notification-рядок у тому ж логічному flow, що й вихідна подія; - задайте
entityTypeіentityId, коли користувачу треба кудись перейти; - викличте
emitNotification(notification)після комміта рядка; - додайте або розширте тести поведінки REST-inbox і WebSocket-бродкасту;
- задокументуйте, чи за замовчуванням подія — direct чи digest.