# `:standalone` — single-jar HTTP-сервер со всеми транспортами ## Что это Главный исполняемый модуль проекта — single-jar HTTP-сервер с: - **A2A** transport на `POST /` (JSON-RPC) + `GET /.well-known/agent-card.json`. - **`:proto`** transport на `POST /agentik/*` (HTTP+JSON+SSE) — наш stateful. - **Embedded LLM backend**: `GOOGLE` (LiteRT) или `OPENAI`-совместимый (vLLM, LiteLLM, OpenAI API). - **SQLite persistence** через `:storage-sqlite`. - **Memory backend**: `md` (файловый) или `vector` (SQLite+JVector+ HTTP/SIGLIP-embeddings). - **Skills** из `~/.agentik/skills/*.md`. - **SOUL** из `~/.agentik/SOUL.md`. - **Background подпроцессы**: рефлексия, skill-mining, memory-reviewer. Решает: даёт пользователю один JAR (10–250 МБ), который запускается через `java -jar agentik-0.1.0-all.jar`, и поднимает сразу все транспорты, которые другие системы могут хавать. ## Как запустить ### Требования - JVM 21+. - (Опционально) CUDA-устройство для `:backend=google` (LiteRT). - (Опционально) LM через OpenAI-совместимый endpoint (vLLM / Ollama / OpenAI) для `:backend=openai`. ### Запуск из готового fatjar ```bash java --enable-native-access=ALL-UNNAMED \ -jar agentik-0.1.0-all.jar ``` С дефолтами — встроенный SQLite, OpenAI-compatible backend на `http://localhost:8001/v1`, порт 8080. ### Запуск через Gradle (dev) ```bash ./gradlew :standalone:run ``` ### `pull-model` subcommand (для LiteRT) ```bash # Сначала скачать модель под LiteRT-Gemma-4-E2B AGENTIK_LLM_BACKEND=google \ AGENTIK_GOOGLE_MODEL_PATH=/root/gemma-4-E2B-it.litertlm \ java --enable-native-access=ALL-UNNAMED \ -jar agentik-0.1.0-all.jar pull-model ``` Скачивает `https://static.binom.pw/models/gemma-4-E2B-it.litertlm` (2.5 ГБ, с Range-resume). Поддерживает override через `AGENTIK_GOOGLE_MODEL_URL` и verify через `AGENTIK_GOOGLE_MODEL_SHA256_URL`. ## Переменные среды Полный список — общий для всего `:standalone`-процесса: | Env | Default | Что делает | |---|---|---| | `AGENTIK_PORT` | `8080` | Порт HTTP-сервера | | `AGENTIK_DB_PATH` | `./agentik.db` | Путь к SQLite | | `AGENTIK_TOKEN` | (пусто) | Bearer-токен для HTTP-фасада `/agentik`. Пусто — авторизация выключена | | `AGENTIK_A2A_TOKEN` | (пусто) | Bearer-токен для A2A-фасада `/a2a`. Пусто — авторизация выключена (независим от `AGENTIK_TOKEN`) | | `AGENTIK_AGENT_ID` | `agentik` | ID агента (для multi-instance) | | `AGENTIK_LLM_BACKEND` | `openai` | `openai` или `google` | | `AGENTIK_LLM_MODEL` | (выбирается по backend) | Имя модели | | `AGENTIK_LLM_API_URL` | `http://localhost:8001/v1` | Endpoint для OpenAI-compatible | | `AGENTIK_LLM_API_KEY` | `no-key-needed` | Auth header | | `AGENTIK_LLM_CONTEXT_TOKENS` | `115000` | Сколько токенов остаётся модели | | `AGENTIK_GOOGLE_MODEL_PATH` | `/root/gemma-4-E2B-it.litertlm` | Путь к `.litertlm` файлу | | `AGENTIK_GOOGLE_MODEL_URL` | `https://static.binom.pw/models/gemma-4-E2B-it.litertlm` | Откуда скачивать | | `AGENTIK_GOOGLE_MODEL_SHA256_URL` | — | Если задан — verify по SHA-256 | | `AGENTIK_AUTO_DOWNLOAD_MODEL` | `0` | `1` = скачать модель если её нет | | `AGENTIK_MEMORY_BACKEND` | `vector` | `md`, `vector` или `off` | | `AGENTIK_EMBEDDING_BACKEND` | `http` | `http` или `siglip` (только для `vector`) | | `AGENTIK_EMBEDDING_API_URL` | `http://localhost:8001/v1` | Endpoint для эмбеддингов | | `AGENTIK_EMBEDDING_MODEL` | `text-embedding-3-small` | Имя embedding-модели | | `AGENTIK_SOUL_PATH` | `~/.agentik/SOUL.md` | Путь к SOUL.md | | `AGENTIK_SKILLS_DIR` | `~/.agentik/skills/` | Каталог SKILL.md | | `AGENTIK_MEMORY_DIR` | `~/.agentik/memory/` | Каталог для md-памяти | | `AGENTIK_TOOLSETS_DEFAULT` | `memory,skills,files,web` | Включённые тулы | | `AGENTIK_DEBUG` | `0` | `1` = verbose logging | | `AGENTIK_TELEGRAM_TOKEN` | (пусто) | Bot token от `@BotFather`. Пусто — Telegram-интеграция выключена, standalone работает ровно как без неё | | `AGENTIK_TELEGRAM_POLLING_TIMEOUT` | `30` | Long-poll timeout в секундах. Bot API держит HTTP-соединение открытым до N секунд, возвращает 200 OK с пустым массивом если апдейтов нет. Меньше — больше polling-нагрузки, больше — дольше «висит» соединение. 25–35 обычно ок | | `AGENTIK_TELEGRAM_PERSISTENT` | `true` | `true` → диалог из чата сохраняется в SQLite как обычный `ConversationRecord` и переживает рестарт. `false` → каждый Telegram-message стартует свежий `temp=true` диалог (как «temporary chat» в Android-клиенте) | Значения читаются через `AgentikConfig.fromEnv()` в `:standalone/.../Main.kt`. ## Telegram-интеграция Опциональный «мост» между Telegram-чатом и standalone-агентом. Реализован отдельным модулем `:integrations-telegram` и подключается ТОЛЬКО при заданном `AGENTIK_TELEGRAM_TOKEN` (нет токена — нет polling'а, нет зависимости в classpath при выключенном флаге, см. ниже). ### Что умеет - Long-polling через Bot API (`getUpdates?timeout=N`). Не webhook — не нужен публичный URL, всё работает за NAT. - Маппинг `chatId ↔ ConversationId` в общей SQLite-БД standalone'а (таблица `tg_chat_map`). Один Telegram-чат = один диалог с агентом; сообщения из чата стримят ответы обратно через `editMessageText`. - Group-чаты (`group`, `supergroup`) и команда `/new` стартуют **новый временный** диалог (`ConversationRecord.isTemporal = true`) — не пишут в мапу, удаляются при следующем `/new`. - Tool-call показывает «⚙️ обрабатываю…» через `sendChatAction(typing)` — включается на `onlineOutbox.onlineEvents` пока обрабатывается ход. - Ошибки агента стримятся в чат одной строкой, polling не падает. ### Как включить 1. Создать бота через `@BotFather`, скопировать токен. 2. Запустить standalone с токеном: ```bash AGENTIK_TELEGRAM_TOKEN=123456:ABC-DEF... \ java --enable-native-access=ALL-UNNAMED \ -jar agentik-0.1.0-all.jar ``` 3. Написать боту в личку (или добавить в группу). В логе standalone'а появится: ``` telegram: enabled (polling timeout=30s, persistent=true) ``` 4. Ответы приходят стримингом — `editMessageText` обновляет одно и то же сообщение каждые ~1с пока агент генерирует. ### Persistent vs temporary | Сценарий | `AGENTIK_TELEGRAM_PERSISTENT` | Поведение | |---|---|---| | Личка с ботом | `true` (default) | Один диалог на пользователя, переживает рестарт, `/new` стартует новый | | Личка с ботом | `false` | Каждое сообщение — свежий `temp` диалог | | Group-чат | (любое) | Всегда `temp` — каждый message может быть от другого юзера, диалоги не персистятся | | `/new` | (любое) | Стартует новый `temp` диалог (для `persistent=true` это закрывает старый `ConversationRecord`) | ### Технические заметки - Либа — своя, [`caffeine-mgn/telegramClient`](https://github.com/caffeine-mgn/telegramClient) (`pw.binom.telegram:telegramClient`), опубликованная через `publishToMavenLocal`. Артефакт `pw.binom.telegram.*` НЕ утекает в classpath standalone'а — host-сайд видит только фабрику `createTelegramBridgeComponent`, которая возвращает `AutoCloseable`. - Polling/stream-джобы живут на отдельном `integrationsScope`. В shutdown-hook'е он отменяется **до** `agent.close()` — иначе long-poll держит соединение до `AGENTIK_TELEGRAM_POLLING_TIMEOUT` и блокирует JVM-выход. - Один бот на standalone (таблица `tg_chat_map` глобальная). Если когда-нибудь захочется multi-agent + multi-bot — мапу надо будет разводить по агентам. ## Эндпоинты | Метод | Путь | Transport | Описание | |---|---|---|---| | `GET` | `/health` | любой | health-check (`{"ok":true}`) | | `POST` | `/` | A2A | JSON-RPC `message/send`, `tasks/get`, `tasks/cancel` | | `GET` | `/.well-known/agent-card.json` | A2A | Discovery | | `POST` | `/agentik/conversations` | :proto | Создать диалог | | `GET` | `/agentik/conversations` | :proto | Список диалогов | | `GET` | `/agentik/conversations/:id` | :proto | Snapshot | | `GET` | `/agentik/conversations/:id/messages` | :proto | История | | `POST` | `/agentik/conversations/:id/send` | :proto | Send (SSE) | | `GET` | `/agentik/conversations/:id/events` | :proto | Live-events (SSE) | | `POST` | `/agentik/conversations/:id/interrupt` | :proto | Прервать | | `POST` | `/agentik/conversations/:id/rename` | :proto | Переименовать | | `DELETE` | `/agentik/conversations/:id` | :proto | Удалить | ## Тесты ``` ./gradlew :standalone:jvmTest # unit-тесты ./gradlew :standalone:integrationTest # integration (Testcontainers) ./gradlew :standalone:shadowJar # → build/libs/agentik-0.1.0-all.jar ``` ## Известные ограничения 1. **vLLM не поддерживает cancel-inference** (`interrupt()` только закрывает client SSE-socket; бэкенд всё равно генерирует до конца). 2. **A2A JSON discriminator — `"kind"`** (text/file/data), а не `"type"`. См. `A2aJson` в `:standalone`. 3. **SSE в не-TTY ssh закрывается на default Ktor timeout**. ## Текущий статус Production-ready. Все KMP-модули проекта интегрированы. Полный manual-test checklist смотрите в [`MANUAL-TESTS.md`](../../MANUAL-TESTS.md) или `MANUAL-TESTS.md` в корне. ## Где скачать - **Source**: `git clone https://git.binom.pw/subochev/agentik` - **Fatjar**: Gitea CI artifacts (через `.gitea/workflows/release.yml` на tag `v*`) или собирается через `./gradlew :standalone:shadowJar`. ## Версии Все `gradle/libs.versions.toml`. Поднять версию → release через `git tag v0.2.0 && git push --tags` → CI собирает все KMP-таргеты публикует артефакты.