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

> Семантический поиск по коду позволяет агенту найти нужный код. Постоянная память позволяет ему помнить решения об этом коде. Если запустить только одно, получите агента, который заново выводит контекст в каждой сессии или помнит выводы, которые уже не может найти в коде. Вот как связать Octocode и Octobrain, чтобы агент и находил, и помнил.

Я наблюдал, как агент четыре раза за один день вникал в незнакомый платёжный сервис. Тот же сервис, четыре новые сессии, четыре раза он с нуля прослеживал обработчик вебхуков, заново обнаруживал, что повторы идемпотентны благодаря ключу дедупликации, и заново делал вывод, что префикс `legacy_` у трёх функций означает «не трогать». В каждой сессии он делал хорошую работу. В каждой сессии он выбрасывал её, когда закрывалось окно контекста.

У агента была карта. Он мог искать по коду и находить что угодно. Чего у него не было — это памяти. Поэтому он снова и снова перерисовывал выводы, которые уже делал, потому что карта говорит, где что находится, — но не говорит, что вы решили об этом в прошлый вторник.

Об этом и пост. Семантический поиск по коду и постоянная память решают две половины одной задачи, и почти все запускают ровно одну из них. Вот почему вам нужны обе и как заставить их работать в связке.

---

## Два режима отказа, одна недостающая половина

Дайте агенту **поиск без памяти** — и получите цикл «четыре раза за день». Он мгновенно находит обработчик вебхуков, но _смысл_ этого обработчика — почему он устроен именно так, что безопасно менять, какой путь устаревший — не хранится нигде надолго. Каждая сессия выводит это заново. Агент компетентен и беспамятен.

Дайте агенту **память без поиска** — и получите противоположный отказ, более тихий и худший. Он помнит вывод — «ключ дедупликации в обработчике вебхуков делает повторы идемпотентными», — но через шесть недель не может _снова найти_ код, к которому относится этот вывод. Обработчик отрефакторили, функцию переименовали, файл разбили надвое. Память теперь — уверенная фраза, указывающая в пустоту. Агент ей доверяет и действует на основе факта, который уже неверен.

Два инструмента закрывают слепые зоны друг друга:

|                         | Находит код | Помнит решения | Без второго                           |
| ----------------------- | ----------- | -------------- | ------------------------------------- |
| **Семантический поиск** | да          | нет            | Заново выводит контекст каждую сессию |
| **Постоянная память**   | нет         | да             | Помнит выводы, которые не может найти |

Карта и память. Запустите одно — не хватает половины.

