- BrainTools - https://www.braintools.ru -
Однажды я потерял чат. Не то чтобы катастрофа — обычная рабочая сессия по проверке студенческих домашек. Экспорт я не делал, в лог ничего не писал. И чат пропал. Скорее всего, я сам его и удалил, но точно уже не вспомню.
Уцелело только то, что я до этого руками сохранил за пределами чата, все остальное просто перестало существовать. И тут мне пришла в голову мысль: ровно то же самое происходит с моделью на каждой длинной сессии, когда данные вылетают из контекста из-за сжатия. А что, если дать агенту независимую память [1], которая живет не внутри чата и не зависит от того, решит модель это выбросить или нет? Так появился проект MemShelf.
В прошлой статье [2] я рассказывал про DocShelf — MCP-сервер, который превращает свалку локальных PDF в полку документов: есть индекс-оглавление, модель по нему находит нужный фрагмент и подтягивает уже конкретный кусок, а не весь архив разом.
MemShelf устроен так же, только полка не с документами, а с «опытом» самого агента. Единица хранения — эпизод: что делали, к какому решению пришли, какие альтернативы отклонили и почему, где ошиблись, что осталось доделать.
Похоже на work log, но «классический» work log обычно «живет» вокруг конкретной фичи и коммита, тогда как эпизод — вокруг сессии работы целиком, включая то, что не попало ни в один коммит: отклоненные альтернативы, грабли, открытые хвосты.
Эпизоды на полку попадают из нескольких источников:
Ночные и дневные смены Claude Code по портфелю из 28 репозиториев. Каждая смена закрывается эпизодом: что смержено, что отклонено и с какими уликами, какие дефекты нашлись в собственных проверках. Следующая смена стартует с чтения полки, а не с пересказа.
Диспетчер и шина между сессиями. Решения по протоколу и ролям живут дольше любой отдельной сессии, поэтому их место на полке.
Хоумлаб: ферма раннеров, ИБП, коммутаторы. Каждый инцидент ложится эпизодом с воспроизведением.
Эксперименты с пререгистрацией: до запуска я записываю, что проверяю и какой результат сочту успехом, чтобы потом не подгонять выводы под цифры. Регистрация, замер, закрытие ветки — три эпизода, которые нельзя терять. Так, на полке лежит, например, опыт [3] с порогом сжатия, о котором речь пойдет ниже.
Фидбэк студентов по учебным материалам, который копится между потоками курса.
К середине сентября на полке набралось 230 эпизодов, около 150 из них — итоги рабочих сессий. Цифры ниже я в основном привожу по срезу на 20 августа (38-й день полки, 157 эпизодов), на нем построены все замеры.
Зачем выносить память из чата, кроме как «на всякий случай»? Допустим, окно контекста — 500 тысяч токенов. Пока беседа помещается в него целиком, все хорошо. Как только перестает, агент запускает сжатие: автоматически решает, что из истории важно, а что нет, и сжимает контекст.
При этом решение принимает не человек, и интерфейса «вот это оставь, вот это выкинь» там нет. Отсюда та самая тупость модели, когда после долгого сеанса работы она начинает забывать [4] то, о чем сама говорила час назад: нужный кусок был выброшен при сжатии.
Можно уменьшить окно, чтобы не так сильно забивать контекст с каждым запросом. Но чем меньше окно, тем чаще срабатывает сжатие и тем выше шанс потерять нужное. У этого порога есть практический «пол»: заметная часть окна занята еще до вашего первого слова. Системный промпт клиента, встроенные инструменты, подключенные MCP-серверы, коннекторы и скиллы — у меня это в медиане 38,6 тысячи токенов, и 32 тысячи из них приходятся на сам клиент, т. е. на них я как пользователь не влияю вообще. А в некоторых инструментах окно меньше определенного размера просто не выставить. Рабочий компромисс — поднять планку до 200–300 тысяч: компакция реже, а экономия уже есть.
Здесь в дело вступает MemShelf. Сжатие полка не отменяет, но забирает у него главное решение: вместо того чтобы отдавать «важные данные» агенту, вы сами закрываете сессию эпизодом и кладете его на полку. В следующей сессии агент не угадывает контекст по тому, что уцелело после сжатия, а читает то, что вы реально зафиксировали.
Механика цикла: закрыли тему — агент собрал эпизод в Markdown — инструмент проверил его и вычистил лишнее — я закоммитил — бот перерисовал индекс. В следующей сессии, уже в другом чате, агент через MCP находит нужную запись по индексу и читает ее целиком или только одну секцию.
Сама полка — git-репозиторий с Markdown-файлами, и все, о чем пойдет речь дальше, лежит в нем:
main-memshelf/
├── shelf.yml # манифест: имя, режим хранения, категории
├── POLICY.md # что редактируется перед записью
├── POLICY.patterns # регулярки известных значений: имена, ключи, токены
├── INDEX.md # оглавление — единственное, что едет в контекст агента
├── ledger.tsv # одна строка на shelve: дата, id, approx_tokens, digest_tokens
├── recall-log.tsv # одна строка на recall: id, секция, fetched_tokens
├── stats.svg # экономика полки, перерисовывает бот
├── docs/
│ ├── topics/ # закрытые темы
│ │ ├── .meta.json
│ │ └── 2026-08-14-shelf-reachable-but-unused.md
│ ├── sessions/ # итоги сессий; обязательна секция Timeline
│ │ └── 2026-08-20-cc-token-audit.md
│ └── research/ # эксперименты с пререгистрацией
│ └── 2026-08-14-autocompact-100k-closed-200k-registered.md
├── archive/ # оригиналы за роллапами; recall по id работает
└── .github/workflows/ # бот: рендер INDEX/ledger/.meta/stats на main + PR-гард
Каждый эпизод — отдельный файл с YAML-шапкой и несколькими секциями: дайджест, решения, открытые хвосты, источники.
Пример эпизода:
---
id: 2026-09-16-tower-rack-case-sliger
kind: topic
span: 2026-09-16
date: 2026-09-16
display_title: "Корпус для башни: Sliger CX3152i, Netiks запасной"
description: "Выбор 3U-корпуса под шок-кейс Gator; отклонены CX2137b и 17-дюймовые"
tags: [homelab, hardware, tower]
approx_tokens: 60000
mode: live
---
## Digest
Корпус для башни в шок-кейсе Gator G-SHOCK-4L: выбран Sliger CX3152i
(3U, 381 мм, стандартный ATX-БП) у DENSITY.sk, запасной — Netiks NI-N303
(303 мм, без растаможки). CX3152a отклонён: срок поставки 3 недели при
пороге 2. CX2137b отклонён: только FlexATX-БП и кулер до 66 мм.
Замер продавца: 42 см до задней рейки, 49 см до крышки — Sliger входит
с запасом 109 мм на кабели. Разделение посылки отклонено: порог
беспошлинного ввоза €50, НДС платится всегда, вторая отправка дороже
экономии. Открыто: ответ Netiks, задняя опора под Sliger.
## Decisions
- Основной корпус — CX3152i; a-версия отклонена по сроку.
- Дробить посылку не имеет смысла: экономия €0,5–3,5 против €49 доставки.
## Open threads
- Netiks не ответил; Density держит последний CX3152i до понедельника.
- Sliger не достаёт до задней рейки на 4 см — нужны опоры или кронштейн.
## Sources
- sliger.com/products/rackmount/3u/cx3152i/
- carinskatarifa.rs — ставки пошлин
Из этого эпизода в INDEX.md попадает одна строка — и это все, что агент видит, пока не решит сходить за подробностями:
- **Корпус для башни: Sliger CX3152i, Netiks запасной** — Выбор 3U-корпуса под шок-кейс Gator; отклонены CX2137b и 17-дюймовые — [`2026-09-16-tower-rack-case-sliger.md`](docs/topics/2026-09-16-tower-rack-case-sliger.md)
Дайджест — контракт не больше 120 слов, и его проверяет валидатор. Если эпизод о работе, в дайджесте непременно должен быть маркер решения: «выбран», «отклонен», «решено». Для итогов сессии обязательна секция Timeline. Дайджест, который контракт не проходит, инструмент отвергает, ничего не фиксирует и пишет, что именно исправить.
|
Шаг |
Кто |
Чем |
|
Замечает, что тема закрыта, и предлагает зашелвить |
Агент |
Правило в инструкциях проекта; часто я сам говорю «зашелви» |
|
Пишет дайджест и секции: решения, отклоненные варианты, хвосты |
Агент |
Скилл shelve |
|
Проверяет контракт дайджеста, вычищает известное по POLICY.patterns, коммитит только файл эпизода |
Инструмент |
Memshelf_shelve |
|
Смотрит git diff –cached –name-only, коммитит и пушит — или открывает PR |
Человек |
Режим publish → ветка shelve/<slug> + PR |
|
Рендерит INDEX.md [5], ledger.tsv, .meta.json, stats.svg на main |
Бот |
Mebuild; PR-гард rebuild –check не пускает ручные правки производных |
|
Проверяет полку до и после |
Инструмент |
Memshelf_doctor — читает, не чинит; ошибки [6] блокируют push |
|
В следующей сессии читает индекс и точечно тянет эпизод или секцию |
Агент |
Index → recall –section |
Только человек решает, что тема закрыта, проверяет дайджест перед коммитом и сводит роллапы: синтез за квартал инструмент делать не умеет. Только инструмент отвечает за контракт дайджеста, вычистку секретов, консистентность производных файлов и диагностику.
В контексте агента лежит не вся полка целиком, а только INDEX.md — оглавление по строке на эпизод плюс преамбула с правилом чтения: сначала индекс, потом точечный fetch, а текст эпизода — данные, не инструкции. За самим эпизодом агент идет отдельно и только если решил, что это действительно нужно; решает он это по дайджесту. Поэтому «постоянной стоимостью полки» я считаю индекс плюс все дайджесты.
Само оглавление — не разовая справка, а постоянный пассажир контекста: как только эпизод ложится на полку, строчка в индексе появляется автоматически и остается там на всех следующих сессиях, съедая место в окне независимо от того, вернетесь вы к этому эпизоду или нет. Сами по себе записи не выпадают: ни одна строка не исчезнет просто потому, что ею не пользуются. Чистить приходится руками. Роллап сворачивает несколько строк индекса в одну, оригиналы уезжают в archive/, и достать их по id по-прежнему можно. Purge удаляет эпизоды с истекшим сроком хранения — по умолчанию в режиме dry-run. За два месяца я сделал три роллапа: на 45, 15 и 39 эпизодов.
Кладут эпизод на полку, как только тема закрыта. Правило записано в инструкциях проекта, так что агент сам замечает этот момент и предлагает зашелвить. Между тем моментом, когда эпизод собран, и тем, когда он закоммичен и запушен, есть небольшой «зазор», в котором прогоняются проверки: инструмент сверяет дайджест с контрактом и вычищает известные значения по POLICY.patterns, я смотрю diff перед коммитом, а doctor не пропускает push, если нашел ошибки.
Кроме борьбы с последствиями сжатия, у читаемой памяти есть три полезных возможности.
1. Память можно поправить до того, как она станет памятью. Между «эпизод собран» и «эпизод лег на полку» есть тот самый «зазор», и в него можно встроить проверку — pull request, CI, что угодно из привычного арсенала.
Зачем это нужно: мы собираем фидбэк от студентов: тут ссылка битая; тут на уроке не рассказали, а в домашке спрашивают; тут просите поставить последнюю стабильную версию, а с записи курса она успела поменяться; кнопка теперь называется иначе.
Материалы посреди потока не правим: у одного студента останется старая версия, у другого новая, и через месяц непонятно, с каким эталоном сравнивать домашку. Поэтому фидбэк копится и применяется между потоками.
Проблема в источнике: обратная связь идет напрямую от студентов через личный кабинет, и вместе с текстом в нее подмешаны их персональные данные. Зазор перед записью на полку — место, где текст можно прогнать через фильтр: вычистить имена, инициалы и прочие известные идентификаторы. Простой «предпубликационный фильтр» по известным значениям. На полке это файл POLICY.patterns с регулярными выражениями для имен, ключей и токенов, и прогоняет его сам инструмент записи.
2. Память читается из другой модели. Та, которую ведет вендор, живет в его формате и по его правилам, выгрузить обычно можно, но руками и с потерей структуры. Каноническая копия полки изначально лежит у вас в формате, не привязанном ни к одной модели.
Проверял я на рабочей полке из 42 эпизодов. К одному MCP-серверу подключил два клиента от разных вендоров, Claude Code и Gemini CLI 0.46, одной и той же командой, без всякой адаптации. Оба вернули одинаковое описание полки и оба признали ее валидной. Дальше Claude создал на полке пробный документ, и Gemini сразу увидел, что тем стало не 15, а 16. Никакой синхронизации и никакого экспорта: общее состояние — просто рабочее дерево git. Claude документ удалил — Gemini увидел обратно 15 и сам заметил, что при прошлой проверке было 16.

3. Память работает как протокол. Начиналась как дневник: закончил день — записал, как он прошел. Но записи с датами, коммитами и историей изменений могут еще быть и материалом для расследования: что пошло не так, когда именно, на какой версии эталона строилось решение.
Все, что описано выше, работает хорошо, пока эпизодов немного, но по мере роста их количества начинаются проблемы.
Первая — индекс и дайджесты сами по себе занимают место в контексте. Пока полка маленькая, это незаметно. Но на 38-й день на полке лежало 157 эпизодов, и одно только оглавление весило 8 045 токенов. Вместе с дайджестами выходило 36,6 тысячи — почти пятая часть окна в 200 тысяч. Эпизоды прибывали со скоростью 4,8 в день, постоянная стоимость росла на 1,1 тысячи токенов в день. При таком темпе память заняла бы треть окна примерно за месяц.
Вторая проблема — гонка. В полку пишут разные сессии, иногда параллельно: скажем, ночная смена Claude Code и мой чат. Поначалу каждый shelve заодно перегенерировал четыре производных файла: INDEX.md, ledger.tsv, .meta.json и stats.svg. Обе ветки приезжали в main с конфликтом [7] во всех четырех.
Первым фиксом стала команда resolve: она объединяет строки учета с обеих сторон, пересобирает индекс и статистику из эпизодов и прогоняет проверку. Настоящее лечение появилось на следующий день и оказалось архитектурным: эпизод — единственный источник, все остальное производное. Shelve пишет и коммитит только файл эпизода, а индекс, учет и статистику рендерит бот на main за PR-гардом. Удалите все четыре файла — rebuild восстановит их байт в байт. Двум сессиям больше не из-за чего конфликтовать: конфликт не разрешается, он не возникает по построению.
Еще одно решение выросло из цены индекса. Полку разделил на общую — для важных межпроектных решений (как принимается домашка, какие форматы файлов не открываются, как обезличивать фидбэк) — и приватные, по одной на проект, работающие автономно. В сессию идут общая и одна проектная полка, и в типичной сессии это около 6 тысяч токенов вместо 36. Есть и второй эффект: индекс на 8 тысяч токенов вслепую никто не открывает, а на тысячу — открывают.
За порядком следит memshelf_doctor — один из 14 инструментов полки. Он проверяет схему эпизодов, контракт дайджеста, строки, похожие на секреты, консистентность учета, раздутый индекс и остановившийся рендерер. Doctor только читает и ничего не чинит, но его ошибки блокируют push. Без присмотра полка деградирует тихо: о битой ссылке узнаешь ровно в тот момент, когда она нужна.
Первый же прогон на живой полке вернул healthy: false,: два дайджеста длиннее 120 слов, одна строка в форме секрета (оказалась учебным фальшивым ключом из разбора домашки) и раздутый индекс. Длинные дайджесты достались от ручной эпохи, когда контракт держался на честном слове агента. Честное слово подвело, и теперь валидатор отвергает такие дайджесты еще на записи.
На 38-й день doctor показал и предел самой полки:
healthy: true · 157 проверено · 0 ошибок · 3 предупреждения
index-bloat — 8 045 токенов при пороге 2 500
stale-index — INDEX не сходится с содержимым
digest-body-mismatch — один эпизод, ~15% пересечения (2026-07-09-runbook-session)
Справедливости ради, часть сигнала оказалась арифметикой: бюджет индекса был абсолютным — 2 500 токенов на файл, который растет вместе с полкой, — так что рано или поздно он бы его пробил. Сейчас бюджет линейный, 200 + 80 токенов на запись, и doctor называет конкретную строку индекса, которая обходится дороже положенного. Но 8 тысяч токенов в каждой сессии от этого никуда не делись, и лечить их пришлось роллапами и разделением полки.
|
Симптом |
Причина |
Решение |
|
Merge-конфликт в четырех производных файлах после параллельных shelve |
Каждый писатель регенерирует все производное |
shelve пишет только эпизод, производное рендерит бот на main; resolve — запасной путь |
|
shelve ложится на устаревшую базу, push отбит |
Клон отстал от origin |
sync — fetch + fast-forward перед записью; грязное дерево или расхождение → отказ с рецептом; push с одной попыткой rebase |
|
doctor сразу после shelve сообщает no-ledger-row, stale-index |
Норма: эпизод есть, производное еще не отрендерено |
Ждать бота; руками не пересобирать — ручной коммит производных столкнется с коммитом бота |
|
Те же предупреждения висят сутки при новых эпизодах |
Рендерер стоит (17.08: устаревший клон на управляющей VM) |
derived-stale уровня error, отсчет по reflog upstream, а не по дате коммита; в свежем клоне — renderer-wait-unknown вместо вердикта |
|
Ложные вердикты «бот живой/мертвый» по git log |
Смешанные коммиты с производными файлами |
Проба живости по журналу воркфлоу, а не по логу |
|
search выдает адреса, которых нет на другой машине; stale-index не снимается никаким rebuild |
DocShelf резал эпизод больше 50 КБ на секции, а shelve коммитил только эпизод |
Разбиение отключено; prune-splits для уже существующих каталогов |
|
index-bloat на 38-й день: INDEX 8 045 токенов при бюджете 2 500 |
Бюджет был абсолютным для файла, растущего с полкой |
Линейный бюджет 200 + 80 × N записей; doctor называет переоцененную строку |
|
Дайджест не соответствует телу; дайджесты длиннее 120 слов |
Ручная эпоха, «валидация честью агента» |
Контракт в shelve и lint_digest; ошибка блокирует запись |
|
approx_tokens 2 400 000 при окне 349 000 |
Поле измеряет объем работы, а не память; 0 = «не измерено» |
Масса в stats режется до окна, unmeasured — отдельный статус |
|
Строка в форме секрета на диске |
Учебный фальшивый ключ из разбора домашки попал в эпизод |
doctor репортит форму, человек судит; вычистка по POLICY.patterns до записи |
20 августа, после 38 дней работы, на полке лежало 157 эпизодов. Медианная сессия, из которой собирался эпизод, содержала около 60 тысяч токенов, а ее дайджест — 184. За это время я обратился к полке 27 раз и перечитал 25 эпизодов; одно обращение добавляло в контекст в среднем 1 212 токенов. Если тянуть не эпизод целиком, а одну секцию, выходят десятки токенов. Например, у эпизода про корпус из начала статьи можно запросить одни только решения:
memshelf recall --shelf ~/main-memshelf --id 2026-09-16-tower-rack-case-sliger --section Decisions –log
Обратно «приезжает» только эта секция:
## Decisions
- Основной корпус — CX3152i; a-версия отклонена по сроку.
- Дробить посылку не имеет смысла: экономия €0,5–3,5 против €49 доставки.
Это примерно 60 токенов вместо 60 000 исходной сессии. Иначе говоря: 60 тысяч токенов, которые иначе ехали бы в каждом запросе, лежат на полке в виде 184 и возвращаются за 1 212 — тогда, когда действительно нужны.
Откуда эти цифры. Каждый shelve оставляет строку в ledger.tsv — журнале полки: дата, id эпизода, объем сессии, из которой он собран, и размер дайджеста. Обращения к полке также пишутся в recall-log.tsv. Пример журнала:
date episode_id mode approx_tokens_in digest_tokens
2026-08-14 2026-08-14-shelf-reachable-but-unused live 60000 137
2026-08-20 2026-08-20-cc-token-audit live 0 208
2026-08-21 2026-08-21-index-budget-linear live 75000 190
approx_tokens_in — самооценка объема сессии (символы / 4 по транскрипту); 0 означает «не измерено», а не ноль.
Параллельно я разбирался, куда вообще уходят токены. Считал по локальным JSONL-транскриптам Claude Code собственным скиллом cc-token-audit: встроенная /stats не учитывает кэш и расходится с фактом в 137 раз, а /cost у подписчиков скрыт. Деньги считал по публичному прайсу API — это оценка «сколько стоил бы тот же объем по API», а не счет.
База 30 дней: 1,63 млрд токенов, или $1 487 в API-эквиваленте при подписке Max 20x за $200. 97,4% всех токенов — чтение кэша. Внутри него 76,5% приходится на накопленный диалог, 18,8% — на статический префикс, 4,7% — на выдачу всех инструментов.
Отсюда простая модель: платишь не за содержимое, а за время, которое токен провел в контексте. Каждый ход заново перечитывает всю историю, поэтому накопление растет квадратично по длине сессии. Токен, положенный на десятом ходу восьмидесятиходовой сессии, оплачивается семьдесят раз, а разрезанная пополам сессия обходится вдвое дешевле при том же объеме работы.
Первым делом я, конечно, заподозрил собственные MCP-серверы DocShelf [2] и MemShelf. За 30 дней — ни одного вызова обоих. Всего правдоподобных гипотез о том, кто ест токены, было четыре, и все четыре опроверг замер. Каждый раз ошибка сидела в инструменте измерения, а не в предмете.
До какого-то момента порог сжатия у меня фактически не работал: за месяц случилось два автосреза, а контексты доживали до 700–990 тысяч токенов. В Claude Code порог задает параметр autoCompactWindow, и сначала я попробовал выставить его пониже. Потом зарегистрировал ветку эксперимента с порогом 100 тысяч, а далее поднял его до 200 тысяч: задача в контекст просто не влезала. При 100 тысячах автосрез приходил каждые 3,7 минуты активной работы, при 200 тысячах — раз в 15,7 минут. Ветку со 100 тысячами я закрыл как прерванную через 24 минуты и 32 хода. Считал по записям compact_boundary, которые пишет сам Claude Code. Это и есть тот практический «пол», о котором шла речь выше.
В итоге я оставил autoCompactWindow на 200 тысячах и отказался от турборежима ultracode в пользу обычного xhigh.
|
Метрика |
До |
После |
|
Расход на ход |
$0,2379 |
$0,0895 |
|
Средний контекст |
283K |
97K |
|
Максимальный контекст |
996K |
166K |
|
Ходов главного треда |
~3,1K |
~5,1K |
|
Доля ходов в субагентах |
45% |
6% |
Расход на ход упал в 2,7 раза, хотя полезной работы в главном треде стало больше (это разведка, а не подтверждающий эксперимент, — ожидаемые пороги я внес в инструмент задним числом).

