docs: per-module READMEs (run vs library) + root navigation hub
ci / JVM build + tests (pull_request) Failing after 54s

Every subproject now has README.md:
- 3 runnable modules (:standalone, :agentik-cli, :agentik-tui):
  quickstart, env table, parameters, known limits
- 11 library modules: what it is, which problem solves, how to
  wire it in, where versions live

Root README.md is the navigation hub (Quickstart, Modules table,
publish + CI/CD notes).

Also: ci.yml prunes the :memory-vector -x excludes now that
text-embedding-kmp artifacts are published to caffeine.

518 tests green.

Verified publish pipeline: :proto:publish to caffeine produces
pom.module + per-target klibs + sources for all 9 KMP targets.

🤖 Generated with [opencode]
This commit is contained in:
SubochevAV
2026-09-16 20:44:16 +03:00
parent 5ad972767d
commit 8f616f359f
22 changed files with 1125 additions and 890 deletions
+106 -67
View File
@@ -1,94 +1,133 @@
# :proto — `pw.binom.agentik.proto`
# `:proto` — протокол общения с агентом (KMP, jvm + native)
**Stateful KMP-протокол взаимодействия клиента с агентом.**
Замена AG-UI в проектах, где агенту нужно **самому владеть** историей диалога и
контекстным окном (компакция, рефлексия, выбор инструментов) — клиент только
стримит сообщения и рисует события.
## Что это
## Какую проблему решает
Типы и контракт in-house протокола `agentik`, заменившего AG-UI:
AG-UI требует, чтобы **клиент** слал полный `messages[]` на каждый ход, а агент
оставался stateless. Это удобно для UI-чатов, но ломается, когда:
- **stateful** — сервер сам владеет диалогом; клиент шлёт только новые
сообщения, а не всю историю (в отличие от AG-UI, где клиент обязан
повторять `messages[]` каждый раз).
- **declarative история vs. события** — `Message` это то, что уже легло
в БД, `Event` это live-стрим от агента во время `send()` или `events()`.
- **чистые интерфейсы** — никаких сетевых и storage зависимостей внутри
`:proto`; это контракт.
- у агента есть долговременная память (md/vector), и контекст должен
автоматически сжиматься / дополняться перед отправкой в LLM;
- у одного пользователя десятки активных диалогов и нельзя каждый раз
пересылать 100K токенов;
- агент сам планирует вызовы инструментов и управляет KV-cache модели.
Решает проблему: AG-UI клиент вынужден каждый раз знать и пересобирать
полную историю, а его серверная часть (`AbstractAgent`) — постоянно
сериализовать-десериализовать всю переписку. В `:proto` сервер один,
контракт тонкий, переписка персистится нативно (SQLite, файлы, что
хотите). Можно подменить front-end или back-end, протокол остаётся.
`:proto` переворачивает ответственность: **агент** владеет `Conversation.messages()`,
`send(content)` отправляет только новый message, а `events(after): Flow<Event>`
стримит live-события с `Instant`-таймстампом для отслеживания прогресса.
## Где используется
## Контракт
- `:server` — Ktor-фасад, маппит `Agent` ↔ HTTP/SSE.
- `:client` — Ktor-клиент, маппит HTTP/SSE ↔ `Agent/Conversation`.
- `:agentik-cli`, `:agentik-tui` — оба работают поверх `:client`,
а следовательно поверх `:proto`.
- `:standalone` — реализует `Agent` (через `ChatAgent`) и пишет/читает
`Message`/`Event` напрямую через storage.
## Как подключить
```kotlin
// build.gradle.kts
kotlin {
sourceSets.commonMain.dependencies {
api("pw.binom.agentik:proto:0.1.0")
}
}
```
Артефакт `pw.binom.agentik:proto:0.1.0` живёт в Nexus-репозитории
`caffeine` (HTTP `http://<your-nexus>/repository/caffeine/`, plain-HTTP,
credentials — через переменные `binom.repo.user/password/url`).
## Версии
Каталог `gradle/libs.versions.toml`, секция `[versions]` → `agentik-proto`.
Поднять версию → переопубликовать все KMP-таргеты через `./gradlew
:proto:publish -Pversion=...` (или триггернуть Gitea release).
Текущие KMP-таргеты: `jvm + macosX64/macosArm64 +
iosX64/iosArm64/iosSimulatorArm64 + linuxX64/linuxArm64 + mingwX64`.
## Публикация
Настройки в `gradle.properties` / env: `binom.repo.url`, `binom.repo.user`,
`binom.repo.password`. `./gradlew :proto:publish` публикует все
target-specific артефакты + общий `kotlinMultiplatform`.
## Основные типы
```kotlin
interface Agent {
val id: String
suspend fun createConversation(temp: Boolean = false): Conversation
fun id: String
suspend fun createConversation(title: String? = null): Conversation
suspend fun getConversation(id: String): Conversation?
suspend fun getConversations(offset: Int = 0, limit: Int = PAGE_SIZE): List<Conversation>
fun getConversations(offset: Int = 0): Flow<Conversation> // cold-flow по страницам
fun events(after: Instant): Flow<AgentEvent> // live-уведомления о диалогах
suspend fun getConversations(offset: Int = 0): Flow<Conversation>
suspend fun events(after: Instant): Flow<AgentEvent> // created/deleted/renamed
}
interface Conversation : AutoCloseable {
val id: String
val title: String?
val updatedAt: Instant
val isSupportImageInput: Boolean
val isSupportImageOutput: Boolean
suspend fun send(content: List<Content>): Unit // write-only, не блокирует
fun events(after: Instant): Flow<Event> // live read (НЕ replay!)
suspend fun getMessages(offset: Int, limit: Int = PAGE_SIZE): List<Message>
fun getMessages(offset: Int = 0): Flow<Message> // cold-flow по страницам
suspend fun send(content: List<Content>): Flow<Event> // write+read вместе, как раньше
suspend fun events(after: Instant): Flow<Event> // отдельная live-подписка
suspend fun getMessages(offset: Int = 0): Flow<Message>
suspend fun rename(title: String): Boolean
suspend fun interrupt(): Unit
fun close() // освобождает ресурсы
fun interrupt()
}
sealed interface Content {
class Text(val body: String) : Content
class Image(val data: ByteArray, val mime: String) : Content
}
sealed interface Message {
val id: String
val date: Instant
interface Body : Message { val content: List<Content> }
interface System : Message
class UserMessage(...) : Body
class AssistantMessage(...) : Body
class ToolCall(...) : System
class ToolResult(...) : System
}
sealed interface Event {
enum ResponseType { TEXT, IMAGE }
class StartReasoning(...) : Event
class StartResponse(val type: ResponseType) : Event
class AppendText(val body: String) : Event
class AppendImage(val body: ByteArray, val mime: String) : Event
class End(...) : Event
class Interrupted(...) : Event
class Error(val message: String, val code: Int? = null) : Event
class ToolCall(...) : Event
class ToolResult(...) : Event
}
```
Иерархии:
## Чего здесь НЕТ
- `Content` — `Text` / `Image(data: ByteArray, mime: String)`
- `Message` — `UserMessage` / `AssistantMessage` (оба `Body`) + `ToolCall(id, name, args)` / `ToolResult(id, result)` / `System` для метаданных
- `Event` — `StartReasoning` / `StartResponse(type)` / `AppendText` / `AppendImage` / `End` / `Interrupted` / `Error`
- `AgentEvent` — `Created(id)` / `Deleted(id)` / `Renamed(id, title)`
- Никакого HTTP/SSE/JSON. Это контракт. Сериализация живёт в `:server`
и `:client`.
- Никакого хранения. Реализации `MessageStore` живут в `:storage-*`.
- Никакой логики прерывания / инструментов / LLM-вызовов. Это всё
внутри `:standalone` (ChatAgent) и выше.
Live-стримы (`events(after)`) **не реплеят** прошедшие события — клиент должен
сам вызвать `getMessages(after)` для бэкфилла, либо подписаться на `events(after=now)`
и начать рисовать с настоящего момента.
## Тесты
## Подключение
Артефакт — `pw.binom.agentik:proto:VERSION`.
```kotlin
// commonMain
implementation("pw.binom.agentik:proto:$version")
// JVM-only
implementation("pw.binom.agentik:proto-jvm:$version")
// Любой KMP-таргет
implementation("pw.binom.agentik:proto-macosArm64:$version")
```
./gradlew :proto:jvmTest
./gradlew :proto:allTests # дополнительно linuxX64 (если Linux) / iosSimulator (если macOS)
```
`version` синхронизируется с `gradle.properties` (`version=0.1.0`) и
прокидывается через `-Pversion=...` в CI (`binom.repo.*` для Nexus).
## Текущий статус
## Где смотреть версии
- текущая разрабатываемая: `gradle.properties` → `version=0.1.0`
- история релизов: `https://git.binom.pw/subochev/agentik/releases`
- опубликованные артефакты: Nexus-репозиторий `caffeine`
(`http://nexus.xx/repository/caffeine/pw/binom/agentik/proto/`)
## Сборка / тесты
```bash
./gradlew :proto:build # все KMP-таргеты + тесты
./gradlew :proto:jvmTest # только JVM-тесты
./gradlew :proto:publishToMavenLocal # для локального потребления
```
KMP-таргеты: `jvm + macosX64 + macosArm64 + iosX64 + iosArm64 + iosSimulatorArm64 + linuxX64 + linuxArm64 + mingwX64`.
Зависимостей минимум: `kotlinx-coroutines-core:1.11.0` + `kotlinx-datetime:0.8.0` (оба `api`).
Используется продакшеном. Иммутабельный API (после рефакторинга из
AG-UI). Возможные будущие расширения: typed tool-result, multi-modal
contents, server-pushed references — все обсуждаются через общий
[IRC-QUESTIONS.md](../IRC-QUESTIONS.md).