refactor(storage): split :storage-core into message-store-api + working-memory-api
ci / JVM build + tests (push) Failing after 2m5s

Разделяет монолитный :storage-core на 3 модуля с чёткими границами:

  :message-store-api   — MessageStore, ReflectionStore, EventStore, ConversationStore +
                          Content, Payload, MessageContext, Ids, MessageEvent
                          (audit log + event stream)
  :working-memory-api  — WorkingMemoryStore + WorkingMemoryEntry
                          (runtime context с compaction)
  :storage-bundle      — StorageBundle агрегатор, зависит от обоих
                          (только для server-side runtime)

Пакеты:
  pw.binom.agentik.storage.*  → УДАЛЕНО
  pw.binom.agentik.messageStore.*        — append-only API
  pw.binom.agentik.messageStore.events.* — EventStore + EventRecord
  pw.binom.agentik.workingMemory.*      — WM API
  pw.binom.agentik.storageBundle.*       — aggregator

Зачем:
  - Тонкий клиент может подтянуть ТОЛЬКО :message-store-api (~15KB, нет
    compaction-логики, нет MessageStore+WorkingMemoryStore cross-deps).
  - Android-agent в будущем подключит :message-store-api для audit log,
    серверный runtime — :storage-bundle со всем.
  - Компиляционные границы защищают от случайной зависимости от WM
    в read-only клиентах (раньше один :storage-core не давал такой
    гарантии).

Миграция:
  - Имплементации (:storage-inmemory, :storage-sqlite, :storage-ksqlite)
    обновили package + добавили deps на оба API модуля + :storage-bundle.
  - Тесты из :storage-core (PersistenceTest, SqliteStoresMigrationTest,
    TokenStatsTest) переехали в :standalone, получили testImplementation
    на оба API модуля и импорты новых типов.
  - 52 файла в :standalone, :agent-toolsets, :llm-tools, :server, :client,
    :agentik-cli обновили FQN.
  - :storage-core удалён.

Совместимость схем не меняется — все 5 impl'ов (3 backend × 5 store) хранят
данные в тех же таблицах, миграция между Sqlite и Ksqlite возможна через SQL dump.

