docs: per-module READMEs (run vs library) + root navigation hub
ci / JVM build + tests (pull_request) Failing after 54s
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:
+106
-67
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user