Постоянная память ИИ без эмбеддингов, Pinecone или докторской степени в области поиска сходства.
Делиться

Всё началось с того, что мой ассистент в Obsidian постоянно терял память. Я не хотел запускать Pinecone или Redis только для того, чтобы Клод вспомнил, что Алиса утвердила бюджет на третий квартал на прошлой неделе. Оказалось, что с более чем 200 тысячами контекстных окон всё это может и не понадобиться.
Хочу поделиться новым механизмом, который я запустил. Это система, построенная на SQLite и прямом логическом выводе LLM, без векторных баз данных и конвейера встраивания. Векторный поиск был в основном обходным путем для маленьких контекстных окон и предотвращения запутывания подсказок. С современными размерами контекста часто можно обойтись без этого и просто позволить модели считывать ваши воспоминания напрямую.
Настройка
Я делаю подробные записи как в личной жизни, так и на работе. Раньше я писал в блокнотах, которые потом терялись или валялись на полке, и я больше никогда к ним не обращался. Несколько лет назад я перешел на Obsidian для всего, и это оказалось просто замечательно. В прошлом году я начал подключать genAI к своим заметкам. Сегодня я использую Claude Code (для личных заметок) и Kiro-CLI (для рабочих заметок). Я могу задавать вопросы, заставлять их создавать сводные отчеты для руководства, отслеживать свои цели и писать отчеты. Но у него всегда была одна большая ахиллесова пята: память. Когда я спрашиваю о совещании, он использует Obsidian MCP для поиска в моем хранилище. Это отнимает много времени, чревато ошибками, и мне нужно, чтобы он был лучше.
Очевидное решение — векторная база данных. Встроить воспоминания. Хранить векторы. Выполнять поиск сходства во время запроса. Это работает. Но это также означает стек Redis, учетную запись Pinecone или локально работающий экземпляр Chroma, плюс API для встраивания, плюс код конвейера для объединения всего этого. Для личного инструмента это много, и существует реальный риск, что он не будет работать именно так, как мне нужно. Мне нужно будет спрашивать, что произошло 1 февраля 2026 года, или «подводить итоги последней встречи с этим человеком» — вещи, с которыми встраивания и RAG не очень хорошо справляются.
Затем я наткнулся на постоянно работающий в памяти агент Google https://github.com/GoogleCloudPlatform/generative-ai/tree/main/gemini/agents/always-on-memory-agent. Идея довольно проста: вообще не нужно выполнять поиск по сходству; просто передайте LLM ваши недавние воспоминания напрямую, и пусть он их обрабатывает.
Мне хотелось узнать, будет ли это работать на AWS Bedrock с Claude Haiku 4.5. Поэтому я его разработал (конечно же, вместе с Claude Code) и добавил несколько дополнительных функций.
Посетите мой репозиторий на GitHub, но обязательно загляните ещё!
https://github.com/ccrngd1/ProtoGensis/tree/main/memory-agent-bedrock
Прозрение, меняющее математику.
В старых моделях максимальное количество токенов составляло 4 или 8 тысяч. В запрос можно было поместить лишь несколько документов. Функция встраивания позволяла получать нужные документы, не загружая всё целиком. Это было действительно необходимо. Haiku 4.5 предлагает контекстное окно размером 250 тысяч, так что же мы можем с ним сделать?
Структурированная память (сводка, сущности, темы, оценка важности) содержит около 300 токенов. Это означает, что мы можем получить около 650 ячеек памяти, прежде чем достигнем предела. На практике это немного меньше, поскольку системные запросы и подсказки также потребляют токены, но для персонального помощника, отслеживающего встречи, заметки и разговоры, это означает месяцы контекста.
Нет векторных представлений, нет индексов векторов, нет косинусного сходства.
Метод LLM рассуждает непосредственно о семантике, и в этом он превосходит метод косинусного сходства.
Архитектура

