reflection: Hermes-style self-reflection (последняя открытая Hermes-фича)

Self-reflection: каждые N пользовательских ходов агент запускает one-shot
LLM-размышление о качестве своих ответов, сохраняет score+weakSpots в SQLite,
подмешивает top-K последних рефлексий в system prompt как "слабые места".

* sqldelight: новая таблица reflection (id, conversation_id?, created_at,
  turns_analyzed, score, summary, weak_spots_json) + индексы по created_at и
  conversation_id.
* persistence: Reflection data class + ReflectionStore interface +
  SqliteReflectionStore (insert/get/listRecent/listForConversation/
  deleteOlderThan/count + events Flow). weakSpots хранятся как JSON-массив,
  парсятся ручным сканером (без kotlinx-serialization в этом модуле).
* agent: LlmReflector (one-shot LiteLlm через createConversation +
  send, structured-output JSON). ReflectionParser (hand-rolled,
  толерантный к ```json fences и лидирующему/завершающему тексту;
  score принимает int или строку; weakSpots — массив).
* agent: ReflectionPrompts (Russian system+user prompts, аналогично
  LlmMemoryReviewer/ReviewPrompts).
* agent: ChatAgent.buildSystemPrompt расширен параметром reflections —
  добавляется секция `## Self-reflection: твои слабые места за последнее время`
  после memory и перед soul-prepend.
* agent: ChatConversation.scheduleReflection — каждые reflectionInterval
  пользовательских ходов (счётчик через workingMemory.list) запускает
  reflector на Dispatchers.IO, результат сохраняет в reflectionStore
  с conversationId. Не блокирует turn.
* AgentikConfig: новые поля reflectionInterval (env AGENTIK_REFLECTION_INTERVAL,
  default 10, clamped 0..1000) и reflectionTopK (env AGENTIK_REFLECTION_TOP_K,
  default 3, clamped 0..20).
* Main.kt: если reflectionInterval > 0 — создаём LlmReflector(llm); загружаем
  top-K из SQLite в system prompt.
* Ids.reflection() — генератор id "refl-<uuid>".
* Tests: ReflectionParserTest (7: clean JSON, fences, лидирующий текст,
  score-as-string, отсутствие score, невалидный JSON, escape-последовательности),
  ReflectionStoreTest (7: round-trip, listRecent с лимитом, фильтр по
  conversation_id, deleteOlderThan, count, encode/decode строк), и
  ChatAgentReflectionTest (3: секция скрыта при пустых, присутствует с
  score+spots, порядок soul→memory→reflection).
* README: новые env-переменные, раздел "Self-reflection", startup output.

Smoke test подтверждает: reflection секция появляется в system prompt когда
в SQLite есть записи (listRecent возвращает непустой список). При
reflectionInterval=0 reflector не создаётся, scheduleReflection — no-op.

Tests: 305 total green (+17: 7+7+3). Fatjar собирается, logback-вывод
работает (logging: см. предыдущий коммит f5a551b).
This commit is contained in:
2026-09-15 05:09:52 +03:00
parent f5a551b2ae
commit 23da1f6498
17 changed files with 1016 additions and 12 deletions
+23
View File
@@ -53,6 +53,8 @@ java -jar standalone/build/libs/standalone-all.jar
| `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` = не подмешивать. |
### OpenAI backend
@@ -178,6 +180,26 @@ export AGENTIK_COMPRESSION_THRESHOLD=0.7 # сжимаем раньше
нового 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.
## Куратор памяти (Curator)
Фоновая корутина (запускается автоматически, если `AGENTIK_MEMORY_DIR != off`):
@@ -356,6 +378,7 @@ agentik standalone listening on http://localhost:8080
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)
```
## Остановка