Files
agentik/MANUAL-TESTS.md
subochev 408caee261 feat(standalone): agentik pull-model subcommand + AGENTIK_AUTO_DOWNLOAD_MODEL=1 trigger for LiteRT-LM
Добавляет 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.
2026-09-16 12:55:43 +03:00

948 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```bash
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-сообщения:
```bash
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
-H "Content-Type: application/json" \
-d '[{"type":"text","body":"..."}]'
```
Чтение истории:
```bash
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
```bash
curl -sS -i http://127.0.0.1:8080/health
```
**Ожидание:** `HTTP/1.1 200 OK`, тело `ok`.
### TC-1.2 — agent card (A2A)
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
curl -sS -i http://127.0.0.1:8080/agentik/conversations/conv-nonexistent
```
**Ожидание:** `404`.
---
## 3. Message sending
### TC-3.1 — simple Q&A
Создай беседу, пошли простой вопрос, прочитай историю.
```bash
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, требующий контекста:
```bash
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
```bash
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
```bash
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
```bash
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)
```bash
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 в той же беседе
```bash
# В существующей беседе
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`):
```bash
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
Создай новую беседу, спроси без подсказок:
```bash
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 → ошибка или автозамена
```bash
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`:
```bash
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`, в системном
промте должна появиться секция с этими навыками.
```bash
ls -la /root/skills/ # должен быть хотя бы один файл
```
```bash
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
```bash
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
Перезапусти агент:
```bash
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'
```
После старта пошли в новую беседу:
```bash
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 подключается
```bash
echo 'Ты — ворчливый капитан дальнего плавания. Отвечай кратко, с морскими метафорами.' > /root/SOUL.md
# Перезапустить агент
```
```bash
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
```bash
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)
```bash
# Длинный 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'а
```bash
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 — перезапуск не теряет беседы и память
```bash
# 1. Создай беседу, пошли сообщение, дождись ответа
# 2. Запомни факт через memory_save
# 3. Перезапусти агент (см. TC-6.3)
# 4. GET /agentik/conversations — беседа должна быть в списке
# 5. GET /agentik/conversations/$CID/messages — история на месте
# 6. Новая беседа + вопрос про запомненный факт — модель помнит
```
### TC-9.2 — temp conversation не переживает рестарт
```bash
# Создай 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 — длинная беседа сжимается
```bash
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`:
```bash
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 создаёт записи
```bash
# Пошли 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`
```bash
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'а
```bash
# Пошли 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`
```bash
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
```bash
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`
```bash
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 работает
```bash
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
```bash
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
```bash
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). Пошли сообщение:
```bash
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:
```bash
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
```bash
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`-файлы:
```bash
ls -la /root/agentik-memory/
```
**Ожидание:** файлы типа `user.md`, `world.md`, `preference.md` (или
всё в одном файле — зависит от реализации).
### TC-17.2 — off backend (память выключена)
Перезапусти с `AGENTIK_MEMORY_DIR=off`:
```bash
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` через модель должна вернуть ошибку:
```bash
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
```bash
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` качает модель вручную
```bash
# Скачать дефолтную модель (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 если файл уже полный
```bash
# Повторный запуск с тем же 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)
```bash
# Симулируем обрыв: удаляем финальный, оставляем .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
```bash
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`
```bash
# Удалить файл, запустить с флагом:
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`
```bash
# Качаем 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.
```bash
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%:
```bash
# 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 |
Всё что помечено ✗ — нужно прогонять руками на реальном окружении.