a3581abf84
Major additions: * :proto (KMP submodule) — in-house stateful protocol replacing AG-UI. Agent owns conversation transcript; Conversation.events(after) is a live, replay-free stream; backfill via Conversation.getMessages(after, offset, limit). Each Event carries an Instant date for client-side resume tracking. Sealed hierarchies (Content/Message/Event/AgentEvent) annotated @Serializable with snake_case @SerialName JSON discriminators so the wire format is decoupled from Kotlin class names. * :server (JVM, Ktor 3.1.3) — REST+SSE facade for Agent. Public entry: Route.agentikAgent(agent, path = "/agentik"). Endpoints: create/list/get/patch/delete conversations, POST messages (202), POST interrupt, GET messages, GET conversation events (SSE), GET agent events (SSE), GET /health. Custom Instant serializer for kotlin.time.Instant registered contextually on agentikJson (ISO-8601, ignoreUnknownKeys=true, explicitNulls=false). * :client (JVM, Ktor HTTP Client + CIO) — mirror of :server returning a pw.binom.agentik.proto.Agent backed by HTTP calls. Custom SSE parser since ktor-client-sse is not on the 3.1.3 client classpath. * standalone — EchoProtoAgent (in-memory Agent for :proto), EchoAgent (existing AG-UI echo), both mounted on the same Netty embedded server on port 8080 (/agui and /agentik); A2A stays on its own CIO engine on 8081. EchoProtoAgent smoke-tested end-to-end against :server: all 11 endpoints, including live SSE delivery of StartResponse/AppendText/End event triplets and Agent-level Created/Deleted events. Design notes pinned in: * agentik/IRC-QUESTIONS.md — closed 13-item checklist for the upcoming :irc-server transport (channel = conversation, CTCP for structural events, draft/chathistory for backfill, ImageStore side-channel, etc). * docs/ARCHITECTURE.md — overall layout snapshot.
41 lines
5.0 KiB
Markdown
41 lines
5.0 KiB
Markdown
# IRC-транспорт — открытые вопросы
|
||
|
||
По мере закрытия отмечаем `- N. [x]`. Закрытый вопрос остаётся в файле с принятым решением.
|
||
|
||
- 1. [x] **История.** Принято: новый абстрактный метод `suspend fun getLatestMessages(offset: Int, limit: Int): List<Message>` в `:proto.Conversation` (offset = пропустить С КОНЦА, 0 = самые свежие). IRC-сервер не держит своего буфера, на `CHATHISTORY` дёргает агента. CAP `draft/chathistory` объявляем.
|
||
- 2. [x] **Tool/Error/Image события — раскладка по IRC.** Принято. Каждый `Event` мапится:
|
||
- `StartReasoning` → дропаем с провода
|
||
- `StartResponse(TEXT|IMAGE)` → CTCP `AGENTIK response-start {"type":"text"|"image"}`
|
||
- `AppendText(body)` → `PRIVMSG #chan :body`
|
||
- `AppendImage(body, mime)` → через `ImageStore` → CTCP `AGENTIK image {"url":..,"mime":..,"ttl":..}`
|
||
- `ToolCall` → CTCP `AGENTIK tool-call {json}`
|
||
- `ToolResult` → CTCP `AGENTIK tool-result {json}`
|
||
- `Error` → CTCP `AGENTIK error {json}`
|
||
- `End` → CTCP `AGENTIK end`
|
||
- `Interrupted` → CTCP `AGENTIK interrupted`
|
||
- 3. [x] **Interrupt.** **Упрощение:** команды `/stop` и `/interrupt` в `PRIVMSG` (т.е. `PRIVMSG #chan :/stop`) вызывают `Conversation.interrupt()`. Если `PRIVMSG` приходит во время активного размышления — сервер сначала зовёт `interrupt()`, затем `send(content)`. CTCP-вариант дропаем.
|
||
- 4. [x] **`AgentEvent.Created/Deleted/Renamed` маппинг.** Принято: Created = IRC `JOIN`-бродкаст; Deleted = `KICK` самого себя; Renamed = `TOPIC #foo :new title`.
|
||
- 5. [x] **NICK агента.** Принято: параметр в DSL, дефолт `"Agent"`.
|
||
- 6. [x] **Multi-user в канале.** Принято: **в канале всегда только наш агент и наш пользователь. Других не будет никогда.**
|
||
- 7. [x] **Маппинг канал ↔ Conversation.** Принято: имя IRC-канала = `Conversation.title`; `Conversation.id` = UUID, выдаётся через `CTCP AGENTIK id #foo`; при переименовании канала id стабилен.
|
||
- 8. [x] **Создание канала.** Принято: `JOIN #foo` → создаём `Conversation(title="foo", id=<uuid>)`. Если уже есть — заходим.
|
||
- 9. [x] **Удаление канала.** Принято: `PART` закрывает сторону клиента; `CTCP AGENTIK delete #foo` — удаление Conversation-а.
|
||
- 10. [x] **Модуль.** Принято: `:irc-server`, KMP через kotlinx-io. Также модуль содержит HTTP staging-эндпоинт для картинок (см. п.13).
|
||
- 11. [x] **Аутентификация клиента.** Принято: без auth, любой может подключиться.
|
||
- 12. [x] **Capabilities (ircv3).** Принято: в первом проходе объявляем `server-time`, `message-tags`, `batch`, `draft/chathistory`. SASL не объявляем (п.11). Остальные CAPs (echo-message, labeled-response, standard-replies, multi-user stuff) добавляем инкрементально.
|
||
- 13. [x] **ImageStore.** Принято: `ImageStore` живёт в `:irc-server`, дефолтная in-memory реализация с **TTL 600 сек**, staging-порт **авто-pick** (0 → свободный). Клиент через IRC картинки **не шлёт** (для этого HTTP `:server`). Конкретную реализацию `ImageStore` пользователь сделает позже сам, в первом проходе — наша in-memory.
|
||
|
||
## Все вопросы закрыты
|
||
|
||
Итого решений по `:irc-server`:
|
||
- Модуль `:irc-server`, KMP через kotlinx-io.
|
||
- Канал IRC = `Conversation` (имя = title, UUID через CTCP `AGENTIK id`).
|
||
- В канале всегда только 1 пользователь + агент (ник `Agent` по умолчанию).
|
||
- `Event` → IRC: `PRIVMSG` для текста, CTCP `AGENTIK <имя> {json}` для всего остального. `StartReasoning` дропается.
|
||
- Interrupt через `/stop` / `/interrupt` в PRIVMSG; входящий PRIVMSG во время размышления = `interrupt()` + `send()`.
|
||
- История через `CHATHISTORY` (LATEST/BEFORE/BETWEEN/AFTER), сервер не буферизует, дёргает новый `:proto` метод `getLatestMessages(offset, limit)`.
|
||
- Картинки только agent → client через `ImageStore` + HTTP staging в том же модуле.
|
||
- CAPs: `server-time`, `message-tags`, `batch`, `draft/chathistory`. Без auth.
|
||
|
||
Можно кодить.
|