Тесты:
  standalone         178 ✅
  agent-toolsets      36 ✅
  storage-inmemory    47 ✅
  storage-sqlite      17 ✅  (включая переехавшие persistence/* + tokenStats)
  storage-ksqlite     36 ✅
  ---
  Total: 314 tests, 0 failures
This commit is contained in:
2026-09-20 15:02:54 +03:00
parent dd7aec8df1
commit 2d9ad526bb
82 changed files with 341 additions and 403 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
// :storage-core — для StorageBundle в ToolsetContext (commit 5+) api(project(":storage-bundle"))
api(project(":storage-core")) // :storage-bundle transitively подтягивает :message-store-api и :working-memory-api.
// litert-kmp: LiteTool интерфейс (sync describe/invoke) // litert-kmp: LiteTool интерфейс (sync describe/invoke)
api(libs.litert.api) api(libs.litert.api)
@@ -136,7 +136,7 @@ internal class AgentClient(
/** /**
* Запись event'а, которую возвращает /events/replay endpoint. * Запись event'а, которую возвращает /events/replay endpoint.
* *
* Это **мини-зеркало** `pw.binom.agentik.storage.events.EventRecord` — клиент * Это **мини-зеркало** `pw.binom.agentik.messageStore.events.EventRecord` — клиент
* не зависит от `:storage-core`, поэтому определяет свою модель (wire-only). * не зависит от `:storage-core`, поэтому определяет свою модель (wire-only).
* *
* Формат полностью совместим с сервером — `agentikJson.encodeToString(...)` там * Формат полностью совместим с сервером — `agentikJson.encodeToString(...)` там
+2 -1
View File
@@ -19,7 +19,8 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
api(project(":memory-api")) api(project(":memory-api"))
api(project(":storage-core")) api(project(":message-store-api"))
api(project(":working-memory-api"))
api(project(":skills")) api(project(":skills"))
api(libs.litert.api) api(libs.litert.api)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
@@ -10,7 +10,7 @@ import pw.binom.agentik.memory.MemoryStore
import pw.binom.agentik.memory.MemoryStoreEvent import pw.binom.agentik.memory.MemoryStoreEvent
import pw.binom.agentik.memory.NewMemoryNote import pw.binom.agentik.memory.NewMemoryNote
import pw.binom.agentik.memory.ReviewedTurn import pw.binom.agentik.memory.ReviewedTurn
import pw.binom.agentik.storage.Ids import pw.binom.agentik.messageStore.Ids
import pw.binom.litert.LiteLlm import pw.binom.litert.LiteLlm
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Instant import kotlin.time.Instant
@@ -5,8 +5,8 @@ import kotlinx.coroutines.withContext
import pw.binom.agentik.memory.ConversationTurn import pw.binom.agentik.memory.ConversationTurn
import pw.binom.litert.LiteConversationConfig import pw.binom.litert.LiteConversationConfig
import pw.binom.litert.LiteLlm import pw.binom.litert.LiteLlm
import pw.binom.agentik.storage.Ids import pw.binom.agentik.messageStore.Ids
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import kotlin.time.Clock import kotlin.time.Clock
/** /**
+33
View File
@@ -0,0 +1,33 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
}
// Public API для append-only storage агента (audit log, replay-after-disconnect,
// conversation metadata). Этот модуль НЕ содержит working memory — он для
// тонких клиентов, которые хотят только события/сообщения без тяжёлой
// runtime-логики.
//
// Зависимости:
// - kotlinx.coroutines / serialization (KMP).
// - БЕЗ :storage-core — мы его полностью выпиливаем, интерфейсы переезжают сюда.
kotlin {
jvmToolchain(21)
jvm()
linuxX64()
mingwX64()
sourceSets {
commonMain.dependencies {
api(libs.kotlinx.coroutines.core)
api(libs.kotlinx.serialization.core)
api(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
}
}
}
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlin.time.Instant import kotlin.time.Instant
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlin.time.Instant import kotlin.time.Instant
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlin.time.Instant import kotlin.time.Instant
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.messageStore
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlin.time.Instant import kotlin.time.Instant
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage.events package pw.binom.agentik.messageStore.events
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlin.time.Instant import kotlin.time.Instant
+2 -1
View File
@@ -23,7 +23,8 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation(project(":proto")) implementation(project(":proto"))
implementation(project(":storage-core")) implementation(project(":message-store-api"))
implementation(project(":working-memory-api"))
// Ktor (без engine — engine подключает потребитель, см. :standalone). // Ktor (без engine — engine подключает потребитель, см. :standalone).
implementation(libs.ktor.server.core) implementation(libs.ktor.server.core)
@@ -6,7 +6,7 @@ import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import io.ktor.server.routing.route import io.ktor.server.routing.route
import pw.binom.agentik.proto.Agent import pw.binom.agentik.proto.Agent
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
/** /**
* Встраивает HTTP/SSE-фасад протокола agentik в твой Ktor-роутинг. * Встраивает HTTP/SSE-фасад протокола agentik в твой Ktor-роутинг.
@@ -25,7 +25,7 @@ import pw.binom.agentik.proto.AllEvent
import pw.binom.agentik.proto.Conversation import pw.binom.agentik.proto.Conversation
import pw.binom.agentik.proto.Content import pw.binom.agentik.proto.Content
import pw.binom.agentik.proto.Event import pw.binom.agentik.proto.Event
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import kotlin.time.Instant import kotlin.time.Instant
internal fun Route.agentikRoutes(agent: Agent, eventStore: EventStore? = null) { internal fun Route.agentikRoutes(agent: Agent, eventStore: EventStore? = null) {
+5 -3
View File
@@ -63,12 +63,14 @@ include(":memory-vector")
// Интерфейсы хранилища и разговорной истории: MessageStore / WorkingMemoryStore / // Интерфейсы хранилища и разговорной истории: MessageStore / WorkingMemoryStore /
// ConversationStore / ReflectionStore + StorageBundle агрегатор. Реализации — // ConversationStore / ReflectionStore + StorageBundle агрегатор. Реализации —
// в :storage-inmemory / :storage-sqlite / :storage-android (последний deferred). // в :storage-inmemory / :storage-sqlite / :storage-android (последний deferred).
include(":storage-core") // In-memory реализация store'ов из :message-store-api и :working-memory-api. KMP,
// In-memory реализация всех 4 store'ов из :storage-core. KMP, без платформенных
// IO-зависимостей. Используется в тестах (быстрый setup, без JDBC) и будет // IO-зависимостей. Используется в тестах (быстрый setup, без JDBC) и будет
// использоваться в Android-сборке (JVector/SQLite не подходят для ART out-of-box). // использоваться в Android-сборке (JVector/SQLite не подходят для ART out-of-box).
include(":message-store-api")
include(":working-memory-api")
include(":storage-bundle")
include(":storage-inmemory") include(":storage-inmemory")
// SQLDelight-реализация всех 4 store'ов из :storage-core. JVM-only (SQLDelight // SQLDelight-реализация store'ов из :message-store-api и :working-memory-api. JVM-only
// native драйверов для KMP вне JVM пока не публикует). Содержит 4 .sq-файла + // native драйверов для KMP вне JVM пока не публикует). Содержит 4 .sq-файла +
// 5 классов: SqliteStores, SqliteConversationStore, SqliteMessageStore, // 5 классов: SqliteStores, SqliteConversationStore, SqliteMessageStore,
// SqliteWorkingMemoryStore, SqliteReflectionStore. Бэкенд для прод-запуска // SqliteWorkingMemoryStore, SqliteReflectionStore. Бэкенд для прод-запуска
+3 -1
View File
@@ -32,6 +32,7 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
api(project(":storage-bundle"))
implementation(project(":proto")) implementation(project(":proto"))
implementation(project(":server")) implementation(project(":server"))
@@ -56,7 +57,8 @@ kotlin {
if (!skipVectorMemory) { if (!skipVectorMemory) {
implementation(project(":memory-vector")) implementation(project(":memory-vector"))
} }
implementation(project(":storage-core")) implementation(project(":message-store-api"))
implementation(project(":working-memory-api"))
implementation(project(":storage-sqlite")) implementation(project(":storage-sqlite"))
implementation(project(":agent-toolsets")) implementation(project(":agent-toolsets"))
// Generic LLM-side tools (LlmReflector, SkillMiner, LlmMemoryReviewer, // Generic LLM-side tools (LlmReflector, SkillMiner, LlmMemoryReviewer,
@@ -14,7 +14,7 @@ import pw.binom.agentik.standalone.agent.ChatConversation
import pw.binom.agentik.llm.tools.LlmReflector import pw.binom.agentik.llm.tools.LlmReflector
import pw.binom.agentik.llm.tools.SkillMiner import pw.binom.agentik.llm.tools.SkillMiner
import pw.binom.agentik.standalone.agent.memory.Curator import pw.binom.agentik.standalone.agent.memory.Curator
import pw.binom.agentik.storage.StorageBundle import pw.binom.agentik.storageBundle.StorageBundle
import pw.binom.agentik.skills.SkillStore import pw.binom.agentik.skills.SkillStore
/** /**
@@ -33,7 +33,7 @@ import pw.binom.agentik.skills.SkillStore
*/ */
internal fun Route.debugRoutes( internal fun Route.debugRoutes(
agent: Agent, agent: Agent,
storage: pw.binom.agentik.storage.StorageBundle, storage: pw.binom.agentik.storageBundle.StorageBundle,
reflector: LlmReflector?, reflector: LlmReflector?,
skillMiner: SkillMiner?, skillMiner: SkillMiner?,
skillStore: SkillStore?, skillStore: SkillStore?,
@@ -127,14 +127,14 @@ internal fun Route.debugRoutes(
* (для debug-триггеров reflector/miner; та же логика, что у хуков * (для debug-триггеров reflector/miner; та же логика, что у хуков
* [ChatConversation]). * [ChatConversation]).
*/ */
internal suspend fun recentTurns(storage: pw.binom.agentik.storage.StorageBundle, conversationId: String, maxTurns: Int): List<ConversationTurn> { internal suspend fun recentTurns(storage: pw.binom.agentik.storageBundle.StorageBundle, conversationId: String, maxTurns: Int): List<ConversationTurn> {
val rows = storage.workingMemoryStore.list(conversationId) val rows = storage.workingMemoryStore.list(conversationId)
val pairs = mutableListOf<ConversationTurn>() val pairs = mutableListOf<ConversationTurn>()
var pendingUser: String? = null var pendingUser: String? = null
for (row in rows) { for (row in rows) {
when (val e = row.entry) { when (val e = row.entry) {
is pw.binom.agentik.storage.WorkingMemoryEntry.User -> pendingUser = e.content.text() is pw.binom.agentik.workingMemory.WorkingMemoryEntry.User -> pendingUser = e.content.text()
is pw.binom.agentik.storage.WorkingMemoryEntry.Assistant -> { is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Assistant -> {
val user = pendingUser ?: "" val user = pendingUser ?: ""
pendingUser = null pendingUser = null
pairs += ConversationTurn(userMessage = user, assistantMessage = e.content.text()) pairs += ConversationTurn(userMessage = user, assistantMessage = e.content.text())
@@ -146,5 +146,5 @@ internal suspend fun recentTurns(storage: pw.binom.agentik.storage.StorageBundle
} }
/** Текстовое содержимое записей working memory (Text-контент, без картинок). */ /** Текстовое содержимое записей working memory (Text-контент, без картинок). */
internal fun List<pw.binom.agentik.storage.Content>.text(): String = internal fun List<pw.binom.agentik.messageStore.Content>.text(): String =
filterIsInstance<pw.binom.agentik.storage.Content.Text>().joinToString("\n") { it.body } filterIsInstance<pw.binom.agentik.messageStore.Content.Text>().joinToString("\n") { it.body }
@@ -317,7 +317,7 @@ private fun runServer() {
// и interval > 0. Загружаем top-K последних рефлексий из SQLite в system prompt. // и interval > 0. Загружаем top-K последних рефлексий из SQLite в system prompt.
val reflector: pw.binom.agentik.llm.tools.LlmReflector? = val reflector: pw.binom.agentik.llm.tools.LlmReflector? =
if (config.reflection.interval > 0) LlmReflector(llm = llm) else null if (config.reflection.interval > 0) LlmReflector(llm = llm) else null
val recentReflections: List<pw.binom.agentik.storage.Reflection> = val recentReflections: List<pw.binom.agentik.messageStore.Reflection> =
if (config.reflection.topK > 0) kotlinx.coroutines.runBlocking { if (config.reflection.topK > 0) kotlinx.coroutines.runBlocking {
storage.reflectionStore.listRecent(config.reflection.topK) storage.reflectionStore.listRecent(config.reflection.topK)
} else emptyList() } else emptyList()
@@ -12,10 +12,10 @@ import pw.binom.agentik.memory.ConversationTurn
import pw.binom.agentik.memory.MemoryReviewer import pw.binom.agentik.memory.MemoryReviewer
import pw.binom.agentik.memory.MemoryStore import pw.binom.agentik.memory.MemoryStore
import pw.binom.agentik.skills.SkillStore import pw.binom.agentik.skills.SkillStore
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.llm.tools.LlmReflector import pw.binom.agentik.llm.tools.LlmReflector
import pw.binom.agentik.llm.tools.SkillMiner import pw.binom.agentik.llm.tools.SkillMiner
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@@ -30,13 +30,13 @@ import pw.binom.agentik.skills.SkillCatalog
import pw.binom.agentik.skills.renderSystemPromptSection import pw.binom.agentik.skills.renderSystemPromptSection
import pw.binom.agentik.standalone.agent.memory.MemoryToolsFactory import pw.binom.agentik.standalone.agent.memory.MemoryToolsFactory
import pw.binom.agentik.standalone.llm.LlmConfig import pw.binom.agentik.standalone.llm.LlmConfig
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.storage.Ids import pw.binom.agentik.messageStore.Ids
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.storage.StorageBundle import pw.binom.agentik.storageBundle.StorageBundle
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import pw.binom.agentik.toolsets.DisableToolsetTool import pw.binom.agentik.toolsets.DisableToolsetTool
import pw.binom.agentik.toolsets.EnableToolsetTool import pw.binom.agentik.toolsets.EnableToolsetTool
import pw.binom.agentik.toolsets.NamedTool import pw.binom.agentik.toolsets.NamedTool
@@ -68,7 +68,7 @@ import pw.binom.litert.LiteLlm
*/ */
class ChatAgent( class ChatAgent(
override val id: String, override val id: String,
private val storage: pw.binom.agentik.storage.StorageBundle, private val storage: pw.binom.agentik.storageBundle.StorageBundle,
private val llm: LiteLlm, private val llm: LiteLlm,
private val llmConfig: LlmConfig, private val llmConfig: LlmConfig,
private val tools: List<NamedTool> = emptyList(), private val tools: List<NamedTool> = emptyList(),
@@ -296,7 +296,7 @@ class ChatAgent(
override fun createConversation(temp: Boolean): ProtoConversation { override fun createConversation(temp: Boolean): ProtoConversation {
val now = now() val now = now()
val id = pw.binom.agentik.storage.Ids.new("conv") val id = pw.binom.agentik.messageStore.Ids.new("conv")
val rec = ConversationRecord( val rec = ConversationRecord(
id = id, id = id,
title = null, title = null,
@@ -6,10 +6,10 @@ import pw.binom.agentik.memory.ConversationTurn
import pw.binom.agentik.memory.MemoryReviewer import pw.binom.agentik.memory.MemoryReviewer
import pw.binom.agentik.memory.MemoryStore import pw.binom.agentik.memory.MemoryStore
import pw.binom.agentik.standalone.agent.memory.materializeReviewNote import pw.binom.agentik.standalone.agent.memory.materializeReviewNote
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.WorkingMemoryRow import pw.binom.agentik.workingMemory.WorkingMemoryRow
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.litert.LiteContentPart import pw.binom.litert.LiteContentPart
import pw.binom.litert.LiteConversation import pw.binom.litert.LiteConversation
import pw.binom.litert.LiteConversationConfig import pw.binom.litert.LiteConversationConfig
@@ -3,8 +3,8 @@ package pw.binom.agentik.standalone.agent
import mu.KotlinLogging import mu.KotlinLogging
import pw.binom.agentik.memory.MemoryPrefetcher import pw.binom.agentik.memory.MemoryPrefetcher
import pw.binom.litert.LiteContentPart import pw.binom.litert.LiteContentPart
import pw.binom.agentik.storage.MessageContext import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.storage.MessageOrigin import pw.binom.agentik.messageStore.MessageOrigin
internal class ContextBuilder( internal class ContextBuilder(
private val memoryPrefetcher: MemoryPrefetcher?, private val memoryPrefetcher: MemoryPrefetcher?,
@@ -8,10 +8,10 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import mu.KotlinLogging import mu.KotlinLogging
import pw.binom.agentik.proto.Event as ProtoEvent import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.storage.Ids import pw.binom.agentik.messageStore.Ids
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
private val log = KotlinLogging.logger {} private val log = KotlinLogging.logger {}
@@ -28,18 +28,18 @@ import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.proto.Message as ProtoMessage import pw.binom.agentik.proto.Message as ProtoMessage
import pw.binom.agentik.proto.MessageContext as ProtoMessageContext import pw.binom.agentik.proto.MessageContext as ProtoMessageContext
import pw.binom.agentik.skills.SkillStore import pw.binom.agentik.skills.SkillStore
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.storage.ConversationStore import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.storage.MessageContext import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.storage.MessageOrigin import pw.binom.agentik.messageStore.MessageOrigin
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.storage.StorageBundle import pw.binom.agentik.storageBundle.StorageBundle
import pw.binom.agentik.storage.TurnTokens import pw.binom.agentik.messageStore.TurnTokens
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.toolsets.ToolsetDispatchPolicy import pw.binom.agentik.toolsets.ToolsetDispatchPolicy
import pw.binom.litert.LiteContentPart import pw.binom.litert.LiteContentPart
import pw.binom.litert.LiteConversation import pw.binom.litert.LiteConversation
@@ -469,7 +469,7 @@ class ConversationLoop(
private fun now(): Instant = private fun now(): Instant =
Instant.fromEpochMilliseconds(System.currentTimeMillis()) Instant.fromEpochMilliseconds(System.currentTimeMillis())
private fun newId(prefix: String): String = pw.binom.agentik.storage.Ids.new(prefix) private fun newId(prefix: String): String = pw.binom.agentik.messageStore.Ids.new(prefix)
private fun encodeArgsJson(arguments: Map<String, Any?>): String { private fun encodeArgsJson(arguments: Map<String, Any?>): String {
val el = JsonElement.serializer() val el = JsonElement.serializer()
@@ -1,7 +1,7 @@
package pw.binom.agentik.standalone.agent package pw.binom.agentik.standalone.agent
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.litert.LiteConversation import pw.binom.litert.LiteConversation
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import pw.binom.agentik.toolsets.NamedTool import pw.binom.agentik.toolsets.NamedTool
@@ -5,9 +5,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import mu.KotlinLogging import mu.KotlinLogging
import pw.binom.agentik.proto.Event as ProtoEvent import pw.binom.agentik.proto.Event as ProtoEvent
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.toolsets.ToolsetDispatchPolicy import pw.binom.agentik.toolsets.ToolsetDispatchPolicy
import pw.binom.litert.LiteToolCall import pw.binom.litert.LiteToolCall
import pw.binom.litert.LiteTool import pw.binom.litert.LiteTool
@@ -5,7 +5,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
import kotlin.time.Instant import kotlin.time.Instant
import pw.binom.agentik.skills.SkillCatalog import pw.binom.agentik.skills.SkillCatalog
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
class ChatAgentReflectionTest { class ChatAgentReflectionTest {
@@ -14,8 +14,8 @@ import pw.binom.agentik.skills.SkillCatalog
import pw.binom.agentik.skills.SkillFile import pw.binom.agentik.skills.SkillFile
import pw.binom.agentik.standalone.llm.LlmBackend import pw.binom.agentik.standalone.llm.LlmBackend
import pw.binom.agentik.standalone.llm.LlmConfig import pw.binom.agentik.standalone.llm.LlmConfig
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.sqlite.SqliteStores import pw.binom.agentik.storage.sqlite.SqliteStores
import pw.binom.litert.LiteContentPart import pw.binom.litert.LiteContentPart
import pw.binom.litert.LiteConversation import pw.binom.litert.LiteConversation
@@ -41,7 +41,7 @@ import pw.binom.agentik.toolsets.NamedTool
class ChatAgentTest { class ChatAgentTest {
private lateinit var storage: pw.binom.agentik.storage.StorageBundle private lateinit var storage: pw.binom.agentik.storageBundle.StorageBundle
private lateinit var fakeLlm: FakeLiteLlm private lateinit var fakeLlm: FakeLiteLlm
@BeforeTest @BeforeTest
@@ -56,7 +56,7 @@ class ChatAgentTest {
} }
private fun newAgent( private fun newAgent(
storage: pw.binom.agentik.storage.StorageBundle = this.storage, storage: pw.binom.agentik.storageBundle.StorageBundle = this.storage,
llm: LiteLlm = this.fakeLlm, llm: LiteLlm = this.fakeLlm,
tools: List<NamedTool> = emptyList(), tools: List<NamedTool> = emptyList(),
skills: SkillCatalog = SkillCatalog.EMPTY, skills: SkillCatalog = SkillCatalog.EMPTY,
@@ -160,10 +160,10 @@ class ChatAgentTest {
// добавим сообщение, чтобы потом убедиться, что каскад сработал // добавим сообщение, чтобы потом убедиться, что каскад сработал
storage.messageStore.append( storage.messageStore.append(
pw.binom.agentik.storage.MessageRecord.UserMessage( pw.binom.agentik.messageStore.MessageRecord.UserMessage(
id = "m1", id = "m1",
conversationId = id, conversationId = id,
content = listOf(pw.binom.agentik.storage.Content.Text("hi")), content = listOf(pw.binom.agentik.messageStore.Content.Text("hi")),
createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000), createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000),
), ),
) )
@@ -190,11 +190,11 @@ class ChatAgentTest {
// user message записан в audit + working memory // user message записан в audit + working memory
val msgs = storage.messageStore.listAll(conv.id) val msgs = storage.messageStore.listAll(conv.id)
assertEquals(2, msgs.size) assertEquals(2, msgs.size)
assertEquals("hi", (msgs[0] as pw.binom.agentik.storage.MessageRecord.UserMessage).content.let { assertEquals("hi", (msgs[0] as pw.binom.agentik.messageStore.MessageRecord.UserMessage).content.let {
(it[0] as pw.binom.agentik.storage.Content.Text).body (it[0] as pw.binom.agentik.messageStore.Content.Text).body
}) })
assertEquals("hello back", (msgs[1] as pw.binom.agentik.storage.MessageRecord.AssistantMessage).content.let { assertEquals("hello back", (msgs[1] as pw.binom.agentik.messageStore.MessageRecord.AssistantMessage).content.let {
(it[0] as pw.binom.agentik.storage.Content.Text).body (it[0] as pw.binom.agentik.messageStore.Content.Text).body
}) })
} }
@@ -327,8 +327,8 @@ class ChatAgentTest {
val msgs = storage.messageStore.listAll(conv.id) val msgs = storage.messageStore.listAll(conv.id)
assertEquals(2, msgs.size) assertEquals(2, msgs.size)
assertIs<pw.binom.agentik.storage.MessageRecord.UserMessage>(msgs[0]) assertIs<pw.binom.agentik.messageStore.MessageRecord.UserMessage>(msgs[0])
val err = assertIs<pw.binom.agentik.storage.MessageRecord.Error>(msgs[1]) val err = assertIs<pw.binom.agentik.messageStore.MessageRecord.Error>(msgs[1])
assertEquals("boom from llm", err.message) assertEquals("boom from llm", err.message)
// backfill через getMessages (polling/reconnect) тоже видит ошибку // backfill через getMessages (polling/reconnect) тоже видит ошибку
@@ -372,7 +372,7 @@ class ChatAgentTest {
// audit: только user (assistant не успел сгенериться) // audit: только user (assistant не успел сгенериться)
val msgs = storage.messageStore.listAll(conv.id) val msgs = storage.messageStore.listAll(conv.id)
assertEquals(1, msgs.size) assertEquals(1, msgs.size)
assertIs<pw.binom.agentik.storage.MessageRecord.UserMessage>(msgs[0]) assertIs<pw.binom.agentik.messageStore.MessageRecord.UserMessage>(msgs[0])
// working memory: только user (assistant skipped because пустой) // working memory: только user (assistant skipped because пустой)
val wm = storage.workingMemoryStore.list(conv.id) val wm = storage.workingMemoryStore.list(conv.id)
@@ -428,7 +428,7 @@ class ChatAgentTest {
// audit: user + toolcall + toolresult (tool выполнился), assistant может быть // audit: user + toolcall + toolresult (tool выполнился), assistant может быть
val msgs = storage.messageStore.listAll(conv.id) val msgs = storage.messageStore.listAll(conv.id)
val toolResult = msgs.filterIsInstance<pw.binom.agentik.storage.MessageRecord.ToolResult>().firstOrNull() val toolResult = msgs.filterIsInstance<pw.binom.agentik.messageStore.MessageRecord.ToolResult>().firstOrNull()
assertNotNull(toolResult, "tool result должен быть в audit — tool выполнился нормально") assertNotNull(toolResult, "tool result должен быть в audit — tool выполнился нормально")
val toolResultResult = toolResult!!.result!! val toolResultResult = toolResult!!.result!!
assertTrue(toolResultResult.contains("echo"), "tool result содержит реальный ответ тулы: $toolResultResult") assertTrue(toolResultResult.contains("echo"), "tool result содержит реальный ответ тулы: $toolResultResult")
@@ -39,7 +39,7 @@ class ChatAgentToolsetsTest {
private fun newAgent( private fun newAgent(
toolsets: List<ToolsetContribution> = emptyList(), toolsets: List<ToolsetContribution> = emptyList(),
): Pair<ChatAgent, pw.binom.agentik.storage.StorageBundle> { ): Pair<ChatAgent, pw.binom.agentik.storageBundle.StorageBundle> {
val storage = SqliteStores.inMemory().asBundle() val storage = SqliteStores.inMemory().asBundle()
val agent = ChatAgent( val agent = ChatAgent(
id = "test-agent", id = "test-agent",
@@ -40,7 +40,7 @@ import pw.binom.agentik.llm.tools.SummaryTurn
*/ */
class CompactionTest { class CompactionTest {
private lateinit var storage: pw.binom.agentik.storage.StorageBundle private lateinit var storage: pw.binom.agentik.storageBundle.StorageBundle
private lateinit var fakeLlm: FakeLiteLlm private lateinit var fakeLlm: FakeLiteLlm
@BeforeTest @BeforeTest
@@ -89,7 +89,7 @@ class CompactionTest {
} }
val wm = storage.workingMemoryStore.list(conv.id) val wm = storage.workingMemoryStore.list(conv.id)
// Без compaction все ходы остаются в памяти (System + 10 user/assistant = 21 строк). // Без compaction все ходы остаются в памяти (System + 10 user/assistant = 21 строк).
val summaries = wm.filter { it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Summary } val summaries = wm.filter { it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary }
assertEquals(0, summaries.size, "compaction must not run without contextWindow") assertEquals(0, summaries.size, "compaction must not run without contextWindow")
} }
@@ -101,7 +101,7 @@ class CompactionTest {
conv.send(listOf(ProtoContent.Text("first"))) conv.send(listOf(ProtoContent.Text("first")))
val wm = storage.workingMemoryStore.list(conv.id) val wm = storage.workingMemoryStore.list(conv.id)
// System + User + Assistant = 3. Без compactor — никаких Summary. // System + User + Assistant = 3. Без compactor — никаких Summary.
val summaries = wm.filter { it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Summary } val summaries = wm.filter { it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary }
assertEquals(0, summaries.size, "no compaction runs without compactor") assertEquals(0, summaries.size, "no compaction runs without compactor")
} }
@@ -119,10 +119,10 @@ class CompactionTest {
assertTrue(compactor.calls > 0, "compactor must be called at least once when above threshold") assertTrue(compactor.calls > 0, "compactor must be called at least once when above threshold")
// В working memory должна появиться Summary. // В working memory должна появиться Summary.
val wm = storage.workingMemoryStore.list(conv.id) val wm = storage.workingMemoryStore.list(conv.id)
val summaries = wm.filter { it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Summary } val summaries = wm.filter { it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary }
assertTrue(summaries.isNotEmpty(), "at least one Summary entry should be present after compaction") assertTrue(summaries.isNotEmpty(), "at least one Summary entry should be present after compaction")
// Summary-текст — то, что вернул наш compactor. // Summary-текст — то, что вернул наш compactor.
val summaryText = (summaries.first().entry as pw.binom.agentik.storage.WorkingMemoryEntry.Summary).text val summaryText = (summaries.first().entry as pw.binom.agentik.workingMemory.WorkingMemoryEntry.Summary).text
assertTrue(summaryText.startsWith("**Goal**"), "summary text should come from compactor: $summaryText") assertTrue(summaryText.startsWith("**Goal**"), "summary text should come from compactor: $summaryText")
} }
@@ -162,8 +162,8 @@ class CompactionTest {
// Должны быть: System + хотя бы один Summary + последние KEEP_RECENT_TURNS ходов. // Должны быть: System + хотя бы один Summary + последние KEEP_RECENT_TURNS ходов.
// KEEP_RECENT_TURNS = 4 → user/assistant последних двух ходов (third + second) могут быть не тронуты. // KEEP_RECENT_TURNS = 4 → user/assistant последних двух ходов (third + second) могут быть не тронуты.
val userAssistantCount = wm.count { val userAssistantCount = wm.count {
it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.User || it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.User ||
it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Assistant it.entry is pw.binom.agentik.workingMemory.WorkingMemoryEntry.Assistant
} }
// Минимум 1 ход остаётся (KEEP_RECENT_TURNS). // Минимум 1 ход остаётся (KEEP_RECENT_TURNS).
assertTrue(userAssistantCount >= 1, "at least one recent turn must be preserved") assertTrue(userAssistantCount >= 1, "at least one recent turn must be preserved")
@@ -1,9 +1,9 @@
package pw.binom.agentik.standalone.agent package pw.binom.agentik.standalone.agent
import pw.binom.agentik.storage.MessageContext import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.storage.MessageOrigin.EVENT import pw.binom.agentik.messageStore.MessageOrigin.EVENT
import pw.binom.agentik.storage.MessageOrigin.SYSTEM import pw.binom.agentik.messageStore.MessageOrigin.SYSTEM
import pw.binom.agentik.storage.MessageOrigin.USER import pw.binom.agentik.messageStore.MessageOrigin.USER
import pw.binom.litert.LiteContentPart import pw.binom.litert.LiteContentPart
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@@ -49,7 +49,7 @@ import kotlin.time.Instant
*/ */
class MemoryWiringTest { class MemoryWiringTest {
private lateinit var storage: pw.binom.agentik.storage.StorageBundle private lateinit var storage: pw.binom.agentik.storageBundle.StorageBundle
private lateinit var fakeLlm: FakeLiteLlm private lateinit var fakeLlm: FakeLiteLlm
private lateinit var root: Path private lateinit var root: Path
@@ -1,4 +1,10 @@
package pw.binom.agentik.storage package pw.binom.agentik.standalone.persistence
import pw.binom.agentik.messageStore.MessageContext
import pw.binom.agentik.messageStore.MessageOrigin
import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.sqlite.SqliteStores import pw.binom.agentik.storage.sqlite.SqliteStores
@@ -1,4 +1,9 @@
package pw.binom.agentik.storage 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.workingMemory.WorkingMemoryEntry
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@@ -1,4 +1,14 @@
package pw.binom.agentik.storage 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.messageStore.ConversationRecord
import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
+24
View File
@@ -0,0 +1,24 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
// Агрегатор всех 5 storage-интерфейсов (MessageStore + ReflectionStore +
// EventStore + ConversationStore + WorkingMemoryStore). Зависит от обоих API
// модулей. Нужен только server-side рантайму (`:standalone`, `:agentik-cli`,
// `:agent-toolsets`, будущий `:android-agent` core). Тонкие клиенты этот модуль
// НЕ подтягивают — они работают с одним только `:message-store-api`.
kotlin {
jvmToolchain(21)
jvm()
linuxX64()
mingwX64()
sourceSets {
commonMain.dependencies {
api(project(":message-store-api"))
api(project(":working-memory-api"))
}
}
}
@@ -0,0 +1,42 @@
package pw.binom.agentik.storageBundle
import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.workingMemory.WorkingMemoryStore
/**
* Агрегатор всех storage-интерфейсов для полного агент-рантайма.
*
* Зависит от **обоих** API модулей — `:message-store-api` и `:working-memory-api`.
* Тонкие клиенты, которым нужен только audit log, могут не подтягивать этот модуль
* и работать с [MessageStore] / [ReflectionStore] / [EventStore] / [ConversationStore]
* напрямую.
*
* **Реализации агрегата**:
* - [SqliteStorageBundle] — JVM-only, SQLDelight + SQLite. В `:storage-sqlite`.
* - [InMemoryStorageBundle] — KMP, Map-based. В `:storage-inmemory`.
*
* `SkillStore` НЕ входит — он в `:skills` (контент-файлы навыков), принимается
* отдельно в `ChatAgent`.
*
* AutoCloseable: один `close()` закрывает все store'ы; в реализациях без
* ресурсов (in-memory) — no-op.
*/
data class StorageBundle(
val conversationStore: ConversationStore,
val messageStore: MessageStore,
val workingMemoryStore: WorkingMemoryStore,
val reflectionStore: ReflectionStore,
val eventStore: EventStore? = null,
) : AutoCloseable {
override fun close() {
conversationStore.close()
messageStore.close()
workingMemoryStore.close()
reflectionStore.close()
eventStore?.close()
}
}
-77
View File
@@ -1,77 +0,0 @@
# `:storage-core` — контракт хранилища (KMP, jvm + native)
## Что это
Интерфейсы persistence-уровня для `:standalone`:
- `MessageStore` — append-only история сообщений по диалогу.
- `WorkingMemoryStore` — rolling buffer текущего хода (`AssistantMessage`,
`ToolExchange`, `UserMessage`, system-prompt) для fast-recovery
при reconnect/relance.
- `ConversationStore` — метаданные диалогов (id, title, model,
timestamps).
- `ReflectionStore` — LLM-reflections (свободная форма заметок
хранителя).
Решает: позволяет запустить агента на Android-in-memory, на
desktop-SQLite, или на production-SQLite, не переписывая логику.
Контракт минимален и async-friendly.
## Где используется
- `:storage-inmemory` — для тестов и Android.
- `:storage-sqlite` — прод (Desktop / server / однодесктопный
Android-development).
## Как подключить
```kotlin
kotlin {
sourceSets.commonMain.dependencies {
api("pw.binom.agentik:storage-core:0.1.0")
}
}
```
## Версии
`gradle/libs.versions.toml` → `[versions] agentik-storage-core`.
## Что в API
```kotlin
interface MessageStore {
suspend fun append(conversationId: String, message: Message): Unit
suspend fun after(conversationId: String, instant: Instant, limit: Int = 100): List<Message>
}
interface WorkingMemoryStore {
suspend fun save(conv: String, entry: WorkingMemoryEntry): Unit
fun load(conv: String): Flow<WorkingMemoryEntry> // cold flow
suspend fun clear(conv: String): Unit
}
sealed interface WorkingMemoryEntry {
val id: String
val date: Instant
class UserMessage(...) : WorkingMemoryEntry
class AssistantMessage(...) : WorkingMemoryEntry
class ToolExchange(val toolName: String, val toolArgsJson: String, val resultText: String, val wasCancelled: Boolean) : WorkingMemoryEntry
class SystemPrompt(...) : WorkingMemoryEntry
}
```
## Тесты
Контрактные тесты (общие для всех имплементаций) — в `:storage-sqlite`
и `:storage-inmemory`.
## Чего здесь НЕТ
- Никакого HTTP / SSE.
- Никакой конкретной БД. Backend'ы в `:storage-*`.
## Текущий статус
Используется продакшеном. Контракт зафиксирован после
interrupt-имплементации (см. [INTERRUPT-DESIGN.md](../../docs/INTERRUPT-DESIGN.md)).
@@ -1,36 +0,0 @@
package pw.binom.agentik.storage
import pw.binom.agentik.storage.events.EventStore
/**
* Агрегатор всех storage-интерфейсов, нужных агенту для работы с историей диалога.
*
* Реализации:
* - [SqliteStorageBundle] — JVM-only, основная. SQLDelight + SQLite.
* - [InMemoryStorageBundle] — Map-based, для тестов.
* - `:storage-android` — Android-специфичная (deferred, Android-сборка в планах).
*
* `SkillStore` НЕ входит сюда — он живёт в модуле `:skills` (другая ответственность:
* не сообщения/рефлексии, а контент-файлы навыков) и принимается отдельно в `ChatAgent`.
*
* [EventStore] входит начиная с commit "event-store" — для replay после
* disconnect (см. `/events/replay` endpoint в `:server`).
*
* AutoCloseable: один `close()` закрывает все store'ы. В реализациях,
* которые не владеют ресурсами (in-memory), close — no-op.
*/
data class StorageBundle(
val conversationStore: ConversationStore,
val messageStore: MessageStore,
val workingMemoryStore: WorkingMemoryStore,
val reflectionStore: ReflectionStore,
val eventStore: EventStore? = null,
) : AutoCloseable {
override fun close() {
conversationStore.close()
messageStore.close()
workingMemoryStore.close()
reflectionStore.close()
eventStore?.close()
}
}
@@ -1,81 +0,0 @@
package pw.binom.agentik.storage
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
/**
* Тесты payload-формата: контекст инициации хода персистится через
* `payload_json` SQLite, старый plain-array формат читается без потерь.
*/
class PayloadTest {
@Test
fun `user context roundtrips through MessageBodyPayload`() {
val ctx = MessageContext(
origin = MessageOrigin.USER,
description = "irc PRIVMSG",
sourceId = "irc:agentik",
)
val encoded = encodeBodyPayload(listOf(Content.Text("hi")), ctx)
// новый формат: wrapper-объект с полем context
assertTrue(encoded.startsWith("{"), "expected wrapped object, got: $encoded")
assertTrue(encoded.contains("\"context\""), "expected context field, got: $encoded")
val decoded = decodeBodyPayload(encoded)
assertEquals(listOf(Content.Text("hi")), decoded.content)
assertEquals(ctx, decoded.context)
}
@Test
fun `event context roundtrips with metadata`() {
val ctx = MessageContext(
origin = MessageOrigin.EVENT,
description = "scheduled cron morning-briefing",
sourceId = "cron-42",
metadata = buildJsonObject {
put("scheduledAt", JsonPrimitive("2026-09-14T08:00:00Z"))
put("rule", JsonPrimitive("0 8 * * *"))
},
)
val encoded = encodeBodyPayload(listOf(Content.Text("wake up")), ctx)
val decoded = decodeBodyPayload(encoded)
assertEquals(MessageOrigin.EVENT, decoded.context?.origin)
assertEquals("scheduled cron morning-briefing", decoded.context?.description)
assertEquals("cron-42", decoded.context?.sourceId)
assertEquals("0 8 * * *", (decoded.context?.metadata?.let { (it as kotlinx.serialization.json.JsonObject)["rule"] } as? JsonPrimitive)?.content)
}
@Test
fun `null context produces wrapper without context field`() {
val encoded = encodeBodyPayload(listOf(Content.Text("hello")))
assertTrue(encoded.contains("\"content\""), "expected content field, got: $encoded")
val decoded = decodeBodyPayload(encoded)
assertEquals(listOf(Content.Text("hello")), decoded.content)
assertNull(decoded.context)
}
@Test
fun `legacy plain-array payload still decodes backward compat`() {
val legacy = "[" +
"""{"type":"text","body":"old message"}""" +
"]"
val decoded = decodeBodyPayload(legacy)
assertEquals(listOf(Content.Text("old message")), decoded.content)
assertNull(decoded.context)
}
@Test
fun `system context with description only roundtrips`() {
val ctx = MessageContext(origin = MessageOrigin.SYSTEM, description = "agent startup greeting")
val encoded = encodeBodyPayload(listOf(Content.Text("boot")), ctx)
val decoded = decodeBodyPayload(encoded)
assertEquals(MessageOrigin.SYSTEM, decoded.context?.origin)
assertEquals("agent startup greeting", decoded.context?.description)
assertNull(decoded.context?.sourceId)
assertNull(decoded.context?.metadata)
}
}
+3 -1
View File
@@ -20,7 +20,9 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
api(project(":storage-core")) api(project(":message-store-api"))
api(project(":storage-bundle"))
api(project(":working-memory-api"))
} }
commonTest.dependencies { commonTest.dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
@@ -3,8 +3,8 @@ package pw.binom.agentik.storage.inmemory
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlin.time.Clock import kotlin.time.Clock
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.storage.ConversationStore import pw.binom.agentik.messageStore.ConversationStore
import kotlin.time.Instant import kotlin.time.Instant
/** /**
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.inmemory package pw.binom.agentik.storage.inmemory
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import kotlin.time.Instant import kotlin.time.Instant
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -5,10 +5,10 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import pw.binom.agentik.storage.MessageEvent import pw.binom.agentik.messageStore.MessageEvent
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.TokenStats import pw.binom.agentik.messageStore.TokenStats
import kotlin.time.Instant import kotlin.time.Instant
/** /**
@@ -5,9 +5,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.storage.ReflectionEvent import pw.binom.agentik.messageStore.ReflectionEvent
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import kotlin.time.Instant import kotlin.time.Instant
/** /**
@@ -1,6 +1,6 @@
package pw.binom.agentik.storage.inmemory package pw.binom.agentik.storage.inmemory
import pw.binom.agentik.storage.StorageBundle import pw.binom.agentik.storageBundle.StorageBundle
import kotlin.time.Clock import kotlin.time.Clock
/** /**
@@ -2,10 +2,10 @@ package pw.binom.agentik.storage.inmemory
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import pw.binom.agentik.storage.Ids import pw.binom.agentik.messageStore.Ids
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.WorkingMemoryRow import pw.binom.agentik.workingMemory.WorkingMemoryRow
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import kotlin.time.Instant import kotlin.time.Instant
/** /**
@@ -1,6 +1,6 @@
package pw.binom.agentik.storage.inmemory package pw.binom.agentik.storage.inmemory
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
@@ -2,8 +2,8 @@ package pw.binom.agentik.storage.inmemory
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.inmemory package pw.binom.agentik.storage.inmemory
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.TurnTokens import pw.binom.agentik.messageStore.TurnTokens
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
@@ -113,8 +113,8 @@ class InMemoryMessageStoreTest {
yield() // даём коллектору подписаться ДО append — иначе SharedFlow без replay потеряет эвент yield() // даём коллектору подписаться ДО append — иначе SharedFlow без replay потеряет эвент
store.append(MessageRecord.UserMessage("u1", "c1", listOf(Content.Text("hi")), t0)) store.append(MessageRecord.UserMessage("u1", "c1", listOf(Content.Text("hi")), t0))
val ev = deferred.await() val ev = deferred.await()
assertTrue(ev is pw.binom.agentik.storage.MessageEvent.Appended) assertTrue(ev is pw.binom.agentik.messageStore.MessageEvent.Appended)
val appended = ev as pw.binom.agentik.storage.MessageEvent.Appended val appended = ev as pw.binom.agentik.messageStore.MessageEvent.Appended
assertEquals("c1", appended.conversationId) assertEquals("c1", appended.conversationId)
assertEquals("u1", appended.record.id) assertEquals("u1", appended.record.id)
} }
@@ -1,7 +1,7 @@
package pw.binom.agentik.storage.inmemory package pw.binom.agentik.storage.inmemory
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.storage.ReflectionEvent import pw.binom.agentik.messageStore.ReflectionEvent
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
@@ -1,7 +1,7 @@
package pw.binom.agentik.storage.inmemory package pw.binom.agentik.storage.inmemory
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
+3 -1
View File
@@ -23,7 +23,9 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
api(project(":storage-core")) api(project(":message-store-api"))
api(project(":storage-bundle"))
api(project(":working-memory-api"))
// ksqlite ещё не опубликован в Maven Central — только в локальном // ksqlite ещё не опубликован в Maven Central — только в локальном
// caffeine-репо. Версия пока 0.1.0-SNAPSHOT (CI fallback из README). // caffeine-репо. Версия пока 0.1.0-SNAPSHOT (CI fallback из README).
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlin.time.Instant import kotlin.time.Instant
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.storage.ConversationStore import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.db.ksqlite.SQLiteConnection import pw.binom.db.ksqlite.SQLiteConnection
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import pw.binom.db.ksqlite.SQLiteConnection import pw.binom.db.ksqlite.SQLiteConnection
import pw.binom.db.ksqlite.SQLitePreparedStatement import pw.binom.db.ksqlite.SQLitePreparedStatement
import pw.binom.db.ksqlite.SQLiteResultSet import pw.binom.db.ksqlite.SQLiteResultSet
@@ -1,11 +1,11 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.TokenStats import pw.binom.agentik.messageStore.TokenStats
import pw.binom.agentik.storage.decodeBodyPayload import pw.binom.agentik.messageStore.decodeBodyPayload
import pw.binom.agentik.storage.encodeBodyPayload import pw.binom.agentik.messageStore.encodeBodyPayload
import pw.binom.db.ksqlite.SQLiteConnection import pw.binom.db.ksqlite.SQLiteConnection
import pw.binom.db.ksqlite.SQLiteResultSet import pw.binom.db.ksqlite.SQLiteResultSet
import kotlin.time.Instant import kotlin.time.Instant
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.storage.ReflectionEvent import pw.binom.agentik.messageStore.ReflectionEvent
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.db.ksqlite.SQLiteConnection import pw.binom.db.ksqlite.SQLiteConnection
import pw.binom.db.ksqlite.SQLiteResultSet import pw.binom.db.ksqlite.SQLiteResultSet
import kotlin.time.Clock import kotlin.time.Clock
@@ -1,11 +1,11 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import pw.binom.agentik.storage.ConversationStore import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.storage.StorageBundle import pw.binom.agentik.storageBundle.StorageBundle
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.db.ksqlite.SQLiteConnection import pw.binom.db.ksqlite.SQLiteConnection
/** /**
@@ -1,10 +1,10 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.WorkingMemoryRow import pw.binom.agentik.workingMemory.WorkingMemoryRow
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import pw.binom.agentik.storage.Ids import pw.binom.agentik.messageStore.Ids
import pw.binom.db.ksqlite.SQLiteConnection import pw.binom.db.ksqlite.SQLiteConnection
import kotlin.time.Instant import kotlin.time.Instant
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -1,9 +1,9 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.decodeBodyPayload import pw.binom.agentik.messageStore.decodeBodyPayload
import pw.binom.agentik.storage.encodeBodyPayload import pw.binom.agentik.messageStore.encodeBodyPayload
import pw.binom.db.ksqlite.SQLiteResultSet import pw.binom.db.ksqlite.SQLiteResultSet
import kotlin.time.Instant import kotlin.time.Instant
@@ -1,7 +1,7 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
@@ -1,9 +1,9 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.TurnTokens import pw.binom.agentik.messageStore.TurnTokens
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
@@ -20,7 +20,7 @@ class KsqliteMessageStoreTest {
stores = KsqliteStores.inMemory("msg-${kotlin.random.Random.nextLong()}") stores = KsqliteStores.inMemory("msg-${kotlin.random.Random.nextLong()}")
kotlinx.coroutines.runBlocking { kotlinx.coroutines.runBlocking {
stores.conversations.upsert( stores.conversations.upsert(
pw.binom.agentik.storage.ConversationRecord( pw.binom.agentik.messageStore.ConversationRecord(
"conv1", null, false, "conv1", null, false,
Instant.parse("2026-09-15T10:00:00Z"), Instant.parse("2026-09-15T10:00:00Z"),
Instant.parse("2026-09-15T10:00:00Z"), Instant.parse("2026-09-15T10:00:00Z"),
@@ -1,7 +1,7 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.ksqlite package pw.binom.agentik.storage.ksqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.Content import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
+3 -1
View File
@@ -15,7 +15,9 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
api(project(":storage-core")) api(project(":message-store-api"))
api(project(":storage-bundle"))
api(project(":working-memory-api"))
api(libs.sqldelight.runtime) api(libs.sqldelight.runtime)
api(libs.sqldelight.coroutines) api(libs.sqldelight.coroutines)
} }
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.sqlite package pw.binom.agentik.storage.sqlite
import kotlin.time.Instant import kotlin.time.Instant
import pw.binom.agentik.storage.ConversationRecord import pw.binom.agentik.messageStore.ConversationRecord
import pw.binom.agentik.storage.ConversationStore import pw.binom.agentik.messageStore.ConversationStore
/** /**
* SQLite-реализация [ConversationStore]. * SQLite-реализация [ConversationStore].
@@ -2,9 +2,9 @@ package pw.binom.agentik.storage.sqlite
import kotlin.time.Instant import kotlin.time.Instant
import mu.KotlinLogging import mu.KotlinLogging
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import pw.binom.agentik.storage.sqlite.Agent_event as DbAgentEvent import pw.binom.agentik.storage.sqlite.Agent_event as DbAgentEvent
private val log = KotlinLogging.logger {} private val log = KotlinLogging.logger {}
@@ -1,11 +1,11 @@
package pw.binom.agentik.storage.sqlite package pw.binom.agentik.storage.sqlite
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import pw.binom.agentik.storage.MessageRecord import pw.binom.agentik.messageStore.MessageRecord
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.TokenStats import pw.binom.agentik.messageStore.TokenStats
import pw.binom.agentik.storage.decodeBodyPayload import pw.binom.agentik.messageStore.decodeBodyPayload
import pw.binom.agentik.storage.encodeBodyPayload import pw.binom.agentik.messageStore.encodeBodyPayload
import kotlin.time.Instant import kotlin.time.Instant
/** /**
@@ -6,9 +6,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import mu.KotlinLogging import mu.KotlinLogging
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.storage.ReflectionEvent import pw.binom.agentik.messageStore.ReflectionEvent
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.storage.sqlite.AgentikDatabase import pw.binom.agentik.storage.sqlite.AgentikDatabase
import pw.binom.agentik.storage.sqlite.Reflection as DbReflection import pw.binom.agentik.storage.sqlite.Reflection as DbReflection
import pw.binom.agentik.storage.sqlite.ReflectionQueries import pw.binom.agentik.storage.sqlite.ReflectionQueries
@@ -3,13 +3,13 @@ package pw.binom.agentik.storage.sqlite
import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import pw.binom.agentik.storage.ConversationStore import pw.binom.agentik.messageStore.ConversationStore
import pw.binom.agentik.storage.MessageStore import pw.binom.agentik.messageStore.MessageStore
import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.messageStore.ReflectionStore
import pw.binom.agentik.storage.StorageBundle import pw.binom.agentik.storageBundle.StorageBundle
import pw.binom.agentik.storage.events.EventStore import pw.binom.agentik.messageStore.events.EventStore
import pw.binom.agentik.storage.sqlite.SqliteReflectionStore import pw.binom.agentik.storage.sqlite.SqliteReflectionStore
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
/** /**
* Корневой объект SQLite-слоя: держит [SqlDriver] и пять store'ов (включая * Корневой объект SQLite-слоя: держит [SqlDriver] и пять store'ов (включая
@@ -1,9 +1,9 @@
package pw.binom.agentik.storage.sqlite package pw.binom.agentik.storage.sqlite
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.workingMemory.WorkingMemoryEntry
import pw.binom.agentik.storage.WorkingMemoryRow import pw.binom.agentik.workingMemory.WorkingMemoryRow
import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.workingMemory.WorkingMemoryStore
import kotlin.time.Instant import kotlin.time.Instant
/** /**
@@ -90,4 +90,4 @@ private fun Working_memory.toRow(): WorkingMemoryRow {
) )
} }
private fun newId(): String = pw.binom.agentik.storage.Ids.new("wm") private fun newId(): String = pw.binom.agentik.messageStore.Ids.new("wm")
@@ -5,8 +5,8 @@ import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.time.Instant import kotlin.time.Instant
import pw.binom.agentik.storage.Reflection import pw.binom.agentik.messageStore.Reflection
import pw.binom.agentik.storage.ReflectionEvent import pw.binom.agentik.messageStore.ReflectionEvent
class ReflectionStoreTest { class ReflectionStoreTest {
@@ -1,8 +1,8 @@
package pw.binom.agentik.storage.sqlite package pw.binom.agentik.storage.sqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import pw.binom.agentik.storage.events.EventRecord import pw.binom.agentik.messageStore.events.EventRecord
import pw.binom.agentik.storage.events.EventType import pw.binom.agentik.messageStore.events.EventType
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
@@ -3,24 +3,22 @@ plugins {
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
// Public API для mutable working-memory — runtime context агента (compaction,
// order_idx, summary entries). Зависит от :message-store-api для Ids (wm- префикс).
//
// НЕ нужен тонким клиентам — только серверному рантайму (`:standalone`, `:agentik-cli`,
// будущий `:android-agent` core).
kotlin { kotlin {
jvmToolchain(21) jvmToolchain(21)
// Зеркалит набор :proto / :server / :memory-api — KMP-модуль с интерфейсами
// хранилища и разговорной истории, без платформенного IO. Конкретные
// реализации (sqlite, in-memory, android) живут в отдельных модулях.
jvm() jvm()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64() linuxX64()
linuxArm64()
mingwX64() mingwX64()
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
api(project(":message-store-api"))
api(libs.kotlinx.coroutines.core) api(libs.kotlinx.coroutines.core)
api(libs.kotlinx.serialization.core) api(libs.kotlinx.serialization.core)
api(libs.kotlinx.serialization.json) api(libs.kotlinx.serialization.json)
@@ -1,7 +1,9 @@
package pw.binom.agentik.storage package pw.binom.agentik.workingMemory
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import pw.binom.agentik.messageStore.Content
import pw.binom.agentik.messageStore.MessageContext
/** /**
* Запись в working memory диалога: ровно то, что агент сейчас видит в * Запись в working memory диалога: ровно то, что агент сейчас видит в
@@ -1,4 +1,4 @@
package pw.binom.agentik.storage package pw.binom.agentik.workingMemory
import kotlin.time.Instant import kotlin.time.Instant