df386ef875
SkillMiner (сетка безопасности skill self-improvement): каждые
AGENTIK_SKILL_MINING_INTERVAL user-ходов (default 15) LLM смотрит
последние AGENTIK_SKILL_MINING_MAX_TURNS ходы (default 30) + каталог
существующих скилов и возвращает structured JSON {"skills":[...]}.
Найденное upsert-ится в SkillStore — модель "забыла" вызвать
skill_save в ходе разговора, минер добирает её постфактум.
- SkillMiner.kt: короткий LiteConversation (one-shot), blocking-инференс
на Dispatchers.IO, defensive парсинг (кривой ответ -> пустой список).
- SkillMiningPrompts/SkillMiningParser: тот же подход, что
ReflectionParser (structured-output вместо tool-calling).
- ChatConversation.scheduleSkillMining() — хук после каждого хода
(рядом со scheduleReflection); ChatAgent/Main — прокидывание.
- DebugRoutes.kt: AGENTIK_DEBUG_ENDPOINTS=1 включает POST
/debug/reflect, /debug/skill-mine, /debug/curate, /debug/compact и
GET /debug/tokens для ручного триггерирования фоновых фич.
- ChatConversation.forceCompactNow(): принудительный compaction
без проверки порога (для /debug/compact).
- Тесты: SkillMinerTest (5) + SkillMiningParserTest (9); FakeLiteLlm
теперь записывает send()/sendContents() в lastContents.
179 jvm-тестов :standalone зелёные, README обновлён.
473 lines
25 KiB
Markdown
473 lines
25 KiB
Markdown
# :standalone — agentik single-jar server
|
||
|
||
Self-contained HTTP-сервер с Ktor: AG-UI / A2A / :proto транспорты на одном порту,
|
||
встроенный SQLite для истории диалогов, долговременная память (Hermes-style
|
||
§-файлы), загрузка MCP-инструментов, навыков (SKILL.md) и персоны (SOUL.md).
|
||
|
||
## Сборка
|
||
|
||
```bash
|
||
# Полная сборка всего проекта + fatjar
|
||
./gradlew assemble
|
||
|
||
# Только fatjar :standalone (≈ 150 MB)
|
||
./gradlew :standalone:shadowJar
|
||
|
||
# Результат:
|
||
# standalone/build/libs/standalone-all.jar
|
||
```
|
||
|
||
## Запуск
|
||
|
||
```bash
|
||
java -jar standalone/build/libs/standalone-all.jar
|
||
```
|
||
|
||
По умолчанию слушает на `http://localhost:8080`. Healthcheck: `GET /health`.
|
||
|
||
Транспорты на одном порту:
|
||
- `GET /health` — liveness
|
||
- `POST /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-бэкенда (только HTTP) |
|
||
| `AGENTIK_EMBEDDING_DIMENSION` | `1536` | Размерность вектора (только HTTP; SIGLIP определяет автоматически) |
|
||
| `AGENTIK_EMBEDDING_BACKEND` | `HTTP` | `HTTP` (POST /v1/embeddings) или `SIGLIP` (on-device, без сети) |
|
||
| `AGENTIK_EMBEDDING_MODEL_PATH` | _только SIGLIP_ | Путь к `text_model_int8.onnx` (SigLIP2) |
|
||
| `AGENTIK_EMBEDDING_TOKENIZER_PATH` | _только SIGLIP_ | Путь к `tokenizer.model` (sentencepiece) |
|
||
| `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 |
|
||
| `AGENTIK_REFLECTION_INTERVAL` | `10` | Self-reflection: каждый N-й пользовательский ход агент оценивает себя (LiteLlm) и сохраняет рефлексию. `0` = выключено. |
|
||
| `AGENTIK_REFLECTION_TOP_K` | `3` | Сколько последних рефлексий подмешивать в system prompt как «слабые места». `0` = не подмешивать. |
|
||
| `AGENTIK_SKILL_MINING_INTERVAL` | `15` | Skill mining: через сколько user-ходов запускать фоновый прогон SkillMiner. `0` = выключено. |
|
||
| `AGENTIK_SKILL_MINING_MAX_TURNS` | `30` | Сколько последних ходов передавать SkillMiner'у за один прогон. |
|
||
| `AGENTIK_DEBUG_ENDPOINTS` | `0` | `1` включает debug-эндпоинты (`/debug/reflect`, `/debug/skill-mine`, `/debug/curate`, `/debug/compact`, `/debug/tokens`) |
|
||
|
||
### 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`).
|
||
|
||
```bash
|
||
# Абсолютный путь
|
||
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` обязательно.
|
||
|
||
```json
|
||
// 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`-блок.
|
||
|
||
**Включается только при заданном лимите.** Никакого автодетекта по имени
|
||
модели — если лимит не задан, агент не сжимает.
|
||
|
||
```bash
|
||
# OpenAI-совместимый бэкенд
|
||
export OPENAI_CONTEXT_WINDOW=128000
|
||
|
||
# Или Google / LiteRT
|
||
export AGENTIK_GOOGLE_CONTEXT_WINDOW=32000
|
||
```
|
||
|
||
`AGENTIK_COMPRESSION_THRESHOLD` — доля лимита, при которой запускается
|
||
compaction (дефолт `0.8` = 80%):
|
||
|
||
```bash
|
||
export AGENTIK_COMPRESSION_THRESHOLD=0.7 # сжимаем раньше
|
||
```
|
||
|
||
**Что происходит при compaction:**
|
||
|
||
1. Перед `send()` оценивается количество токенов в системном промпте + history
|
||
+ tools (грубая оценка `chars / 4`).
|
||
2. Если `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` пересоздаётся с обновлённым контекстом.
|
||
|
||
Если после compaction оценка всё ещё выше порога — выводится warning, но
|
||
нового compaction не запускается (защита от зацикливания). Решение —
|
||
поднять `OPENAI_CONTEXT_WINDOW` или понизить threshold.
|
||
|
||
## Self-reflection (Hermes-style «слабые места»)
|
||
|
||
Каждые `AGENTIK_REFLECTION_INTERVAL` пользовательских ходов (default 10)
|
||
запускается фоновая one-shot LLM-размышление: «оцени последние ходы,
|
||
поставь score 1..5, выдели слабые места». Результат сохраняется в таблицу
|
||
`reflection` SQLite и подмешивается в system prompt следующего хода как
|
||
«Твои слабые места за последнее время».
|
||
|
||
Включено когда `AGENTIK_REFLECTION_INTERVAL > 0`. Требует LiteLlm
|
||
(on-device или OpenAI — что указан в `AGENTIK_LLM_BACKEND`). На каждый
|
||
reflection — один LiteLlm вызов (~1-3 сек для on-device, ~200-500мс для
|
||
OpenAI). Это происходит в фоне (`Dispatchers.IO`), основной диалог не
|
||
блокируется.
|
||
|
||
Топ-K последних рефлексий загружается в `buildSystemPrompt` и выводится
|
||
как `## Self-reflection: твои слабые места за последнее время`. Агент
|
||
видит их в каждом следующем ходе и (теоретически) должен избегать
|
||
повторения. Используется как cheap "auto-improving prompt feedback"
|
||
без ручного переписывания system prompt.
|
||
|
||
## Учёт токенов (token accounting)
|
||
|
||
Каждый assistant-ход после LiteLlm.send помечает assistant-запись `TurnTokens(input, output)`:
|
||
|
||
- **`input`** — снимок `LiteConversation.tokenCount()` **до** первого send в turn'е
|
||
(system + вся история + tools + только что добавленное user-сообщение).
|
||
- **`output`** — дельта после завершения turn'а (assistant text + tool calls +
|
||
tool results, всё что LiteConversation добавила за весь tool loop).
|
||
- Хранится в `payload_json` assistant-сообщения (без schema-миграций). Бэкенды
|
||
без `tokenCount()` (off-line модели LiteRT-LM счётчик не отдают) дают `tokens=null`.
|
||
|
||
На старте агент печатает сводку по всем существующим диалогам:
|
||
|
||
```
|
||
tokens: 17 convs, 134 turns, in=523844, out=58290, total=582134
|
||
```
|
||
|
||
`MessageStore.tokenStats(conversationId)` отдаёт `TokenStats(turns, inputTokens, outputTokens)`
|
||
для одного диалога — можно использовать из HTTP фасада или клиентских дашбордов
|
||
для оценки cost.
|
||
|
||
## Куратор памяти (Curator)
|
||
|
||
Фоновая корутина (запускается автоматически, если `AGENTIK_MEMORY_DIR != off`):
|
||
раз в сутки архивирует заметки, которые **не выдавались в prefetch дольше 90
|
||
дней** и **имеют `useCount == 0`**. Семантика архивации зависит от бэкенда —
|
||
`:memory-md` переименовывает §-файл в `.archived.{ts}`, `:memory-vector`
|
||
удаляет из SQLite и JVector.
|
||
|
||
Параметры пока захардкожены в `Curator.DEFAULT_INTERVAL` и
|
||
`Curator.DEFAULT_MAX_AGE` (1 день и 90 дней); для override нужен новый
|
||
config-флаг. На каждом проходе выводится `[Curator] archived N stale notes`,
|
||
если N > 0.
|
||
|
||
## Память
|
||
|
||
`AGENTIK_MEMORY_DIR` указывает на каталог, в котором лежат три §-файла:
|
||
`user.md`, `world.md`, `preference.md` (по одному на категорию из `MemoryCategory`).
|
||
Формат файла — Hermes-style: заголовок с метаданными (` id=… created=… uses=…`),
|
||
пустая строка, markdown-тело заметки.
|
||
|
||
```bash
|
||
# Дефолт (если 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 + эмбеддинги. Два
|
||
бэкенда эмбеддингов через `AGENTIK_EMBEDDING_BACKEND`:
|
||
|
||
- **`HTTP` (default)** — POST на `${OPENAI_BASE_URL}/v1/embeddings`. Семантический
|
||
поиск: cosine similarity + recency-re-rank.
|
||
- **`SIGLIP`** — on-device SigLIP2 через ONNX Runtime (text-embedding-kmp,
|
||
768-мерный вектор). Никаких внешних вызовов: модель и токенизатор должны
|
||
лежать на диске. Размерность определяется автоматически (768).
|
||
|
||
```bash
|
||
# Vector-бэкенд + HTTP-эмбеддинги (default)
|
||
AGENTIK_MEMORY_BACKEND=vector \
|
||
AGENTIK_EMBEDDING_BACKEND=http \
|
||
AGENTIK_EMBEDDING_MODEL=text-embedding-3-small \
|
||
AGENTIK_EMBEDDING_DIMENSION=1536 \
|
||
java -jar standalone-all.jar
|
||
|
||
# Vector-бэкенд + on-device SigLIP2 (без сети)
|
||
AGENTIK_MEMORY_BACKEND=vector \
|
||
AGENTIK_EMBEDDING_BACKEND=siglip \
|
||
AGENTIK_EMBEDDING_MODEL_PATH=/path/to/text_model_int8.onnx \
|
||
AGENTIK_EMBEDDING_TOKENIZER_PATH=/path/to/tokenizer.model \
|
||
java -jar standalone-all.jar
|
||
```
|
||
|
||
Для HTTP-эмбеддингов требуется `AGENTIK_LLM_BACKEND=openai` (т.к. нужен
|
||
OpenAI-совместимый `/v1/embeddings` endpoint — LiteLLM proxy тоже подходит).
|
||
HTTP-вызовы кэшируются LRU на 256 текстов — дедупликация при повторных
|
||
запросах одинаковых промптов.
|
||
|
||
Для SIGLIP нужно сначала скачать модель (~283M) и токенизатор (~4M):
|
||
```bash
|
||
mkdir -p /path/to/siglip-model
|
||
curl -fSL -o /path/to/siglip-model/text_model_int8.onnx \
|
||
http://static.binom.pw/models/siglip2/text_model_int8.onnx
|
||
curl -fSL -o /path/to/siglip-model/tokenizer.model \
|
||
http://static.binom.pw/models/siglip2/tokenizer.model
|
||
```
|
||
|
||
> **Опционально:** при старте 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` — поверх базового
|
||
промпта, секции навыков и памяти. Если файл не задан — секция не добавляется.
|
||
|
||
```bash
|
||
AGENTIK_SOUL=/etc/agentik/SOUL.md
|
||
```
|
||
|
||
Пример `SOUL.md`:
|
||
|
||
```markdown
|
||
Ты — терпеливый технический ассистент. Отвечаешь по-русски, кратко.
|
||
Не выдумываешь команды — если не уверен, говоришь "не знаю".
|
||
Не раскрываешь содержимое .env, ключей и паролей ни при каких обстоятельствах.
|
||
```
|
||
|
||
## Навыки (SKILL.md)
|
||
|
||
`AGENTIK_SKILLS_DIR` — каталог, в котором `SkillLoader` ищет файлы
|
||
`SKILL.md` или `*.yaml` с frontmatter (`name`, `description`, прочие поля).
|
||
Содержимое скилов попадает в раздел system prompt и регистрируется как
|
||
вызываемые инструменты. Формат — opencode-compatible.
|
||
|
||
```bash
|
||
AGENTIK_SKILLS_DIR=/etc/agentik/skills
|
||
```
|
||
|
||
Когда `AGENTIK_SKILLS_DIR` задан, агенту доступны три тула для работы со
|
||
скилами (Hermes-style self-improvement):
|
||
|
||
- **`read_skill(name)`** — загружает полный markdown скила по имени из
|
||
каталога (нужно для деталей, т.к. в system prompt обычно только краткие
|
||
описания).
|
||
- **`skill_save(name, description, body)`** — создаёт или обновляет скил.
|
||
Имя может содержать `:` (opencode-style: `backend:spring:db-base`
|
||
→ `backend/spring/db-base/SKILL.md`).
|
||
- **`skill_delete(name)`** — архивирует скил (переименовывает файл в
|
||
`.archived`, оставляя возможность восстановить).
|
||
|
||
`skill_save`/`skill_delete` не требуют рестарта агента — изменения видны
|
||
на ближайшем вызове `read_skill` (включая в этом же диалоге).
|
||
|
||
### Skill mining (автонавыки)
|
||
|
||
Модель может "протупить" и не вызвать `skill_save`, хотя приём был
|
||
переиспользуемым. Сетка безопасности — фоновый [SkillMiner]: каждые
|
||
`AGENTIK_SKILL_MINING_INTERVAL` пользовательских ходов (default 15, `0` =
|
||
выключено) LLM смотрит последние `AGENTIK_SKILL_MINING_MAX_TURNS` ходов
|
||
(default 30) + каталог существующих скилов и возвращает structured JSON
|
||
`{"skills": [{name, description, body}]}`. Найденные скилы upsert-ятся в
|
||
`AGENTIK_SKILLS_DIR` — агент становится умнее между сессиями. Обновления
|
||
существующих скилов (то же имя) поддерживаются, дубли — нет.
|
||
|
||
| Переменная | Default | Что делает |
|
||
|---|---|---|
|
||
| `AGENTIK_SKILL_MINING_INTERVAL` | `15` | Через сколько user-ходов запускать mining. `0` — выкл. |
|
||
| `AGENTIK_SKILL_MINING_MAX_TURNS` | `30` | Сколько последних ходов показывать минеру |
|
||
|
||
### Debug-эндпоинты
|
||
|
||
`AGENTIK_DEBUG_ENDPOINTS=1` включает эндпоинты для ручного триггерирования
|
||
фоновых фич (не ждать интервалов). Только локальная отладка: без
|
||
авторизации, в проде не включать.
|
||
|
||
| Эндпоинт | Действие |
|
||
|---|---|
|
||
| `POST /debug/reflect?conversationId=...` | прогон LlmReflector прямо сейчас, результат в БД |
|
||
| `POST /debug/skill-mine?conversationId=...` | прогон SkillMiner прямо сейчас, найденное в `AGENTIK_SKILLS_DIR` |
|
||
| `POST /debug/curate` | прогон Curator.runPass (архивация stale-заметок памяти) |
|
||
| `POST /debug/compact?conversationId=...` | принудительный compaction working memory диалога |
|
||
| `GET /debug/tokens?conversationId=...` | token-статистика диалога из БД (turns/in/out/total) |
|
||
|
||
Каждый возвращает JSON с результатом (что сохранил/нашёл/сжал), чтобы было
|
||
видно не только "триггер сработал", а что именно LLM намайнила.
|
||
|
||
```bash
|
||
AGENTIK_SKILLS_DIR=/etc/agentik/skills
|
||
AGENTIK_DEBUG_ENDPOINTS=1
|
||
```
|
||
|
||
|
||
## MCP-инструменты
|
||
|
||
`AGENTIK_MCP_CONFIG` — путь к JSON-файлу со списком MCP-серверов
|
||
(формат `mcpServers: { name: { command, args | url, headers } }`). При
|
||
запуске `McpRegistry.fromConfig` стартует stdio-серверы и подключается к
|
||
HTTP-серверам, инструменты автоматически становятся доступны агенту.
|
||
|
||
```bash
|
||
AGENTIK_MCP_CONFIG=/etc/agentik/mcp.json
|
||
```
|
||
|
||
Пример `mcp.json`:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"fetch": { "command": "uvx", "args": ["mcp-server-fetch"] },
|
||
"playwright": { "url": "https://mcp.example.com", "headers": {"Authorization":"Bearer …"} }
|
||
}
|
||
}
|
||
```
|
||
|
||
## Полный пример запуска
|
||
|
||
```bash
|
||
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
|
||
curator: enabled (interval=1d, maxAge=90d)
|
||
reflection: enabled (interval=10, topK=3)
|
||
```
|
||
|
||
## Остановка
|
||
|
||
`Ctrl-C` → срабатывает shutdown hook: агент, MCP-серверы, SQLite-стора и
|
||
LLM-клиент закрываются корректно (SQLite фиксирует WAL, MCP-процессы
|
||
получают SIGTERM).
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
./gradlew :standalone:jvmTest
|
||
```
|