# Совместимость с native (linuxX64) — чеклист Цель: `:standalone` собирается и запускается как `linuxX64` executable (и потенциально остальные нативные таргеты KMP). Текущее состояние — JVM-only executable на `runJvm`-таске. Формат: `- N. [ ]` — задача; `- N. [x]` — выполнена; `- N. [-]` — отменена/не нужна. Решения (что выбрали и почему) — курсивом внизу пункта. --- ## Блок 1. Подготовка (уже сделана или тривиальная) - 1. [x] **Замена `java.util.UUID` на KMP-stdlib (`kotlin.uuid.Uuid`).** Helper `Ids.new(prefix)` в `standalone/src/commonMain/.../persistence/Ids.kt`, три call-site (`ChatAgent`, `ChatConversation`, `SqliteWorkingMemoryStore`). Коммит `e816d8d`. - 2. [x] **HTTP-движок: `ktor-server-netty` → `ktor-server-cio`.** Netty — JVM-only; CIO — KMP (jvm + linuxX64 + ios/...). Каталог-эntry Netty оставлен в `libs.versions.toml` на случай отката. Коммит `e816d8d`. ## Блок 2. Библиотеки litert-kmp (наши) - 3. [ ] **`litert-api`: добавить `linuxX64()` в `kotlin{}`.** Сейчас только `androidTarget() + jvm()`. commonMain уже KMP-ready (только `kotlinx-coroutines-core`). Объём: одна строка в `litert-api/build.gradle.kts`, один `litert-api-linuxx64`-артефакт публикуется в Nexus. - 4. [ ] **`litert-openai`: добавить `linuxX64()` в `kotlin{}`.** Та же история. commonMain deps (`ktor-client-core`, `ktor-client-cio`, `kotlinx-serialization-json`) — все KMP с готовыми linuxX64-артефактами. Объём: одна строка + проверить, что Android-only фичи (если есть) изолированы в `androidMain` (сейчас модуль не имеет `androidMain`-специфичного кода). *Каталог-запись `litert-openai-jvm` → переименовать в `litert-openai`* (чтобы Linux-таргет подтягивал `-linuxx64`-вариант автоматически, не нужно ручного `when target`). - 5. [-] **`litert-google`: linuxX64 — невозможно.** Upstream-блокер: Google публикует `litertlm-android` (AAR с `.so`) и `litertlm-jvm` (fat-jar с `.so` для linux/mac/win JVM), но НЕ нативного `litertlm-linuxx64`. Без правки upstream LiteRT-LM KMP-враппер для linuxX64 не напишется. Варианты: (a) выкинуть `litert-google` на linuxX64 (`backend=google` → отказ); (b) альтернативный движок (llama.cpp через JNI/Kotlin/Native); (c) upstream PR в `litertlm`. *Решение: (a) для v1 linuxX64 — упасть с понятной ошибкой `LlmBackend.GOOGLE is not supported on this platform` вместо reflection-fallback на JVM .so, который на linuxX64 не сработает бы.* ## Блок 3. Наш `:server` модуль - 6. [x] **Переписать `:server` с `kotlin("jvm")` на `kotlin("multiplatform")`.** Все 9 целей сборки (jvm + macosX64 + macosArm64 + iosX64 + iosArm64 + iosSimulatorArm64 + linuxX64 + linuxArm64 + mingwX64) собираются `./gradlew :server:assemble` без ошибок. Блокеров нет: Ktor `ktor-server-{core,sse,cio,content-negotiation}` и `ktor-io` (для `ByteWriteChannel.writeStringUtf8`) есть в KMP-вариантах под все цели; `kotlin.time.Instant` уже использовался. Единственное изменение по сравнению с JVM-версией: `respondTextWriter` (JVM-only) → `respondBytesWriter` + `writeStringUtf8` (KMP, тот же `ByteWriteChannel` API). Источники переехали `src/main/kotlin/...` → `src/commonMain/kotlin/...`. `:standalone:jvmTest` 44/44 зелёные после миграции. ## Блок 4. Наш `:proto` модуль - 7. [x] **`:proto`: убрать `kotlinx-datetime` api-dep, оставить `kotlin.time.Instant`.** Источник уже был на `kotlin.time.Instant` (мигрирован ранее), но `kotlinx-datetime` оставался в gradle как мёртвая `api`-зависимость. Удалено: - `api(libs.kotlinx.datetime)` из `proto/build.gradle.kts` - `implementation(libs.kotlinx.datetime)` из `standalone/build.gradle.kts` - `[versions] kotlinx-datetime` и `[libraries] kotlinx-datetime` из `gradle/libs.versions.toml` `kotlin.time.Instant` доступен во всех KMP-целях без opt-in. `./gradlew :server:assemble` + `:standalone:jvmTest` 44/44 — зелёные. ## Блок 5. SQLDelight — нативный драйвер - 8. [ ] **`sqlite-driver` (JVM/JDBC) → `native-driver` для linuxX64.** В jvmMain: оставить `sqlite-driver` (JDBC over `java.sql.DriverManager`). В linuxX64Main: добавить `native-driver` — KMP driver поверх cinterop с `libsqlite3` (нужен `-lsqlite3` linker-флаг или `SQLiteBundle` из `co.touchlab:sqliter` для бандлинга). Создать `SqliteStores`- нейтральный интерфейс хранилищ (он уже есть в `commonMain`), разделить `jvmMain/SqliteStores.kt` и `linuxX64Main/SqliteStoresNative.kt` — оба реализуют один `commonMain`-интерфейс. Объём: ~150 строк (новый `SqliteStoresNative.kt` + gradle wiring). *Решение по бандлу: использовать `SQLiteBundle` из `co.touchlab:sqliter` (KMP-обёртка, тянет свой libsqlite) — без зависимости от системного libsqlite3, бинарь работает на любом linux.* - 9. [ ] **Тесты SQLite переписать на commonTest + `expect`/`actual` обёртку.** `inMemory()`-открытие в `native-driver` использует другой API (нет JDBC `:memory:`). Объём: один `expect fun openInMemory()` в commonMain + два `actual` (JDBC `jdbc:sqlite::memory:` / native-driver нативный API). ## Блок 6. Внешние либы (`a2a-server`) — наши - 10. [x] **AGUI убран из проекта.** catalog-entries `agui-api`/`agui-client`/`agui-server`, `[versions] agui` и `implementation(libs.agui.server)` в `:standalone` удалены — AGUI больше не нужен (см. Memory #3675 и переписанный `docs/ARCHITECTURE.md`). AGUI-фасад был stateless request/response, а agentik переходит на свой stateful протокол `:proto` / `:server`. - 11. [ ] **`a2a-server`: JVM-only → KMP.** Per Memory #3561 — JVM-only (client/server; shared уже KMP). Без native-таргета весь стек A2A недоступен на linuxX64. Объём: переписать на `multiplatform`, выделить engine-нейтральную маршрутизацию. *Альтернатива: на linuxX64 не подключать a2a-server вообще (только `:server`-фасад). Решение отложено — зависит от того, нужен ли нативный MCP-юзер-кейс вообще подключаться к A2A.* ## Блок 7. `:standalone` — добавить native target - 12. [ ] **`standalone/build.gradle.kts`: добавить `linuxX64()` и `linuxX64Main`/`linuxX64Test` source-sets.** В `linuxX64Main`: `:server` (после п.6), `litert-openai` (после п.4), `litert-api` (после п.3), `ktor-server-cio` (engine), `ktor-client-cio`, `kotlin-sdk-client` (MCP — KMP, готов), `kotlinx-io` (KMP, готов). Исключить `litert-google` (п.5) и `a2a-server` (п.11) из linuxX64Main. - 13. [ ] **`Main.kt`: разделить на `commonMain` (бизнес-логика: создание ChatAgent, MCP, LlmConfig) + `jvmMain`/`linuxX64Main` (выбор engine, embeddedServer).** embeddedServer(CIO, …) — один и тот же код на обеих платформах, KMP-нейтрален. Главное отличие — `SqliteStores.open()` и обработка GOOGLE-бэкенда на linuxX64 (fail-fast). ## Блок 8. Тесты и CI - 14. [ ] **`linuxX64Test` source-set.** Покрыть те же 44 теста на native: должен зелёный прогон через `./gradlew :standalone:linuxX64Test`. SQLDelight-тесты на native требуют `libsqlite3`/`SQLiteBundle` на машине (CI-linux предоставляет). - 15. [ ] **Smoke e2e на linuxX64.** Запустить `./gradlew :standalone:runLinuxX64` локально, проверить `POST /agentik/conversations` → 201, цикл с реальным LLM через OpenAI backend; убедиться что LiteRT-LM GOOGLE не подключается (отказ с понятной ошибкой). ## Блок 9. Дистрибуция native-бинаря - 16. [ ] **`runLinuxX64`-таска через KMP binaries DSL.** После добавления `linuxX64()` KMP генерирует её автоматически — нужно только убедиться что `binaries { executable { mainClass.set("pw.binom.agentik.standalone.MainKt") } }` работает для linuxX64 (по аналогии с уже настроенным `runJvm`). - 17. [ ] **Дистрибутив `.tar`/`.zip`/`AppImage/Docker`.** KMP `assembleDist` для linuxX64 делает tar.gz/zip. Для прод-дистрибуции — Docker-образ (`FROM gcr.io/distroless/cc`) или AppImage. Объём: ~50 строк Dockerfile + GitHub Action. --- ## Что НЕ блокирует linuxX64 (закрыто или тривиально) - `kotlinx-coroutines-core` — KMP linuxX64 ✓ - `kotlinx-serialization-json` — KMP linuxX64 ✓ - `kotlinx-io` — KMP linuxX64 (используется в MCP stdio-transport) ✓ - `kotlin-sdk-client` (MCP) — KMP full targets ✓ - `ktor-server-core`, `ktor-server-sse`, `ktor-server-cio` — KMP linuxX64 ✓ - `ktor-client-core`, `ktor-client-cio` — KMP linuxX64 ✓ - `pw.binom.uuid` — **отсутствует** в проекте (используем `kotlin.uuid`) ✓ ## Жёсткие блокеры (не обойти без upstream-работы) - `litertlm-jvm` → нужен `litertlm-native` от Google (см. п.5) - `a2a-server` JVM-only (см. п.11) — нужно переписать или явно не подключать