refactor: migrate EventStore and MessageStore to :outbox-api and :journal-api
ci / JVM build + tests (push) Failing after 1m9s
ci / JVM build + tests (push) Failing after 1m9s
- Replaced usages of `:message-store-api` and `:working-memory-api` with `:journal-api`, `:outbox-api`, and `:context-api`. - Deprecated legacy `EventStore` and `MessageStore` interfaces, added `typealias` for backward compatibility. - Updated imports across all modules with references to `:journal-api` and `:outbox-api`. - Introduced `journalRoutes` and `outboxRoutes` in `:server` for audit log and live event stream endpoints. - Adjusted `Agent` to expose read-only `journal` and `outbox` stores for improved modularity and clarity. - Removed legacy Event and AgentEvent definitions from `:proto`, migrated to `:outbox-api`. - Storage-related modules have been updated to support the new APIs consistently.
This commit is contained in:
@@ -2,6 +2,8 @@ package pw.binom.agentik.proto
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
@@ -10,12 +12,46 @@ import kotlin.time.Instant
|
||||
* Транспортно-агностично. [Agent] — фабрика stateful-диалогов:
|
||||
* [createConversation] возвращает [Conversation], который сам хранит историю
|
||||
* и которому отправляют ходы через [Conversation.send].
|
||||
*
|
||||
* **Хранилища вынесены в [Agent.journal] и [Agent.outbox]**: оба read-only.
|
||||
* События больше НЕ часть [Agent] (раньше были `events()`/`allEvents()`) —
|
||||
* они теперь живут в [outbox] как `OutboxStore.events(after)` /
|
||||
* `outbox.agentEvents(after)`. Это даёт единый путь для всех read-операций
|
||||
* по хранилищу и убирает дублирование между протоколом и хранилищем.
|
||||
*/
|
||||
public interface Agent {
|
||||
|
||||
/** Идентификатор агента. */
|
||||
val id: String
|
||||
|
||||
/**
|
||||
* Append-only audit log всех сообщений диалогов (read-only view).
|
||||
*
|
||||
* Используется HTTP-фасадом `:server` для endpoint'а
|
||||
* `GET /{path}/journal/conversations/{id}/messages` — внешние клиенты
|
||||
* (дашборды, parent-агенты, A2A-bridge) могут читать полный transcript
|
||||
* диалога, включая tool-call/tool-result/error, без необходимости идти
|
||||
* через `Conversation.getMessages` (который возвращает уже
|
||||
* project'нутый proto-Message).
|
||||
*
|
||||
* **Read-only**: write-доступ только через `MutableJournalStore`
|
||||
* внутри ChatAgent / ConversationLoop, не через [Agent] interface.
|
||||
*/
|
||||
val journal: JournalStore
|
||||
|
||||
/**
|
||||
* Bounded-tail live event stream агента (read-only view).
|
||||
*
|
||||
* Используется HTTP-фасадом `:server` для endpoint'а
|
||||
* `GET /{path}/outbox/events?after=` (SSE) — внешние клиенты подписываются
|
||||
* на agent lifecycle + conversation events. Catchup+live контракт — см.
|
||||
* KDoc `OutboxStore.events`.
|
||||
*
|
||||
* **Read-only**: write-доступ только через `MutableOutboxStore` внутри
|
||||
* ChatAgent / ConversationLoop, не через [Agent] interface.
|
||||
*/
|
||||
val outbox: OutboxStore
|
||||
|
||||
/** Создаёт новый stateful-диалог с агентом. */
|
||||
fun createConversation(temp: Boolean): Conversation
|
||||
|
||||
@@ -39,28 +75,6 @@ public interface Agent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Live-подписка на изменения в множестве диалогов агента: создание,
|
||||
* удаление, переименование (см. [AgentEvent]). События внутри конкретного
|
||||
* диалога приходят через [Conversation.events].
|
||||
*
|
||||
* **Не реплеит** прошлое — для снимка множества используй [getConversations]
|
||||
* или [getConversation].
|
||||
*/
|
||||
fun events(after: Instant): Flow<AgentEvent>
|
||||
|
||||
/**
|
||||
* All events in one stream: agent lifecycle (Created/Deleted/Renamed) +
|
||||
* all conversation turns. Useful for admin dashboards, debug tools,
|
||||
* parent agents.
|
||||
*
|
||||
* For UI use [events] + [Conversation.events]. This one-feed variant is
|
||||
* for cases where everything-in-one is preferred.
|
||||
*
|
||||
* Cold (no replay). For catchup use EventStore.
|
||||
*/
|
||||
fun allEvents(after: Instant): Flow<CommonEvent>
|
||||
|
||||
companion object {
|
||||
|
||||
const val PAGE_SIZE: Int = 100
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
package pw.binom.agentik.proto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* Live-события уровня [Agent]: изменения в множестве диалогов
|
||||
* (создание, удаление, переименование). События, происходящие **внутри**
|
||||
* конкретного диалога, приходят через [Conversation.events], а не сюда.
|
||||
* Backward-compat typealias: `AgentEvent` теперь живёт в `:outbox-api`
|
||||
* (логически принадлежит сущности outbox, не wire-протоколу `:proto`).
|
||||
*
|
||||
* Каждое событие несёт [date] — момент эмиссии в UTC. Семантика подписки
|
||||
* идентична [Conversation.events]: поток **не реплеит** прошлое, для бэкфилла
|
||||
* используются `getConversations`/`getConversation`.
|
||||
* Существующие импорты `pw.binom.agentik.proto.AgentEvent` продолжают
|
||||
* работать транспарентно. Использовать typealias в новом коде.
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface AgentEvent {
|
||||
/** Момент эмиссии события в UTC. */
|
||||
val date: Instant
|
||||
|
||||
/**
|
||||
* Создан новый диалог. Передаётся его id — handle можно получить через
|
||||
* [Agent.getConversation]. Подписчик после [Created] может сразу открыть
|
||||
* live-подписку на этот диалог через [Conversation.events].
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("created")
|
||||
data class Created(override val date: Instant, val conversationId: String) : AgentEvent
|
||||
|
||||
/**
|
||||
* Диалог удалён. Переданный [Conversation]-handle реализация обязана
|
||||
* закрыть (`close()`) до эмиссии этого события — после [Deleted]
|
||||
* пользоваться handle нельзя.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("deleted")
|
||||
data class Deleted(override val date: Instant, val id: String) : AgentEvent
|
||||
|
||||
/** У диалога сменился заголовок. */
|
||||
@Serializable
|
||||
@SerialName("renamed")
|
||||
data class Renamed(override val date: Instant, val id: String, val title: String?) : AgentEvent
|
||||
}
|
||||
typealias AgentEvent = pw.binom.agentik.outbox.AgentEvent
|
||||
|
||||
@@ -1,39 +1,9 @@
|
||||
package pw.binom.agentik.proto
|
||||
|
||||
import kotlin.time.Instant
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Unified wrapper for all agent events in a single stream.
|
||||
* Backward-compat typealias: `CommonEvent` теперь живёт в `:outbox-api`.
|
||||
*
|
||||
* Useful for admin dashboards, debug tools, parent agents: one subscription
|
||||
* instead of N+1. For regular UI use two separate SSE feeds
|
||||
* ([AgentEvent] via /events and [Event] via /conversations/{id}/events);
|
||||
* [CommonEvent] is for those who need everything in one place.
|
||||
*
|
||||
* Server endpoint: GET /events/all (SSE), or replay via EventStore.
|
||||
*
|
||||
* Not used for persistence payload: EventStore stores AgentEvent and
|
||||
* Conversation.Event natively (compact form); this wrapper is wire-format
|
||||
* only.
|
||||
* Существующие импорты `pw.binom.agentik.proto.CommonEvent` продолжают
|
||||
* работать транспарентно.
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface CommonEvent {
|
||||
val date: Instant
|
||||
|
||||
@Serializable
|
||||
@SerialName("agent")
|
||||
data class Agent(
|
||||
override val date: Instant,
|
||||
val event: AgentEvent,
|
||||
) : CommonEvent
|
||||
|
||||
@Serializable
|
||||
@SerialName("conversation")
|
||||
data class Conversation(
|
||||
override val date: Instant,
|
||||
val conversationId: String,
|
||||
val event: Event,
|
||||
) : CommonEvent
|
||||
}
|
||||
typealias CommonEvent = pw.binom.agentik.outbox.CommonEvent
|
||||
|
||||
@@ -1,88 +1,11 @@
|
||||
package pw.binom.agentik.proto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* Элемент live-потока [Conversation.events].
|
||||
* Backward-compat typealias: `Event` теперь живёт в `:outbox-api`.
|
||||
*
|
||||
* Каждое событие несёт [date] — момент эмиссии в UTC. Используется клиентом
|
||||
* для трекинга «где остановился» при обрыве/переподключении и для разрешения
|
||||
* порядка при равных timestamps.
|
||||
*
|
||||
* Базовая структура хода:
|
||||
* `StartReasoning?` → `StartResponse(TEXT|IMAGE)` → ...контент... → `End` | `Interrupted` | `Error`.
|
||||
* `StartReasoning` может отсутствовать, если агент не показывал рассуждения.
|
||||
* Существующие импорты `pw.binom.agentik.proto.Event` продолжают
|
||||
* работать транспарентно. `Conversation.events(after): Flow<Event>` в
|
||||
* `:proto.Conversation` теперь фактически возвращает
|
||||
* `pw.binom.agentik.outbox.Event` — тот же тип, другое имя.
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Event {
|
||||
/** Момент эмиссии события в UTC. */
|
||||
val date: Instant
|
||||
|
||||
@Serializable
|
||||
enum class ResponseType {
|
||||
@SerialName("text") TEXT,
|
||||
@SerialName("image") IMAGE
|
||||
}
|
||||
|
||||
/** Ассистент начал рассуждение (опциональный маркер; контент рассуждения приходит через [AppendText]). */
|
||||
@Serializable
|
||||
@SerialName("start_reasoning")
|
||||
data class StartReasoning(override val date: Instant) : Event
|
||||
|
||||
/** Начало ответа ассистента заданного типа. После него идут соответствующие `Append*`/`Tool*`-события, потом [End]/[Interrupted]/[Error]. */
|
||||
@Serializable
|
||||
@SerialName("start_response")
|
||||
data class StartResponse(override val date: Instant, val responseType: ResponseType) : Event
|
||||
|
||||
/** Ход завершён нормально. Соответствующий [Message.AssistantMessage] появится в `getMessages`. */
|
||||
@Serializable
|
||||
@SerialName("end")
|
||||
data class End(override val date: Instant) : Event
|
||||
|
||||
/** Ход прерван через [Conversation.interrupt]. Частичный ответ НЕ сохраняется в истории. */
|
||||
@Serializable
|
||||
@SerialName("interrupted")
|
||||
data class Interrupted(override val date: Instant) : Event
|
||||
|
||||
@Serializable
|
||||
@SerialName("append_text")
|
||||
data class AppendText(override val date: Instant, val body: String) : Event
|
||||
|
||||
@Serializable
|
||||
@SerialName("append_image")
|
||||
data class AppendImage(override val date: Instant, val body: ByteArray, val mime: String) : Event
|
||||
|
||||
/**
|
||||
* Агент начал вызов тула. Аргументы приходят целиком — стриминга нет.
|
||||
* [id] совпадает с id соответствующего [Message.ToolCall] в истории
|
||||
* после завершения хода.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("tool_call")
|
||||
data class ToolCall(
|
||||
override val date: Instant,
|
||||
val id: String,
|
||||
val title: String?,
|
||||
val toolName: String,
|
||||
val toolArgs: String,
|
||||
) : Event
|
||||
|
||||
/**
|
||||
* Результат вызова тула. Приходит целиком после завершения исполнения.
|
||||
* [id] совпадает с [ToolCall.id], к которому относится результат, и
|
||||
* с id [Message.ToolResult] в истории.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("tool_result")
|
||||
data class ToolResult(override val date: Instant, val id: String, val result: String?) : Event
|
||||
|
||||
/**
|
||||
* Ошибка хода. После неё поток завершается; дальнейшие события могут
|
||||
* прийти, но ход считается проваленным.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("error")
|
||||
data class Error(override val date: Instant, val message: String, val code: String? = null) : Event
|
||||
}
|
||||
typealias Event = pw.binom.agentik.outbox.Event
|
||||
|
||||
Reference in New Issue
Block a user