# `:proto` — протокол общения с агентом (KMP, jvm + native) ## Что это Типы и контракт in-house протокола `agentik`, заменившего AG-UI: - **stateful** — сервер сам владеет диалогом; клиент шлёт только новые сообщения, а не всю историю (в отличие от AG-UI, где клиент обязан повторять `messages[]` каждый раз). - **declarative история vs. события** — `Message` это то, что уже легло в БД, `Event` это live-стрим от агента во время `send()` или `events()`. - **чистые интерфейсы** — никаких сетевых и storage зависимостей внутри `:proto`; это контракт. Решает проблему: AG-UI клиент вынужден каждый раз знать и пересобирать полную историю, а его серверная часть (`AbstractAgent`) — постоянно сериализовать-десериализовать всю переписку. В `:proto` сервер один, контракт тонкий, переписка персистится нативно (SQLite, файлы, что хотите). Можно подменить front-end или back-end, протокол остаётся. ## Где используется - `:server` — Ktor-фасад, маппит `Agent` ↔ HTTP/SSE. - `:client` — Ktor-клиент, маппит HTTP/SSE ↔ `Agent/Conversation`. - `:agentik-cli` — работает поверх `:client`, а следовательно поверх `:proto`. *(`:agentik-tui` был исключён из сборки 2026-09-17.)* - `: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:///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`. ## Основные типы `:proto` — **тонкий** контракт: сами интерфейсы `Agent`/`Conversation`/`Message`, а общие типы содержимого и события живут в нижележащих модулях: `Content`/`MessageContext`/`MessageOrigin`/`TurnTokens` — в `:content-api`, `Event`/`OnlineEvent`/`OutboxStore`/`OnlineOutbox` — в `:outbox-api`. ```kotlin interface Agent : AutoCloseable { val id: String val info: AgentInfo val journal: JournalStore // append-only audit (read-only) val outbox: OutboxStore // durable-события: catchup+live по курсору val onlineOutbox: OnlineOutbox // live-only: стриминг, без курсора val conversationStore: ConversationStore fun createConversation(temp: Boolean): Conversation suspend fun getConversation(id: String): Conversation? suspend fun deleteConversation(id: String): Boolean suspend fun renameConversation(id: String, title: String?): Instant? } interface Conversation : AutoCloseable { val id: String val updatedAt: Instant val isTemporal: Boolean val title: String? val isSupportImageInput: Boolean val isSupportImageOutput: Boolean suspend fun send(content: List, context: MessageContext? = null) // fire-and-forget suspend fun getMessages(after: Instant, offset: Int, limit: Int): List suspend fun rename(title: String) suspend fun interrupt() } // :content-api sealed interface Content { data class Text(val body: String) : Content data class Image(val data: ByteArray, val mime: String) : Content } enum class MessageOrigin { USER, SYSTEM, EVENT } data class MessageContext(origin: MessageOrigin, description: String?, sourceId: String?, metadata: JsonElement?) // :proto sealed interface Message { val id: String val date: Instant class UserMessage(id, content: List, date, context: MessageContext?) : Message class AssistantMessage(id, content: List, date, tokens: TurnTokens?, reasoning: String?) : Message class ToolCall(...) : Message class ToolResult(...) : Message class Error(...) : Message } // :outbox-api — durable (перезапрашиваются по курсору `after`) sealed interface Event { val date: Instant class UserMessage(date, id, content: List, context: MessageContext?) : Event class AssistantMessage(date, id, content: List, reasoning: String?, tokens: TurnTokens?) : Event class ToolCall(...) : Event class ToolResult(...) : Event class ToolFailed(...) : Event class Interrupted(date) : Event class Error(date, message, code) : Event class ConversationClosing(...) : Event class CompactionTriggered(...) : Event } // :outbox-api — online (live-only, НИКОГДА не сохраняются) sealed interface OnlineEvent { val date: Instant enum ResponseType { TEXT, IMAGE } class Working(date) : OnlineEvent class End(date) : OnlineEvent class StartReasoning(date) : OnlineEvent class StartResponse(date, responseType: ResponseType) : OnlineEvent class AppendText(date, body: String) : OnlineEvent class AppendImage(date, body: ByteArray, mime: String) : OnlineEvent } ``` Ход в терминах маркеров: онлайн `Working` → … → онлайн `End`; durable — `UserMessage` в начале и `AssistantMessage`/`Interrupted`/`Error` в конце. ## Чего здесь НЕТ - Никакого HTTP/SSE/JSON. Это контракт. Сериализация живёт в `:server` и `:client`. - Никакого хранения. Реализации `JournalStore` живут в `:storage-*`. - Никакой логики прерывания / инструментов / LLM-вызовов. Это всё внутри `:standalone` (ChatAgent) и выше. ## Тесты ``` ./gradlew :proto:jvmTest ./gradlew :proto:allTests # дополнительно linuxX64 (если Linux) / iosSimulator (если macOS) ``` ## Текущий статус Используется продакшеном. Иммутабельный API (после рефакторинга из AG-UI). Возможные будущие расширения: typed tool-result, multi-modal contents, server-pushed references — все обсуждаются через общий [IRC-QUESTIONS.md](../IRC-QUESTIONS.md).