# Память для ИИ-агента без шума: области видимости и забывание в Octobrain

> Дайте ИИ-агенту постоянную память — и он будет запоминать всё подряд, пока поиск не утонет в собственных заметках. Вот как области видимости памяти в Octobrain, MCP-поверхность из четырёх инструментов и автономная консолидация превратили шумный ящик хлама обратно в память, которая действительно отвечает, — плюс практическое руководство по настройке.

На той неделе, когда мы дали агенту настоящую долгосрочную память, поиск стал хуже.

Не сразу. Первый день был волшебным: агент в среду вспомнил особенность развёртывания из сессии в понедельник — без повторных объяснений. К концу второй недели он возвращал пять почти одинаковых заметок на запрос «как мы обрабатываем ограничение частоты запросов», каждая чуть-чуть неверная, и ни одна не была той, что мне на самом деле нужна. Агент усердно вызывал `memorize` на каждое наблюдение, каждый тупик, каждую полусформированную догадку. Он построил себе ящик хлама и теперь рылся в нём при каждом запросе.

Это та часть фразы «дайте агенту память», о которой никто не предупреждает. Память, которая только растёт, — это не память, это журнал. А журнал, по которому ищут по смыслу, становится _более шумным_ по мере роста, потому что каждый сохранённый почти-дубликат борется за один и тот же кусок ранжирования. Больше воспоминаний — хуже поиск. Ровно противоположно тому, на что вы рассчитывали.

