refactor(storage): split MessageStore into :message-log-api
ci / JVM build + tests (push) Successful in 6m15s

Выделяет append-only message log в отдельный KMP-модуль.
Цель — разделить ДВЕ сущности по своей природе:

  :message-log-api  — append-only audit log (User/Assistant/ToolCall/
                       ToolResult/Error). Никаких update, только insert + read.
                       Это иммутабельная история диалога.

  :working-memory-api — mutable runtime context (compact, summary, WM order).
                          Live state. Compaction-логика.

Раньше оба жили в :message-store-api, что:
  - смешивало контракты: append-only audit vs mutable runtime;
  - делало невозможным лёгкого клиента который читает только audit log
    без WM-runtime зависимости;
  - затрудняло compaction-логике жить в одном модуле с audit-записью.

Миграция:
  - В :message-log-api переехали: Content, MessageRecord, MessageStore,
    MessageContext (с MessageOrigin), MessageEvent, TokenStats, TurnTokens,
    helpers (encode/decodeBodyPayload, MessageBodyPayload, BodyDecoded).
    Пакет pw.binom.agentik.messageLog.
  - В :message-store-api остались: ConversationStore, ConversationRecord,
    ReflectionStore, Ids, legacy events.EventStore (paginated replay).
    Пакет pw.binom.agentik.messageStore.
  - :working-memory-api: обновил deps (api → :message-log-api для Content/MessageContext).
  - 23 consumer-файла обновлены (FQN renames).
  - storage-sqlite/ksqlite: убраны недостижимые ветки Summary/System
    (эти synthetic records живут ТОЛЬКО в :working-memory-api, не попадают
    в audit log :message-log-api).

Файлы:
  + :message-log-api (5 файлов, ~280 строк)
  - :message-store-api (5 файлов, ~430 строк)
  ~ 23 файла обновлены

