Документирует:
- Три принципа дизайна (TTL внутри, catchup+live в одном Flow,
read-only контракт для observer'ов)
- Архитектуру двухуровневого хранилища событий со схемой
- Reconnect pattern с gap detection
- API EventStore + MutableEventStore (когда какой использовать)
- Таблица: какой caller принимает какой интерфейс
- Текущее состояние: interfaces готовы, implementations в работе
- Зависимости (минимальные: :proto + kotlinx-coroutines)
В том же стиле что и :agent-toolsets/README.md.
Разделяет интерфейс на read-only (EventStore) и write (MutableEventStore).
EventStore (read-only, для consumer'ов):
- events(after: Instant?): Flow<CommonEvent>
- earliestEventDate(): Instant
- close()
MutableEventStore : EventStore (для producer'ов):
- + append(event: CommonEvent)
- suspend, не идемпотентный, может быть silently evicted
Зачем:
- Consumer'ы (server SSE, admin dashboard, parent agents) принимают
EventStore — compile-time гарантия что они не могут писать в store.
- Producer'ы (ChatAgent, sub-agents, A2A-bridge) принимают MutableEventStore.
- Тесты могут использовать EventStore без опасности случайной модификации.
Миграция:
- :event-store пока без implementations, поэтому ничего не сломалось.
- Когда добавим InMemoryEventStore — он будет реализовывать оба
(MutableEventStore = EventStore + append). Подписки получают только
read-only projection через приведение типа.
Также: импорт обновлён AllEvent → CommonEvent (по rename в :proto).
Заменяет generic Event envelope (id, date, payload) на типизированный AllEvent
из :proto. EventStore теперь:
- append(event: AllEvent)
- events(after: Instant?): Flow<AllEvent>
- earliestEventDate(): Instant
Изменения:
- :event-store теперь зависит от :proto (api dependency).
- Event.kt удалён — AllEvent уже живёт в :proto и несёт date, conversationId,
typed envelope (Agent или Conversation variant).
- Generic opaque payload выкинут — typesafety до самого storage.
- append НЕ идемпотентен (AllEvent не имеет уникального id, два retry
дадут дубликат). Документировано: для exactly-once использовать catchup
через :message-store-api (там есть монотонный id).
В KDoc примере reconnect убран лишний null-check у earliestEventDate
(теперь всегда non-null).
Миграция (когда будем интегрировать):
Producer в :standalone перестаёт делать двойную работу
(MutableSharedFlow + persistAgentEvent). Вместо этого — один
eventStore.append(allEvent). Caller'ы SSE будут делать
store.events(after) → Flow<AllEvent>, без ручной конвертации.
Build green на JVM.
Раньше возвращал Instant? — null для пустого буфера. Клиенту приходилось
проверять if (earliest != null && ...) — легко ошибиться.
Теперь всегда Instant. Если буфер пуст, возвращает Clock.System.now()
на момент вызова. Это убирает nullable + сохраняет семантически корректное
поведение:
- Клиент может безопасно сделать store.events(earliest) → получит только
live event'ы, без ложного catchup.
- Если бы возвращали null/DISTANT_PAST/null-check, клиент мог бы ошибочно
подписаться на несуществующий catchup-диапазон.
Edge case (в KDoc): клиент, подключившийся ДО первого event'а, получает
earliest ≈ now. Его lastSeen < earliest → адаптируется в первом poll'е.
Изменение breaking — но :event-store ещё не имеет implementations и
никакие consumer'ы не используют этот метод.
Выделяет bounded-tail event log в отдельный KMP-модуль. Это **новый
контракт** (не замена :message-store-api/events/EventStore — тот пока жив).
**Двухуровневое хранилище событий**:
1. :event-store (этот PR) — короткий bounded tail, авто-TTL.
Для live SSE и recent replay (catchup после короткого disconnect).
2. :message-store-api (MessageStore) — полный audit log, никогда не
эвиктится. Source of truth для длинного disconnect / audit query.
**API**:
- append(Event) — put, идемпотентный по id
- events(after: Instant?): Flow<Event> — catchup + live в одном Flow
- earliestEventDate(): Instant? — для gap detection у клиента
**Чего НЕТ в API** (by design):
- delete/prune методов — TTL/cap eviction полностью на стороне impl.
Caller'ы не должны забыть вызвать cleanup (single source of truth).
- conversationId / type в Event — opaque payload, тип envelope'а
решает producer.
Пока interfaces only — implementations (InMemoryEventStore, persistent)
появятся в следующих коммитах. Никаких изменений в существующем
EventStore в :message-store-api, чтобы не ломать зависимости.
KMP targets: jvm + linuxX64 + mingwX64 (Apple auto-disabled на Linux).
Modules:
+ :event-store — новый, commonMain only, ~150 строк