- BrainTools - https://www.braintools.ru -

AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code

Изображение сгенерировано в Recraft

Изображение сгенерировано в Recraft

Разрабатываю AI-агента персональной аналитики для себя. Любопытной инженерной задачей стала архитектура памяти [1]: как сделать, чтобы агент помнил не только последний разговор, но и паттерны, накопленные за месяцы. В этой статье описана архитектура, рабочие решения и грабли, на которые я наступила.

Зачем он мне нужен

Я уже давно не техред и даже не техпис, а последний текст на Хабре был написан мной больше трёх лет назад. За это время я стала дипломированным системным аналитиком, работаю в финтехе, и три года потихоньку расту в AI и ML. 

Невзирая на профессиональные перемены, в моей жизни есть что-то неизменное — много лет я трекаю данные о движении и здоровье, работаю над улучшением своих HRV и VO2, слежу за трендами longevity и пью БАДы по результатам анализов, а не по рилсам. 

Пару лет носила Oura, но базовый носитель – это Apple Watch, данные о тренировках и маркерах с которых копятся в Apple Health с 2017 года. Это 9 лет и около 9 миллионов записей. 

Как и многие продвинутые любители wearables, я храню анализы в PDF, пользуюсь умными весами, считаю КБЖУ через приложение, делаю замеры, обожаю дашборды, сравнивать данные, видеть прогресс, фиксить регресс и анализировать свой образ жизни через разнообразные показатели.

Ещё я люблю рефлексировать через дневниковые записи, периодически пользуюсь услугами психотерапевтов для наведения порядка в ментальных вселенных. Конечно, я безумно много общалась с ChatGPT, пока не пришёл Клод, и вообще представляю собой достаточно богатый массив данных, связанных с внедрением дисциплины и прогресса в свою жизнь. 

Неудивительно, что в какой-то момент я просто устала кормить этим всем нейронки, путаться в архивах и захотела одну большую аналитическую кнопку.

Зачерпнула из своего изобильного data lake живой воды — получила внятный срез, где всё разложено по полочкам: отчёты и выборки, подсвеченные проблемные места, скорректированный план тренировок и питания, понимание как улучшить сон [2]. А обратная связь — живой разбор с когнитивными искажениями, схемами реагирования [3] и конкретными действиями. КПТ и схема-терапия под капотом. Ну просто карманные Брайан Джонсон с любимым психоаналитиком, тренером и семейным доктором строчат тебе сообщения — удобно же.  

Так и родилась идея агента с точкой входа через ТГ, которому я буду всё это рассказывать и показывать, а он сам будет всё мэтчить и выдавать отчёты по запросу. И вот –  Sketching… Gusting… Boondoggling… Sublimating… Flummoxing… Flowing… Perambulating… — кайфинг от взаимодействия с СС начался. 

Что получилось: обзор системы

AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 2

Стек: Python 3.12, FastAPI, PostgreSQL 16 + pgvector, aiogram 3, n8n (только для триггеров и вебхуков, не логика [4]), Docker Compose.

Интерфейс — telegram-бот, голосовые сообщения, мультимодальность. Голос транскрибируется через Groq Whisper (бесплатно 2 часа в день, качество отличное).

LLM routing

Два провайдера с разными ролями:

  • Claude Haiku — агентика: /ask, /report, /scope, /mind, Memory Synthesizer. Следует системному промпту, не добавляет RLHF safety-дисклеймеров (например, «проконсультируйтесь с врачом» в каждый ответ про здоровье) и таблицы сырых данных вопреки инструкциям, выбран в качестве аналитика для отчётов.

  • GPT-4o-mini — парсинг: еда, фазы сна [5] по фото, замеры тела, даты, медицинские документы. Дешевле, достаточно точно для структурированного извлечения, отвратительно по tone-of-voice и слабенько для аналитики. Сикофансия ChatGPT раздражает полмира, а назойливые присказки в начале и в конце сообщений не лечатся самыми жёсткими промптами.

