refactor: migrate EventStore and MessageStore to :outbox-api and :journal-api
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:
2026-09-21 02:38:40 +03:00
parent 499db812ef
commit aef5083801
64 changed files with 978 additions and 960 deletions
+6 -4
View File
@@ -42,8 +42,9 @@ kotlin {
implementation(project(":proto"))
implementation(project(":server"))
implementation(project(":message-store-api"))
implementation(project(":message-log-api"))
implementation(project(":working-memory-api"))
implementation(project(":journal-api"))
implementation(project(":outbox-api"))
implementation(project(":context-api"))
// Commons
implementation(libs.kotlinx.coroutines.core)
@@ -67,13 +68,14 @@ kotlin {
implementation(project(":memory-vector"))
}
implementation(project(":message-store-api"))
implementation(project(":working-memory-api"))
implementation(project(":journal-api"))
implementation(project(":outbox-api"))
implementation(project(":context-api"))
implementation(project(":storage-ksqlite"))
// Новый единый канал событий агента — заменил старые
// `agentEvents: MutableSharedFlow<AgentEvent>` и per-conv `ConversationEvents._flow`.
// Bounded tail + auto-TTL, generic CommonEvent envelope.
implementation(project(":event-store"))
implementation(project(":event-store-in-memory"))
implementation(project(":agent-toolsets"))
// Generic LLM-side tools (LlmReflector, SkillMiner, LlmMemoryReviewer,
@@ -10,10 +10,10 @@ import pw.binom.a2a.model.Message
import pw.binom.a2a.model.Role
import pw.binom.a2a.model.TextPart
import pw.binom.a2a.server.AgentHandler
import pw.binom.agentik.outbox.Event
import pw.binom.agentik.proto.Agent
import pw.binom.agentik.proto.Content
import pw.binom.agentik.proto.Conversation
import pw.binom.agentik.proto.Event
import java.util.concurrent.ConcurrentHashMap
private val log = KotlinLogging.logger {}
@@ -16,7 +16,7 @@ import pw.binom.agentik.llm.tools.LlmReflector
import pw.binom.agentik.llm.tools.SkillMiner
import pw.binom.agentik.standalone.agent.memory.Curator
import pw.binom.agentik.skills.SkillStore
import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.context.ContextStore
/**
* Debug-эндпоинты для ручного триггерирования фоновых фич (без ожидания
@@ -37,7 +37,7 @@ import pw.binom.agentik.workingMemory.WorkingMemoryStore
*/
internal fun Route.debugRoutes(
agent: Agent,
workingMemoryStore: WorkingMemoryStore,
workingMemoryStore: ContextStore,
reflectionStore: ReflectionStore,
reflector: LlmReflector?,
skillMiner: SkillMiner?,
@@ -118,14 +118,14 @@ internal fun Route.debugRoutes(
* (для debug-триггеров reflector/miner; та же логика, что у хуков
* [ChatConversation]).
*/
internal suspend fun recentTurns(workingMemoryStore: WorkingMemoryStore, conversationId: String, maxTurns: Int): List<ConversationTurn> {
internal suspend fun recentTurns(workingMemoryStore: ContextStore, conversationId: String, maxTurns: Int): List<ConversationTurn> {
val rows = workingMemoryStore.list(conversationId)
val pairs = mutableListOf<ConversationTurn>()
var pendingUser: String? = null
for (row in rows) {
when (val e = row.entry) {
is pw.binom.agentik.workingMemory.WorkingMemoryEntry.User -> pendingUser = e.content.text()
is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Assistant -> {
is pw.binom.agentik.context.WorkingMemoryEntry.User -> pendingUser = e.content.text()
is pw.binom.agentik.context.WorkingMemoryEntry.Assistant -> {
val user = pendingUser ?: ""
pendingUser = null
pairs += ConversationTurn(userMessage = user, assistantMessage = e.content.text())
@@ -137,5 +137,5 @@ internal suspend fun recentTurns(workingMemoryStore: WorkingMemoryStore, convers
}
/** Текстовое содержимое записей working memory (Text-контент, без картинок). */
internal fun List<pw.binom.agentik.messageLog.Content>.text(): String =
filterIsInstance<pw.binom.agentik.messageLog.Content.Text>().joinToString("\n") { it.body }
internal fun List<pw.binom.agentik.journal.Content>.text(): String =
filterIsInstance<pw.binom.agentik.journal.Content.Text>().joinToString("\n") { it.body }
@@ -12,10 +12,10 @@ 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.messageLog.Content
import pw.binom.agentik.journal.Content
import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.context.WorkingMemoryEntry
import pw.binom.agentik.context.ContextStore
import pw.binom.agentik.llm.tools.LlmReflector
import pw.binom.agentik.llm.tools.SkillMiner
import java.util.concurrent.atomic.AtomicLong
@@ -46,7 +46,7 @@ internal data class BackgroundConfig(
internal class BackgroundScheduler(
private val state: ConversationState,
private val workingMemory: WorkingMemoryStore,
private val workingMemory: ContextStore,
private val config: BackgroundConfig,
private val backgroundEvents: BackgroundEventBus,
) {
@@ -16,9 +16,11 @@ import pw.binom.agentik.memory.MemoryPrefetcher
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.outbox.AgentEvent
import pw.binom.agentik.outbox.CommonEvent
import pw.binom.agentik.outbox.MutableOutboxStore
import pw.binom.agentik.journal.JournalStore
import pw.binom.agentik.outbox.OutboxStore
import pw.binom.agentik.proto.Conversation as ProtoConversation
import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.skills.SkillCatalog
@@ -30,8 +32,8 @@ import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.messageStore.Ids
import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.messageLog.MutableMessageStore
import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.journal.MutableJournalStore
import pw.binom.agentik.context.ContextStore
import pw.binom.agentik.toolsets.DisableToolsetTool
import pw.binom.agentik.toolsets.EnableToolsetTool
import pw.binom.agentik.toolsets.NamedTool
@@ -64,8 +66,8 @@ import pw.binom.litert.LiteLlm
class ChatAgent(
override val id: String,
private val conversationStore: ConversationStore,
private val messageStore: MutableMessageStore,
private val workingMemoryStore: WorkingMemoryStore,
private val messageStore: MutableJournalStore,
private val workingMemoryStore: ContextStore,
private val reflectionStore: ReflectionStore,
private val llm: LiteLlm,
private val llmConfig: LlmConfig,
@@ -205,42 +207,46 @@ class ChatAgent(
* - `agentEvents: MutableSharedFlow<AgentEvent>` (agent lifecycle)
* - per-conv `ConversationEvents._flow: MutableSharedFlow<ProtoEvent>`
*
* Теперь оба пишут сюда через [MutableEventStore.append], а consumer'ы
* Теперь оба пишут сюда через [MutableOutboxStore.append], а consumer'ы
* читают через [EventStore.events]/[conversationEvents]/[agentEvents].
*
* **Live tail + auto-TTL** — клиенты больше не должны заботиться о persistence
* или подписке на два отдельных канала.
*/
private val eventStore: MutableEventStore = pw.binom.agentik.eventStore.inmemory.InMemoryEventStore(
private val eventStore: MutableOutboxStore = pw.binom.agentik.eventStore.inmemory.InMemoryEventStore(
maxMessages = null,
ttl = null,
)
/**
* Read-only view of [messageStore] для HTTP-фасада в `:server`
* (`Route.agentikAgent` → `/journal/...` endpoint'ы).
*
* [MutableJournalStore] == [MutableJournalStore] (typealias), поэтому
* narrowing с мутабельного writer'а на read-only view тривиальна.
* Используется [Agent.journal] override'ом из `:proto`.
*/
override val journal: JournalStore
get() = messageStore
/**
* Read-only view of [eventStore] для HTTP-фасада в `:server`
* (`Route.agentikAgent` → `/outbox/...` endpoint'ы).
*
* [MutableOutboxStore] == [MutableOutboxStore] (typealias), narrowing тривиальна.
* Используется [Agent.outbox] override'ом из `:proto`.
*/
override val outbox: OutboxStore
get() = eventStore
/** Защищает карту живых диалогов. */
private val liveLock = Mutex()
private val live: MutableMap<String, ChatConversation> = HashMap()
/** Live-подписка на события уровня агента (создание/удаление/переименование). */
override fun events(after: Instant): Flow<AgentEvent> =
eventStore.agentEvents(after).map { it.event }
/**
* Все события в одном потоке: agent lifecycle + events всех диалогов.
*
* Snapshot живых диалогов берётся на момент подписки. Новые Created-Event'ы
* НЕ переподписывают — это ответственность caller'а (см. KDoc в :event-store
* про reconnect pattern + gap detection через [eventStore.earliestEventDate]).
*/
override fun allEvents(after: Instant): Flow<CommonEvent> = flow {
emitAll(eventStore.agentEvents(after))
live.values
.asSequence()
.filterNot { it.isClosed }
.forEach { conv: ProtoConversation ->
val cid: String = conv.id
emitAll(eventStore.conversationEvents(after, cid).map { it as CommonEvent.Conversation })
}
}
// events()/allEvents() больше НЕ override'ятся — эти методы удалены
// из :proto.Agent после миграции событий в outbox-сущность.
// Live-подписки теперь идут через Agent.outbox.events() / .agentEvents()
// (см. README :proto для контракта catchup+live).
override fun createConversation(temp: Boolean): ProtoConversation {
val now = now()
@@ -6,10 +6,10 @@ 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.messageLog.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.workingMemory.WorkingMemoryRow
import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.journal.Content
import pw.binom.agentik.context.WorkingMemoryEntry
import pw.binom.agentik.context.WorkingMemoryRow
import pw.binom.agentik.context.ContextStore
import pw.binom.litert.LiteContentPart
import pw.binom.litert.LiteConversation
import pw.binom.litert.LiteConversationConfig
@@ -26,7 +26,7 @@ internal class CompactionCoordinator(
private val contextCompactor: ContextCompactor?,
private val memoryReviewer: MemoryReviewer?,
private val memoryStoreForReview: MemoryStore?,
private val workingMemory: WorkingMemoryStore,
private val workingMemory: ContextStore,
private val liteLlm: LiteLlm,
private val systemPrompt: String,
private val backgroundEvents: BackgroundEventBus,
@@ -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.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
import pw.binom.agentik.journal.MessageContext
import pw.binom.agentik.journal.MessageOrigin
internal class ContextBuilder(
private val memoryPrefetcher: MemoryPrefetcher?,
@@ -3,12 +3,12 @@ package pw.binom.agentik.standalone.agent
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.outbox.MutableOutboxStore
import pw.binom.agentik.outbox.CommonEvent
import pw.binom.agentik.proto.Event as ProtoEvent
internal class ConversationEvents(
private val globalEventStore: MutableEventStore,
private val globalEventStore: MutableOutboxStore,
private val conversationId: String,
) {
fun tryEmit(event: ProtoEvent): Boolean {
@@ -24,21 +24,21 @@ import pw.binom.agentik.memory.MemoryReviewer
import pw.binom.agentik.memory.MemoryStore
import pw.binom.agentik.proto.Content as ProtoContent
import pw.binom.agentik.proto.Conversation as ProtoConversation
import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.outbox.Event as ProtoEvent
import pw.binom.agentik.messageStore.ReflectionStore
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.messageLog.Content
import pw.binom.agentik.journal.Content
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.MutableMessageStore
import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.messageLog.TurnTokens
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.journal.MessageContext
import pw.binom.agentik.journal.MessageOrigin
import pw.binom.agentik.journal.MessageRecord
import pw.binom.agentik.journal.MutableJournalStore as MutableJournalStore
import pw.binom.agentik.journal.TurnTokens
import pw.binom.agentik.context.WorkingMemoryEntry
import pw.binom.agentik.context.ContextStore
import pw.binom.agentik.toolsets.ToolsetDispatchPolicy
import pw.binom.litert.LiteContentPart
import pw.binom.litert.LiteConversation
@@ -55,10 +55,10 @@ import pw.binom.agentik.toolsets.NamedTool
class ConversationLoop(
record: ConversationRecord,
private val conversationStore: ConversationStore,
private val messageStore: MutableMessageStore,
private val workingMemoryStore: WorkingMemoryStore,
private val messageStore: MutableJournalStore,
private val workingMemoryStore: ContextStore,
private val reflectionStore: ReflectionStore?,
private val eventStore: pw.binom.agentik.eventStore.MutableEventStore,
private val eventStore: pw.binom.agentik.outbox.MutableOutboxStore,
private val llm: LiteLlm,
private val systemPrompt: String,
private val tools: List<NamedTool> = emptyList(),
@@ -4,10 +4,10 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import mu.KotlinLogging
import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.MutableMessageStore
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.outbox.Event as ProtoEvent
import pw.binom.agentik.journal.MessageRecord
import pw.binom.agentik.journal.MutableJournalStore as MutableJournalStore
import pw.binom.agentik.context.WorkingMemoryEntry
import pw.binom.agentik.toolsets.ToolsetDispatchPolicy
import pw.binom.litert.LiteToolCall
import pw.binom.litert.LiteTool
@@ -16,7 +16,7 @@ import pw.binom.agentik.toolsets.NamedTool
internal class ToolDispatcher(
private val state: ConversationState,
private val messageStore: MutableMessageStore,
private val messageStore: MutableJournalStore,
private val events: ConversationEvents,
private val backgroundEvents: BackgroundEventBus,
private val toolsByName: MutableMap<String, NamedTool>,
@@ -7,15 +7,15 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import pw.binom.agentik.proto.AgentEvent
import pw.binom.agentik.outbox.AgentEvent
import pw.binom.agentik.proto.Content
import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.outbox.Event as ProtoEvent
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.messageLog.MessageRecord
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.journal.MessageRecord
import pw.binom.agentik.context.WorkingMemoryEntry
import pw.binom.agentik.storage.ksqlite.KsqliteStores
import pw.binom.litert.LiteContentPart
import pw.binom.litert.LiteConversation
@@ -163,10 +163,10 @@ class ChatAgentTest {
// добавим сообщение, чтобы потом убедиться, что каскад сработал
sqliteStores.messages.append(
pw.binom.agentik.messageLog.MessageRecord.UserMessage(
pw.binom.agentik.journal.MessageRecord.UserMessage(
id = "m1",
conversationId = id,
content = listOf(pw.binom.agentik.messageLog.Content.Text("hi")),
content = listOf(pw.binom.agentik.journal.Content.Text("hi")),
createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000),
),
)
@@ -193,11 +193,11 @@ class ChatAgentTest {
// user message записан в audit + working memory
val msgs = sqliteStores.messages.listFlow(conv.id, Instant.DISTANT_PAST).toList()
assertEquals(2, msgs.size)
assertEquals("hi", (msgs[0] as pw.binom.agentik.messageLog.MessageRecord.UserMessage).content.let {
(it[0] as pw.binom.agentik.messageLog.Content.Text).body
assertEquals("hi", (msgs[0] as pw.binom.agentik.journal.MessageRecord.UserMessage).content.let {
(it[0] as pw.binom.agentik.journal.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
assertEquals("hello back", (msgs[1] as pw.binom.agentik.journal.MessageRecord.AssistantMessage).content.let {
(it[0] as pw.binom.agentik.journal.Content.Text).body
})
}
@@ -330,8 +330,8 @@ class ChatAgentTest {
val msgs = sqliteStores.messages.listFlow(conv.id, Instant.DISTANT_PAST).toList()
assertEquals(2, msgs.size)
assertIs<pw.binom.agentik.messageLog.MessageRecord.UserMessage>(msgs[0])
val err = assertIs<pw.binom.agentik.messageLog.MessageRecord.Error>(msgs[1])
assertIs<pw.binom.agentik.journal.MessageRecord.UserMessage>(msgs[0])
val err = assertIs<pw.binom.agentik.journal.MessageRecord.Error>(msgs[1])
assertEquals("boom from llm", err.message)
// backfill через getMessages (polling/reconnect) тоже видит ошибку
@@ -375,7 +375,7 @@ class ChatAgentTest {
// audit: только user (assistant не успел сгенериться)
val msgs = sqliteStores.messages.listFlow(conv.id, Instant.DISTANT_PAST).toList()
assertEquals(1, msgs.size)
assertIs<pw.binom.agentik.messageLog.MessageRecord.UserMessage>(msgs[0])
assertIs<pw.binom.agentik.journal.MessageRecord.UserMessage>(msgs[0])
// working memory: только user (assistant skipped because пустой)
val wm = sqliteStores.workingMemory.list(conv.id)
@@ -431,7 +431,7 @@ class ChatAgentTest {
// audit: user + toolcall + toolresult (tool выполнился), assistant может быть
val msgs = sqliteStores.messages.listFlow(conv.id, Instant.DISTANT_PAST).toList()
val toolResult = msgs.filterIsInstance<pw.binom.agentik.messageLog.MessageRecord.ToolResult>().firstOrNull()
val toolResult = msgs.filterIsInstance<pw.binom.agentik.journal.MessageRecord.ToolResult>().firstOrNull()
assertNotNull(toolResult, "tool result должен быть в audit — tool выполнился нормально")
val toolResultResult = toolResult!!.result!!
assertTrue(toolResultResult.contains("echo"), "tool result содержит реальный ответ тулы: $toolResultResult")
@@ -574,7 +574,10 @@ class ChatAgentTest {
val agent = newAgent()
val events = mutableListOf<AgentEvent>()
val job = launch(start = kotlinx.coroutines.CoroutineStart.UNDISPATCHED) {
agent.events(Instant.DISTANT_PAST).collect { events.add(it) }
// Agent.events() удалён из :proto — события живут в
// agent.outbox.agentEvents(): Flow<CommonEvent.Agent>;
// распаковываем .event для получения AgentEvent.
agent.outbox.agentEvents(Instant.DISTANT_PAST).collect { events.add(it.event) }
}
val conv = agent.createConversation(temp = false)
agent.deleteConversation(conv.id)
@@ -92,7 +92,7 @@ class CompactionTest {
}
val wm = sqliteStores.workingMemory.list(conv.id)
// Без compaction все ходы остаются в памяти (System + 10 user/assistant = 21 строк).
val summaries = wm.filter { it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary }
val summaries = wm.filter { it.entry is pw.binom.agentik.context.WorkingMemoryEntry.Summary }
assertEquals(0, summaries.size, "compaction must not run without contextWindow")
}
@@ -104,7 +104,7 @@ class CompactionTest {
conv.send(listOf(ProtoContent.Text("first")))
val wm = sqliteStores.workingMemory.list(conv.id)
// System + User + Assistant = 3. Без compactor — никаких Summary.
val summaries = wm.filter { it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary }
val summaries = wm.filter { it.entry is pw.binom.agentik.context.WorkingMemoryEntry.Summary }
assertEquals(0, summaries.size, "no compaction runs without compactor")
}
@@ -122,10 +122,10 @@ class CompactionTest {
assertTrue(compactor.calls > 0, "compactor must be called at least once when above threshold")
// В working memory должна появиться Summary.
val wm = sqliteStores.workingMemory.list(conv.id)
val summaries = wm.filter { it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary }
val summaries = wm.filter { it.entry is pw.binom.agentik.context.WorkingMemoryEntry.Summary }
assertTrue(summaries.isNotEmpty(), "at least one Summary entry should be present after compaction")
// Summary-текст — то, что вернул наш compactor.
val summaryText = (summaries.first().entry as pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary).text
val summaryText = (summaries.first().entry as pw.binom.agentik.context.WorkingMemoryEntry.Summary).text
assertTrue(summaryText.startsWith("**Goal**"), "summary text should come from compactor: $summaryText")
}
@@ -165,8 +165,8 @@ class CompactionTest {
// Должны быть: System + хотя бы один Summary + последние KEEP_RECENT_TURNS ходов.
// KEEP_RECENT_TURNS = 4 → user/assistant последних двух ходов (third + second) могут быть не тронуты.
val userAssistantCount = wm.count {
it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.User ||
it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Assistant
it.entry is pw.binom.agentik.context.WorkingMemoryEntry.User ||
it.entry is pw.binom.agentik.context.WorkingMemoryEntry.Assistant
}
// Минимум 1 ход остаётся (KEEP_RECENT_TURNS).
assertTrue(userAssistantCount >= 1, "at least one recent turn must be preserved")
@@ -1,9 +1,9 @@
package pw.binom.agentik.standalone.agent
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.agentik.journal.MessageContext
import pw.binom.agentik.journal.MessageOrigin.EVENT
import pw.binom.agentik.journal.MessageOrigin.SYSTEM
import pw.binom.agentik.journal.MessageOrigin.USER
import pw.binom.litert.LiteContentPart
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -1,10 +1,10 @@
package pw.binom.agentik.standalone.persistence
import pw.binom.agentik.messageLog.MessageContext
import pw.binom.agentik.messageLog.MessageOrigin
import pw.binom.agentik.journal.MessageContext
import pw.binom.agentik.journal.MessageOrigin
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageLog.MessageRecord
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.journal.MessageRecord
import pw.binom.agentik.journal.Content
import pw.binom.agentik.context.WorkingMemoryEntry
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest