EventStore (persistent event log) и AllEvent (sealed wrapper для
третьего типа подписки — ВСЕ events в одном потоке). Touches 7 modules.
Архитектура:
Producer (ChatAgent + ConversationEvents) → EventStore + SharedFlow
↓ ↓
Live SSE (cold, no replay) Replay endpoints (cursor-based)
(1) :storage-core — EventStore interface
- append(record): idempotent по record.id (INSERT OR IGNORE)
- query(conversationId?, afterId?, limit): пагинированный catchup
- pruneOlderThan(instant): TTL cleanup
- count(): maintenance метрика
- @Serializable EventRecord(id, conversationId?, createdAt, type, payload)
- enum EventType: AGENT_*/CONVERSATION_* (forward-compat fallback)
- StorageBundle дополнен eventStore: EventStore? = null (backward-compat)
(2) :storage-inmemory — InMemoryEventStore
- Thread-safe (Mutex), binarySearch для упорядоченной вставки
- Записи сортируются по createdAt ASC, ties по id ASC (стабильно)
- Idempotency по id (повторный append no-op)
(3) :storage-sqlite — SqliteEventStore
- sqldelight schema: agent_event (id PK, conversation_id?, created_at,
type, payload BLOB) + 2 индекса (conversation_id+created_at,
created_at)
- Миграция v3: CREATE TABLE IF NOT EXISTS (additive)
- 5 запросов: insert, queryGlobal, queryByConv, pruneOlderThan, count
- Forward-compat: неизвестный EventType в БД → fallback AGENT_CREATED
(чтобы старые клиенты не падали на новых enum values)
- Добавлен в SqliteStores (open/inMemory + asBundle())
(4) :standalone — Producer wiring
- ChatAgent.persistAgentEvent() — fire-and-forget append при каждом
AgentEvent (Created/Deleted/Renamed)
- ConversationEvents — персистит в EventStore при каждом tryEmit/emit
(концертный случай от connect disconnect)
- ChatAgent.allEvents() — merge agent-events + snapshot всех живых
диалогов в единый Flow<AllEvent>
(5) :proto — AllEvent sealed interface
- AllEvent.Agent(date, event: AgentEvent)
- AllEvent.Conversation(date, conversationId, event: Event)
- Agent.allEvents(after): Flow<AllEvent> — третий тип подписки
(в дополнение к events() и Conversation.events)
(6) :server — Endpoints
- GET /events/all — SSE поток AllEvent (cold)
- GET /events/replay?after_id=&limit= — пагинированный catchup
(503 если EventStore не сконфигурирован)
- GET /conversations/{id}/events/replay?after_id=&limit= — то же per-conv
- Module.kt принимает eventStore: EventStore? параметром
(7) :client — Client API
- AgentClient.allEvents(after) — подписка на /events/all SSE
- AgentClient.replayAllEvents(afterId, limit) — catchup /events/replay
- AgentClient.replayConversationEvents(convId, afterId, limit)
- EventRecordDto — wire-зеркало EventRecord (клиент не зависит
от :storage-core, определяет DTO локально; формат совместим с
серверным JSON)
Тесты: 22 новых теста (12 InMemory + 10 Sqlite), все зелёные.
Все три слоя синхронизированы: proto contract + standalone impl +
server endpoint + client API.
:standalone — single-jar HTTP-сервер со всеми транспортами
Что это
Главный исполняемый модуль проекта — single-jar HTTP-сервер с:
- AG-UI transport на
POST /agui(SSE) +GET /health. - A2A transport на
POST /(JSON-RPC) +GET /.well-known/agent-card.json. :prototransport на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
java --enable-native-access=ALL-UNNAMED \
-jar agentik-0.1.0-all.jar
С дефолтами — встроенный SQLite, OpenAI-compatible backend на
http://localhost:8001/v1, порт 8080.
Запуск через Gradle (dev)
./gradlew :standalone:run
pull-model subcommand (для LiteRT)
# Сначала скачать модель под 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 |
Значения читаются через AgentikConfig.fromEnv() в :standalone/.../Main.kt.
Эндпоинты
| Метод | Путь | Transport | Описание |
|---|---|---|---|
GET |
/health |
любой | health-check ({"ok":true}) |
POST |
/agui |
AG-UI | Стриминг run (SSE) |
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
Известные ограничения
- vLLM не поддерживает cancel-inference (
interrupt()только закрывает client SSE-socket; бэкенд всё равно генерирует до конца). - A2A JSON discriminator —
"kind"(text/file/data), а не"type". См.A2aJsonв:standalone. - SSE в не-TTY ssh закрывается на default Ktor timeout.
Текущий статус
Production-ready. Все KMP-модули проекта интегрированы. Полный
manual-test checklist смотрите в MANUAL-TESTS.md
или MANUAL-TESTS.md в корне.
Где скачать
- Source:
git clone https://git.binom.pw/subochev/agentik - Fatjar: Gitea CI artifacts (через
.gitea/workflows/release.ymlна tagv*) или собирается через./gradlew :standalone:shadowJar.
Версии
Все gradle/libs.versions.toml. Поднять версию → release через
git tag v0.2.0 && git push --tags → CI собирает все KMP-таргеты
публикует артефакты.