- :memory-api — общий контракт MemoryStore/Prefetcher/Reviewer/Tools/MemorySystem
- :memory-md (KMP, kotlinx-io) — Hermes-style §-файлы, keyword overlap
- :memory-vector (JVM-only) — JVector ANN + SQLite + HttpEmbeddingClient
- :standalone — AGENTIK_MEMORY_BACKEND={md,vector,off}, выбор в Main.kt
- :standalone — compaction рабочего контекста (LiteLlmContextCompactor + reviewPreCompaction)
- :proto — MessageContext (origin: user/system/event) на send и в Message
- :server — backward-compat dual-format для POST /messages
- README — env-vars, vector-бэкенд docs
16 KiB
:standalone — agentik single-jar server
Self-contained HTTP-сервер с Ktor: AG-UI / A2A / :proto транспорты на одном порту, встроенный SQLite для истории диалогов, долговременная память (Hermes-style §-файлы), загрузка MCP-инструментов, навыков (SKILL.md) и персоны (SOUL.md).
Сборка
# Полная сборка всего проекта + fatjar
./gradlew assemble
# Только fatjar :standalone (≈ 150 MB)
./gradlew :standalone:shadowJar
# Результат:
# standalone/build/libs/standalone-all.jar
Запуск
java -jar standalone/build/libs/standalone-all.jar
По умолчанию слушает на http://localhost:8080. Healthcheck: GET /health.
Транспорты на одном порту:
GET /health— livenessPOST /agentik/conversations— создать беседуGET /agentik/conversations/{id}/events— SSE-стрим ответов
Переменные окружения
Все переменные читаются AgentikConfig.fromEnv(). Бланк или отсутствие → дефолт.
| Переменная | Дефолт | Назначение |
|---|---|---|
AGENTIK_PORT |
8080 |
Порт HTTP-сервера |
AGENTIK_DB_PATH |
./agentik.db |
Путь к SQLite (история бесед + метаданные памяти) |
AGENTIK_SKILLS_DIR |
выкл. | Каталог со скилами (SKILL.md / *.yaml) |
AGENTIK_MEMORY_DIR |
~/.agentik/memory (md) или AGENTIK_DB_PATH (vector) |
Каталог памяти (md); "off" отключает |
AGENTIK_MEMORY_BACKEND |
md |
md (Hermes-style §-файлы) / vector (SQLite + JVector + LLM-эмбеддинги) / off |
AGENTIK_EMBEDDING_MODEL |
text-embedding-3-small |
Модель эмбеддингов для vector-бэкенда |
AGENTIK_EMBEDDING_DIMENSION |
1536 |
Размерность вектора (должна совпадать с моделью) |
AGENTIK_SOUL |
выкл. | Путь к SOUL.md — файл персоны (markdown), вставляется в начало system prompt |
AGENTIK_LLM_BACKEND |
— | openai или google (см. ниже) |
AGENTIK_MCP_CONFIG |
выкл. | Путь к JSON со списком MCP-серверов |
AGENTIK_SYSTEM_PROMPT |
be brief |
Базовый system prompt |
OPENAI_CONTEXT_WINDOW |
выкл. | Лимит контекстного окна в токенах (для compaction'а) |
AGENTIK_GOOGLE_CONTEXT_WINDOW |
выкл. | То же для Google backend |
AGENTIK_COMPRESSION_THRESHOLD |
0.8 |
Доля лимита, при которой запускается compaction |
OpenAI backend
| Переменная | Обязательна | Назначение |
|---|---|---|
OPENAI_BASE_URL |
да | Например, https://api.openai.com/v1 |
OPENAI_API_KEY |
да | API key |
OPENAI_MODEL |
да | Имя модели (gpt-4o-mini и т.п.) |
Google backend
| Переменная | Обязательна | Назначение |
|---|---|---|
AGENTIK_GOOGLE_MODEL_PATH |
да | Путь к .litertlm файлу |
SQLite: путь к базе диалогов
AGENTIK_DB_PATH указывает на файл SQLite, в котором хранятся таблицы
conversations, messages, working_memory. SQLDelight-драйвер создаёт
файл при первом запуске. Если в пути есть несуществующие директории — их
нужно создать заранее (mkdir -p).
# Абсолютный путь
AGENTIK_DB_PATH=/var/lib/agentik/state.db
# Относительный путь — резолвится от CWD
cd /opt/agentik && AGENTIK_DB_PATH=./data/state.db
# Временная база (на RAM, теряется при рестарте) — не поддерживается напрямую,
# но можно подменить в коде через SqliteStores.inMemory().
Файл базы — обычный SQLite, можно инспектировать sqlite3 CLI или Adminer.
Контекст инициации сообщения (MessageContext)
Каждое user-сообщение может нести контекст инициации хода —
кто/что его вызвало. Для обычного user-сообщения поле context опускается
(обратная совместимость: старые клиенты, шлющие голый массив Content,
работают как раньше). Для cron/webhook/system событий context обязательно.
// POST /agentik/conversations/{id}/messages — новый формат
{
"content": [{"type": "text", "body": "wake up"}],
"context": {
"origin": "event",
"description": "scheduled cron morning-briefing",
"sourceId": "cron-42",
"metadata": { "scheduledAt": "2026-09-14T08:00:00Z" }
}
}
// Старый формат (всё ещё работает) — голый массив
[{"type": "text", "body": "hello"}]
origin |
Когда использовать | Префикс в LLM |
|---|---|---|
user |
Обычное сообщение из чата (дефолт) | нет |
system |
Программное сообщение (старт агента, режим обслуживания) | [SYSTEM] description (sourceId=…) |
event |
Cron, webhook, file-changed, внешний триггер | [EVENT] description (sourceId=…) |
Семантика:
origin— кто/что инициировал ход.user= человек в чате (UI/IRC/HTTP).description— короткая человекочитаемая фраза для модели (обязательна дляsystem/event).sourceId— id cron-job'а / webhook endpoint'а / IRC-канала, помогает в логах и при ручном разборе.metadata— произвольный JSON, никогда не попадает в LLM-нагрузку (только в audit log для пост-аналитики).
Что происходит при не-USER origin'е:
В working memory текст user-сообщения предваряется префиксом —
например, [EVENT] scheduled cron morning-briefing (sourceId=cron-42)\n….
Модель видит, что её разбудил не пользователь, и может реагировать
иначе (например, не начинать диалог с приветствия). Префикс добавляется
только к LLM-нагрузке, в audit log и при getMessages возвращается
оригинальный текст + context отдельно.
Сжатие рабочего контекста (compaction)
Когда диалог становится длинным, working memory диалога может превысить
контекстное окно модели. Чтобы этого не случилось, агент умеет сжимать
старые ходы в один синтетический Summary-блок.
Включается только при заданном лимите. Никакого автодетекта по имени модели — если лимит не задан, агент не сжимает.
# OpenAI-совместимый бэкенд
export OPENAI_CONTEXT_WINDOW=128000
# Или Google / LiteRT
export AGENTIK_GOOGLE_CONTEXT_WINDOW=32000
AGENTIK_COMPRESSION_THRESHOLD — доля лимита, при которой запускается
compaction (дефолт 0.8 = 80%):
export AGENTIK_COMPRESSION_THRESHOLD=0.7 # сжимаем раньше
Что происходит при compaction:
- Перед
send()оценивается количество токенов в системном промпте + history- tools (грубая оценка
chars / 4).
- tools (грубая оценка
- Если
estimated / contextWindow ≥ threshold— асинхронный шаг:- Старые ходы (User/Assistant, кроме последних 4) скармливаются в
LiteLlmContextCompactor— отдельный one-shot LLM-вызов с промптом «Goal / Active / Resolved / Blocked / Remaining». - Параллельно
MemoryReviewer.reviewPreCompactionизвлекает из старых ходов факты и кладёт их в долговременную память (триггерMemoryStore). - Атомарный
working_memory.compact(fromIdx, summary)— старые строки удаляются, на их место вставляется однаSummaryзапись. LiteConversationпересоздаётся с обновлённым контекстом.
- Старые ходы (User/Assistant, кроме последних 4) скармливаются в
Если после compaction оценка всё ещё выше порога — выводится warning, но
нового compaction не запускается (защита от зацикливания). Решение —
поднять OPENAI_CONTEXT_WINDOW или понизить threshold.
Память
AGENTIK_MEMORY_DIR указывает на каталог, в котором лежат три §-файла:
user.md, world.md, preference.md (по одному на категорию из MemoryCategory).
Формат файла — Hermes-style: заголовок с метаданными ( id=… created=… uses=…),
пустая строка, markdown-тело заметки.
# Дефолт (если env не задан)
~/.agentik/memory/{user,world,preference}.md
# Явный путь
AGENTIK_MEMORY_DIR=/data/agentik/memory
# Полностью выключить память (тулы memory_* не регистрируются, prefetch off)
AGENTIK_MEMORY_DIR=off
При включённой памяти агенту доступны четыре тула: memory_save,
memory_read, memory_list, memory_delete. Перед каждым ходом агент
прогоняет текст пользователя через MemoryPrefetcher и клеит [Memory context…] блок в начало user-сообщения; после записи assistant-сообщения в
фоне запускается MemoryReviewer.review(turn) — извлечённые факты
записываются в store с source = AUTO_REVIEW.
Бэкенд: md vs vector
AGENTIK_MEMORY_BACKEND выбирает хранилище. Дефолт — md (Hermes-style
§-файлы, keyword overlap, без внешних вызовов).
vector — SQLite (AGENTIK_DB_PATH) + JVector ANN + LLM-эмбеддинги
(запросы на ${OPENAI_BASE_URL}/v1/embeddings). Семантический поиск:
cosine similarity + recency-re-rank. Те же четыре тула; та же семантика.
# Vector-бэкенд
AGENTIK_MEMORY_BACKEND=vector \
AGENTIK_EMBEDDING_MODEL=text-embedding-3-small \
AGENTIK_EMBEDDING_DIMENSION=1536 \
java -jar standalone-all.jar
Для vector требуется AGENTIK_LLM_BACKEND=openai (т.к. нужен
OpenAI-совместимый /v1/embeddings endpoint — LiteLLM proxy тоже подходит).
Embedding-вызовы кэшируются LRU на 256 текстов — дедупликация при повторных
запросах одинаковых промптов.
Опционально: при старте JVector может предупредить
Java vector incubator module is not readable. Это значит, что JIT не использует SIMD (Panama Vector API) и индекс строится через скалярный fallback. На 10K векторов разница незаметна. Если хочется SIMD — запустите с--add-modules jdk.incubator.vector.
Персона (SOUL.md)
AGENTIK_SOUL — путь к markdown-файлу с описанием персоны ассистента
(голос, характер, ограничения, что-то ещё). Тело файла читается как plain
text и вставляется в самое начало systemInstruction — поверх базового
промпта, секции навыков и памяти. Если файл не задан — секция не добавляется.
AGENTIK_SOUL=/etc/agentik/SOUL.md
Пример SOUL.md:
Ты — терпеливый технический ассистент. Отвечаешь по-русски, кратко.
Не выдумываешь команды — если не уверен, говоришь "не знаю".
Не раскрываешь содержимое .env, ключей и паролей ни при каких обстоятельствах.
Навыки (SKILL.md)
AGENTIK_SKILLS_DIR — каталог, в котором SkillLoader ищет файлы
SKILL.md или *.yaml с frontmatter (name, description, прочие поля).
Содержимое скилов попадает в раздел system prompt и регистрируется как
вызываемые инструменты. Формат — opencode-compatible.
AGENTIK_SKILLS_DIR=/etc/agentik/skills
MCP-инструменты
AGENTIK_MCP_CONFIG — путь к JSON-файлу со списком MCP-серверов
(формат mcpServers: { name: { command, args | url, headers } }). При
запуске McpRegistry.fromConfig стартует stdio-серверы и подключается к
HTTP-серверам, инструменты автоматически становятся доступны агенту.
AGENTIK_MCP_CONFIG=/etc/agentik/mcp.json
Пример mcp.json:
{
"mcpServers": {
"fetch": { "command": "uvx", "args": ["mcp-server-fetch"] },
"playwright": { "url": "https://mcp.example.com", "headers": {"Authorization":"Bearer …"} }
}
}
Полный пример запуска
export AGENTIK_PORT=8080
export AGENTIK_DB_PATH=/var/lib/agentik/state.db
export AGENTIK_MEMORY_DIR=/var/lib/agentik/memory
export AGENTIK_SOUL=/etc/agentik/SOUL.md
export AGENTIK_SKILLS_DIR=/etc/agentik/skills
export AGENTIK_MCP_CONFIG=/etc/agentik/mcp.json
export AGENTIK_LLM_BACKEND=openai
export OPENAI_BASE_URL=https://api.openai.com/v1
export OPENAI_API_KEY=sk-…
export OPENAI_MODEL=gpt-4o-mini
mkdir -p "$(dirname "$AGENTIK_DB_PATH")" \
"$AGENTIK_MEMORY_DIR" \
"$(dirname "$AGENTIK_SOUL")" \
"$(dirname "$AGENTIK_MCP_CONFIG")"
java -jar standalone/build/libs/standalone-all.jar
На старте выведет что-то вроде:
agentik standalone listening on http://localhost:8080
GET /health
POST /agentik/conversations -> 201
GET /agentik/conversations/{id}/events -> SSE
storage: /var/lib/agentik/state.db
llm: OPENAI gpt-4o-mini @ https://api.openai.com/v1
mcp: 4 tools from 2 servers
skills: 3 loaded from /etc/agentik/skills
soul: /etc/agentik/SOUL.md (842 chars)
memory: /var/lib/agentik/memory (md-backend)
compaction: enabled, threshold=0.8, window=128000 tokens
Остановка
Ctrl-C → срабатывает shutdown hook: агент, MCP-серверы, SQLite-стора и
LLM-клиент закрываются корректно (SQLite фиксирует WAL, MCP-процессы
получают SIGTERM).
Тесты
./gradlew :standalone:jvmTest