
Часть 2 из 3 — «Память для AI-агентов» Архитектура. Конкретно. С формулами и lifecycle.
В предыдущем посте я разнёс пич «RAG = память» на три неудобных проблемы: чанк не знает, что он чанк; в retrieval нет структуры, только косинус; время не существует как first-class понятие. Короче — RAG это поиск, в маркетинговой обёртке «память».
Сегодня — что должно быть вместо.
Сразу дисклеймер. Я не претендую на изобретение ни одного пункта в этом списке. Атомарные факты восходят к Витгенштейну. Temporal validity — базовая логика. Knowledge graphs — целое поле с учебниками. Lifecycle для данных — стандарт в любой нормальной информационной системе.
Я претендую на другое. Я утверждаю, что все семь свойств должны работать в одной системе одновременно, и что любая система, в которой реально работают только пять из семи, продолжает врать пользователю с уверенным лицом. Увидеть это можно ровно одним способом — попробовать собрать все семь в один кодбаз и посмотреть, что получится.
Я попробовал. Получилось. Назвал braincore. Open-source, Apache-2.0, один Go-бинарь, MCP-stdio. Не превращаю статью в питч — но в каждой секции ниже добавлю одну строчку про то, как это сделано в braincore, чтобы было ясно, что мы не теоретизируем.
Поехали.
Принцип 1. Атомарные единицы знаний с lifecycle, а не «чанки в Qdrant»
Боль. В RAG любой входящий текст — диалог, design-документ, git-коммит, расшифровка митинга — нарезается на чанки и улетает в векторную БД без вопросов. Дальше что бы ни происходило — все чанки эквивалентны, все одинаково «свежие», все одинаково «истинные». Через полгода в одной коллекции лежит суп из устаревших, актуальных, гипотетических и опровергнутых фактов. И у каждого ровно один шанс попасть в retrieval — по косинусу.
Что должно быть в схеме. Любая входящая информация не течёт в память напрямую. Она прогоняется через пайплайн:
input
→ initial trust (по источнику: user=0.9, llm=0.3, web=0.4..0.7)
→ parse (entity / fact / relation / event / rule / hypothesis)
→ atomic knowledge units
→ validate (source / graph / dedup / contradiction / temporal / rule)
→ link (минимум 1 ребро в граф ИЛИ review item)
→ working memory (TTL + activation)
→ iterative verification loop
→ consolidation
→ long-term memory (только подтверждённое + связанное)
→ edge strengthening (usage + success + co-occurrence − decay)
Главное правило: ничего не попадает в long-term memory сразу. У каждой атомарной единицы знаний минимум:
truth_status:hypothesis | candidate | confirmed | contradicted | deprecatedlifecycle:staging | working | consolidated | archivedsource_ref— откуда прилетелconfidence— численная оценка уверенностиvalid_from/valid_until— когда правда
Сравни это с RAG-чанком, у которого есть только text и embedding. Это разница между ящиком хлама и складом с инвентаризацией.
Что это даёт. Когда вчера ты сказал «мы используем Postgres», а сегодня — «мы переехали на ClickHouse, Postgres теперь только OLTP» — старый факт автоматически получает valid_until = today и superseded_by = new_fact_id. На retrieve он либо вообще не появляется, либо приходит с пометкой «историческое, не текущее». Не из-за умной модели. Из-за схемы.
Как это сделано в braincore. Пайплайн staging → working → consolidated реализован буквально — три отдельные таблицы SQLite плюс промежуточный verification-loop. Запись доходит до consolidated, только если truth_status = confirmed, есть хотя бы одно ребро в графе, нет нерешённых противоречий и confidence ≥ threshold. Иначе остаётся в working с TTL или уходит в review queue.
Принцип 2. Strict Mode и право на abstain
Это, возможно, самый важный пункт во всей серии. И самый отсутствующий в коммерческих memory-фреймворках.
Боль. Стандартная метрика, по которой меряют AI-системы, — «как часто они дают правильный ответ». Это гнилая метрика. 95% правильных ответов и 5% уверенных галлюцинаций — это система, которой нельзя доверять в проде. Потому что ты заранее не знаешь, в каких 5% ты сейчас.
Правильная метрика звучит:
0% уверенно-неверных действий при приемлемом abstain rate.
Не «всегда отвечать». А «никогда не выполнять неверного действия без верификации». А если верификации нет — сказать «не знаю» и поставить себе задачу это починить.
Что должно быть в схеме. Прежде чем факт окажется в контексте промпта, он проходит через ворота:
- есть ли
source_ref? confidence ≥ threshold?trust_score ≥ threshold(для источника)?temporal_valid == true(валидно на момент запроса)?- нет нерешённого
contradictionв графе? - нет нерешённой
ambiguity?
Если хотя бы одно требование не выполнено — факт не попадает в контекст. Если для запроса ни один факт не прошёл — система возвращает abstain = true с reason = no_accepted_facts (или contradiction_unresolved, или temporal_invalid — всегда явно).
И — внимание, тут происходит магия — abstain не доставляется пользователю как тупик. Он становится brain task в backlog’е: «мне нужны evidence по X, чтобы ответить с confidence. Источник вот, конфликт вот». Система знает, чего не знает, и сама ставит себе задачу это починить.
Что это даёт. AI-агент, которому можно доверять. Не потому что он всегда прав — а потому что когда не уверен, он молчит или просит уточнения. А когда действует — действие основано на фактах, прошедших ворота, а не «ну, ChatGPT решил, что так лучше».
Покажи мне один RAG-стек, который так умеет. Я подожду.
Как это сделано в braincore. Пакет internal/strictmode — отдельный модуль с явными правилами ворот. По умолчанию каждый запрос идёт через strict mode; для UX-сценариев, где abstain неприемлем (брейншторминг, например), его можно отключить явным флагом --allow-uncertainty. Все события abstain логируются как brain tasks с источником и причиной.
Принцип 3. Causal Decision Chains, а не плоские факты
Боль. В RAG любое решение хранится как «текст про решение». На retrieve ты получаешь кусок текста, который описывает решение, — но не отвечает на «почему?», «какие альтернативы рассматривали?», «что из этого вышло?»
Через полгода ты спрашиваешь «почему мы выбрали JWT, а не сессии?» — RAG возвращает три фрагмента декларации, и модель сама дописывает reasoning. Иногда правильно. Иногда выдумывая по популярным паттернам из обучающих данных. И ты не знаешь, в каком случае какой.
Что должно быть в схеме. Сущность называется не «документ» и не «запись памяти». Сущность называется decision и имеет схему:
problem → что решали
alternatives → что рассматривали и отбросили (с причинами)
decision → что выбрали
reasoning → почему именно это
outcome → что вышло (заполняется позже, post-hoc)
superseded_by → ссылка на новое решение, если это пересмотрели
Это не «запихнём текст в эмбеддинг». Это причинная цепочка, отвечающая на ПОЧЕМУ, а не только на ЧТО.
Что это даёт. Через полгода ты спрашиваешь «почему JWT?» — система возвращает структурированный ответ:
- Problem: масштабирование сессий + audit-требования.
- Alternatives (rejected): stateful-сессии с Redis (нарушает audit), opaque-токены с централизованным lookup (latency).
- Decision: JWT с коротким TTL.
- Reasoning: stateless, audit-нейтрально, latency приемлемо.
- Outcome (зафиксирован через 4 месяца): сложность инвалидации выше ожидаемой; добавили refresh-токены.
- Superseded by: none.
RAG возвращает три фрагмента. Decision chain возвращает reasoning. Это разные продукты.
Как это сделано в braincore. Decisions — отдельный тип сущности в графе с обязательными полями problem, alternatives[], decision, reasoning и опциональными outcome/superseded_by. Хранятся не как чанки, а как структурированные записи с явными рёбрами в code-graph и в другие decisions.
Принцип 4. Стабильная идентичность кода через AST, а не строки
Боль. Этот пункт специфичен для AI-агентов, работающих с кодом, — но бьёт по всем. Ты переименовал GetUser → FetchUser, перенёс из pkg/auth в pkg/user, изменил сигнатуру с pointer-receiver на value-receiver. Все ссылки в RAG-памяти, указывающие на «GetUser в pkg/auth», теперь мертвы. Потому что RAG привязан к строкам.
И никто тебе не скажет. Чанк продолжает жить в Qdrant, его косинус к auth-запросам остаётся высоким. Агент тащит мёртвую информацию и работает по ней. Поздравляю, у тебя memory rot, замаскированный под память.
Что должно быть в схеме. Парсинг кода через go/ast (для Go) и tree-sitter (для PHP, JS, TS, Python, Rust, Java и далее). Идентичность узла строится не из строки и не из пути файла, а из структурного хеша:
node_id = sha256(qualified_name + kind + signature_hash)
Что значит:
- Переименование функции не ломает ссылки на неё (
qualified_nameизменился, но связь обновляется автоматически на следующем парсе, со back-reference на старыйnode_idкакrenamed_from). - Перенос между пакетами — то же самое.
- Изменение сигнатуры (pointer → value receiver) —
signature_hashизменился, и старые ссылки автоматически помечаютсяstale— мозг знает, что они теперь требуют review.
Что это даёт. Когда AI-агент собирается редактировать FetchUser, система подтягивает три прошлых решения про эту функцию, две регрессии в этом модуле и активные правила проекта — до того, как агент начнёт писать код. Не из-за того что косинус удачно совпал. А потому что это code graph, и у FetchUser есть рёбра к decisions, regressions и rules по идентичности, а не по текстовому сходству.
Я называю это pre-edit warning. И это качественно другой класс предотвращения ошибок, чем «запустим линтер после генерации».
Как это сделано в braincore. Code graph — отдельный слой поверх AST/tree-sitter, с фоновым reindex на filesystem watch events. Identity-хеши живут в SQLite, рёбра тоже там. На pre-edit hook агент автоматически получает контекст связанных decisions/rules/regressions.
Принцип 5. Internal Git как memory versioning
Боль. В RAG нет понятия времени, кроме created_at. Это метаданные о записи, а не о состоянии знания. Ты не можешь спросить «покажи, что я знал про этот код месяц назад». Не можешь откатить состояние памяти до того, как агент натащил мусора. Не можешь переключиться на feature-ветку и иметь параллельное ментальное состояние под неё.
Что должно быть в схеме. Каждое изменение в памяти — это commit. Не метафорически. Буквально, через go-git, в скрытый .internal-git/ репозиторий, лежащий параллельно основному репо проекта.
Это даёт:
git logпо памяти проекта — что было добавлено, что изменилось, когда.git checkoutдля отката состояния мозга на N дней — для аудита, для расследования регрессий, для тестов.- Когда ты переключаешься на feature-ветку в основном репо — мозг зеркалит это, и у каждой ветки своё ментальное состояние. Эксперимент в feature-ветке не загрязняет память master’а.
Что это даёт. Time-travel запросы: «какое решение я считал актуальным 30 дней назад?» Аудит: «когда именно агент начал считать, что мы используем ClickHouse?» Branch isolation: «в feature/oauth у нас другой подход к auth, но это знание не должно протекать в main».
RAG так не умеет. У RAG нет понятия «состояние знания» — есть только набор векторов, который растёт.
Как это сделано в braincore. .internal-git/ создаётся на braincore init. Коммиты делаются автоматически на каждое изменение knowledge units и графовых рёбер. Branch tracking синхронизирован с основным git через post-checkout hook.
Принцип 6. Memory Scoring — потому что не всё знание равно
Боль. В RAG все чанки равны. Top-k по косинусу не различает «это подтверждено десятью прошлыми использованиями» и «это написали вчера и больше не использовали». Не различает «это критично для архитектуры» и «это случайная заметка в углу». Не различает «это в активном использовании» и «это пылится с прошлого года».
Что должно быть в схеме. У каждой единицы знания есть составной MemoryScore, считаемый как взвешенная сумма:
MemoryScore =
+ 0.22 * ImportanceScore (явная важность или производная от связности)
+ 0.22 * TrustScore (надёжность источника + история подтверждений)
+ 0.20 * TaskRelevanceScore (релевантность текущему контексту работы)
+ 0.12 * UsageScore (как часто используется)
+ 0.10 * RecencyScore (свежесть)
+ 0.10 * StabilityScore (как часто меняется — стабильное надёжнее)
+ 0.08 * NoveltyScore (новизна как лёгкий буст)
− 0.18 * RiskScore (потенциальный вред от использования)
− 0.18 * NoiseScore (шум, дубликаты, низкая когерентность)
И на retrieve работает уже не cosine similarity, а:
RetrievalScore =
+ 0.35 * semantic_similarity
+ 0.20 * memory_score
+ 0.15 * graph_relevance
+ 0.15 * temporal_validity
+ 0.10 * trust_score
− 0.15 * ambiguity_penalty
Эти веса — не абсолютная истина, они эмпирически подобраны и сдвигаются с профилем использования. Точка не в числах, а в архитектурном сдвиге: retrieval перестаёт быть «текстовым сходством» и становится «сходство × важность × доверие × свежесть».
Lifecycle-переходы автоматические:
memory_score ≥ 0.80иtrust ≥ 0.75→consolidated(знание становится «прошивкой»)memory_score ≥ 0.55→ остаётся вworkingmemory_score ≥ 0.30→stagingmemory_score < 0.30→archive candidate
Что это даёт. Активную память. Не хранилище. Активную среду, в которой важное усиливается через использование, а шум сам затухает — как в биологическом мозге, где редко используемые синапсы ослабевают, а часто используемые усиливаются.
RAG = жёсткий диск, который никогда не дефрагментируется. Brain = мозг, в котором мусор сам оседает и архивируется автоматически.
Как это сделано в braincore. Scoring пересчитывается фоновым джобом раз в N часов. Lifecycle-переходы атомарны и логируются (см. Принцип 5). Все веса вынесены в конфиг — настраивай под свой проект.
Принцип 7. Negative Memory и Rule Engine
Боль. Вот что делает любой LLM-агент сегодня: повторяет ошибки. Вчера сломал миграцию — сегодня сломает похожую. RAG не поможет, потому что сломанная миграция в RAG не попадает. В RAG попадает «как писать миграции» из официальной документации. Факт, что ты лично уже наступил на эти грабли, — нигде не записан.
Что должно быть в схеме. Отдельный класс — negative memory: что сломалось, почему сломалось, как починили, какой коммит/тест это подтверждает. First-class сущность, а не маргинальное поле.
И при планировании каждый патч прогоняется через Rule Engine до генерации кода:
patch
→ architectural rules
→ code rules
→ security rules
→ performance rules
→ anti-patterns (включая «ровно эту ошибку я уже делал»)
→ repair plan ИЛИ abstain
Если нарушено правило с severity critical или high — код не пишется. Создаётся repair plan. Если ремонт невозможен — abstain (см. Принцип 2). Никаких генераций «авось проскочит».
И, критически, safe execution pipeline замыкает цикл:
checkpoint
→ apply patch
→ rules validate
→ build
→ tests
→ success → commit
→ fail → rollback → запись в negative memory
Каждое выполненное действие либо подтверждено тестами, либо откатано, либо записано как negative evidence для будущих решений.
Что это даёт. Агента, который не может повторить твою прошлогоднюю ошибку. Не потому что у него хорошая модель — а потому что rule engine физически отказывается пропускать любой патч, нарушающий правило, выведенное из этой ошибки.
RAG помогает агенту что-то найти. Хорошая память не даёт агенту что-то сломать.
Это разные продукты. И мне жаль тех, кто продолжает их путать.
Как это сделано в braincore. Negative memory — отдельный тип сущности с обязательной ссылкой на failing-тест или git-коммит. Rule engine — pre-execution гейт, severity-aware, с возможностью override только через явное подтверждение пользователя.
Бонус-принцип. Entity Disambiguation
Формально частный случай Принципа 1 (атомарные единицы), но ломается отдельно достаточно часто, чтобы заслужить свой call-out.
В RAG нет понятия сущности. Есть только текст. Если в твоём проекте две User-сущности — одна в pkg/auth, другая в pkg/billing — для RAG это два куска текста с похожими эмбеддингами. На retrieve они смешиваются, и модель уверенно объясняет логику auth в контексте billing.
Это не теория. Это происходит прямо сейчас в каждом code-RAG-агенте.
Лечение — EntityFingerprint:
fingerprint(symbol) = hash(
project_id +
file_path +
symbol_name +
symbol_type +
signature +
language
)
Две User-сущности в разных файлах = два fingerprint’а = две различные сущности, которые никогда не сливаются автоматически. Когда приходит новый кандидат, считается SameEntityScore:
SameEntityScore =
+ 0.30 * name_similarity
+ 0.20 * alias_match
+ 0.20 * context_similarity
+ 0.15 * graph_neighborhood_similarity
+ 0.10 * temporal_consistency
+ 0.05 * source_consistency
И:
≥ 0.92→auto_merge≥ 0.82→same_aslink (мягкая связь, не merge)≥ 0.65→ambiguous— создаётся ambiguity record, требующий human review- иначе — новая сущность
Главное правило: никогда не сливать сущности при низкой confidence. Лучше создать ambiguity-запись и спросить человека, чем тихо склеить и врать после этого вечно.
Зачем всё это вместе
Я намеренно не подаю это как «нигде такого не делали, я первый». Каждый из семи принципов уже существует. Атомарные факты с lifecycle — в системах knowledge management. Strict mode + abstain — в expert systems прошлого века. Causal chains — в decision support. AST-идентичность — в IDE. Internal git — в инструментах вроде Pijul и в экспериментах с Datalog-БД. Memory scoring — в исследованиях про episodic memory. Negative memory — в RL и reliability engineering.
Уникальность не в идеях. Она в сборке.
Если у тебя есть атомарные единицы, но нет strict mode — у тебя структурированная база галлюцинаций. Если есть strict mode, но нет causal chains — ты делаешь abstain, не понимая почему. Если есть causal chains, но нет AST-идентичности — твои decisions указывают в пустоту после двух рефакторингов. Если есть всё вышеперечисленное, но нет memory scoring — у тебя идеально структурированный дамп, в котором важное тонет в шуме.
Каждое свойство по отдельности — улучшение. Все семь вместе — другая категория продукта.
Это, кстати, и ответ на вопрос, который я слышу чаще всего: «зачем писать что-то новое, если есть Mem0/Letta/Zep?» Ответ — посмотри на их схемы и проверь, сколько из семи принципов там реализовано не как маркетинговое заявление, а как enforced gate в коде. Для большинства честный счёт — два-три. Для некоторых — четыре. Это не плохие продукты. Это частичные решения, которые честнее называть «структурированный retrieval», а не «память».
В части 3
Семь принципов — это инженерия. То, что должно быть в архитектуре. Но за инженерией стоит более глубокий вопрос: почему AI-агент должен знать, чего он не знает? Зачем вообще abstain, если можно просто ответить?
Часть 3 — про право AI-агента молчать. Про self-tasking. Про то, почему cognitive runtime важнее размера модели. И про то, почему правильная метрика для продакшен-AI — это не accuracy, а 0% уверенно-неверных действий при приемлемом abstain rate.
Самая короткая и самая философская часть серии. Через неделю.
Часть 2 из 3. Если пропустил Часть 1 — здесь (про то, почему RAG — это поиск, а не память). Если откликнулось — репост поможет.