[Octobrain](https://github.com/muvon/octobrain) — наше решение этой проблемы с открытым исходным кодом: MCP-сервер памяти на Rust, который даёт любому агенту постоянную, семантически-искомую долгосрочную память. Мы [представили его ещё в апреле](/blog/introducing-octobrain-mcp-memory-server), а в [релизе 0.7.0](/blog/octobrain-0-7-0-memory-that-sleeps) память научилась забывать. Эта статья — то руководство, которого мне самому не хватало: как настроить постоянную память LLM, остающуюся точной, с помощью двух идей — **разграничьте её по проектам и дайте ей консолидироваться** — плюс практический вопрос о том, что хранить, а что оставить в покое.

---

## Настоящая проблема — это поиск, а не хранение

Хранить вещи легко. Это умеет любое key-value хранилище. Сложная часть долгосрочной памяти агента — та же, что и у человеческой памяти: вернуть _нужное_ в _нужный_ момент из кучи, которая никогда не перестаёт расти.

Octobrain ищет по смыслу, а не по ключевым словам. Под капотом он встраивает каждое воспоминание в вектор с помощью локальной модели (по умолчанию `fastembed:nomic-ai/nomic-embed-text-v1.5`, 768 измерений, работает на CPU, без API-ключа) и хранит их в векторной базе данных [LanceDB](https://lancedb.com/) на диске. Запрос `remember` встраивается так же, и возвращаются ближайшие векторы. Чтобы чисто семантический поиск не упускал точные строки, поиск **гибридный**: полнотекстовый BM25 и векторное сходство объединяются через Reciprocal Rank Fusion. Поверх этого — опциональное переранжирование cross-encoder'ом.

Эта механика хороша. На бенчмарке SciFact из [BEIR](https://github.com/beir-cellar/beir) гибридный поиск Octobrain даёт **0,742 nDCG@10** против 0,665 у классического BM25 и 0,713 у чистого эмбеддинга — слияние добавляет около +2 nDCG@10 поверх самого эмбеддера. Качество поиска никогда не было узким местом.

Узким местом было то, _чем мы его кормили_. Пять перекрывающихся заметок об одной и той же ошибке не поднимают ни одну из них выше в ранжировании — они делят сигнал. Хороший поиск по шумному корпусу всё равно даёт шумный ответ. Так что решение — не лучший эмбеддер. Это курирование корпуса, причём автоматическое, потому что ни один человек не будет вручную подрезать память агента.

---

## Идея первая: разграничьте память по проектам

Первый источник шума — перекрёстное загрязнение. Без изоляции воспоминания всех проектов лежат в одном пуле, и запрос про «поток аутентификации» в вашем веб-приложении вытягивает заметки об аутентификации из трёх несвязанных сервисов.

Octobrain изолирует воспоминания по **области видимости** (scope). Область — это строка, и по умолчанию она выводится из вашего git-remote. Откройте репозиторий этого самого блога — и областью будет `github.com/muvon/muvon-web`. Вывод именно такой, как вы и предположили бы:

```
git remote get-url origin  →  host/org/repo
```

Так что `git@github.com:muvon/octobrain.git` превращается в `github.com/muvon/octobrain`. Нет git-remote? Тогда применяется область на основе пути — `local/<parent>/<dir>`, чтобы даже черновой каталог получил изоляцию. И есть одна зарезервированная область — пустая строка `""` — для **глобальных** воспоминаний, которые должны быть видны везде.

Это разделение глобальное/проектное — вся ментальная модель, и оно соответствует реальному различию:

| Область                            | Хранить здесь                              | Примеры                                                                                                                                                |
| ---------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Проект** (`github.com/org/repo`) | Факты, истинные _внутри этого репозитория_ | «Тесты запускаются через `pnpm -r test:unit`», «staging-API требует заголовок `X-Acme-Token`», «здесь мы выбрали token-bucket для ограничения частоты» |
| **Глобальная** (`""`)              | Факты, истинные _о вас, везде_             | «Предпочитает табы», «всегда хочет conventional-commit сообщения», «использует fish, а не bash»                                                        |

Выигрыш: когда ваш агент работает в репозитории блога, вызов `remember` ранжирует только по воспоминаниям этого репозитория плюс ваши глобальные предпочтения. Заметки об аутентификации из того другого сервиса просто отсутствуют в наборе кандидатов. Чище кандидаты — точнее поиск, ещё до того, как запустится хоть какая-то консолидация.

### Как задаётся область

Если вы управляете Octobrain через MCP-клиент, который выполняет рукопожатие сессии, область автоматически блокируется по рабочему каталогу — агенту вообще не нужно о ней думать, а параметр `scope` полностью убирается из схемы инструмента, чтобы он не мог ошибиться. Сервер даже сообщает доступные области в своих MCP-инструкциях:

```
Available project scopes (pass as the 'scope' parameter):
  github.com/muvon/muvon-web
```

Когда сессия _не_ заблокирована, инструменты выставляют `scope` напрямую, а `memorize` принимает флаг `global: true`, чтобы намеренно положить факт в общую область независимо от того, где вы находитесь. Этот флаг появляется только тогда, когда есть заблокированная проектная область, которую можно переопределить; иначе вы просто сами передаёте `scope: ""`. Указание, зашитое в описание инструмента, прямолинейно: используйте `global` для межпроектных фактов вроде предпочтений и соглашений; **не** используйте его для проектно-специфичных знаний. Утечка области — это тот самый шум, которого вы пытаетесь избежать.

---

## Поверхность инструментов, которую вы реально вызываете

Octobrain намеренно выставляет небольшую MCP-поверхность. Каждый выставленный инструмент стоит агенту контекста на _каждом ходу_ — он читает схему, прежде чем что-либо сделать, — поэтому [релиз 0.7.0 сузил поверхность](/blog/octobrain-0-7-0-memory-that-sleeps), а 0.8.0 сохранил её лёгкой. Четыре инструмента, четыре задачи:

```
memorize    сохранить воспоминание (с опциональными встроенными связями)
remember    семантический поиск (возвращает соседей графа на 1 шаг)
forget      удалить (требует confirm=true)
knowledge   индексировать/искать/читать/сопоставлять внешние документы и URL
```

### memorize

Глагол сохранения. Обязательные: `title` (5–200 символов) и `content` (10–10000 символов). Всё остальное опционально, но стоит понимать:

- **`memory_type`** — категория вроде `architecture`, `bug_fix`, `decision`, `user_preference`, `goal`. Используется для фильтрации при поиске.
- **`importance`** — 0.0–1.0. В описании инструмента даны реальные ориентиры: факты, заявленные пользователем, 0.8–1.0; решения 0.7–0.9; исправления ошибок 0.6–0.8; выводы ИИ 0.3–0.6.
- **`source`** — уровень доверия: либо `user_confirmed` (пользователь сказал это явно), либо `agent_inferred` (агент сделал вывод). Это не косметика: подтверждённые факты ранжируются выше при поиске, а результаты возвращаются с метками `[CONFIRMED]` / `[INFERRED]`, чтобы агент знал, насколько доверять каждому.
- **`tags`**, **`related_files`** — для фильтрации и для привязки воспоминания к коду, который оно описывает.
- **`related_to`** — самый интересный. Массив типизированных связей с _существующими_ воспоминаниями, создаваемых в том же вызове. Так вы строите граф без второго обращения.

Конкретное сохранение в том виде, как его выдал бы агент:

```json
{
	"title": "Rate limiting uses a token bucket",
	"content": "We rate-limit the public API with a token bucket (100 req/min/key), returning 429 + Retry-After on exhaustion. Chosen over fixed-window to avoid burst-at-boundary.",
	"memory_type": "decision",
	"importance": 0.85,
	"source": "user_confirmed",
	"tags": ["api", "rate-limiting"],
	"related_files": ["src/middleware/ratelimit.rs"]
}
```

### remember

Глагол поиска, и его стоит вызывать _первым_ — перед запоминанием (чтобы не сохранить дубликат) и в начале задачи (чтобы загрузить контекст). Он принимает `query`, который представляет собой либо одну строку, либо, предпочтительно, массив из 2–5 связанных терминов для более широкого семантического охвата. Он возвращает лучшие совпадения _плюс_ их непосредственных соседей по графу автоматически, так что попадание в одно воспоминание вытаскивает связанные с ним.

Он также принимает фильтры, которые стали действующими в недавних релизах: `created_after` / `created_before` (ISO-8601) для временных вопросов вроде «что мы решили на прошлой неделе», `min_relevance` для порога качества и `memory_types` / `tags` для сужения. Расширение запроса, которое спасает расплывчатые формулировки, — псевдорелевантная обратная связь HyDE-lite от Octobrain — работает автоматически и добавляет заявленные **+10–30% к полноте поиска на запросах длинного хвоста** без LLM и без настройки.

### forget

Постоянное удаление. Требует `confirm: true` — случайного забывания не бывает. Удаляйте по `memory_id` для одного или по `query` + фильтры для массовой зачистки. Описание инструмента несёт правило, которое стоит усвоить: **не забывайте воспоминания только потому, что они старые** — этим занимается затухание (об этом ниже). Используйте `forget` только тогда, когда что-то по-настоящему _неверно_ или было заменено.

### knowledge

Другая половина памяти — внешние документы. `memorize` — для дистиллированных инсайтов, которые производит агент; `knowledge` — для исходного материала, который агент читает. Пять команд, диспетчеризуемых через поле `command`:

```bash
# Индексирует и ищет в одном источнике (индексируется на лету)
knowledge command=search query="how to handle async tasks" source=https://docs.rs/tokio/

# Читает полный текст файла или URL (крайнее средство, когда поиск скуден)
knowledge command=read source=/path/to/design-doc.md

# Делает grep по индексированному содержимому регуляркой
knowledge command=match pattern="spawn_blocking|block_in_place"

# Сохраняет и удаляет сырой текст под ключом (в рамках сессии)
knowledge command=store key=meeting-notes content="..."
knowledge command=delete key=meeting-notes
```

Одно жёсткое правило, которое схема предписывает, а сервер повторяет в своих инструкциях: **`source` — это всегда один файл или URL, никогда не каталог.** Передадите каталог — он будет отклонён. Поддерживаемые типы: `.html`, `.txt`, `.md`, `.pdf`, `.docx`, а также URL http/https.

---

## Идея вторая: дайте памяти консолидироваться самой

Разграничение области режет межпроектный шум. Оно ничего не делает с проблемой пяти-почти-дубликатов-в-одном-проекте. Для этого есть консолидация, и ключевым проектным решением в 0.7.0 было сделать её **автономной** — потому что единственное, на что нельзя полагаться, это что агент (или человек) вспомнит навести порядок.

### Консолидация во сне

При запуске, если со времени последнего прохода прошёл день, Octobrain выполняет проход «сна»: находит кластеры схожих, недавних, всё ещё в состоянии `Working` воспоминаний и сворачивает каждый кластер в один консолидированный инсайт. Оригиналы не удаляются — они переходят в состояние `Consolidated` с приглушённой важностью, связанные под новым родителем как происхождение. Пять размытых заметок об ограничении частоты становятся одним чистым воспоминанием с более высокой уверенностью, а след назад к сырым заметкам сохраняется для аудита.

Он управляется файлом-маркером, поэтому он ленивый — не cron и не фоновый планировщик, — и медленный или неудачный проход никогда ничего не блокирует. Значения по умолчанию консервативны:

```toml
[memory]
sleep_consolidation_enabled          = true   # opt-out, not opt-in
sleep_consolidation_interval_hours   = 24     # at most once a day
sleep_consolidation_threshold        = 0.85   # cosine similarity to cluster
sleep_consolidation_min_cluster_size = 3      # need 3+ to bother
sleep_consolidation_max_age_days     = 7      # only recent Working memories
```

Можно принудительно запустить проход из CLI для тестирования — `octobrain memory sleep-consolidate --threshold 0.85 --min-size 3` — но смысл в том, что вам никогда не придётся.

### Консолидация, привязанная к цели

Консолидация во сне группирует по _сходству_. Более острая версия группирует по _намерению_. В Octobrain есть тип воспоминания `goal` и настоящий жизненный цикл: `Working → Consolidated → Archived`. Рабочий процесс, целиком выраженный через связи `related_to` инструмента `memorize`, таков:

1. `memorize` воспоминание типа `goal`, фиксирующее то, что вы пытаетесь сделать.
2. Для каждой находки по пути — `memorize` с `related_to: [{ target_id: <goal_id>, relationship_type: "achieves" }]`.
3. Когда задача закрывается — `memorize` заметку с извлечённым уроком и `relationship_type: "closes"`.

Эта связь `closes` запускает консолидацию автоматически, без отдельного вызова инструмента. Закрывающая заметка становится консолидированным родителем (её важность поднимается до `max(sources) * 1.1`), а каждый источник `achieves` переходит в состояние `Consolidated` с приглушённой важностью, оставаясь доступным для запроса в целях аудита. Пять разрозненных находок схлопываются в одно воспоминание, которое что-то значит, привязанное к намерению, которое его породило.

Это также объясняет, почему отдельного инструмента `consolidate` больше нет в MCP-поверхности: закрытие цели теперь — это просто `memorize` со связью `closes`, а консолидация во сне работает сама. Меньше инструментов — та же мощь. (CLI-команда `octobrain memory consolidate <goal-id>` всё ещё на месте для ручного и административного использования.)

### Затухание по полупериоду: забывание как сигнал ранжирования

Третий механизм работает непрерывно. Важность каждого воспоминания затухает по графику полупериода [Эббингауза](https://en.wikipedia.org/wiki/Forgetting_curve) — и каждый раз, когда вы обращаетесь к воспоминанию, оно подкрепляется. Часто вспоминаемые воспоминания остаются сильными; те, к которым никто не прикасается, мягко угасают в ранжировании.

```toml
[memory]
decay_enabled            = true
decay_half_life_days     = 90    # higher = memories stay relevant longer
access_boost_factor      = 1.2   # reinforcement multiplier per access
min_importance_threshold = 0.05  # floor — decay never deletes
```

Две защитные меры делают это безопасным. Есть _пол_ важности: устаревшее воспоминание опускается назад, но никогда не исчезает, потому что затухание — это сигнал переранжирования, а не удаление. И счётчики обращений сохраняются между перезапусками, так что ощущение памяти о том, «что горячо», не сбрасывается каждую сессию. Чистый эффект: воспоминания, которыми вы реально пользуетесь, всплывают, а те, что вы сохранили однажды и больше не понадобились, тонут — без всякого курирования с вашей стороны. Именно поэтому инструмент `forget` говорит агенту _не_ удалять вещи за их возраст: затухание уже справляется с этим, изящно, не теряя историю.

---

## Что хранить, а что оставить в покое

Механизм доводит вас только до определённой точки. Дисциплина, которая починила наш поиск, заключалась в решении о том, что _заслуживает_ воспоминания. Инстинкт агента — хранить всё; правильная политика ближе к обратному.

**Храните:**

- **Решения и их обоснование.** «Мы выбрали token-bucket вместо фиксированного окна из-за burst-at-boundary». _Почему_ — это та часть, которую дорого реконструировать.
- **Трудно добытые факты об этом репозитории.** Неочевидная команда тестов, заголовок staging, миграция, которая должна выполниться первой. То, что агент иначе переоткрывает (или переугадывает) каждую сессию.
- **Предпочтения пользователя** — в глобальной области. Сказано один раз, истинно везде.
- **Исправления ошибок с корневой причиной.** Не «починил webhook», а «повторам webhook нужны были ключи идемпотентности; без них повторы списывали оплату дважды».

**Не храните:**

- **Преходящее состояние.** Текущую ветку, что в рабочем дереве, какой тест падает сегодня. Это устаревает до следующей сессии и навсегда после неё становится чистым шумом.
- **Всё, что тривиально перевыводится.** Если однострочная команда shell это воссоздаёт, то именно эта команда — воспоминание, а не её вывод.
- **Непроверенные догадки как факты.** Если агент _обязан_ записать предположение, для этого есть `source: agent_inferred` и низкая важность. Помечайте неопределённость, чтобы поиск ставил её ниже подтверждённых знаний.
- **Один и тот же инсайт пять раз.** Вызывайте `remember` _перед_ `memorize`. Если возвращается почти-совпадение, обновите или замените его вместо того, чтобы наслаивать ещё один почти-дубликат.

У последнего пункта есть поддержка инструмента. Когда новый факт заменяет старый — изменилось значение, отменено решение — `memorize` новый факт с `related_to: [{ target_id: <old_id>, relationship_type: "supersedes" }]`. Тогда поиск ставит текущий факт выше устаревшего, который остаётся доступным для истории. Вы исправляете запись, не теряя её.

---

## Настройка

Установите и направьте на него MCP-клиент.

```bash
# crates.io
cargo install octobrain

# или на macOS через Homebrew
brew install muvon/tap/octobrain
```

Сначала попробуйте полный цикл из CLI:

```bash
octobrain memory memorize --title "API Design Pattern" \
  --content "Use REST for CRUD, GraphQL for complex queries" \
  --memory-type architecture --tags "api,design"

octobrain memory remember "how should I design APIs"
```

Затем подключите к MCP-клиенту. Для Claude Desktop добавьте в `claude_desktop_config.json`:

```json
{
	"mcpServers": {
		"octobrain": {
			"command": "/path/to/octobrain",
			"args": ["mcp"]
		}
	}
}
```

Запустите `octobrain mcp` для stdio или `octobrain mcp --bind 0.0.0.0:12345` для HTTP. Хранилище находится в `~/.local/share/octobrain/` (macOS/Linux) или `%APPDATA%\octobrain\` (Windows), а проектные воспоминания изолированы по области, как описано выше. У всего есть разумные значения по умолчанию: консолидация во сне, затухание, гибридный поиск и HyDE включены из коробки. Полный набор настроек задокументирован в `config-templates/default.toml` в репозитории.

Единственное решение по конфигурации, которое стоит принять осознанно, — это `decay_half_life_days`. Значение по умолчанию в 90 дней подходит для равномерно развивающейся кодовой базы. Поднимите его для стабильного проекта, где решения остаются в силе долго; понизьте для быстро меняющегося прототипа, где истина прошлого месяца — ложь этого.

---

## Часто задаваемые вопросы

**Теряет ли забывание информацию?**

Нет. Затухание — это сигнал ранжирования с полом: малоиспользуемые воспоминания тонут, но никогда не удаляются. Консолидация переводит источники в состояние `Consolidated`, приглушённые, но всё ещё доступные для запроса, связанные под их родителем как происхождение. Единственное, что по-настоящему удаляет, — это явный `forget` с `confirm: true`. Вы всегда можете проследить назад.

**Чем `memorize` отличается от `knowledge`?**

`memorize` хранит инсайты, которые агент _производит_ — решения, исправления, выводы. `knowledge` индексирует документы, которые агент _читает_ — доки, файлы дизайна, URL — чтобы он мог их искать и цитировать. Дистиллированная мудрость против исходного материала. Разные жизненные циклы, разные инструменты.

**Нужно ли управлять областями вручную?**

Обычно нет. С MCP-клиентом, выполняющим рукопожатие сессии, область блокируется по git-remote вашего рабочего каталога, а параметр `scope` убирается из схемы. Вы трогаете область явно только для того, чтобы сохранить глобальное предпочтение (`global: true` или `scope: ""`).

**Почему всего четыре MCP-инструмента? Другие серверы памяти выставляют дюжину.**

Потому что каждый выставленный инструмент — это схема, которую модель перечитывает на каждом ходу, а точность выбора инструмента падает по мере роста каталога. Консолидация стала автономной, а `relate`/`consolidate` свернулись в связи отношений у `memorize`, так что задачи, которым раньше нужен был собственный инструмент, теперь обходятся без него. В CLI по-прежнему есть полный набор для административной работы.

**Нужен ли API-ключ или GPU?**

Нет. Модель эмбеддинга по умолчанию работает локально на CPU через fastembed — без ключа, без сети. Облачные эмбеддеры (Voyage, OpenAI, Google, Jina) поддерживаются, если вам нужно более высокое качество, но они опциональны.

---

То, что починило наш поиск, было не лучшим алгоритмом поиска — поиск Octobrain уже обыгрывал BM25 на бенчмарках, пока мы тонули. Это было признание того, что память должна забывать, чтобы оставаться полезной. Разграничьте её так, чтобы нужные вещи вообще были _кандидатами_; дайте ей консолидироваться, чтобы дубликаты схлопывались; дайте ей затухать, чтобы мёртвый груз тонул. Тогда агент перестаёт рыться в ящике хлама и начинает отвечать из памяти.

Храните решения и трудно добытые факты. Пропускайте преходящий шум. Вызывайте `remember` перед `memorize`. Остальное пусть работает само.

— Don

---

_[Octobrain](https://github.com/muvon/octobrain) — открытый исходный код под Apache-2.0. Механика консолидации и затухания, описанная здесь, появилась в [0.7.0](/blog/octobrain-0-7-0-memory-that-sleeps); система областей видимости и более лёгкая поверхность инструментов перешли в 0.8.0. Если какая-то деталь настройки вас споткнула или вы хотите изменить значение по умолчанию для области или затухания, [откройте issue](https://github.com/muvon/octobrain/issues) — мы их читаем._