Совместимость схем не меняется. Все 5 storage impl'ов (3 backend × 5 store)
работают на тех же таблицах.
This commit is contained in:
2026-09-20 17:21:46 +03:00
parent a0b1209457
commit 15f3952eba
43 changed files with 406 additions and 605 deletions
@@ -146,5 +146,5 @@ internal suspend fun recentTurns(storage: pw.binom.agentik.storageBundle.Storage
}
/** Текстовое содержимое записей working memory (Text-контент, без картинок). */
internal fun List<pw.binom.agentik.messageStore.Content>.text(): String =
filterIsInstance<pw.binom.agentik.messageStore.Content.Text>().joinToString("\n") { it.body }
internal fun List<pw.binom.agentik.messageLog.Content>.text(): String =
filterIsInstance<pw.binom.agentik.messageLog.Content.Text>().joinToString("\n") { it.body }
@@ -12,7 +12,7 @@ import pw.binom.agentik.memory.ConversationTurn
import pw.binom.agentik.memory.MemoryReviewer
import pw.binom.agentik.memory.MemoryStore
import pw.binom.agentik.skills.SkillStore
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.workingMemory.WorkingMemoryStore
@@ -2,8 +2,6 @@ package pw.binom.agentik.standalone.agent
import kotlin.time.Instant
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
@@ -11,7 +9,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import pw.binom.agentik.llm.tools.ContextCompactor
import pw.binom.agentik.llm.tools.LlmReflector
import pw.binom.agentik.llm.tools.SkillMiner
@@ -20,6 +17,7 @@ import pw.binom.agentik.memory.MemoryReviewer
import pw.binom.agentik.memory.MemorySystemGuidance
import pw.binom.agentik.proto.Agent as ProtoAgent
import pw.binom.agentik.proto.AgentEvent
import pw.binom.agentik.eventStore.MutableEventStore
import pw.binom.agentik.proto.CommonEvent
import pw.binom.agentik.proto.Conversation as ProtoConversation
import pw.binom.agentik.proto.Event as ProtoEvent
@@ -195,97 +193,47 @@ class ChatAgent(
},
)
private val agentEvents = MutableSharedFlow<AgentEvent>(
extraBufferCapacity = 64,
)
/** Json-encoder для payload в EventStore. Один на весь agent. */
private val eventJson = Json {
ignoreUnknownKeys = true
encodeDefaults = true
}
/**
* Fire-and-forget persist в EventStore. Если EventStore настроен (production
* deployment с `:storage-sqlite`), каждый AgentEvent также уходит в SQLite
* с уникальным id, чтобы клиенты могли сделать /events/replay после
* disconnect. Если EventStore == null (например, dev mode с in-memory
* storage или Android без persistent event log) — no-op.
* Единый канал всех событий агента — bounded tail с auto-TTL.
*
* Background launch + runCatching: ошибки БД не должны ронять agent loop.
* Логирование — если persistence падает, видно в логах, но live-stream
* продолжает работать.
* Заменил ранее существовавшие два канала:
* - `agentEvents: MutableSharedFlow<AgentEvent>` (agent lifecycle)
* - per-conv `ConversationEvents._flow: MutableSharedFlow<ProtoEvent>`
*
* Теперь оба пишут сюда через [MutableEventStore.append], а consumer'ы
* читают через [EventStore.events]/[conversationEvents]/[agentEvents].
*
* **Live tail + auto-TTL** — клиенты больше не должны заботиться о persistence
* или подписке на два отдельных канала.
*/
private fun persistAgentEvent(event: AgentEvent) {
val store = storage.eventStore ?: return
kotlinx.coroutines.CoroutineScope(
kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.IO
).launch {
runCatching {
store.append(
EventRecord(
id = "ev-${Ids.new("agent")}",
conversationId = when (event) {
is AgentEvent.Created -> event.conversationId
is AgentEvent.Deleted -> event.id
is AgentEvent.Renamed -> event.id
},
createdAt = event.date,
type = when (event) {
is AgentEvent.Created -> EventType.AGENT_CREATED
is AgentEvent.Deleted -> EventType.AGENT_DELETED
is AgentEvent.Renamed -> EventType.AGENT_RENAMED
},
payload = eventJson.encodeToString(AgentEvent.serializer(), event),
)
)
}.onFailure {
mu.KotlinLogging.logger("ChatAgent").warn(it) {
"failed to persist agent event ${event::class.simpleName}: ${it.message}"
}
}
}
}
private val eventStore: MutableEventStore = pw.binom.agentik.eventStore.inmemory.InMemoryEventStore(
maxMessages = null,
ttl = null,
)
/** Защищает карту живых диалогов. */
private val liveLock = Mutex()
private val live: MutableMap<String, ChatConversation> = HashMap()
/** Live-подписка на события уровня агента (создание/удаление/переименование). */
override fun events(after: Instant): Flow<AgentEvent> {
// Реализация событийной шины упрощённая: возвращаем общий поток.
// Фильтр по `after` не делаем — для v1 после-семантика не нужна
// (см. Memory #3704: replay-free, бэкфилл через getConversations/getConversation).
return agentEvents.asSharedFlow()
}
override fun events(after: Instant): Flow<AgentEvent> =
eventStore.agentEvents(after).map { it.event }
/**
* Все события в одном потоке: agent lifecycle + events всех диалогов.
* Реализация — merge двух cold-flow'ов. Snapshot-список живых диалогов
* берётся на момент подписки; новые Created-Event'ы НЕ переподписывают
* (это ответственность caller'а: если хочет всё — он может
* переподписаться или следить за AgentEvent.Created сам).
*
* Для admin/debug — допустимое упрощение. Для long-running мониторинга
* (admin-дашборд часами) надо добавить reactive re-subscribe (см.
* notes 04-sub-agents.md, Variant B).
* Snapshot живых диалогов берётся на момент подписки. Новые Created-Event'ы
* НЕ переподписывают — это ответственность caller'а (см. KDoc в :event-store
* про reconnect pattern + gap detection через [eventStore.earliestEventDate]).
*/
override fun allEvents(after: Instant): Flow<CommonEvent> = flow {
// Agent lifecycle events
emitAll(agentEvents.asSharedFlow().map { e: AgentEvent ->
CommonEvent.Agent(date = e.date, event = e)
})
// Snapshot живых диалогов на момент подписки.
// ВАЖНО: не подписываемся на новые Created — это ответственность caller'а
// (см. KDoc выше).
emitAll(eventStore.agentEvents(after))
live.values
.asSequence()
.filterNot { it.isClosed }
.forEach { conv: ProtoConversation ->
val cid: String = conv.id
emitAll(conv.events(after).map { ev: ProtoEvent ->
CommonEvent.Conversation(date = ev.date, conversationId = cid, event = ev)
})
emitAll(eventStore.conversationEvents(after, cid).map { it as CommonEvent.Conversation })
}
}
@@ -309,6 +257,7 @@ class ChatAgent(
val conv = ChatConversation(
record = rec,
storage = storage,
eventStore = eventStore,
llm = llm,
systemPrompt = systemPrompt,
tools = allTools,
@@ -327,8 +276,14 @@ class ChatAgent(
runBlocking {
liveLock.withLock { live[conv.id] = conv }
}
agentEvents.tryEmit(AgentEvent.Created(date = now(), conversationId = conv.id))
persistAgentEvent(AgentEvent.Created(date = now(), conversationId = conv.id))
runBlocking {
eventStore.append(
CommonEvent.Agent(
date = now(),
event = AgentEvent.Created(date = now(), conversationId = conv.id),
)
)
}
return conv
}
@@ -346,8 +301,7 @@ class ChatAgent(
val ok = storage.conversationStore.delete(id)
if (ok) {
val event = AgentEvent.Deleted(date = now(), id = id)
agentEvents.tryEmit(event)
persistAgentEvent(event)
eventStore.append(CommonEvent.Agent(date = now(), event = event))
}
return ok
}
@@ -363,6 +317,7 @@ class ChatAgent(
private fun newConversation(rec: ConversationRecord): ChatConversation = ChatConversation(
record = rec,
storage = storage,
eventStore = eventStore,
llm = llm,
systemPrompt = systemPrompt,
tools = allTools,
@@ -6,7 +6,7 @@ import pw.binom.agentik.memory.ConversationTurn
import pw.binom.agentik.memory.MemoryReviewer
import pw.binom.agentik.memory.MemoryStore
import pw.binom.agentik.standalone.agent.memory.materializeReviewNote
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.workingMemory.WorkingMemoryRow
import pw.binom.agentik.workingMemory.WorkingMemoryStore
@@ -3,8 +3,8 @@ package pw.binom.agentik.standalone.agent
import mu.KotlinLogging
import pw.binom.agentik.memory.MemoryPrefetcher
import pw.binom.litert.LiteContentPart
import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.messageStore.MessageOrigin
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
internal class ContextBuilder(
private val memoryPrefetcher: MemoryPrefetcher?,
@@ -1,120 +1,30 @@
package pw.binom.agentik.standalone.agent
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import mu.KotlinLogging
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import pw.binom.agentik.eventStore.MutableEventStore
import pw.binom.agentik.proto.CommonEvent
import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.messageStore.Ids
import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.messageStore.events.EventType
private val log = KotlinLogging.logger {}
/**
* SSE-события диалога + (опционально) persistence в [EventStore].
*
* Двойная ответственность:
* 1. Live-streaming через [flow] — клиенты подписываются на long-lived SSE.
* 2. Durable storage через [eventStore] — для replay после disconnect
* через /conversations/{id}/events/replay?after_id=X.
*
* **Persistence strategy**: при каждом [tryEmit]/[emit] параллельно пишем в
* EventStore (fire-and-forget в IO scope). Ошибка БД НЕ должна ронять live-stream
* — оборачиваем в runCatching и логируем.
*
* **Idempotency**: каждое event имеет детерминированный id (из messageStore при
* создании), append с тем же id в EventStore — no-op. Это критично для retry
* между producer и БД.
*
* **Dual-write cost**: на каждый event одно INSERT в SQLite. SQLite на локальном
* диске выдерживает ~50K events/sec; для hot pathов можно вынести persist в
* отдельную batched очередь. Для v1 — синхронный launch — OK.
*/
internal class ConversationEvents(
private val eventStore: EventStore? = null,
private val conversationId: String? = null,
private val eventJson: Json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
},
private val globalEventStore: MutableEventStore,
private val conversationId: String,
) {
private val _flow = MutableSharedFlow<ProtoEvent>(
replay = 0,
extraBufferCapacity = 4096,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
val flow: SharedFlow<ProtoEvent> get() = _flow.asSharedFlow()
/**
* Emit event в live-stream + persist в EventStore (если настроен).
*
* @return true если event попал в live-stream (false если buffer overflow
* и event был дропнут — DROP_OLDEST policy).
*/
fun tryEmit(event: ProtoEvent): Boolean {
val ok = _flow.tryEmit(event)
if (ok) persistAsync(event)
return ok
}
/**
* Same as [tryEmit] но suspend — ждёт места в buffer'е (а не дропает).
* Используется реже — там где мы хотим гарантировать доставку подписчикам.
*/
suspend fun emit(event: ProtoEvent) {
_flow.emit(event)
persistAsync(event)
}
/**
* Persist в EventStore в fire-and-forget. Если [eventStore] == null — no-op
* (in-memory dev или Android без persistence).
*
* **Не использует agentScope** — мы не знаем о нём здесь (ConversationEvents
* не владеет lifecycle). Если нужна более аккуратная lifecycle management —
* передавать scope параметром или держать свой CoroutineScope.
*
* **Сейчас**: создаём transient `GlobalScope`-like через `MainScope()`-style —
* НЕТ, лучше через `CoroutineScope(SupervisorJob + Dispatchers.IO).launch`.
* Это сделано лениво, чтобы не плодить треды при hot path.
*/
private fun persistAsync(event: ProtoEvent) {
val store = eventStore ?: return
val convId = conversationId ?: return // не знаем к чему привязать
kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.IO).launch {
runCatching {
store.append(
EventRecord(
id = "ev-${Ids.new("conv")}",
conversationId = convId,
createdAt = event.date,
type = mapEventType(event),
payload = eventJson.encodeToString(ProtoEvent.serializer(), event),
)
runBlocking {
globalEventStore.append(
CommonEvent.Conversation(
date = event.date,
conversationId = conversationId,
event = event,
)
}.onFailure {
log.warn(it) {
"failed to persist conversation event ${event::class.simpleName}: ${it.message}"
}
}
)
}
return true
}
private fun mapEventType(event: ProtoEvent): EventType = when (event) {
is ProtoEvent.StartReasoning -> EventType.CONVERSATION_START_REASONING
is ProtoEvent.StartResponse -> EventType.CONVERSATION_START_RESPONSE
is ProtoEvent.AppendText -> EventType.CONVERSATION_APPEND_TEXT
is ProtoEvent.AppendImage -> EventType.CONVERSATION_APPEND_IMAGE
is ProtoEvent.ToolCall -> EventType.CONVERSATION_TOOL_CALL
is ProtoEvent.ToolResult -> EventType.CONVERSATION_TOOL_RESULT
is ProtoEvent.End -> EventType.CONVERSATION_END
is ProtoEvent.Interrupted -> EventType.CONVERSATION_INTERRUPTED
is ProtoEvent.Error -> EventType.CONVERSATION_ERROR
}
fun events(after: kotlin.time.Instant?): Flow<ProtoEvent> =
globalEventStore.conversationEvents(after = after, conversationId = conversationId)
.map { it.event }
}
@@ -28,16 +28,16 @@ import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.proto.Message as ProtoMessage
import pw.binom.agentik.proto.MessageContext as ProtoMessageContext
import pw.binom.agentik.skills.SkillStore
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.messageStore.MessageOrigin
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.MessageStore
import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.storageBundle.StorageBundle
import pw.binom.agentik.messageStore.TurnTokens
import pw.binom.agentik.messageLog.TurnTokens
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.toolsets.ToolsetDispatchPolicy
@@ -56,6 +56,7 @@ import pw.binom.agentik.toolsets.NamedTool
class ConversationLoop(
record: ConversationRecord,
private val storage: StorageBundle,
private val eventStore: pw.binom.agentik.eventStore.MutableEventStore,
private val llm: LiteLlm,
private val systemPrompt: String,
private val tools: List<NamedTool> = emptyList(),
@@ -85,7 +86,7 @@ class ConversationLoop(
)
private val events = ConversationEvents(
eventStore = storage.eventStore,
globalEventStore = eventStore,
conversationId = state.id,
)
@@ -208,7 +209,7 @@ class ConversationLoop(
}
override fun events(after: Instant): Flow<ProtoEvent> =
events.flow
events.events(after)
override suspend fun getMessages(after: Instant, offset: Int, limit: Int): List<ProtoMessage> =
messageStore.list(conversationId = id, after = after, offset = offset, limit = limit)
@@ -563,16 +564,8 @@ internal fun MessageRecord.toProto(): ProtoMessage = when (this) {
message = message,
code = code,
)
is MessageRecord.Summary -> ProtoMessage.AssistantMessage(
id = id,
date = createdAt,
content = listOf(ProtoContent.Text(body = text)),
)
is MessageRecord.System -> ProtoMessage.UserMessage(
id = id,
date = createdAt,
content = listOf(ProtoContent.Text(body = text)),
)
// MessageRecord sealed — все варианты покрыты выше (User/Assistant/ToolCall/ToolResult/Error).
// Summary/System из :working-memory НЕ попадают в audit log (:message-log-api).
}
private fun readTokenCount(liteConv: LiteConversation): Int? = try {
@@ -5,8 +5,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import mu.KotlinLogging
import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.MessageStore
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.toolsets.ToolsetDispatchPolicy
import pw.binom.litert.LiteToolCall
@@ -14,7 +14,7 @@ import pw.binom.agentik.skills.SkillCatalog
import pw.binom.agentik.skills.SkillFile
import pw.binom.agentik.standalone.llm.LlmBackend
import pw.binom.agentik.standalone.llm.LlmConfig
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.sqlite.SqliteStores
import pw.binom.litert.LiteContentPart
@@ -160,10 +160,10 @@ class ChatAgentTest {
// добавим сообщение, чтобы потом убедиться, что каскад сработал
storage.messageStore.append(
pw.binom.agentik.messageStore.MessageRecord.UserMessage(
pw.binom.agentik.messageLog.MessageRecord.UserMessage(
id = "m1",
conversationId = id,
content = listOf(pw.binom.agentik.messageStore.Content.Text("hi")),
content = listOf(pw.binom.agentik.messageLog.Content.Text("hi")),
createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000),
),
)
@@ -190,11 +190,11 @@ class ChatAgentTest {
// user message записан в audit + working memory
val msgs = storage.messageStore.listAll(conv.id)
assertEquals(2, msgs.size)
assertEquals("hi", (msgs[0] as pw.binom.agentik.messageStore.MessageRecord.UserMessage).content.let {
(it[0] as pw.binom.agentik.messageStore.Content.Text).body
assertEquals("hi", (msgs[0] as pw.binom.agentik.messageLog.MessageRecord.UserMessage).content.let {
(it[0] as pw.binom.agentik.messageLog.Content.Text).body
})
assertEquals("hello back", (msgs[1] as pw.binom.agentik.messageStore.MessageRecord.AssistantMessage).content.let {
(it[0] as pw.binom.agentik.messageStore.Content.Text).body
assertEquals("hello back", (msgs[1] as pw.binom.agentik.messageLog.MessageRecord.AssistantMessage).content.let {
(it[0] as pw.binom.agentik.messageLog.Content.Text).body
})
}
@@ -327,8 +327,8 @@ class ChatAgentTest {
val msgs = storage.messageStore.listAll(conv.id)
assertEquals(2, msgs.size)
assertIs<pw.binom.agentik.messageStore.MessageRecord.UserMessage>(msgs[0])
val err = assertIs<pw.binom.agentik.messageStore.MessageRecord.Error>(msgs[1])
assertIs<pw.binom.agentik.messageLog.MessageRecord.UserMessage>(msgs[0])
val err = assertIs<pw.binom.agentik.messageLog.MessageRecord.Error>(msgs[1])
assertEquals("boom from llm", err.message)
// backfill через getMessages (polling/reconnect) тоже видит ошибку
@@ -372,7 +372,7 @@ class ChatAgentTest {
// audit: только user (assistant не успел сгенериться)
val msgs = storage.messageStore.listAll(conv.id)
assertEquals(1, msgs.size)
assertIs<pw.binom.agentik.messageStore.MessageRecord.UserMessage>(msgs[0])
assertIs<pw.binom.agentik.messageLog.MessageRecord.UserMessage>(msgs[0])
// working memory: только user (assistant skipped because пустой)
val wm = storage.workingMemoryStore.list(conv.id)
@@ -428,7 +428,7 @@ class ChatAgentTest {
// audit: user + toolcall + toolresult (tool выполнился), assistant может быть
val msgs = storage.messageStore.listAll(conv.id)
val toolResult = msgs.filterIsInstance<pw.binom.agentik.messageStore.MessageRecord.ToolResult>().firstOrNull()
val toolResult = msgs.filterIsInstance<pw.binom.agentik.messageLog.MessageRecord.ToolResult>().firstOrNull()
assertNotNull(toolResult, "tool result должен быть в audit — tool выполнился нормально")
val toolResultResult = toolResult!!.result!!
assertTrue(toolResultResult.contains("echo"), "tool result содержит реальный ответ тулы: $toolResultResult")
@@ -1,9 +1,9 @@
package pw.binom.agentik.standalone.agent
import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.messageStore.MessageOrigin.EVENT
import pw.binom.agentik.messageStore.MessageOrigin.SYSTEM
import pw.binom.agentik.messageStore.MessageOrigin.USER
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin.EVENT
import pw.binom.agentik.messageLog.MessageOrigin.SYSTEM
import pw.binom.agentik.messageLog.MessageOrigin.USER
import pw.binom.litert.LiteContentPart
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -1,9 +1,9 @@
package pw.binom.agentik.standalone.persistence
import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.messageStore.MessageOrigin
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlinx.coroutines.test.runTest
@@ -1,8 +1,8 @@
package pw.binom.agentik.standalone.persistence
import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlin.test.Test
@@ -1,13 +1,13 @@
package pw.binom.agentik.standalone.persistence
import pw.binom.agentik.messageStore.TurnTokens
import pw.binom.agentik.messageStore.MessageBodyPayload
import pw.binom.agentik.messageStore.encodeBodyPayload
import pw.binom.agentik.messageStore.decodeBodyPayload
import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.messageStore.MessageOrigin
import pw.binom.agentik.messageLog.TurnTokens
import pw.binom.agentik.messageLog.MessageBodyPayload
import pw.binom.agentik.messageLog.encodeBodyPayload
import pw.binom.agentik.messageLog.decodeBodyPayload
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlin.test.Test