Вклад полки в эти «2,7 раза» я измерил отдельно, и он нулевой. Все тяжелые контексты обрываются примерно на 159–166 тысячах токенов. Так ведет себя порог сжатия: он срабатывает всегда у одной и той же черты. Если бы контекст разгружала полка, эпизоды уходили бы на нее в разные моменты, и потолок гулял бы от сессии к сессии. Вся выдача инструментов — 0,1% чтений, доля MemShelf — $0,09 за шесть дней. Экономию дали порог сжатия и отказ от ultracode.
Мало того, сама полка не бесплатная. Ее постоянная стоимость — 36,6 тысячи токенов: 8 тысяч индекса плюс 28,6 тысячи дайджестов, то есть около 18,3% окна в 200 тысяч. В этой цифре нет ни тел эпизодов, ни схем инструментов memshelf — схемы живут в префиксе отдельно и весят еще 2–3 тысячи. И 84% постоянной стоимости, 30 756 токенов, приходилось на 132 эпизода, к которым я ни разу не вернулся.

При 230 эпизодах общая полка стоит уже 52,5 тысячи — больше четверти окна. Роллапы это не остановили, целиком в сессию она не едет только благодаря разделению на проектные полки.
Раз уж речь зашла про экономию, можно вспомнить про кэш: зачем городить что-то свое, если кэшированные токены у некоторых провайдеров дешевле некэшированных почти на порядок? Но кэш и полка решают разные задачи. Кэш дает скидку на уже «увиденный» кусок истории, а объема, который места этот кусок занимает в окне, не меняет: если в истории накопилось 150 тысяч токенов, окно занято на 150 тысяч, просто часть из них считают по льготному тарифу. Кэш делает дешевле, но не меньше. К тому же скидка применяется одинаковая в обоих сценариях, с полкой и без, так что победителя в сравнении не меняет.
Главное различие — во времени жизни. Кэш живет считанные минуты (у Claude по умолчанию — пять) и умирает вместе с сессией. Полка живет между сессиями и между вендорами. Практическое пересечение у них одно: класть эпизод на полку лучше сразу по закрытии темы, в хвост контекста. Поздняя выгрузка из середины ломает уже закешированный префикс.
За первые полтора месяца я вернулся примерно к каждому шестому эпизоду на полке. Остальные просто занимали место в окне — на них пришлось 84% ее постоянной стоимости. Страховка это (которая пока не пригодилась) или я выгружаю слишком охотно — данные не различают. Но одна причина точно не техническая: запись и чтение работают по-разному. Запись — событие: тема закрылась, момент сам о себе заявляет. Чтение — не событие: нельзя вспомнить то, чего не знал. Сигнала «пора сходить в память» не возникает, и вы переизобретаете то, что лежит в трех файлах отсюда.
Пример: 14 августа я спросил ассистента, чем отличаются две поверхности Cowork. Ответ лежал на полке с 4 августа отдельным эпизодом. Ассистент весь день собирал его по документации вендора, написал регламент без главного различия и в первой же записи дня зафиксировал так: «полка из веб-чата недоступна». Полка была подключена и работала. Причина — отложенная загрузка инструментов: тулы memshelf не видны в основном списке, а правило «сначала прочитай полку» записано внутри INDEX — его видит только тот, кто уже открыл полку. Доступность не равна использованию. За те же шесть дней: 4 обращения к полке против 23 записей.
В общем, систему памяти можно спроектировать идеально и все равно ею не пользоваться, потому что спроектировали хранилище, а проблема в моменте обращения.
Когда я начинал писать эту статью, у меня уже были идеи, как приучить агента заглядывать в память. Три из них я реализовал. Правило «начинай с чтения полки» теперь записано не только в индексе, но и в инструкциях проектов и пользовательских настройках. В адаптере для Claude Code появились скилл /shelve и хуки на старт сессии, ее конец и сжатие (SessionStart, SessionEnd, PreCompact), а чтение логируется по умолчанию. Хук на старте убирает само решение «идти ли в память»: это механизм того же класса, что autoCompactWindow, а не дисциплина, которая уже однажды подвела. Хук после сжатия срабатывает в единственный момент, когда точно известно, что контекст потерян.
Код открыт: memshelf-mcp [8] (MIT, пакет memshelf-mcp на PyPI), формат полки описан отдельно в shelf-spec [9]. В Claude Code полка подключается командой
claude mcp add memshelf -- uvx memshelf-mcp
Для Claude Desktop есть .mcpb. А из шелла (pip install memshelf-mcp, Python 3.10+) тот же цикл работает и без MCP:
memshelf init --shelf ~/my-shelf --name "My working memory"
memshelf shelve --shelf ~/my-shelf --slug 2026-09-16-topic --kind topic
--digest "Что решено, что отклонено и почему, что осталось открытым."
--section "Decisions=Что решено и что отклонено вместо этого — по строке."
memshelf recall --shelf ~/my-shelf --id 2026-09-16-topic --section Decisions --log
memshelf stats --shelf ~/my-shelf # заявленная и реализованная экономия
memshelf doctor --shelf ~/my-shelf # exit 1 при ошибках целостности
Все инструменты полки
|
Тул |
Что делает |
|
init |
Создать полку |
|
shelve |
Выгрузить закрытую тему как эпизод; –amend переписывает на месте |
|
lint_digest |
Проверить дайджест по контракту, ничего не записывая |
|
import |
Задним числом зашелвить экспортированный диалог, не протаскивая его через контекст |
|
index |
Вернуть INDEX — точку входа для recall |
|
recall |
Эпизод по id или одна секция |
|
search |
grep по полке (с [semantic] — гибридный поиск) |
|
stats |
Экономика: постоянная стоимость против выгруженной массы, заявленное против реализованного |
|
advise |
Из чего состоит контекст и что можно отложить — только предложения |
|
rebuild |
Перерисовать все производные файлы из эпизодов |
|
rollup |
Архивировать период за одним сводным дайджестом |
|
purge |
Удалить эпизоды с истекшим сроком хранения; по умолчанию dry-run |
|
resolve |
Развести конфликт двух писателей: пересобрать производное, объединить recall-log |
|
doctor |
Диагностика: схема, контракт дайджеста, секреты, ledger, бюджет INDEX |
Автор: Philipp451
Источник [10]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/37065
URLs in this post:
[1] память: http://www.braintools.ru/article/4140
[2] прошлой статье: https://habr.com/ru/companies/basis/articles/1066908/
[3] опыт: http://www.braintools.ru/article/6952
[4] забывать: http://www.braintools.ru/article/333
[5] INDEX.md: http://INDEX.md
[6] ошибки: http://www.braintools.ru/article/4192
[7] конфликтом: http://www.braintools.ru/article/7708
[8] memshelf-mcp: https://github.com/ignatenkofi/memshelf-mcp
[9] shelf-spec: https://github.com/ignatenkofi/shelf-spec
[10] Источник: https://habr.com/ru/companies/basis/articles/1093924/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1093924
Нажмите здесь для печати.