Оркестратор — это не отдельная служба. Это класс Python внутри процесса FastAPI, который координирует работу трех агентов.
Задача IngestAgent проста: взять необработанный текст и спросить у Haiku, что стоит запомнить. Он извлекает краткое содержание, сущности (имена, места, вещи), темы и оценку важности от 0 до 1. Этот пакет помещается в таблицу `memories`.
ConsolidateAgent работает по интеллектуальному планированию: при запуске, если существуют какие-либо воспоминания, при достижении порогового значения (по умолчанию 5 и более воспоминаний), а также ежедневно в качестве принудительного прохода. При срабатывании он объединяет неконсолидированные воспоминания в пакеты и запрашивает у Haiku поиск сквозных связей и генерацию аналитических выводов. Результаты сохраняются в таблице `consolidations`. Система отслеживает метку времени последней консолидации, чтобы обеспечить регулярную обработку даже при низком объеме накопленного памяти.
QueryAgent считывает недавние воспоминания и информацию о консолидации в один запрос и возвращает синтезированный ответ с идентификаторами цитирований. Это весь путь выполнения запроса.
Что именно хранится
Когда вы вводите текст типа «Сегодня встречался с Алисой. Бюджет на 3 квартал утвержден, 2,4 млн долларов», система не просто записывает эту строку в базу данных. Вместо этого IngestAgent отправляет ее в Haiku и спрашивает: «Что здесь важно?»
LLM извлекает структурированные метаданные:
{ "id": "a3f1c9d2-...", "summary": "Alice confirmed Q3 budget approval of $2.4M", "entities": ["Alice", "Q3 budget"], "topics": ["finance", "meetings"], "importance": 0.82, "source": "notes", "timestamp": "2026-03-27T14:23:15.123456+00:00", "consolidated": 0 } Таблица memories содержит эти отдельные записи. При приблизительно 300 токенах на каждое воспоминание, отформатированное в подсказку (включая метаданные), теоретический потолок составляет около 650 воспоминаний в контекстном окне Haiku размером 200 000 символов. Я намеренно установил значение по умолчанию равным 50 недавним воспоминаниям, поэтому я значительно не достиг этого потолка.
Когда запускается ConsolidateAgent, он не просто суммирует воспоминания. Он анализирует их. Он находит закономерности, устанавливает связи и генерирует выводы о том, что означают эти воспоминания в совокупности. Эти выводы сохраняются в виде отдельных записей в таблице consolidations :
{ "id": "3c765a26-...", "memory_ids": ["a3f1c9d2-...", "b7e4f8a1-...", "c9d2e5b3-..."], "connections": "All three meetings with Alice mentioned budget concerns...", "insights": "Budget oversight appears to be a recurring priority...", "timestamp": "2026-03-27T14:28:00.000000+00:00" }При выполнении запроса система загружает в один и тот же запрос как исходные данные из памяти, так и результаты консолидации. LLM анализирует оба уровня одновременно, включая недавние факты и синтезированные закономерности. Именно так вы получаете ответы, например: «Алиса поднимала вопросы, касающиеся бюджета, на трех отдельных встречах [память:a3f1c9d2, память:b7e4f8a1], и закономерность указывает на то, что это имеет высокий приоритет [консолидация:3c765a26]».
Эта двухтабличная структура представляет собой весь уровень хранения данных. Один файл SQLite. Никакого Redis. Никакой Pinecone. Никакого конвейера встраивания. Только структурированные записи, с которыми LLM может напрямую работать.
Чем на самом деле занимается агент по консолидации долгов.
Большинство систем памяти работают исключительно в режиме поиска. Они хранят, ищут и возвращают похожий текст. Агент консолидации работает иначе: он считывает набор несвязанных воспоминаний и задает вопросы: «Что их объединяет?», «Что у них общего?», «Как они связаны?»
Эти выводы записываются в отдельную consolidations запись. При выполнении запроса вы получаете как исходные данные , так и синтезированные выводы. Агент не просто вспоминает, он рассуждает.
Аналогия со спящим мозгом из оригинальной реализации Google кажется довольно точной. В свободное время система обрабатывает информацию, а не просто ждет. Это то, с чем я часто сталкиваюсь при создании агентов: как сделать их более автономными, чтобы они могли работать, когда я не работаю, и это хорошее применение этого «свободного времени».
Для личного использования это важно. Фраза «В этом месяце у вас было три встречи с Алисой, и на всех встречах обсуждались бюджетные проблемы» полезнее, чем три отдельных подтверждения воспоминаний.
В первоначальной версии использовался простой пороговый уровень для консолидации: система ждала 5 сохраненных фрагментов памяти, прежде чем приступить к консолидации. Это работает при активном использовании. Но если вы загружаете данные лишь спорадически, например, заметку или изображение, то можете ждать несколько дней, прежде чем достигнете порогового значения. Тем временем эти фрагменты памяти остаются необработанными, и запросы не получают никакой пользы от распознавания образов агентом консолидации.
Поэтому я решил добавить еще два триггера. При запуске сервера он проверяет наличие неконсолидированных воспоминаний из предыдущей сессии и обрабатывает их немедленно. Без ожидания. А по ежедневному таймеру (настраиваемому) он принудительно запускает процесс консолидации, если что-то ожидает обработки, независимо от того, достигнут ли порог в 5 воспоминаний. Таким образом, даже одна заметка в неделю будет консолидирована в течение 24 часов.
Оригинальный режим, основанный на пороговом значении, по-прежнему работает при активном употреблении. Но теперь под ним есть система безопасности. Если вы активно употребляете наркотики, пороговое значение это обнаруживает. Если нет, то срабатывает дневной пропуск. И при перезапуске ничто не останется без внимания.
Отслеживание файлов и обнаружение изменений
У меня есть хранилище Obsidian с сотнями заметок, и я не хочу вручную добавлять каждую из них. Я хочу указать наблюдателю на хранилище и позволить ему сделать все остальное. Именно это и делает данный код.
При запуске наблюдатель сканирует каталог и загружает все, что ранее не видел. Он работает в двух режимах в фоновом режиме: быстрое сканирование каждые 60 секунд проверяет наличие новых файлов (быстрое, без вычисления хеша, просто «есть ли этот путь в базе данных?»), и полное сканирование каждые 30 минут, вычисляет хеши SHA256 и сравнивает их с сохраненными значениями. Если файл изменился, система удаляет старые записи, очищает все консолидации, которые ссылались на него, повторно загружает новую версию и обновляет запись отслеживания. Никаких дубликатов. Никаких устаревших данных.
Для работы с личными заметками наблюдатель охватывает все ожидаемые функции:
- Текстовые файлы (.txt, .md, .json, .csv, .log, .yaml, .yml)
- Изображения (.png, .jpg, .jpeg, .gif, .webp), проанализированные с помощью методов компьютерного зрения Клода Хайку.
- PDF-файлы (.pdf), текст извлечен с помощью PyPDF2.
Рекурсивное сканирование и исключения из каталогов можно настроить. Отредактируйте заметку в Obsidian, и в течение 30 минут память агента отразит внесенные изменения.
Почему нет базы данных векторов?
Необходимость использования встроенных элементов для ваших личных заметок сводится к двум моментам: количеству ваших заметок и способу поиска по ним.
Векторный поиск действительно необходим, когда у вас миллионы документов и вы не можете отобрать из них релевантные в контексте. Это оптимизация поиска для крупномасштабных задач.
В масштабах персонального компьютера вы работаете с сотнями воспоминаний, а не с миллионами. Vector означает, что вы запускаете конвейер встраивания, оплачиваете вызовы API, управляете индексом и реализуете поиск по сходству для решения задачи, которую уже решает контекстное окно размером в 200 000 элементов.
Вот как я оцениваю компромиссы:
Сложность
Точность
Шкала
Я не мог оправдать необходимость создания и поддержки векторной базы данных, даже FAISS, для тех немногих заметок, которые я создаю.
Кроме того, этот новый метод обеспечивает мне более точный поиск в моих заметках.
Увидеть это в действии
Вот как это выглядит на практике. Конфигурация осуществляется через файл .env с разумными значениями по умолчанию. Вы можете скопировать пример и начать использовать его (при условии, что вы уже запустили aws configure на своем компьютере).
cp .env.example .envЗатем запустите сервер с активным файловым монитором.
./scripts/run-with-watcher.shДля тестирования примера загрузки данных выполните команду CURL для конечной точки /ingest. Это необязательный параметр, демонстрирующий принцип работы. При настройке для реального сценария использования его можно пропустить.
-H "Content-Type: application/json" -d '{"text": "Met with Alice today. Q3 budget is approved, $2.4M.", "source": "notes"}'Ответ будет выглядеть следующим образом.
{ "id": "a3f1c9d2-...", "summary": "Alice confirmed Q3 budget approval of $2.4M.", "entities": ["Alice", "Q3 budget"], "topics": ["finance", "meetings"], "importance": 0.82, "source": "notes" }Для последующего запроса используйте команду CURL для доступа к конечной точке запроса.
query?q=What+did+Alice+say+about+the+budgetИли воспользуйтесь интерфейсом командной строки:
python cli.py ingest "Paris is the capital of France." --source wikipedia python cli.py query "What do you know about France?" python cli.py consolidate # trigger manually python cli.py status # see memory count, consolidation stateКак сделать его полезным не только для CURL
curl работает, но вы же не будете запускать её в 2 часа ночи, когда у вас появилась идея, поэтому у проекта два пути интеграции.
Навык Claude Code / Kiro-CLI. Я добавил встроенный навык, который автоматически активируется при необходимости. Скажите: «Помните, что Алиса утвердила бюджет на 3 квартал», и он сохранит его без необходимости что-либо активировать. Спросите: «Что Алиса сказала о бюджете?» на следующей неделе, и он проверит память перед ответом. Он обрабатывает ввод данных, запросы, загрузку файлов и проверку статуса посредством естественного диалога. Именно так я чаще всего взаимодействую с системой памяти, поскольку большую часть времени я провожу в CC/Kiro.
Интерфейс командной строки. Для пользователей терминала или для написания скриптов.
python cli.py ingest "Paris is the capital of France." --source wikipedia python cli.py query "What do you know about France?" python cli.py consolidate python cli.py status python cli.py list --limit 10Интерфейс командной строки взаимодействует с той же базой данных SQLite, поэтому вы можете использовать API, CLI и навыки взаимозаменяемо. Ввод данных из скрипта, запросы из Claude Code и проверка статуса из терминала — все данные поступают в одно и то же хранилище.
Что дальше?
Хорошая новость: система работает, и я использую её сегодня, но вот несколько дополнений, которые могли бы ей пригодиться.
Фильтрация запросов с учетом важности. Сейчас агент запросов считывает N самых последних воспоминаний. Это означает, что старые, но важные воспоминания могут быть вытеснены недавним шумом. Я хочу фильтровать по показателю важности перед построением контекста, но пока не уверен, насколько агрессивно. Я не хочу, чтобы очень важное воспоминание двухмесячной давности исчезло только потому, что я просмотрел кучу заметок с совещаний на этой неделе.
Фильтрация метаданных. Аналогично, поскольку каждое воспоминание имеет связанные с ним метаданные, я мог бы использовать эти метаданные для фильтрации заведомо неверных воспоминаний. Если я задаю вопросы об Алисе, мне не нужны воспоминания, в которых фигурируют только Боб или Чарли. В моем случае это может быть основано на иерархии моих заметок, поскольку я храню заметки, привязанные к клиентам и/или конкретным проектам.
Удаление и обновление конечных точек. Сейчас хранилище работает только в режиме добавления данных. Это нормально, пока не произойдет ошибка при обработке данных, и вам не потребуется ее исправить. DELETE /memory/{id} — это очевидный пробел. Просто пока он мне не был настолько необходим, чтобы его создавать.
Интеграция с MCP. Использование этого решения в качестве MCP-сервера позволит любому клиенту, совместимому с Claude, использовать его в качестве постоянной памяти. Это, пожалуй, самая сложная задача в этом списке, но и самая трудоемкая.
Попробуйте!
Проект размещен на GitHub в рамках начатой мной серии, в которой я реализую результаты научных исследований, изучаю передовые идеи и перепрофилирую удобные инструменты для Bedrock (https://github.com/ccrngd1/ProtoGensis/tree/main/memory-agent-bedrock).
Это Python без каких-либо экзотических зависимостей, только boto3, FastAPI и SQLite.
Модель по умолчанию — `us.anthropic.claude-haiku-4-5-20251001-v1:0` (профиль межрегионального вывода Bedrock), который можно настроить через .env .
Несколько слов о безопасности: сервер по умолчанию не имеет аутентификации; он предназначен для локального использования. Если вы предоставляете к нему доступ по сети, сначала добавьте аутентификацию. База данных SQLite будет содержать все данные, которые вы когда-либо загружали, поэтому обращайтесь с ней соответствующим образом (для начала достаточно установить chmod 600 memory.db ).
Если вы разрабатываете собственные инструменты для ИИ и застряли на проблеме с памятью, этот подход заслуживает внимания. Дайте мне знать, если вы решите его попробовать, как он вам подходит и в каком проекте вы его используете.
О
Николаус Лоусон — архитектор решений с опытом работы в области разработки программного обеспечения и искусственного интеллекта и машинного обучения. Он работал в самых разных отраслях, включая промышленную автоматизацию, здравоохранение, финансовые услуги и компании-разработчики программного обеспечения, от стартапов до крупных предприятий.
Данная статья и любые мнения, высказанные Николаусом, являются его собственными и не отражают позицию его нынешних, прошлых или будущих работодателей, а также его коллег или партнеров.
Вы можете связаться с Николаусом через LinkedIn по ссылке: https://www.linkedin.com/in/nicholaus-lawson/
Ник Лоусон. Все материалы от Ника Лоусона.
Источник: towardsdatascience.com






