Выбор пал на Haiku, а не на великолепные Sonnet или Opus, потому что пока играю в свою маленькую игрушку и практикую на мелких масштабах. Anthropic Tier 1 ограничивает 30k токенов/мин. Sonnet потребляет 35–60k за два шага агента — rate limit на каждом запросе. Haiku вписывается в лимит. Все вызовы идут через единую функцию ask_model() с автофоллбэком по провайдерам и логированием токенов, стоимости и времени.

Tool-calling агент: 14 инструментов

AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 3

Tool-calling — базовый подход для персональной аналитики: динамическое ре��ение, что достать, вместо захардкоженного набора запросов. Агент сам определяет нужные данные, делает столько вызовов инструментов сколько нужно, потом синтезирует ответ. 

14 инструментов в четырёх группах:

  • данные тела: get_health_metrics, get_sleep, get_body_metrics, get_nutrition, get_workouts

  • память и профиль: get_memory_insights, get_user_profile, search_personal_data, search_knowledge_base, get_mind_entries

  • медицина: get_lab_results, get_doctor_reports

  • аналитика: compute_correlation (коэффициент Спирмена с p-value), get_trend (помесячная агрегация).

Все инструменты поддерживают days (последние N дней) или from_date/to_date. При периоде больше 90 дней происходит автоматическая агрегация по месяцам: 24 строки вместо 730. Это ключевой момент: без агрегации один вызов get_health_metrics за 2 года возвращал 730 строк — 234k токенов, модель захлёбывалась.

Динамическая выборка инструментов

Для точечных /ask-запросов — показать динамику веса или состава тела за полгода — запускать все 14 инструментов избыточно. Навайбкодила select_tools() — keyword-matching по 8 категориям возвращает 3–4 инструмента вместо 14. Всегда включает get_user_profile и get_memory_insights. Fallback на все 14, если запрос слишком размытый или охватывает 4+ категории одновременно.

def select_tools(query: str) -> list[dict]:
    q = query.lower()
    selected = set(_ALWAYS_TOOL_NAMES)  # user_profile + memory_insights
    matched = 0
    for config in _TOOL_CATEGORIES.values():
        if any(kw in q for kw in config["keywords"]):
            selected.update(config["tools"])
            matched += 1
    if matched == 0 or matched >= _FALLBACK_THRESHOLD:
        return ANALYST_TOOLS  # всё
    return [_TOOL_BY_NAME[name] for name in selected]
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 4 [6]

Результат: /ask точечный запрос — 9,7k токенов вместо 101k. Основной выигрыш не в данных, а в схемах инструментов: каждая схема весит ~1k токенов, и они передаются на каждой итерации agent loop.

Компактный формат ответов инструментов

Вместо json.dumps() строк из БД — pre-aggregation в Python: средние за период, отдельно аномалии. 9 из 14 инструментов переведены на этот формат. Вот как выглядит вывод get_health_metrics за короткий период:

Метрики 2026-02-20 — 2026-03-06 (средние):
  HRV 41.2мс, ЧСС 72.4, ЧССп 58.1, шаги 9840/д, 512ккал, 7.8км
Аномалии:
  2026-02-23: HRV 28мс (низкий)
  2026-03-01: шаги 312 (нет активности)
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 5 [6]

Агент получает готовую картину и тратит токены на анализ, а не на разбор сырых строк.

Долгосрочная память: 5 слоёв

AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 6

Это центральное инженерное решение агента. Стандартное контекстное окно не решает задачу: каждый новый запрос начинается с нуля, агент не помнит, что наблюдал неделю назад. Нужна архитектура, которая накапливает знание о конкретном человеке.

Реализовано 5 слоёв:

Слой 1 — сессионная память. Последние 6 сообщений из БД, SQL-запрос, только для plain text. Дёшево, быстро, покрывает follow-up вопросы в рамках одного разговора.

Слой 2 — структурированная эпизодическая. PostgreSQL: health_metrics, sleep_sessions, lab_results, body_metrics, nutrition_logs, messages. Весь исторический массив агент достаёт через SQL-инструменты.

