0.21.0
Итерация 17 плана аудита: память — conflict resolution с историей и MMR.
Added
- Миграция
023_fact_history(sqlite + pg, up/down): - В
memory_factsдобавлены колонкиconfidence DOUBLE PRECISION DEFAULT 1.0,superseded_by BIGINT REFERENCES memory_facts(id),valid_from TIMESTAMPTZ,valid_to TIMESTAMPTZ. - Новая таблица
memory_facts_history(id, fact_id, action, old_content, new_content, old_confidence, new_confidence, reason, created_at)— append-only журнал изменений факта. - Индексы
idx_memory_facts_superseded,idx_memory_facts_history_fact. MemoryFactрасширен полямиConfidence,SupersededBy,ValidFrom,ValidTo. Новый типMemoryFactHistoryдля записей журнала.- Store-методы:
UpdateFactConfidence(ctx, factID, confidence)(при <0.2 автоматически деактивирует),SupersedeFact(ctx, factID, supersededByID)(атомарно: superseded_by + valid_to + is_active=false),AddFactHistory(ctx, h),ListFactHistory(ctx, factID, limit). Реализованы для SQLite, Postgres, DualStore. - LLM-merge в
Consolidator.persistFacts: при cosine ≥ 0.95 вместо простого UpdateFact вызываетсяaskMerge— LLM выдаёт решениеKEEP_OLD | UPDATE | MERGE | CONFLICTс предложенным content. При CONFLICT оба факта остаются, у старого confidence понижается на 0.3. Все решения фиксируются вmemory_facts_history. При ошибке LLM/невалидном JSON — fallback на UPDATE (не блокирует консолидацию). parseJSONObjectIntoвinternal/memory/json_parse.go— парсер JSON-объекта из ответа LLM с поддержкой markdown-fence и мусора до/после.- Промпт
conflictResolutionPromptдля LLM-merge с явным форматом ответа{"action":"...","content":"...","reason":"..."}. Dream.executeдляdeactivate: вместо моментальной деактивации — понижение confidence на 0.3 (черезUpdateFactConfidence); при confidence < 0.2 store-метод сам деактивирует. Это даёт «второй шанс» фактам, которые могут оказаться актуальными. История изменений:confidence_lower→deactivate. Аналогичноmerge/update/createтеперь пишут в history.Dream.executeдляmerge: дополнительные факты помечаютсяsuperseded_by = главного факта(вместо простого DeactivateFact), чтобы UI смог показать «заменён фактом #N».- MMR (Maximal Marginal Relevance) в
ContextBuilder.buildMemoryContext(internal/agent/mmr.go): отбор разнообразных результатов поверх вектор-поиска. Формула:score(d) = λ·sim(q, d) − (1−λ)·max sim(d, selected), λ=0.7. Применяется к 30 retrieval-кандидатам перед reranker (15 для facts, 10 для chunks). При ошибке embedder’а — fallback на top-K по релевантности. - Retrieval pipeline:
SearchFacts/SearchChunksтеперь берут 30 кандидатов (было 20) → MMR → reranker → top-K. Это улучшает разнообразие фактов в контексте при сохранении релевантности. - Handler
GET /api/v1/agents/{id}/memory/facts/{factID}/history— список изменений факта (append-only, anti-IDOR черезcanAccessFact). Параметр?limit=(max 500, default 100). - UI: на странице
/agents/[id]/memoryдля каждого факта добавлены: индикаторc=N.NN(confidence, цветовая шкала), значок→ #N(superseded_by), кнопка «История» с разворачиваемым inline-списком изменений (action badge + старый/новый content в diff-виде + reason от LLM/Dream). - TypeScript: типы
MemoryFact(поляconfidence,superseded_by,valid_from,valid_to),MemoryFactHistory. API-методapi.memory.getFactHistory(agentId, factId, limit?). - 30+ юнит-тестов:
internal/agent/mmr_test.go(8 тестов): SelectsDiverse (lambda=0.3 выбирает разнообразный C над почти-дубликатом B), LambdaOne_PureRelevance, LambdaZero_PureDiversity, FewerCandidatesThanK, EmbedError_Fallback, LambdaClamping, TopKZero, нормализация и dot-product.internal/memory/iter17_test.go(9 тестов): ConflictResolution_KEEP_OLD/UPDATE/CONFLICT/FallbackOnLLMError, Store_FactHistory_RoundTrip, Store_UpdateFactConfidence_BelowThreshold (граница 0.2), Store_SupersedeFact (superseded_by + valid_to + is_active=false атомарно), Dream_Deactivate_LowersConfidence (3 цикла понижения 1.0→0.7→0.4→0.1, последний цикл деактивирует с записьюdeactivateв истории), TestParseJSONObjectInto (clean / markdown-fence / garbage / no_json / invalid_json).
Changed
Consolidator.persistFacts: при создании нового факта черезAddFactWithVectorсразу пишется записьcreateвmemory_facts_historyсnew_contentиnew_confidence. Это даёт consistent timeline в UI.MemoryFact.Confidenceучаствует в дедупликации Consolidator-а: приConfidence == 0вAddFactWithVectorсохраняется default 1.0 (обратная совместимость со старым кодом).DualStore.copyAllTables/syncSequences/purge — добавлена таблицаmemory_facts_historyв правильном порядке (послеmemory_facts).buildMemoryContextтеперь срезает top-K (5/3) даже при отсутствии reranker’а — после MMR-фильтрации возвращалось до 15/10 элементов, что было больше документированного поведения.
Notes
- Внешний API совместим:
GET /memory/facts/*возвращает старые поля плюс новыеconfidence,superseded_by,valid_from,valid_to. Старые SQLite-БД получают defaultconfidence=1.0через миграцию (NOT NULL DEFAULT 1.0). - Dream остаётся в admin-режиме (видит все факты включая приватные); user-scope при merge/dedup отложен — см. план итерации 19.