Коммит заменяет SQLDelight на ksqlite и добавляет journal/context/outbox
ci / JVM build + tests (push) Failing after 1m24s

This commit is contained in:
2026-09-21 01:12:31 +03:00
parent bd65c29b48
commit acc7237e51
61 changed files with 1623 additions and 1750 deletions
+37
View File
@@ -0,0 +1,37 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
}
// Public API для mutable working-memory — runtime context агента (compaction,
// order_idx, summary entries). Зависит от :message-log-api для Ids (wm- префикс).
//
// НЕ нужен тонким клиентам — только серверному рантайму (`:standalone`, `:agentik-cli`,
// будущий `:android-agent` core).
kotlin {
jvmToolchain(21)
jvm()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.dependencies {
api(project(":message-log-api"))
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)
}
}
}
@@ -0,0 +1,58 @@
package pw.binom.agentik.context
import kotlin.time.Instant
/**
* Одна строка `working_memory` таблицы (внутреннее представление store).
*
* Используется для тестов и для перестроения [WorkingMemoryEntry] из row.
* Агент не должен с этим типом работать напрямую — он работает с
* [WorkingMemoryEntry] через [ContextStore].
*/
data class WorkingMemoryRow(
val id: String,
val conversationId: String,
val orderIdx: Long,
val sourceMessageId: String?,
val entry: WorkingMemoryEntry,
val createdAt: Instant,
)
/**
* Мутируемое представление LLM-контекста диалога (`working_memory` table).
*
* Аудит-лог — [MessageStore], неизменный; здесь — ровно то, что агент сейчас
* «видит»: системный промпт + реплики + (опционально) суммаризации.
* Строки упорядочены по `order_idx` ASC; `source_message_id` NULL указывает
* на синтетические строки (System, а в v2 — Summary).
*
* Суммаризация / чистка — один атомарный вызов [compact].
*/
interface ContextStore : AutoCloseable {
/** Добавить запись в конец working memory (новый максимальный `order_idx`). */
suspend fun append(conversationId: String, entry: WorkingMemoryEntry, now: Instant)
/** Все строки working memory диалога в порядке отправки. */
suspend fun list(conversationId: String): List<WorkingMemoryRow>
/** Очистить working memory диалога (используется при reset/rebuild). */
suspend fun clear(conversationId: String)
/**
* Атомарная суммаризация: удаляет все строки с `order_idx` в диапазоне
* `[dropFromOrderIdx, +∞)`. Если [summaryText] непустое — вместо удалённых
* строк вставляется одна синтетическая [WorkingMemoryEntry.Summary]
* с этим текстом и `order_idx = max(old order_idx after delete) + 1`
* (т.е. summary становится хвостом working memory).
*
* Если [summaryText] == null — работает как «отрезать хвост» (v1 поведение).
*
* Возвращает новый максимальный `order_idx` после операции.
*/
suspend fun compact(
dropFromOrderIdx: Long,
conversationId: String,
summaryText: String? = null,
): Long
}
@@ -0,0 +1,86 @@
package pw.binom.agentik.context
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import pw.binom.agentik.messageLog.Content
import pw.binom.agentik.messageLog.MessageContext
/**
* Запись в working memory диалога: ровно то, что агент сейчас видит в
* LLM-контексте. Упорядочено по `order_idx` (заполняется в store при append).
*
* Sealed-иерархия: `User`/`Assistant` (реплики с ссылкой на audit log
* через [sourceMessageId]), `ToolExchange` (синтетическая запись об одном
* tool-вызове + его результате — для replay в LiteMessage(TOOL, ToolResult)
* при пересоздании LiteConv), `Summary` (суммаризация при compaction).
*/
@Serializable
sealed interface WorkingMemoryEntry {
/** Ссылка на исходное сообщение в audit log (`message.id`). `null` для синтетических строк. */
val sourceMessageId: String?
/** Реплика пользователя. */
@Serializable
@SerialName("user")
data class User(
override val sourceMessageId: String,
val content: List<Content>,
/**
* Контекст инициации хода. Применяется при сборке `LiteConversation`:
* если `origin != USER`, текст префиксуется `[origin] description (sourceId=…)`,
* чтобы модель видела, что её разбудил не пользователь.
* `null` = обычное user-сообщение.
*/
val context: MessageContext? = null,
) : WorkingMemoryEntry
/** Реплика ассистента. */
@Serializable
@SerialName("assistant")
data class Assistant(
override val sourceMessageId: String,
val content: List<Content>,
) : WorkingMemoryEntry
/**
* Синтетический блок: один tool-вызов + его результат. Синтетический — потому
* что в audit log это две отдельные записи (`MessageRecord.ToolCall` +
* `MessageRecord.ToolResult`), а в working_memory мы храним одной строкой
* для удобства replay'а.
*
* При создании новой LiteConv каждая такая запись превращается в
* `LiteMessage(TOOL, [ToolResult(callId, name, response)])` — LiteRT-LM
* матчит по `name`, `callId` берётся из [sourceMessageId] (= id исходного
* [MessageRecord.ToolCall]). Если [wasCancelled] = true, [resultText]
* содержит маркер `[cancelled by user]` — модель видит честную причину
* отсутствия результата.
*
* [sourceMessageId] = id исходного [MessageRecord.ToolCall] (для трассировки
* в audit log).
*/
@Serializable
@SerialName("tool_exchange")
data class ToolExchange(
override val sourceMessageId: String,
val toolName: String,
val toolArgsJson: String,
val resultText: String,
val wasCancelled: Boolean = false,
) : WorkingMemoryEntry
/**
* Синтетический блок: суммаризация старых ходов, сгенерированная при
* compaction'е working memory. Не имеет ссылки на конкретное сообщение
* в audit log — это наша собственная интерпретация контекста.
*/
@Serializable
@SerialName("summary")
data class Summary(
val text: String,
/** Ходы, которые были свёрнуты в этот summary (диапазон order_idx в виде меты). */
val coversUpToOrderIdx: Long? = null,
) : WorkingMemoryEntry {
override val sourceMessageId: String? = null
}
}