Слой 3 — семантическая личная. Pgvector, таблица message_embeddings. Эмбеддинги всех сообщений (OpenAI text-embedding-3-small, 1536 dims). Поиск по смыслу «когда я писала про усталость» найдёт релевантное, даже если слово не встречалось буквально.

Слой 4 — база знаний. Pgvector, таблица knowledge_chunks. Загруженные документы: PDF, TXT, MD, URL. Статьи и исследования по витаминам, метаболизму, женскому здоровью, протоколы тренировок, медицинские материалы — многое из этого собирается в NotebookLM и пока руками приносится боту. search_knowledge_base.

Слой 5 — синтезированные паттерны. Memory_insights, верифицированные пользователем инсайты агент читает при каждом запросе.

Для верификации придуман Memory Synthesizer — синтезатор памяти. Запускается механизм по еженедельному фоновому job (n8n cron). Один LLM-вызов анализирует данные за 28 дней по пяти источникам: health_metrics, messages, nutrition_logs, body_metrics, user_profile. Возвращает JSON с новыми паттернами и подтверждёнными старыми.

Механизм накопления доказательной базы — ключевой момент архитектуры. Паттерн не уходит пользователю сразу: он накапливает подтверждения при каждом прогоне. Порог — 3 подтверждения. Только потом — пуш в Telegram, пользователь верифицирует через /memory. «Да» → memory_insights, агент читает при каждом запросе. «нет» → rejected. TTL pending_insights — 30 дней: паттерн, который не набрал подтверждений, удаляется.

# synthesizer.py — фрагмент накопления паттернов
for pid in confirmed_ids:
    row = await conn.fetchrow(
        """
        UPDATE pending_insights
        SET confirmations = confirmations + 1, last_seen = $1
        WHERE id = $2 AND user_id = $3 AND status = 'pending'
        RETURNING id, pattern_text, confirmations
        """,
        today, pid, user_id,
    )
    if row and row["confirmations"] == CONFIRMATIONS_THRESHOLD:  # 3
        push_patterns.append((row["id"], row["pattern_text"]))
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 7 [6]

Пример паттерна, который система нашла: «Когда HRV < 40 мс, в следующие 2 дня калораж выше нормы на ~200 ккал». Это наблюдение из данных конкретного человека, подтверждённое трижды за три недели и верифицированное им самим.

User_profile. Это скорее постоянный контекст, чем слой памяти: подгружается в system prompt при каждом запросе. Медицинский фон, цели, личностный профиль. Инструкция агенту в формате: «используй такой-то факт для интерпретации данных».

Pgvector — опциональный путь. Эмбеддинги создаются фоново при каждом сохранении сообщения, но поиск запускается, только когда агент явно вызывает search_personal_data или search_knowledge_base. На каждый запрос это не нужно и дорого.

RAG по документам

Два слоя RAG с одинаковым техническим пайплайном, но разной семантикой.

Message_embeddings — личная история: всё, что пользователь говорил боту. Search_personal_data находит релевантное по смыслу. Полезно для запросов типа «когда я в последний раз писала, что устала после тренировки» — точный SQL не поможет.

Knowledge_chunks — загруженные материалы. Пайплайн: документ → PyMuPDF (текст) или Vision fallback, если нет текстового слоя → чанкинг → OpenAI text-embedding-3-small → pgvector с ivfflat индексом. Cosine search. Добавить документ: переслать PDF/TXT/MD боту или вставить URL.

В одном ответе агент может совместить оба слоя: данные из БД + релевантный кусок из загруженного исследования. Агент сам решает, когда обращаться к каждому.

Источники данных

Apple Health

Программирование на шоткатах — для очень упорных и терпеливых

Программирование на шоткатах — для очень упорных и терпеливых

Изначально планировался полный пайплайн через iOS Shortcuts → n8n → PostgreSQL. По факту работает только один большой шоткат: активность (HRV, шаги, ЧСС, калории, VO2max и ещё пять показателей). Установлен автозапуск шотката на iPhone, но работает он через раз, и если телефон заблочен, то может не сработать. Поэтому запускаю шоткат перед сном [7] сама.

Сон и тренировки на шоткатах убраны в бэклог — iOS 26.3 не принимает комбинацию Source filter + Value filter в одном шоткате, отдаёт пустой результат. Фундаментальный баг HealthKit, из-за которого 5 часов жизни и сотни токенов СС потрачено впустую.

Внезапной задачей стала дедупликация: iPhone и Apple Watch пишут одни метрики параллельно. Решение — source filter только для Apple Watch + нормализация на входе в Python: запятая → точка (русская локаль iPhone), вложенный объект {“”: “2026-02-25”} → дата. Оба бага нашлись уже в проде.

# intake.py
def normalize_decimal(value) -> Decimal | None:
    """Запятая → точка (русская локаль iPhone), пустая строка → None."""
    if value is None:
        return None
    s = str(value).strip().replace(",", ".")
    if not s:
        return None
    try:
        return Decimal(s)
    except InvalidOperation:
        return None
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 9 [6]

Решение пока такое: еженедельный экспорт export.xml. Import-скрипты автоматически берут from_date по MAX(recorded_date) в БД, обрабатывают только новое. Таким образом качественная глубокая аналитика будет раз в неделю, между экспортами фазы сна и ЧСС тренировок  только исторические.

Тренировки из XML

Import_workouts.py парсит <Workout> + <WorkoutStatistics> из export.xml. WorkoutStatistics содержит HKQuantityTypeIdentifierHeartRate — avg и max HR для каждой тренировки без JOIN к записям пульса. Залито 4019 тренировок за 2017–2026.

Первая версия на iterparse накапливала DOM-дерево в памяти: 6+ ГБ на файле 3.9 ГБ. Ubuntu, которую я накатила на свой старенький MacBookPro, не справлялась. Попросила СС что-то с этим сделать, и он переписал на xml.sax.ContentHandler — событийная модель, 26 МБ:

class WorkoutHandler(xml.sax.ContentHandler):
    def startElement(self, name, attrs):
        if name == 'Workout':
            self.current = {
                'type': attrs.get('workoutActivityType', ''),
                'start': attrs.get('startDate'),
                'duration': attrs.get('duration'),
            }
        elif name == 'WorkoutStatistics' and self.current:
            qty = attrs.get('type', '')
            if 'HeartRate' in qty:
                self.current['avg_hr'] = attrs.get('average')
                self.current['max_hr'] = attrs.get('maximum')
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 10 [6]

Маппинг типов HKWorkoutActivityType условный → четыре категории: strength, cardio, low_intensity, other. Например, FunctionalStrengthTraining, TraditionalStrengthTraining, CrossTraining, CoreTraining → strength. Walking, Hiking, Yoga, Pilates, Barre → low_intensity. Агент фильтрует не по сырому типу Apple, а по категории.

Сон через скриншоты

iOS не отдаёт фазы сна через Shortcuts, с этим тоже было несколько часов мучений. Решение: пользователь присылает скриншот Apple Health → Vision (GPT-4o-mini) извлекает Core/Deep/REM/Awake в минутах → sleep_sessions. Принимает как сводный экран со всеми фазами, так и отдельные экраны интервалов — мёрджит в один буфер, через /done сохраняет.

Идея передавать данные через шоткаты была в том, чтобы получать аналитику по запросу в любое время дня и ночи. И в том, чтобы не покупать стороннее приложение или не делать своё для парсинга в реальном времени. Но из-за того, пайплайн данных через шоткаты не удалось внедрить, качественную аналитику смогу проводить раз в неделю, когда буду передавать обновлённый export, а на неделе аналитика будет либо глубокой на исторических данных, либо без фаз сна и ЧСС во время тренировок на текущей неделе.

Для сравнения:

Apple Watch. Данных много, но нет публичного API, единственный путь — экспорт export.xml вручную из приложения Health или через своё приложение в AppStore. Шоткаты работают нестабильно, к тому же iOS ограничивает динамические фильтры по датам — нельзя отправить «данные за вчера», только текущий снимок. Трекинг сна по сравнению с Oura у часов проигрывает по точности.

Oura Ring. REST API с OAuth 2.0, данные доступны на следующий день автоматически. HRV и фазы сна — наиболее точные среди потребительских устройств по независимым исследованиям. Для пайплайна: cron раз в сутки → API → upsert в БД, никаких ручных действий.

Whoop. REST API с OAuth 2.0, поддерживает вебхуки на новые данные. Уникальные метрики: strain score (накопленная нагрузка), recovery score (готовность к нагрузке), respiratory rate.

Garmin. Garmin Health API (партнёрский, нужна заявка) или Connect IQ [8] SDK. Для персонального проекта используют неофициальный garminconnect Python-пакет на свой страх [9] и риск.

Лабораторные анализы: EAV-паттерн

AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 11

Разные лаборатории называют одни и те же показатели по-разному: «ТТГ», «TSH», «тиреотропин» — одно и то же, но три разные строки, если хранить как есть. И количество показателей не фиксировано: один анализ — 5 строк, другой — 48.

Решение: EAV-паттерн (Entity-Attribute-Value). Каждый числовой показатель = одна строка в lab_results. Два ключа: parameter_name (как в документе) и parameter_key (стандартный, для агента):

CREATE TABLE lab_results (
    id              SERIAL PRIMARY KEY,
    session_id      INTEGER REFERENCES lab_sessions(id),
    user_id         TEXT NOT NULL,
    parameter_name  TEXT,       -- «ТТГ» / «TSH» / «тиреотропин»
    parameter_key   TEXT,       -- стандартный: «tsh»
    category        TEXT,       -- «thyroid», «hormones», «lipids»
    value_numeric   NUMERIC,
    unit            TEXT,
    ref_min         NUMERIC,
    ref_max         NUMERIC,
    is_abnormal     BOOLEAN,
    test_date       DATE
);
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 12 [6]

Пайплайн: PDF → PyMuPDF (текст) → LLM парсит, стандартизирует parameter_key. Если PDF без текстового слоя (скан) → рендерим первую страницу в PNG → Vision LLM. Агент запрашивает динамику по parameter_key и получает все замеры независимо от источника и лаборатории.

Сompute_correlation работает с lab_results через единый METRIC_MAP: insulin, glucose, tsh, estradiol, ferritin и 10+ других показателей на равных с метриками Apple Health. Можно спросить: «есть ли связь между инсулином и весом за три года» — агент посчитает коэффициент Спирмена и расскажет всю базу на Haiku c причинами и следствиями.

Что работает, как работаем

Сегодня, спустя три недели плотной работы в перерывах между работой:

  • Агент отвечает на произвольные вопросы — роутинг через select_tools() по ключевым словам

  • 9 лет истории доступны для анализа: /ask динамика ТТГ за 5 лет — реальный результат из БД

  • Сompute_correlation: /ask есть ли связь между шагами и HRV — коэффициент Спирмена с p-value и интерпретацией на русском

  • Memory Synthesizer накапливает паттерны, верифицированные инсайты агент читает автоматически

  • Мультимодальность: голос → Whisper, скриншот весов → Vision → body_metrics, PDF анализов → EAV

  • Индикатор загрузки (send_chat_action(typing)) со статусными фразами, пока агент думает. Реализовала в духе клодклодовских абсурдных герундиев, которые захардкодил ему Anthropic в CLI. В бэклоге — сделать стриминг.

Слабые места

  • URL-индексация в базу знаний: Cloudflare блокирует большинство сайтов с качественным контентом, который агент может использовать.

  • Размерность ответов Haiku, формат ответов, глубина и полнота в процессе отладки. Нужно больше времени и опыта [10] взаимодействия.

Документационный пайплайн

В процессе разработки выстроила документационный пайплайн для работы с Claude Code. Внедрила кастомный skill /updatedocs: СС читает код как источник правды и затем обновляет всю документацию.

