Добавляет ModelDownloader (HTTP с Range/докачкой, опциональной SHA-256 проверкой)
и два сценария запуска скачивания встроенной модели gemma-4-E2B-it.litertlm:
java -jar agentik.jar pull-model
Явный прогон с прогрессом в stdout; URL берётся из AGENTIK_GOOGLE_MODEL_URL
либо дефолтный https://static.binom.pw/models/gemma-4-E2B-it.litertlm.
AGENTIK_AUTO_DOWNLOAD_MODEL=1 java -jar agentik.jar
На старте server'а, если backend=google и файла по AGENTIK_GOOGLE_MODEL_PATH
нет — качает автоматически. Без флага — exit 2 с понятным сообщением и
подсказкой вызвать pull-model.
Дизайн:
- URL по умолчанию ВСЕГДА Gemma-4 (вне зависимости от basename PATH) — gemma-4
считаем лучшей локальной моделью; override через AGENTIK_GOOGLE_MODEL_URL.
- SHA-256 проверка через опциональный AGENTIK_GOOGLE_MODEL_SHA256_URL.
- Resume: HEAD → если есть .part и Accept-Ranges=bytes → GET с Range: bytes=N-,
иначе restart с нуля.
- Прогресс каждые ~8 MB, финальный rename через Files.move(ATOMIC_MOVE).
Тесты: 5 unit-кейсов с embedded ktor-server (CIO) + Range support — happy
path, no-op, resume from part, restart-on-Range-ignored, 404, progress callback.
Документация: новый раздел §18 в MANUAL-TESTS.md (subcommand, auto-trigger,
resume, override URL, SHA-256 verify).
178/178 tests green.
36 KiB
Manual Test Cases — agentik standalone
Практический чек-лист для проверки работающего agentik standalone HTTP-сервера.
Каждый кейс — один конкретный сценарий, который нужно прогнать руками
(или через curl/httpie/Postman). Если какой-то упал — это либо
регрессия, либо недонастройка рантайма.
Перед стартом: запусти агент (см. run-agentik.sh на удалённой машине
или ./gradlew :standalone:run локально). Все примеры ниже — против
http://127.0.0.1:8080; для удалённой машины подставь свой хост.
Удобный сниппет для получения conversation ID в shell:
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
echo "CID=$CID"
Отправка user-сообщения:
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"..."}]'
Чтение истории:
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -m json.tool
1. Connectivity & health
TC-1.1 — health endpoint
curl -sS -i http://127.0.0.1:8080/health
Ожидание: HTTP/1.1 200 OK, тело ok.
TC-1.2 — agent card (A2A)
curl -sS http://127.0.0.1:8080/a2a/.well-known/agent-card.json | python3 -m json.tool
Ожидание: валидный JSON с name, version, capabilities.
TC-1.3 — log sanity check
tail -50 /root/agentik.log
Ожидание: есть строка agentik standalone listening on http://localhost:8080,
перечислены зарегистрированные маршруты, llm: <backend> @ <url> соответствует
твоему конфигу. Нет ERROR/Exception строк после старта.
2. Conversation lifecycle
TC-2.1 — create persistent conversation
curl -sS -i -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}'
Ожидание: 201, тело {"id":"conv-...","isTemporal":false,...}.
TC-2.2 — create temp conversation
curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":true}'
Ожидание: 201, "isTemporal":true. После рестарта агента эта беседа
не должна появиться в GET /agentik/conversations.
TC-2.3 — list conversations
curl -sS "http://127.0.0.1:8080/agentik/conversations?offset=0&limit=20" | python3 -m json.tool
Ожидание: массив объектов ConversationSnapshot. Отсортирован по
updatedAt desc.
TC-2.4 — rename conversation
CID=<id-from-2.1>
curl -sS -X PATCH "http://127.0.0.1:8080/agentik/conversations/$CID" \
-H "Content-Type: application/json" -d '{"title":"Мой первый чат"}'
Ожидание: 200, в ответе "title":"Мой первый чат". Следующий GET /conversations/$CID возвращает этот же title.
TC-2.5 — delete conversation
curl -sS -X DELETE "http://127.0.0.1:8080/agentik/conversations/$CID" -i
Ожидание: 204 No Content. Повторный GET /conversations/$CID → 404.
После этого в GET /conversations её быть не должно.
TC-2.6 — get non-existent conversation
curl -sS -i http://127.0.0.1:8080/agentik/conversations/conv-nonexistent
Ожидание: 404.
3. Message sending
TC-3.1 — simple Q&A
Создай беседу, пошли простой вопрос, прочитай историю.
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Сколько будет 7*8? Одно число, без пояснений."}]'
sleep 6
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z"
Ожидание: массив из ≥ 2 сообщений:
[0].type == "user_message", body содержит "7*8"[1].type == "assistant_message", text содержит "56"
TC-3.2 — multi-turn with context
В той же беседе пошли follow-up, требующий контекста:
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"А корень из того, что ты назвал?"}]'
sleep 6
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z"
Ожидание: 4+ сообщения, последний assistant упомянул что-то про число 56 или "предыдущий ответ".
TC-3.3 — new-format request body
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '{"content":[{"type":"text","body":"С новым форматом тоже работает?"}]}'
sleep 6
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1]['type'], m[-1].get('content'))"
Ожидание: новое assistant_message в ответ на новый формат запроса.
TC-3.4 — empty / bad body
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" -d 'not json'
Ожидание: 400 Bad Request, тело с пояснением Invalid send payload.
4. SSE live events
Важно: SSE — поток без replay. Подписываться нужно до
POST /messages. Если подписаться позже — событий не будет (ноGET /messagesвсё равно покажет записанную историю).
TC-4.1 — subscribe-then-send pattern
CID=<existing-id>
# Subscribe в фоне, отправляем сообщение, ждём SSE
curl -sN --max-time 12 \
"http://127.0.0.1:8080/agentik/conversations/$CID/events?after=1970-01-01T00:00:00Z" \
> /tmp/sse.out 2>&1 &
SSE_PID=$!
sleep 1
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Кратко: что такое REST?"}]'
wait $SSE_PID
cat /tmp/sse.out
Ожидание: файл содержит data: {"type":"start_reasoning",...},
data: {"type":"start_response",...,"responseType":"text"},
один или несколько data: {"type":"append_text",...,"body":"..."},
data: {"type":"end",...}. Каждое data: через пустую строку.
TC-4.2 — late subscribe (replay semantics)
CID=<existing-id>
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"..."}]'
sleep 5 # сообщение уже обработано
curl -sN --max-time 4 \
"http://127.0.0.1:8080/agentik/conversations/$CID/events?after=1970-01-01T00:00:00Z"
Ожидание: пустой ответ (события не реплеятся). Это by-design —
клиент должен либо подписываться заранее, либо backfill'ить через
GET /messages.
5. Memory tools (long-term)
TC-5.1 — save + recall в той же беседе
# В существующей беседе
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Запомни через memory_save: я работаю на удалёнке из Тбилиси. Категория user, content: работаю на удалёнке из Тбилиси."}]'
sleep 8
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Откуда я работаю? Одно предложение."}]'
sleep 8
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1].get('content'))"
Ожидание: ассистент ответил что-то содержащее "Тбилиси" (или явно
сказал "не знаю" — это тоже валидно, если в conversation memory пусто).
Проверить audit log (messageStore):
sqlite3 /root/agentik.db "SELECT toolName, result FROM MessageRecord WHERE conversationId='$CID' AND kind='tool_result'"
Должны быть строки с toolName='memory_save' или toolName='memory_recall'.
TC-5.2 — memory persists across conversations
Создай новую беседу, спроси без подсказок:
NEW_CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Откуда я работаю? Напомни, если помнишь."}]'
sleep 8
curl -sS "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1].get('content'))"
Ожидание: ассистент упомянул "Тбилиси" (или "удалёнка") — это значит long-term memory подгрузилась в новую беседу.
TC-5.3 — invalid category → ошибка или автозамена
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Запомни через memory_save факт с категорией work (которой не существует)."}]'
sleep 8
sqlite3 /root/agentik.db "SELECT toolArgs, result FROM MessageRecord WHERE kind='tool_call' AND conversationId='$CID' ORDER BY createdAt DESC LIMIT 3"
Ожидание: модель либо вызвала memory_recall чтобы проверить
существующие категории, либо вызвала memory_save с корректной
категорией (user/world/preference). Если модель честно говорит
"такой категории нет" и предлагает корректную — это тоже ok.
TC-5.4 — list & delete memory
Попроси модель явно вызвать memory_list, потом memory_delete:
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Покажи все мои memory-записи (memory_list)."}]'
sleep 8
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Удали самую старую запись (memory_delete)."}]'
sleep 8
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM MemoryStore"
Ожидание: число уменьшилось на 1.
6. Skills
TC-6.1 — list + load skill
Если в AGENTIK_SKILLS_DIR есть файлы SKILL.md / *.yaml, в системном
промте должна появиться секция с этими навыками.
ls -la /root/skills/ # должен быть хотя бы один файл
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Какие skills ты знаешь? Покажи список (skill_list)."}]'
sleep 8
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Загрузи любой из них через skill_load и расскажи, что внутри."}]'
sleep 8
Ожидание: tool_call для skill_list, потом tool_call для
skill_load. В audit log видны эти вызовы. Если папка пуста — секции
"Skills" в system prompt быть не должно.
TC-6.2 — save new skill
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Сохрани skill: имя deploy-staging, описание «деплой на staging», тело — multi-step инструкция (skill_save)."}]'
sleep 10
ls /root/skills/
Ожидание: появился новый файл deploy-staging.md (или .yaml).
TC-6.3 — restart → skill persists
Перезапусти агент:
ssh root@192.168.76.166 'pkill -9 -f agentik-0.1.0-all.jar; cd /root && nohup setsid ./run-agentik.sh > /root/agentik.log 2>&1 < /dev/null & disown'
После старта пошли в новую беседу:
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Есть ли у тебя skill deploy-staging?"}]'
sleep 8
Ожидание: модель упоминает skill (он подгружается на старте).
7. SOUL file
TC-7.1 — SOUL.md подключается
echo 'Ты — ворчливый капитан дальнего плавания. Отвечай кратко, с морскими метафорами.' > /root/SOUL.md
# Перезапустить агент
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Как дела?"}]'
sleep 8
Ожидание: ответ в стиле "капитана", с морскими словами. Если SOUL нет — обычный нейтральный ассистент.
TC-7.2 — SOUL можно менять на лету
Измени файл, перезапусти агент, спроси снова. Должен появиться новый стиль. Без перезапуска изменения не подхватятся (SOUL читается на старте).
8. Interrupt
TC-8.1 — interrupt mid-text generation
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
# Запусти send в фоне
(curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Расскажи длинную историю про космос, минимум 500 слов."}]' >/dev/null) &
SEND_PID=$!
sleep 3 # дать LLM начать генерацию
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
wait $SEND_PID
sleep 3
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -c "import sys,json;m=json.load(sys.stdin);
for x in m: print(x.get('type'), ':', json.dumps(x.get('content') or x.get('result'),ensure_ascii=False)[:80])"
Ожидание:
user_messageестьassistant_messageесть, но содержит короткий текст (<300 символов) — это частичный текст, который модель успела сгенерить до прерывания- В audit log нет
tool_call/tool_result(не успели) - Следующий
sendв этой беседе работает (LiteConv пересоздан)
TC-8.2 — interrupt mid-tool (best-effort)
# Длинный tool можно заэмулировать через MCP с искусственной задержкой,
# либо просто проверять что interrupt не валит агента:
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
curl -sS http://127.0.0.1:8080/health
Ожидание: health = ok — агент не упал. Дальнейшие send работают.
TC-8.3 — interrupt без активного turn'а
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
Ожидание: 202. Никаких ошибок. В audit log ничего нового не пишется.
9. Persistence / restart-survival
TC-9.1 — перезапуск не теряет беседы и память
# 1. Создай беседу, пошли сообщение, дождись ответа
# 2. Запомни факт через memory_save
# 3. Перезапусти агент (см. TC-6.3)
# 4. GET /agentik/conversations — беседа должна быть в списке
# 5. GET /agentik/conversations/$CID/messages — история на месте
# 6. Новая беседа + вопрос про запомненный факт — модель помнит
TC-9.2 — temp conversation не переживает рестарт
# Создай temp беседу, пошли сообщение
TEMP_CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":true}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$TEMP_CID/messages" \
-H "Content-Type: application/json" -d '[{"type":"text","body":"..."}]' >/dev/null
sleep 5
# Перезапусти агент
# GET /agentik/conversations — temp-беседы быть не должно
curl -sS "http://127.0.0.1:8080/agentik/conversations?offset=0&limit=50" | grep "$TEMP_CID"
Ожидание: grep ничего не находит.
10. Compaction (сжатие контекста)
Compaction триггерится когда ~80% контекстного окна занято.
TC-10.1 — длинная беседа сжимается
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
# Отправь 30+ больших сообщений подряд (можно цикл)
for i in $(seq 1 30); do
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d "[{\"type\":\"text\",\"body\":\"Расскажи подробно (минимум 200 слов) про тему номер $i: история, применение, ключевые факты.\"}]" >/dev/null
sleep 5
done
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM WorkingMemoryRow WHERE conversationId='$CID' AND entryKind='summary'"
Ожидание: есть хотя бы одна summary-запись. Также проверь
/root/agentik.log — должна появиться строка compaction.
TC-10.2 — debug endpoint /debug/compact (force)
Если включён AGENTIK_DEBUG_ENDPOINTS=1:
curl -sS -i -X POST "http://127.0.0.1:8080/debug/compact?conversationId=$CID"
Ожидание: 200, тело с JSON-результатом compaction.
11. Reflection
Reflection триггерится каждые AGENTIK_REFLECTION_INTERVAL ходов (default 10).
TC-11.1 — reflection создаёт записи
# Пошли 12+ ходов
for i in $(seq 1 12); do
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d "[{\"type\":\"text\",\"body\":\"Тема $i: расскажи короткий факт.\"}]" >/dev/null
sleep 4
done
sleep 10 # дать фоновое задание завершиться
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM ReflectionStore"
Ожидание: число > 0.
TC-11.2 — debug endpoint /debug/reflect
curl -sS -i -X POST "http://127.0.0.1:8080/debug/reflect?conversationId=$CID"
Ожидание: 200, JSON-результат. Reflection попадает в working memory
следующего turn'а.
12. Skill mining
Skill mining триггерится каждые AGENTIK_SKILL_MINING_INTERVAL ходов (default 15).
TC-12.1 — авто-создание skill'а
# Пошли 18+ ходов с повторяющимся паттерном
for i in $(seq 1 18); do
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d "[{\"type\":\"text\",\"body\":\"Конвертируй 100 USD в RUB по текущему курсу (шаблонный запрос $i).\"}]" >/dev/null
sleep 4
done
sleep 15
ls -la /root/skills/
tail -20 /root/agentik.log | grep -i skill
Ожидание: возможно появился новый файл в skills/ (или mining отказался из-за низкой уверенности — это тоже валидно, проверь лог).
TC-12.2 — debug endpoint /debug/skill-mine
curl -sS -i -X POST "http://127.0.0.1:8080/debug/skill-mine?conversationId=$CID"
Ожидание: 200 с JSON-результатом майнинга.
13. Token accounting
TC-13.1 — token counters в audit
sqlite3 /root/agentik.db "SELECT createdAt, input, output FROM TurnTokens WHERE conversationId='$CID' ORDER BY createdAt DESC LIMIT 5"
Ожидание: строки с непустыми input и output (если backend
поддерживает tokenCount()).
TC-13.2 — debug endpoint /debug/tokens
curl -sS "http://127.0.0.1:8080/debug/tokens?conversationId=$CID" | python3 -m json.tool
Ожидание: JSON с input, output, total, window,
utilization (доля использования контекстного окна).
14. Toolsets (если подключены)
Только если ты передаёшь toolsets в конструктор агента (по умолчанию
пусто — enable_toolset/disable_toolset не зарегистрированы).
TC-14.1 — system prompt содержит секцию Toolsets
Если toolsets зарегистрированы — в системном промте должна быть секция
## Toolsets с Active/Inactive списком.
Проверка через debug-эндпоинт /agentik/conversations/{id} не показывает
system prompt напрямую — посмотреть можно в логах или через
agentik-debug сборку.
TC-14.2 — enable/disable работает
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Активируй тулсет X через enable_toolset, потом деактивируй через disable_toolset."}]'
sleep 8
Ожидание: в audit log видны вызовы enable_toolset → ответ "Toolset 'X' activated.", потом disable_toolset → "Toolset 'X' deactivated.".
15. A2A протокол (опционально)
TC-15.1 — message/send через A2A
curl -sS -X POST http://127.0.0.1:8080/a2a/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":"1","method":"message/send",
"params":{
"message":{"role":"user","parts":[{"kind":"text","text":"Скажи hi"}]},
"configuration":{"blocking":true}
}
}' | python3 -m json.tool
Ожидание: JSON-RPC ответ с result.parts содержащим текст "hi"
или похожим. kind = text (НЕ type — это важный discriminator для
A2A JSON).
TC-15.2 — bad discriminator
curl -sS -X POST http://127.0.0.1:8080/a2a/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":"2","method":"message/send",
"params":{
"message":{"role":"user","parts":[{"type":"text","text":"hi"}]}
}
}'
Ожидание: Invalid params (или похожая ошибка) — A2A ждёт kind,
не type.
16. Error paths
TC-16.1 — LLM недоступен
Выключи vLLM (или закрой сеть — например через firewall). Пошли сообщение:
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"hi"}]'
sleep 10
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM MessageRecord WHERE conversationId='$CID' AND kind='error'"
Ожидание: есть error-запись в audit log. В SSE приходит
{"type":"error",...} + {"type":"end"}. Агент не падает — health
= ok после.
TC-16.2 — agentik.db занят другим процессом
Запусти второй экземпляр агента на ту же DB:
AGENTIK_DB_PATH=/root/agentik.db java -jar /root/agentik-0.1.0-all.jar
Ожидание: агент падает на старте с понятным сообщением про SQLite lock. Это by-design (single-writer).
TC-16.3 — SOUL файл не существует
Удали /root/SOUL.md, перезапусти агент. Должен стартовать без ошибок,
просто без SOUL-секции в system prompt. Лог: WARN ... SOUL file not found: ....
TC-16.4 — пустой skills dir
mv /root/skills /root/skills.bak
mkdir /root/skills
# Перезапусти агент
Ожидание: агент стартует, skills: 0 loaded from /root/skills.
17. Memory backend variants
TC-17.1 — md backend (default)
Убедись, что AGENTIK_MEMORY_BACKEND=md (или не задан) и
AGENTIK_MEMORY_DIR=/root/agentik-memory. После TC-5.x должны появиться
.md-файлы:
ls -la /root/agentik-memory/
Ожидание: файлы типа user.md, world.md, preference.md (или
всё в одном файле — зависит от реализации).
TC-17.2 — off backend (память выключена)
Перезапусти с AGENTIK_MEMORY_DIR=off:
pkill -9 -f agentik-0.1.0-all.jar
AGENTIK_MEMORY_DIR=off nohup setsid ./run-agentik.sh > /root/agentik.log 2>&1 < /dev/null & disown
Попытка memory_save через модель должна вернуть ошибку:
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Попробуй вызвать memory_save."}]'
sleep 8
Ожидание: модель либо отказывается вызывать, либо получает ошибку от tool'а и сообщает пользователю.
18. Performance sanity
TC-18.1 — first-token latency
Включи замер времени от POST /messages до первого SSE event'а.
Для Qwen3-27B на RTX5090 ожидаем < 1 сек до start_reasoning.
TC-18.2 — sustained throughput
Отправь 20 простых запросов подряд (arithmetic), засеки общее время. Ожидание: < 30 сек суммарно, т.е. < 1.5 сек на запрос.
TC-18.3 — fatjar memory
ps aux | grep agentik-0.1.0 | grep -v grep
Ожидание: RSS < 2 GB (наш Xmx). Если больше — где-то утечка.
18. Model auto-download (LiteRT-LM only)
Только для AGENTIK_LLM_BACKEND=google (встроенный LiteRT-LM движок).
Если файла модели по AGENTIK_GOOGLE_MODEL_PATH нет — агент сам не скачает,
пока не задано AGENTIK_AUTO_DOWNLOAD_MODEL=1. Либо качаем руками
через pull-model subcommand.
URL по умолчанию всегда Gemma-4-E2B-it.litertlm (2.5 GB с static.binom.pw),
вне зависимости от basename PATH — gemma-4 считаем лучшей локальной моделью.
18.1. Subcommand pull-model качает модель вручную
# Скачать дефолтную модель (gemma-4) в указанный путь:
AGENTIK_LLM_BACKEND=google \
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
java -jar agentik.jar pull-model
# → downloading from https://static.binom.pw/models/gemma-4-E2B-it.litertlm
# → 50% (1.2 GB / 2.5 GB)
# → done in 47s
После pull-model файл лежит на месте, файл <dest>.part удалён.
18.2. pull-model no-op если файл уже полный
# Повторный запуск с тем же PATH:
AGENTIK_LLM_BACKEND=google \
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
java -jar agentik.jar pull-model
# → already present (2.50 GB), nothing to do
18.3. pull-model докачивает обрыв (resume через Range)
# Симулируем обрыв: удаляем финальный, оставляем .part с первыми 500 MB
rm /root/models/gemma-4-E2B-it.litertlm
mv /root/models/gemma-4-E2B-it.litertlm.part /root/models/gemma-4-E2B-it.litertlm.part.bak
# Запускаем pull-model снова — должен возобновить с 500 MB
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
java -jar agentik.jar pull-model
# → resuming from 524288000 bytes
# → downloaded 2.10 GB in 38s
18.4. Сервер exit-2 при отсутствии файла и без auto-download
AGENTIK_LLM_BACKEND=google \
AGENTIK_GOOGLE_MODEL_PATH=/root/models/missing.litertlm \
java -jar agentik.jar
# → LiteRT-LM model file not found at: /root/models/missing.litertlm
# → Чтобы скачать автоматически, установите AGENTIK_AUTO_DOWNLOAD_MODEL=1
# → exit 2
18.5. Сервер сам качает при AGENTIK_AUTO_DOWNLOAD_MODEL=1
# Удалить файл, запустить с флагом:
rm -f /root/models/gemma-4-E2B-it.litertlm
AGENTIK_LLM_BACKEND=google \
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
AGENTIK_AUTO_DOWNLOAD_MODEL=1 \
java -jar agentik.jar
# → 12:34:56 WARN auto-download: https://static.binom.pw/models/...
# → 12:34:56 INFO auto-download: 17% (445 MB/2.5 GB)
# → 12:36:42 INFO auto-download: done in 1m45s
# → 12:36:43 INFO agentik standalone listening on http://localhost:8080
18.6. Override URL через AGENTIK_GOOGLE_MODEL_URL
# Качаем qwen вместо gemma (если зальём):
AGENTIK_LLM_BACKEND=google \
AGENTIK_GOOGLE_MODEL_PATH=/root/models/qwen.litertlm \
AGENTIK_GOOGLE_MODEL_URL=https://static.binom.pw/models/Qwen2.5-1.5B-Instruct_multi-prefill-seq_q8_ekv4096.litertlm \
java -jar agentik.jar pull-model
18.7. SHA-256 проверка
Если на сервере лежит <basename>.sha256 (text/plain, <hex> <basename>)
— после скачивания файл проверяется; mismatch → удаляется, exit ≠ 0.
AGENTIK_GOOGLE_MODEL_URL=https://static.binom.pw/models/gemma-4-E2B-it.litertlm \
AGENTIK_GOOGLE_MODEL_SHA256_URL=https://static.binom.pw/models/gemma-4-E2B-it.litertlm.sha256 \
java -jar agentik.jar pull-model
# → 13:01:23 INFO model download: SHA-256 verified (4ab1...e0d)
Быстрый smoke-test (5 минут)
Если времени мало — этот минимум покрывает 80%:
# 1. health
curl -sS http://127.0.0.1:8080/health
# → ok
# 2. create + simple Q&A
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
-H "Content-Type: application/json" -d '{"temp":false}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Привет! 2+2=?"}]'
sleep 6
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z"
# → должен быть user + assistant_message с "4"
# 3. SSE live
(curl -sN --max-time 8 "http://127.0.0.1:8080/agentik/conversations/$CID/events?after=1970-01-01T00:00:00Z" \
> /tmp/sse.out 2>&1) &
sleep 1
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Скажи ок"}]'
wait
cat /tmp/sse.out
# → start_reasoning, start_response, append_text, end
# 4. multi-turn
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"А 3+3?"}]'
sleep 6
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1])"
# → assistant_message с "6"
# 5. interrupt
(curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"Длинная история про драконов, 1000 слов"}]' >/dev/null) &
sleep 3
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
wait
sleep 3
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
| python3 -c "import sys,json;m=json.load(sys.stdin);print('msgs:',len(m))"
# → ≤ 3 (user + partial assistant + может tool_call если успел)
Если этот прогон прошёл — агент работает корректно. Более глубокие кейсы — выше по разделам.
Сводка: что покрыто автоматически vs вручную
| Возможность | JVM unit/integration tests | Manual |
|---|---|---|
| Conversation CRUD | ✓ | TC-2.x |
| send/messages pagination | ✓ | TC-3.x |
| SSE event format | ✗ | TC-4.x |
| Memory tools | ✓ (in-memory) | TC-5.x (real backend) |
| Skills tools | ✓ (in-memory) | TC-6.x (real dir) |
| SOUL | ✗ | TC-7.x |
| Interrupt | ✓ (FakeLiteLlm) | TC-8.x (real LLM) |
| Compaction | ✓ | TC-10.x (real long context) |
| Reflection | ✓ | TC-11.x |
| Skill mining | ✓ | TC-12.x |
| Token accounting | ✓ | TC-13.x |
| A2A protocol | ✓ (litert tests) | TC-15.x |
| Error paths | partial | TC-16.x |
| Persistence/restart | ✗ | TC-9.x |
Всё что помечено ✗ — нужно прогонять руками на реальном окружении.