В Muvon мы выпускаем обе половины как MCP-серверы с открытым кодом — [Octocode](https://github.com/Muvon/octocode) для карты, [Octobrain](https://github.com/Muvon/octobrain) для памяти — и хост, который запускает их вместе, [Octomind](https://github.com/Muvon/octomind). Оба под Apache-2.0. Этот пост — руководство по рабочему процессу использования их в паре. Если хотите глубокие разборы, [семантический поиск по коду Octocode](/blog/octocode-semantic-code-search) и [представление Octobrain](/blog/introducing-octobrain-mcp-memory-server) разбирают каждый инструмент по отдельности. Я буду ссылаться, а не пересказывать.

---

## Что на самом деле предоставляет каждая сторона

Сначала фундамент, потому что связка имеет смысл, только если вы знаете реальный набор инструментов. Это MCP-инструменты, а не список пожеланий.

**Octocode** индексирует ваш репозиторий с AST-разбором tree-sitter — настоящие символы, а не плоские текстовые фрагменты — и предоставляет агенту четыре инструмента:

| Инструмент          | Что делает                                                                                                                                                                                            |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `semantic_search`   | Поиск с упором на полноту по концепции или поведению. «Где обрабатывается аутентификация», «код, который повторяет неудавшиеся запросы». Находит код, даже когда имена не совпадают с вашими словами. |
| `structural_search` | Сопоставление по AST-шаблону и точный поиск символов. Точно и дёшево, когда вы знаете имя, строку или места вызова.                                                                                   |
| `view_signatures`   | Извлекает сигнатуры функций и определения типов без тел — самый дешёвый способ составить карту файла перед чтением.                                                                                   |
| `graphrag`          | Запросы к графу знаний по `imports`, `calls`, `implements`, `extends`. «Что зависит от платёжного модуля».                                                                                            |

Индекс ограничен проектом и работает локально. Вы строите его один раз командой `octocode index`, и он остаётся актуальным. Что стоит запомнить: Octocode отвечает на вопрос _«где это и как связано?»_ — и всегда отражает код таким, какой он есть прямо сейчас.

**Octobrain** даёт агенту долговременную память, ограниченную проектом по нормализованному URL Git-remote (`host/org/repo`). Четыре MCP-инструмента:

| Инструмент  | Что делает                                                                                                                                                                                                                                 |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `memorize`  | Сохраняет инсайт, решение или факт. Принимает `title`, `content`, `memory_type` (`architecture`, `decision`, `bug_fix`, `security`, …), `importance`, `tags`, `related_files` и `related_to[]` для inline-связей с другими воспоминаниями. |
| `remember`  | Семантический поиск по сохранённым воспоминаниям. Автоматически возвращает соседей графа на 1 переход. Поддерживает `created_after` / `created_before` для временных запросов.                                                             |
| `forget`    | Удаляет воспоминание по `memory_id` или по запросу — необратимо, требует `confirm=true`.                                                                                                                                                   |
| `knowledge` | Отдельная база знаний для индексации и поиска по внешним документам и URL (`search`, `store`, `read`, `match`, `delete`).                                                                                                                  |

Само описание `memorize` велит агенту _сначала вызвать `remember`, чтобы избежать дубликатов_, и помечать факты как `user_confirmed` (высокая важность) против `agent_inferred` (ниже). Octobrain автоматически связывает семантически похожие воспоминания в стиле Zettelkasten и поддерживает связи `supersedes`, чтобы исправленный факт ранжировался выше устаревшего, который он заменяет, — старый остаётся доступным для истории. Запомните: Octobrain отвечает на вопрос _«что мы об этом решили и когда?»_ — и это единственная сторона, которая сохраняется между сессиями.

Обратите внимание на симметрию. Octocode знает текущий код, но забывает каждый разговор. Octobrain помнит каждый разговор, но не знает код. Ни одно поле — `related_files` у воспоминания, путь к файлу в результате поиска — не имеет смысла без второго инструмента, который его разрешит.

---

## Как они складываются: цикл

Два инструмента не просто сосуществуют — они образуют цикл, и этот цикл и есть вся техника. Четыре хода:

**1. Поиск, чтобы найти.** Агент не знает, где живёт логика идемпотентности вебхука. Он вызывает `semantic_search("webhook retry idempotency dedupe")`. Octocode возвращает обработчик и проверку ключа дедупликации. Теперь у агента есть _местоположение_ и _текущий код_.

**2. Запомнить решение, а не местоположение.** Порассуждав об этом коде — подтвердив, что повторы идемпотентны, выявив устаревший путь, согласовав с вами границу области — агент вызывает `memorize`. Критично: он сохраняет _вывод и рассуждение_, помеченные `related_files`, а не копию кода и не номер строки. `memory_type = "architecture"`, высокий `importance`, если вы подтвердили. Решение теперь долговечно.

**3. Вспомнить в следующий раз.** Новая сессия, окно контекста пустое. Прежде чем что-либо трогать, агент вызывает `remember("webhook payments idempotency")`. Octobrain возвращает сохранённое решение _плюс его соседей на 1 переход_ — связанную заметку по безопасности, привязанное предупреждение об устаревшем пути. Агент начинает сессию, уже зная то, на что раньше уходило четыре сессии.

**4. Снова искать, чтобы проверить.** Этот ход люди пропускают, и именно он держит память честной. Память говорит: «идемпотентность живёт в обработчике вебхуков». Прежде чем действовать, агент вызывает `semantic_search` или `view_signatures` по `related_files`, чтобы подтвердить, что код всё ещё соответствует памяти. Если обработчик отрефакторили и ключ дедупликации переехал, новый поиск выявляет несоответствие. Агент обновляет память — `memorize` со связью `supersedes` к старой — и продолжает на текущей истине.

```
        ┌─────────────────────────────────────────────┐
        │                                             │
        ▼                                             │
  semantic_search ──► рассуждать ──► memorize ──► remember
   (найти код)        (решить)     (сохранить    (след. сессия:
        ▲                           решение)      загрузить)
        │                                          │
        └────────── снова искать для проверки ◄─────┘
            (код всё ещё соответствует памяти?)
```

Карта держит память привязанной к реальному коду. Память не даёт агенту заново выводить то, что он уже знает. Поиск без шагов 2 и 3 — это беспамятный агент. Память без шагов 1 и 4 — уверенный-но-неправый агент. Цикл — это обе половины, делающие свою работу.

Почему шаг 4 так важен: память — это утверждение о коде в определённый момент, а код меняется. Octobrain может поставить исправленный `supersedes`-факт выше устаревшего, но _что-то_ должно сначала заметить устаревание. Octocode — это «что-то». Новый поиск — проверка агента на реальность против собственной памяти.

---

## Регистрация обоих серверов, чтобы агент имел их вместе

Ни один инструмент не помогает, если агент дотягивается только до одного. Весь смысл в том, чтобы карта и память были доступны в _одной_ сессии, чтобы агент гонял цикл, а вы не работали посредником. Вот проводка.

Octomind объявляет MCP-серверы в конфиге под `[[mcp.servers]]`. Встроенные серверы (`core`, `runtime`, `agent`, `orchestration`) есть всегда; вы добавляете Octocode и Octobrain как два `stdio`-сервера:

```toml
[[mcp.servers]]
name = "octocode"
type = "stdio"
command = "octocode"
args = ["mcp", "--path=."]
timeout_seconds = 240
tools = []

[[mcp.servers]]
name = "octobrain"
type = "stdio"
command = "octobrain"
args = ["mcp"]
timeout_seconds = 60
tools = []
```

`tools = []` означает «выставить все инструменты этого сервера». `--path=.` ограничивает Octocode текущим репозиторием; Octobrain ограничивает себя автоматически по нормализованному URL Git-remote (`host/org/repo`), так что воспоминания, сохранённые агентом в одном проекте, остаются изолированными от другого. Таймаут Octocode щедрый, потому что первый поиск после индекса может подтянуть локальную модель эмбеддингов в кеш; у Octobrain короче, потому что запросы к памяти дёшевы.

Если вы на Claude Code, а не на Octomind — это те же два сервера, зарегистрированные через CLI:

```bash
claude mcp add octocode  -- octocode mcp --path .
claude mcp add octobrain -- octobrain mcp
```

Claude Desktop, Cursor и Windsurf принимают тот же JSON-блок `mcpServers` — по одной записи на сервер. Механика задокументирована по каждому инструменту; здесь важно лишь _оба, в одном агенте._ Агент с Octocode, но без Octobrain — карта без памяти. Агент с Octobrain, но без Octocode — память без карты. Вам нужна пара.

Одно требование перед первым запуском: проиндексировать репозиторий. `octocode index` в корне проекта. Octobrain не требует ничего — он стартует пустым и наполняется по мере того, как агент сохраняет решения. Если репозиторий достаточно велик, чтобы индексация была серьёзной операцией, родственный пост об [индексации большой кодовой базы для семантического поиска локально](/blog/indexing-large-codebase-semantic-search-locally) покрывает это без API-ключей.

---

## Разбор примера: ввод агента в незнакомый сервис

Вот платёжный сервис из начала поста, на этот раз проделанный правильно. Смотрите, как цикл делает работу, которую четыре беспамятные сессии не осилили.

**Сессия 1 — холодный старт, строим карту и первые воспоминания.**

Агент никогда не видел этот сервис. Он начинает с составления карты, а не с чтения:

```
view_signatures("src/payments/**/*.rs")
  → webhook.rs: handle_webhook, verify_signature, dedupe_key
  → charge.rs:  create_charge, capture, legacy_settle, legacy_refund, legacy_void
```

Три функции с префиксом `legacy_` бросаются в глаза. Агент спрашивает; вы подтверждаете, что это устаревший путь расчётов, который всё ещё вызывает cron-задача, и который никому не позволено «прибрать». Это решение стоит сохранить — поэтому оно идёт в память, а не в пустоту:

```
memorize(
  title: "legacy_ settlement path in charge.rs is load-bearing",
  content: "legacy_settle/refund/void are called by the nightly
            reconciliation cron. Deprecated but NOT dead. Do not remove
            or refactor without checking cron/reconcile.rs.",
  memory_type: "architecture",
  importance: 0.9,
  source: "user_confirmed",
  tags: ["payments", "legacy", "cron"],
  related_files: ["src/payments/charge.rs", "src/cron/reconcile.rs"]
)
```

Затем он прослеживает идемпотентность через `semantic_search("webhook retry idempotency")`, приземляется на `dedupe_key`, рассуждает о ней и сохраняет второе воспоминание — `memory_type: "architecture"`, связанное с первым через `related_to`, чтобы оба всплыли вместе позже. Сессия завершается. Два воспоминания сохранены; индекс кода сохранён. Рассуждения за день больше не одноразовые.

**Сессия 2 — тёплый старт, три недели спустя.**

Новое окно контекста. Вместо повторного прослеживания агент сначала загружает то, что знает:

```
remember("payments webhook legacy settlement")
  → "legacy_ settlement path is load-bearing" (importance 0.9, CONFIRMED)
  → сосед на 1 переход: "webhook idempotency via dedupe_key"
```

Один вызов — и агент начинает там, где закончилась сессия 1, включая связанного соседа, которого он явно не запрашивал. Теперь шаг проверки. Память указывает на `src/payments/charge.rs`, поэтому, прежде чем доверять ей, агент снова ищет:

```
view_signatures("src/payments/charge.rs")
  → create_charge, capture, settle_v2, refund_v2, void_v2
```

Функций `legacy_` больше нет. Кто-то выкатил `settle_v2` и удалил устаревший путь. Память теперь устарела — и поскольку агент _проверил против карты, а не доверился памяти вслепую_, он это поймал. Он заменяет старое решение, а не действует на основе факта трёхнедельной давности:

```
memorize(
  title: "settlement path migrated to settle_v2",
  content: "legacy_settle/refund/void removed in the v2 migration.
            cron/reconcile.rs now calls settle_v2. Earlier 'do not remove'
            note no longer applies.",
  memory_type: "architecture",
  related_to: [{ target_id: <old_memory_id>, relationship_type: "supersedes" }],
  related_files: ["src/payments/charge.rs", "src/cron/reconcile.rs"]
)
```

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

---

## Антипаттерны

Цикл прост. Способы его сломать конкретны. Избегайте таких:

**Запоминать изменчивые номера строк.** «Баг в `charge.rs:142`» обесценивается в тот момент, когда кто-то добавляет import выше. Храните _что_ и _почему_, привязанное к `related_files` и именам символов — пусть шаг повторного поиска найдёт _где_. Octocode находит `dedupe_key`, будь он на строке 142 или 90; запомненный номер строки — это просто ложь с меткой времени.

**Чрезмерно индексировать в память.** Описание `memorize` в Octobrain недвусмысленно: пропускайте временное состояние и то, что легко вывести заново. Если `semantic_search` находит это за один вызов, ему не место в памяти — память для _выводов и решений_, а не для фактов, которые карта уже держит. Запоминать «модуль auth в src/auth» — тратить слот на то, что карта отдаёт бесплатно, и размывать полноту тех воспоминаний, что действительно важны. Память хранит то, что код _не может вам сказать_: почему, границу области, «не трогай это», которое вы знаете лишь потому, что кто-то сказал.

**Доверять устаревшим воспоминаниям без перепроверки.** Этот кусает сильнее всего, и поэтому существует шаг 4. Память — это утверждение о коде в определённый момент. Код меняется. Всегда заново ищите `related_files`, прежде чем действовать на основе старого решения; когда код уехал, замените (`supersede`) память, а не навязывайте старый вывод новому коду. Память, которую вы никогда не проверяете, — это технический долг, который огрызается.

**Давать замещённым воспоминаниям гнить вместо связывания.** Когда факт меняется, не просто `memorize` новый, осиротив старый — свяжите их через `supersedes`. Octobrain ранжирует текущий факт выше устаревшего _и_ сохраняет историю доступной. Осиротевшие исправления оставляют два одинаково уверенных противоречивых воспоминания и никакого способа понять, какое актуально.

**Запускать одну половину и считать дело сделанным.** Поиск в одиночку выводит заново вечно. Память в одиночку рассинхронизируется с кодом. Ценность не в каком-то одном инструменте — она в цикле между ними. Если вы регистрируете только один сервер, вы построили полмозга.

---

## Короткая версия

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

Карта говорит, где что находится. Память говорит, что вы об этом решили. Дайте агенту обе и свяжите между ними цикл. Это вся техника. Если гигиена памяти — та часть, которую хочется углубить, родственный пост о [памяти агентов без шума](/blog/ai-agent-memory-without-the-noise) подробнее о том, что стоит хранить.

— Don

---

_[Octocode](https://github.com/Muvon/octocode) и [Octobrain](https://github.com/Muvon/octobrain) — открытый код под Apache-2.0, работают вместе внутри [Octomind](https://github.com/Muvon/octomind). Нашли острый угол в цикле? [Откройте issue](https://github.com/Muvon/octobrain/issues) — рабочий процесс становится лучше, когда люди говорят нам, где он ломается._
