> ## Documentation Index
> Fetch the complete documentation index at: https://docs.velesagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Исследование размера и жизненного цикла vector.db

> Фактическая диагностика клиентской базы векторной памяти, реализованное ограничение нагрузки и правила безопасной работы со старым индексом.

# Исследование размера и жизненного цикла `vector.db`

Дата фиксации: 10 июля 2026 года.

Эта заметка сохраняет результаты исследования векторной памяти Велеса для последующего продолжения работы. Она относится к текущей реализации в `veles/agent/vector_memory.py` и к реальной базе из рабочей среды клиента.

## Решение от 25 августа 2026 года

Повторное наблюдение под нагрузкой показало около 3,36 ГБ в `vector.db`, около 774 МБ в журнале WAL и 77 579 фрагментов. Главной причиной высокой резидентной памяти оказался не сервер Nerve, а подготовка всех эмбеддингов крупного файла в одном списке Python. Одновременно в памяти оставались исходный текст, все фрагменты, строки для запроса, списки чисел из ответов и буферы SQLite. Сырые аварийные архивы давали значительную долю нагрузки.

Реализовано следующее решение:

* один файл допускается к индексации только до `tools.vectorMemory.maxFileBytes`; значение по умолчанию — 8 МиБ;
* `memory/*-raw-archive.md` сохраняются на диске, но исключены из обнаружения и обоих видов поиска;
* файл разбивается и отправляется поставщику порциями не более 20 фрагментов;
* до первого запроса эмбеддингов все фрагменты подсчитываются во временной базе, проверяются предел в 8192 фрагмента и нижняя оценка текстовой записи; после первого ответа полная оценка с известной размерностью вектора проверяется до упаковки и публикации, а поколение более 64 МиБ отклоняется; за один проход полное чтение, первичное хеширование, переиндексация и постановка исчезнувших путей в очередь выполняются ограниченно, не более чем для 20 файлов каждого вида работы;
* обнаружение путей не создаёт общий список в памяти Python: дерево обходится потоком, полный снимок имён складывается порциями во временную дисковую базу, а между каждыми 100 просмотренными записями управление возвращается шлюзу; сортировка читается из индекса временного снимка, а позиция справедливого продолжения сохраняется в служебных метаданных основного индекса;
* список уже индексированных путей сверяется с тем же временным снимком дисковыми порциями по 100 строк, без отдельного запроса Python для каждого файла; если обход каталога завершился не полностью из-за ошибки чтения, удаление якобы исчезнувших путей на этом проходе не выполняется;
* суммарная расчётная запись поколений за проход не превышает 64 МиБ; обращения к поставщику заранее резервируются для целого файла, а допустимый файл, которому мало обычного бюджета, может превысить его только первым, не делит обращения с другим файлом и всё равно ограничен числом фрагментов;
* каждая порция сразу переводится в двоичный вид и записывается во временную дисковую базу, поэтому список эмбеддингов всего файла больше не удерживается в Python;
* одинаковый текст внутри обработки использует один запрос эмбеддинга, но отдельные пути и диапазоны строк сохраняются как самостоятельные источники;
* полностью подготовленное поколение файла заменяет прежнее одной ограниченной транзакцией; перед публикацией и перед постановкой отклонённого поколения в очередь повторно и с тем же пределом размера проверяются хеш исходника и редакция настроек;
* повторный сбой эмбеддингов для неизменившегося файла не переписывает текст, полнотекстовый индекс и временные метки;
* первый сбой поставщика открывает общий интервал ожидания, поэтому остальные файлы не создают сотни одинаковых неудачных запросов;
* новые и обновлённые строки хранят вектор только в `chunks_vec`; поле `chunks.embedding` остаётся для чтения старых баз, но новая JSON-копия туда не записывается;
* размерность сначала восстанавливается из объявления `chunks_vec`; несовпадение размерности прекращает публикацию и никогда не удаляет действующую таблицу;
* параметры разбиения новых и обновлённых файлов записываются в `files.index_signature`, поэтому их последующее изменение приводит к пофайловому обновлению; пустая подпись старой схемы остаётся явным признаком наследия и сама по себе не запускает массовую перезапись;
* размер, время изменения и другие признаки исходника сохраняются в `files`, поэтому неизменившиеся файлы не хешируются заново при каждом минутном проходе;
* исключённый, исчезнувший или явно удаляемый крупный путь сначала удаляется из `files` и сразу пропадает из поиска, затем `cleanup_queue` удаляет до 100 фрагментов за транзакцию; новые полнотекстовые строки имеют индексированное соответствие с фрагментом, а старые очищаются справедливо чередуемыми окнами до 100 строк по `rowid`;
* при постановке старого полнотекстового поколения в очередь фиксируется верхняя граница `rowid`, поэтому очистка не гонится за новыми строками других файлов; соответствие `files.fts_map_complete` позволяет современным поколениям вообще не выполнять общий проход;
* режим двоичного, смешанного или старого JSON-хранилища и неизвестность профиля прежней схемы записываются в `vector_index_meta` до добавления новых столбцов; запуск не соединяет целиком многогигабайтные таблицы ради доказательства полноты двоичных копий, а неизменившиеся поколения с неизвестным профилем сохраняются без повторного полного хеширования;
* компактный счётчик оставшихся JSON-поколений уменьшается в одной транзакции с удалением или заменой их строк в `files`, в том числе пока доступность двоичного расширения ещё не выяснена; нулевое значение снимает ограничение смысловой индексации без обхода `chunks`;
* точное удаление и фоновая очистка одинаково обрабатывают исторические пути с `/` и `\`;
* соединение ограничивает внутренний кэш, задаёт автоматические контрольные точки и предпочтительный предел журнала 64 МиБ; обычный проход использует неблокирующую контрольную точку, а усечение выполняется при спокойном завершении; занятый читатель откладывает его без изменения данных.

Существующая база намеренно не переписывается целиком при запуске. Старые JSON-векторы и освобождённые страницы не исчезнут только от обновления программы, поэтому основной файл может остаться большим. Физическое уплотнение реализовано как отдельная явная операция при остановленном Велесе; оно не выполняется при обычном запуске или фоновой синхронизации. Прямые `VACUUM`, удаление `vector.db` и очистка старых JSON-векторов в действующей базе по-прежнему запрещены.

## Ручное уплотнение от 25 августа 2026 года

Для уменьшения активного `vector.db` добавлена одна узкая команда `veles vector-memory compact`. Без `--yes` она только показывает план; изменения начинаются только при явном `--yes`.

Контракт операции:

* шлюз и все процессы агента для рабочей области должны быть остановлены; межпроцессная блокировка владельца не позволяет уплотнению и индексатору одновременно открыть базу;
* после получения блокировки команда выполняет контрольную точку WAL, чтобы ожидающие записи вошли в стабильный снимок; замена не продолжается с непустым журналом;
* на той же файловой системе создаётся свежая теневая база, в которую попадают только живые файлы и фрагменты;
* достоверные старые JSON-векторы переносятся в двоичное хранилище; исторические нулевые заглушки считаются отсутствующими смысловыми данными, но их фрагменты остаются в полнотекстовом поиске, а недостающие векторы не строятся через поставщика модели;
* полнотекстовый индекс строится заново; целостность, количества строк, размерность и связи проверяются до публикации;
* после проверок теневая база атомарно заменяет активную, а байт-в-байт резервная копия прежней базы с отметкой времени всегда остаётся в `memory`;
* исходные файлы, записи памяти и настройки не изменяются, внешние вызовы к поставщику модели не выполняются;
* любая ошибка до атомарной замены оставляет прежний `vector.db` активным.

Уплотнение уменьшает активный `vector.db`, но не возвращает место тому в целом, пока оператор не проверит новую базу и вручную не переместит или не удалит резервную копию. Это намеренная цена безопасного отката.

Разделы ниже фиксируют первоначальное исследование от 10 июля и принятый тогда консервативный промежуточный план. Если они расходятся с решениями от 25 августа, актуальными считаются разделы выше; исторические числа и варианты сохранены как контекст диагностики.

Важно различать источники сведений:

* код и тесты исследовались в рабочей копии `C:\projects\veles`;
* числовая диагностика ниже получена другим агентом непосредственно из реальной базы `/data/veles/workspace/memory/vector.db`;
* результаты осмотра тестовой базы намеренно не используются как доказательство состояния клиентских данных;
* никакие изменения реальной базы в рамках этого исследования не выполнялись.

## Краткий вывод

Первоначальная гипотеза о массовом сохранении удалённых файлов в индексе не подтвердилась для исследованной реальной базы.

На момент проверки:

* все записи `files.path` существовали на диске;
* хеши проиндексированных файлов совпадали с текущим содержимым;
* проиндексированных, но больше не обнаруживаемых файлов не было;
* основных фрагментов без родительской записи `files` в проверенных агрегатах не найдено;
* число свободных страниц SQLite было практически нулевым.

Главная причина большого `vector.db` архитектурная: каждый эмбеддинг хранится как большой JSON в `chunks.embedding` и повторно как двоичный вектор `float32` в `chunks_vec`. Текст также хранится в `chunks.text` и во внутренних таблицах FTS5. Отдельно большой объём занимает WAL.

На момент первого исследования было принято консервативное промежуточное решение не менять запись новых строк. Позднее его заменило решение от 25 августа: старые строки не переписываются, но новые JSON-копии эмбеддингов больше не создаются.

## Фактическое состояние реальной базы на 10 июля

### Размеры файлов

| Файл | Размер |
| - | -: |
| `/data/veles/workspace/memory/vector.db` | 1 238 249 472 байта, около 1,24 ГБ |
| `vector.db-wal` | 310 450 272 байта, около 310 МБ |
| `vector.db-shm` | около 196 КБ |

SQLite работал в режиме WAL.

### Страницы SQLite

| Показатель | Значение |
| - | -: |
| `page_count` | 302 307 |
| `page_size` | 4 096 байт |
| `freelist_count` | 88 |

Проверка арифметики:

```text theme={null}
302 307 × 4 096 = 1 238 249 472 байта
88 × 4 096 = 360 448 свободных байт
```

Свободно примерно 0,029% основной базы. Поэтому обычный `VACUUM` без предварительного удаления живых данных смог бы вернуть лишь около 360 КБ, переписав при этом весь файл объёмом 1,24 ГБ.

### Сопоставление с файловой системой

Результаты проверки:

```text theme={null}
STALE_MISSING_FILES = 0
CHANGED_HASH_FILES = 0
UNREADABLE = 0
discoverable_on_disk = 737
indexed_files = 735
indexed_not_discoverable = 0
```

На диске обнаружены, но ещё не присутствовали в `files`:

* `docs/Astras/astras_issues_2025.json`;
* `memory/HISTORY.md`.

Это не объясняет размер базы. Возможные причины: файлы появились после последнего успешного прохода либо их индексация завершилась ошибкой эмбеддингов.

### Ограничения диагностики

Полные запросы по `dbstat`, `SUM(LENGTH(embedding))`, виртуальным таблицам и некоторым соединениям выполнялись слишком долго или завершались по тайм-ауту. Поэтому точного распределения 1,24 ГБ по внутренним таблицам нет.

При последующей диагностике нельзя без необходимости полностью читать все JSON-эмбеддинги и таблицы sqlite-vec/FTS5 на работающем экземпляре.

## Как данные хранились на 10 июля

Основные уровни хранения:

1. `files` — путь, проект, хеш файла, модель и состояние эмбеддингов.
2. `chunks` — текст каждого фрагмента и JSON-представление эмбеддинга.
3. `chunks_fts` — полнотекстовое представление текста и внутренние таблицы FTS5.
4. `chunks_vec` — двоичное представление того же эмбеддинга через sqlite-vec.

Размерность реальной `chunks_vec` равна 1536.

Для одного фрагмента:

* двоичный вектор `float32`: `1536 × 4 = 6144` байта;
* JSON того же вектора часто занимает около 30 КБ или больше;
* текст хранится как минимум в `chunks` и FTS5;
* дополнительно присутствуют индексы и служебные страницы SQLite.

В исследованной прежней реализации JSON использовался для восстановления размерности при запуске и как запасная копия эмбеддинга, а поиск выполнялся по `chunks_vec`.

## Что прежний жизненный цикл уже делал правильно

### Изменение файла

Полный проход вычислял SHA-256. Если хеш изменялся, `_index_file()` получал новое содержимое, а перед вставкой вызывал `_clear_file_data()` для старого пути.

При нормальном успешном проходе старые основные фрагменты файла заменялись.

### Удаление вручную

`_sync_once()` сравнивал `files.path` с `_discover_files()` и вызывал `_remove_file()` для отсутствующих путей.

Ручное изменение файловой системы поэтому согласовывалось не мгновенно, а при следующем успешном проходе. По умолчанию интервал был равен 60 секундам.

### Удаление через RPC рабочей области

Прежний `VelesControlRuntime.delete_workspace_path_file()` сначала переименовывал файл в карантин, вызывал физическую очистку `delete_file_index()`, затем удалял карантин. При ошибке очистки исходный файл восстанавливался. Текущий порционный контракт описан в актуальном разделе выше и в `docs/developers/backend_ru.mdx`.

### Скрытие разговора

Стандартное `tools.sessionMemory.deleteBehavior = "hide"` не удаляет файл памяти. Такой разговор по замыслу остаётся на диске и в векторном индексе. Это живые данные, а не мусор.

### Старые файлы памяти

`memory/**/*.md` индексируются без ограничения по возрасту. Временной спад влияет только на оценку результатов и не удаляет файлы или фрагменты.

## Найденные риски в прежнем коде

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

### 1. Перезапись при повторной ошибке эмбеддингов

Файл с `embed_ok=0` повторно обрабатывался каждый проход. При новой ошибке прежний код снова удалял и вставлял те же `chunks` и FTS-строки.

Это не меняет полезные данные, но создаёт новые записи WAL и сегменты/метки удаления FTS5 каждые 60 секунд. Для большого файла такой цикл может быстро раздувать WAL.

### 2. Гонка удаления с незавершённым индексированием

Последовательность возможной ошибки:

1. синхронизация прочитала файл;
2. начался сетевой запрос эмбеддингов;
3. в это время RPC удалил файл и очистил индекс;
4. запрос эмбеддингов завершился;
5. старая задача снова вставила уже удалённый файл.

Следующий полный проход обычно исправит состояние, но немедленная гарантия удаления нарушается.

### 3. Удаление векторов зависит от `_dims`

`_clear_file_data()` и `delete_file_index()` удаляют строки `chunks_vec` только когда `_dims` уже восстановлен. До первого успешного восстановления размерности удаление может пропустить двоичные векторы.

Для удаления по идентификатору размерность не нужна. Достаточно существования и доступности виртуальной таблицы.

### 4. Исторические разделители путей

Старая реализация на Windows сохраняла `str(relative_to(...))`, то есть пути с `\`. Сменившая её реализация стала создавать пути через `.as_posix()`.

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

### 5. Скрытые ошибки FTS/vector

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

Прежний FTS-поиск читал `chunks_fts` напрямую, поэтому потерянная FTS-строка теоретически могла оставаться видимой. Векторный поиск соединял `chunks_vec` с `chunks`, поэтому вектор без основного фрагмента занимал место, но не возвращался поиском.

### 6. Дубликаты FTS внутри одного прохода

`chunks.id` уникален, но в FTS используется обычный `INSERT`. Если алгоритм разбиения создаст одинаковый `chunk_id` несколько раз, основная таблица заменит строку, а FTS сохранит несколько записей.

### 7. Опасная смена размерности

Если новая размерность отличалась от объявления `chunks_vec`, прежний `_ensure_vector_table()` выполнял `DROP TABLE chunks_vec`.

Это противоречит требованию сохранения клиентского индекса. До появления отдельного безопасного перестроения несовпадение размерности должно оставлять текущую таблицу нетронутой и прекращать новую запись.

### 8. Нет отпечатка алгоритма разбиения

Прежнее условие пропуска учитывало хеш файла, модель и `embed_ok`, но не `chunkTokens`, `chunkOverlap` и версию алгоритма. Изменение этих параметров не перестраивало неизменившиеся файлы.

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

### 9. Физический размер не уменьшается после логического удаления

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

В исследованной базе это не главный источник размера основной БД, потому что список свободных страниц почти пуст.

## Исторический минимальный план (заменён решением от 25 августа)

Цель первого изменения — устранить будущую лишнюю запись и опасные гонки без изменения формата `vector.db`.

### Восстановление размерности

* Сначала читать `float[N]` из SQL-объявления `chunks_vec` в `sqlite_master`.
* Оставить чтение JSON запасным путём.
* Не прекращать запись JSON-эмбеддингов.
* При несовпадении размерности не удалять `chunks_vec`; отменять индексирование файла до любых изменений БД и сообщать о необходимости отдельного перестроения.

### Повторные ошибки эмбеддингов

* Первая ошибка для нового или изменённого файла по-прежнему создаёт актуальный текстовый индекс с `embed_ok=0`.
* Если хеш и модель уже совпадают и `embed_ok=0`, следующая неудачная попытка не должна выполнять `DELETE`, `INSERT` или менять временные метки.
* Успешная попытка позднее заполняет векторы и устанавливает `embed_ok=1`.

### Защита от гонки

* Читать точный снимок содержимого и вычислять его хеш.
* Получать эмбеддинги вне транзакции.
* Непосредственно перед первой операцией SQLite повторно проверить существование и хеш файла.
* Если файл исчез или изменился, отбросить подготовленный результат.
* После проверки выполнять все синхронные изменения без промежуточных `await`.

### Транзакции и удаление

* Замену одного файла выполнять в явной транзакции под существующей блокировкой.
* При любой ошибке выполнять откат.
* Проверять существование `chunks_vec` независимо от `_dims`.
* Всегда рассматривать варианты пути с `/` и `\`.
* При точном удалении очищать все FTS-строки этого пути, включая исторические дубликаты.
* Не скрывать ошибку обязательной очистки, чтобы карантинный механизм смог вернуть файл.
* В пределах одной индексации пропускать уже встреченный `chunk_id`.

### Ограничение WAL

При открытии соединения:

```sql theme={null}
PRAGMA journal_mode = WAL;
PRAGMA wal_autocheckpoint = 1000;
PRAGMA journal_size_limit = 67108864;
```

После успешного полного прохода:

* проверить размер `vector.db-wal`;
* если он больше 64 МБ, выполнить лучший возможный `PRAGMA wal_checkpoint(TRUNCATE)`;
* результат `BUSY` считать безопасной отсрочкой;
* не считать невозможность контрольной точки ошибкой индексации;
* повторить попытку при чистом завершении.

### Что не меняется

* схема и названия таблиц;
* формат существующих строк;
* JSON в `chunks.embedding`;
* периодический полный проход;
* поведение ручных изменений файловой системы;
* RPC шлюза;
* область индексируемых файлов;
* правила хранения старых файлов памяти.

## Ожидаемый результат первого исправления

### Что уменьшится

При отсутствии долгоживущих читателей WAL сможет сократиться с примерно 310 МБ до диапазона 0–64 МБ.

Ожидаемая немедленная экономия общего места: примерно 246–310 МБ.

### Что не уменьшится

Основной `vector.db` размером около 1,24 ГБ существенно не уменьшится, потому что:

* почти все его страницы заняты живыми данными;
* JSON-эмбеддинги сохраняются;
* автоматический `VACUUM` не выполняется.

### Как изменится будущий рост

* повторные ошибки эмбеддингов перестанут переписывать неизменившийся текстовый индекс;
* FTS перестанет получать одинаковый идентификатор несколько раз за один проход;
* WAL будет иметь ограничение и возможность усечения;
* обычная индексация новых данных продолжит использовать прежний объём на фрагмент.

## Осознанно отклонённые изменения

### Периодическая сборка мусора

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

### `PRAGMA user_version` и общая система миграций

Не добавляются в первом исправлении, потому что формат базы не меняется.

### Автоматический `VACUUM` и `auto_vacuum`

Не добавляются. Для существующей базы включение инкрементального уплотнения всё равно потребовало бы полного `VACUUM`. При текущем `freelist_count` результат не оправдывает переписывание 1,24 ГБ и необходимость большого временного свободного места.

### Ограничение файла в 2 МБ

Это исторически отклонённый вариант до решения от 25 августа. Значение 2 МиБ не добавлялось: оно молча удалило бы из поиска уже поддерживаемые большие документы и не решило бы дублирование векторов. Актуальная реализация использует настраиваемый предел 8 МиБ по умолчанию и отдельные пределы производной работы.

### Немедленная интеграция файлового наблюдателя

Не добавляется. Полная синхронизация уже согласовала реальную базу, а событийная очередь потребовала бы фильтрации всплесков, WAL/SHM и файлов памяти. Точное RPC-удаление остаётся немедленным, ручные изменения — согласованными в пределах интервала.

### Широкий административный интерфейс

Набор команд `stats`, `repair`, `rebuild`, `vacuum`, `checkpoint` и новых RPC не добавляется. Он увеличил бы поверхность поддержки без необходимости для согласованной базы. Позднее добавленная узкая команда `veles vector-memory compact` для остановленного Велеса не вводит общего административного интерфейса или сетевого метода.

### Автоматическое удаление JSON-эмбеддингов

Не выполняется при запуске или фоновой синхронизации. Только явная команда с `--yes` при остановленном Велесе переносит достоверные старые JSON-векторы в двоичный вид в отдельной теневой базе, а байт-в-байт копия прежней базы остаётся для отката.

## Исторический проект уменьшения основного файла

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

### Возможные стратегии

#### Постепенное уменьшение новых данных

Это исторический вариант, который не был принят в решении от 25 августа. Предлагалось хранить полный JSON только у одного представительного фрагмента каждого файла, а остальные векторы оставлять только в `chunks_vec`.

Плюсы:

* небольшое изменение кода;
* старый `_restore_dims()` продолжает находить образец;
* существующая база не переписывается автоматически.

Минусы:

* основной файл сразу не уменьшается;
* контракт «один JSON на файл» надо поддерживать при удалении и переиндексации;
* старые версии снова записывают полный JSON.

#### Полная теневая миграция

Последовательность:

1. Остановить синхронизацию и новые записи.
2. Создать согласованную резервную копию средствами SQLite, а не обычным копированием активного WAL-файла.
3. Работать только с теневой копией.
4. Для каждого JSON-эмбеддинга проверить существование соответствующего двоичного вектора.
5. Восстановить отсутствующий `chunks_vec` из валидного JSON, если размерность совпадает.
6. Обнулить JSON только после подтверждения двоичной копии.
7. Сохранить повреждённые или единственные копии без изменений.
8. Выполнить уплотнение в новый файл через `VACUUM INTO`.
9. Загрузить sqlite-vec и проверить целостность, количества идентификаторов, размерность, FTS и результаты известных запросов.
10. Закрыть соединения, выполнить контрольную точку и атомарно переключить файлы.
11. Хранить исходную базу без автоматического удаления.

Любая ошибка должна оставлять прежний `vector.db` активным.

### Контракт отката после полной миграции

Нужно заранее выбрать один из вариантов:

* старый выпуск обязан открывать новую активную базу без дополнительных действий — тогда JSON лучше сохранять;
* откат выполняется восстановлением исходной резервной базы — тогда проверенные JSON-копии можно удалить.

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

## Первоначальный список проверок для минимального исправления

Дополнить существующие тесты следующими сценариями:

1. Первый сбой эмбеддингов создаёт текстовый индекс с `embed_ok=0`.
2. Второй одинаковый сбой вызывает API, но не меняет строки, временные метки и `Connection.total_changes`.
3. Следующая успешная попытка добавляет векторы и устанавливает `embed_ok=1`.
4. Файл удаляется во время приостановленного запроса эмбеддингов и не возвращается в индекс.
5. Файл изменяется во время запроса, и устаревший снимок не фиксируется.
6. Ошибка на каждом этапе замены файла откатывает всю транзакцию.
7. При `_dims=None` существующие строки `chunks_vec` всё равно удаляются.
8. Пути с `/` и `\` очищаются одинаково.
9. Повторный `chunk_id` создаёт только одну FTS-строку.
10. Размерность восстанавливается из `sqlite_master`, даже когда JSON отсутствует.
11. Несовпадение размерности не удаляет `chunks_vec`.
12. Контрольная точка WAL не меняет количество `files`, `chunks`, FTS и векторов.
13. Активный читатель приводит к безопасному `BUSY`, а не к ошибке синхронизации.

Согласно правилам репозитория, тесты, сборку и проверку стиля должен запускать пользователь после реализации.

## Проверка на копии реальной базы после реализации

Не работать непосредственно с единственным клиентским экземпляром.

На согласованной копии зафиксировать до и после:

* размеры `vector.db`, WAL и SHM;
* `page_count`, `page_size`, `freelist_count`;
* количества `files` и `chunks`;
* количества по проектам;
* список обнаруживаемых, но не проиндексированных файлов;
* `PRAGMA integrity_check`;
* результаты нескольких известных FTS- и векторных запросов.

Ожидания:

* логические количества и результаты поиска не меняются;
* массовой переиндексации 735 существующих файлов нет;
* основной файл остаётся примерно прежнего размера;
* WAL сокращается при отсутствии блокирующих читателей.

## Полезные указатели в коде

* `veles/agent/vector_memory.py`
  * `_restore_dims()` — восстановление размерности и чтение сохранённого режима хранилища без полного обхода фрагментов;
  * `_ensure_vector_table()` — безопасная проверка размерности без удаления действующей таблицы;
  * `_discover_files()` — область индексирования;
  * `_sync_once_locked()` — ограниченный проход, скрытие отсутствующих путей и проверка снимков;
  * `_spool_chunks()` / `_embed_staged_chunks()` — предварительный подсчёт и порционная подготовка эмбеддингов;
  * `_replace_file_from_stage()` — атомарная публикация готового поколения;
  * `_clear_file_data()` / `_drain_cleanup_queue()` / `delete_file_index()` — точная и порционная очистка;
  * `sync_loop()` — периодический проход.
* `veles/api/runtime.py`
  * `_cleanup_vector_index_for_deleted_file()`;
  * `delete_workspace_path_file()` — карантин и восстановление при ошибке.
* `veles/agent/loop.py`
  * создание, запуск, горячая смена конфигурации и закрытие менеджера.
* `veles/config/schema.py`
  * `VectorMemoryConfig`, включая интервал и параметры разбиения.
* `tests/test_vector_memory_indexing.py`, `tests/test_vector_search_tool.py` и `tests/test_workspace_file_delete.py`
  * проверки ограничений, сбоев, старых схем, пустых файлов, разделителей путей и точного удаления.

## Решения, которые нельзя забыть

* Реальная база сейчас согласована с файловой системой; не называть размер доказательством оставшихся удалённых файлов.
* `VACUUM` без удаления JSON почти ничего не даст при `freelist_count = 88`.
* WAL и основной файл — разные проблемы.
* Ручное уплотнение выполняется только через `veles vector-memory compact --yes` при остановленном Велесе; блокировку владельца нельзя обходить.
* Резервная копия прежней базы всегда сохраняется; не обещать уменьшение общего объёма тома до её ручного перемещения или удаления после проверки.
* Не удалять `vector.db` ради смены модели или исправления индекса.
* Не удалять `chunks_vec` при несовпадении размерности.
* Не очищать JSON-эмбеддинги автоматически без отдельного согласия на миграцию и контракт отката.
* Безопаснее временно хранить лишние данные, чем повредить или молча перестроить клиентскую базу.
