diff --git a/MANUAL-TESTS.md b/MANUAL-TESTS.md new file mode 100644 index 0000000..f56bc99 --- /dev/null +++ b/MANUAL-TESTS.md @@ -0,0 +1,852 @@ +# 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: @ ` соответствует +твоему конфигу. **Нет** 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= +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= +# 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= +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). Если больше — где-то утечка. + +--- + +## Быстрый 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 | + +Всё что помечено ✗ — нужно прогонять руками на реальном окружении.