Эта же документация — связка между сессиями. Файлы, которые загружаются в контекст автоматически при каждом старте:

CLAUDE.md              ← архитектура, стек, соглашения, команды бота
memory/MEMORY.md       ← живая оперативная память: правила, баги, паттерны
docs/CHANGELOG.md      ← что сделано и когда
docs/backlog.md        ← что делать дальше
docs/decisions/        ← feasibility-документы для сложных фич
docs/diagrams.html     ← визуальная архитектура
docs/architecture.md   ← архитектура агента
docs/data-schema.md    ← схемы данных агента
AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code - 13 [6]

Claude Code по ходу сессии редактирует MEMORY.md как инструкцию для следующей версии себя. Семантически документируются паттерны и анти-паттерны в формате «docker compose down -v — ЗАПРЕЩЕНО», «никогда не светить пароль в bash-команде», «не дропать таблицы в БД без предварительного согласования», «iOS Shortcuts не принимает magic variables в date filter — это фундаментальный баг, не пробовать». Документируются провалы, которые уже случились, и следующий Клод их не повторяет.

После нескольких сессий пользуюсь командой /insights. Попросила СС писать инсайты в .md по-русски и складывать в папку с документами. Каждый файл, полученный по команде — по структуре как ретро (что получилось, где возникли проблемы, стиль разработки (!), что нового, что изменилось) и с обратной связью для меня как для мультиперсонажа во всём процессе. Инсайты СС помогают выстраивать совместную работу.

Пример того, как выглядит позитивный блок ретро-инсайта от СС

Пример того, как выглядит позитивный блок ретро-инсайта от СС

И о тайном страхе всех людей на земле: нет, AI не заменил мне меня. Я давно хотела личного ассистента, который помнит мои данные и может помочь находить взаимосвязи между активностью, питанием, сном и состоянием. Первые попытки были с Cursor, первый коммит в GitHub в прошлом году. Дело шло медленно.

Всё это время шла фоновая внутренняя работа. И вот, с Claude Code за 18 дней до и после основной работы — ранними утрами и поздними вечерами — персональный агент-аналитик из «когда-нибудь» превратился в крепкий MVP на проде.

121 час чистого времени разработки с СС (примерно в 5-7 раз быстрее обычной разработки, по замерам СС), 82 коммита на сегодняшний момент. Общее напряжение выше среднего, я стала хуже спать и поэтому больше есть. Ну хотя бы агент, из-за которого всё это произошло, сумел сопоставить данные и составил план на ближайшие пару недель.

Claude Сode не сделал всё за меня по кнопке: 17 раз он пошёл в неверном направлении, 11 раз не так понял задачу, один раз снёс production-данные DROP TABLE-ом.

Каждое решение об архитектуре, каждый дебаг, каждая итерация по поведению [11] агента, каждое выстраивание процесса — это моя работа. AI ускорил рутину, но не заменил мышление [12]. Разница в том, что раньше на эту рутину уходило 80% времени, теперь — 20%.

Что дальше? Другие модели, качество аналитики, туллинг, новые агенты. Заметки по ходу веду в TГ @analaiml.

Автор: Svetafo

Источник [13]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/26836

URLs in this post:

[1] памяти: http://www.braintools.ru/article/4140

[2] сон: http://www.braintools.ru/article/9809

[3] реагирования: http://www.braintools.ru/article/1549

[4] логика: http://www.braintools.ru/article/7640

[5] фазы сна: http://www.braintools.ru/article/9811

[6] Image: https://sourcecraft.dev/

[7] сном: http://www.braintools.ru/article/9150

[8] IQ: http://www.braintools.ru/article/7605

[9] страх: http://www.braintools.ru/article/6134

[10] опыта: http://www.braintools.ru/article/6952

[11] поведению: http://www.braintools.ru/article/9372

[12] мышление: http://www.braintools.ru/thinking

[13] Источник: https://habr.com/ru/articles/1007940/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1007940

www.BrainTools.ru

Rambler's Top100