# Zulip Message History — SQLite (поиск истории переписки) _Создано: 2026-09-29_ ## Что это Вся история переписки Zulip (134 734 сообщения, 197 тредов, 11 стримов) выгружена в **один SQLite-файл**. Zulip как транспорт выводится из эксплуатации — история остаётся в этом файле. | Параметр | Значение | |---|---| | **Файл** | `~/Documents/zulip_message_history.sqlite` | | Размер | 81.9 MB на диске (78.1 MB логических данных) | | Сообщений | **134 734** (134 688 в стримах + 46 личек) | | Стримов | 11 | | Тредов (`topics`) | 197 | | Источник | `zulip-database-1` (Postgres 14, `~/Developer/zulip/docker-compose.yml`) | | Снимок на | 2026-09-29 | | Бэкап | `~/backup/zulip-final/` (см. §Бэкапы) | ## Схема ```sql streams (id, name) -- 11 каналов topics (id, stream_id → streams.id, name) -- 197 тредов messages (id, topic_id → topics.id, kind, sender, sender_email, date_sent, content) -- 134 734 messages_fts (fts5: content, content='messages') -- полнотекстовый поиск ``` - `kind` = `stream` | `dm` - `content` — **markdown-исходник** Zulip (не HTML-рендер); HTML был в `rendered_content` и в дамп не попал осознанно - `date_sent` — ISO-8601 UTC (`2026-09-29 06:29:41.829084+00`) ### Индексы | Индекс | Назначение | |---|---| | `messages_fts` (FTS5) | поиск по тексту через `MATCH` | | `idx_messages_topic` | сообщения одного треда | | `idx_messages_date` | диапазон по датам | | `idx_topics_stream` | треды одного стрима | ## Как искать историю (главное) ```bash DB=~/Documents/zulip_message_history.sqlite ``` ### 1. Поиск по тексту ```bash sqlite3 "$DB" " SELECT s.name AS stream, t.name AS topic, m.sender, m.date_sent, substr(m.content, 1, 3000) AS content FROM messages_fts f JOIN messages m ON m.id = f.rowid JOIN topics t ON t.id = m.topic_id JOIN streams s ON s.id = t.stream_id WHERE messages_fts MATCH 'docker jellyfin' ORDER BY m.date_sent DESC LIMIT 30;" ``` ### 2. Список тредов с количеством сообщений ```bash sqlite3 -column -header "$DB" " SELECT s.name AS stream, t.name AS topic, count(*) AS msgs FROM messages m JOIN topics t ON t.id = m.topic_id JOIN streams s ON s.id = t.stream_id GROUP BY t.id ORDER BY msgs DESC;" ``` ### 3. Прочитать тред целиком ```bash sqlite3 "$DB" " SELECT m.date_sent || ' | ' || m.sender || char(10) || m.content || char(10) || '===' FROM messages m WHERE m.topic_id = (SELECT id FROM topics WHERE name = 'Home assistant setup' LIMIT 1) ORDER BY m.date_sent;" ``` ### 4. Найти сообщения конкретного человека по ключевым словам ```bash sqlite3 "$DB" " SELECT t.name, m.date_sent, substr(m.content,1,500) FROM messages m JOIN topics t ON t.id = m.topic_id WHERE m.sender = 'Alex' AND m.content LIKE '%план%' ORDER BY m.date_sent DESC LIMIT 20;" ``` ### ⚠️ FTS5-синтаксис Токенизатор — `unicode61` (дефолт): поиск по **целым словам**, стемминга нет. | Хочу | Запрос | |---|---| | слово `docker` | `MATCH 'docker'` | | префикс `dock…` | `MATCH 'dock*'` | | фраза | `MATCH '"zulip router"'` | | AND | `MATCH 'docker jellyfin'` | | OR | `MATCH 'docker OR jellyfin'` | | НЕ | `MATCH 'docker NOT podman'` | `собрал` **не** найдёт `собрать` — стемминга нет. Для поиска по подстроке внутри слова нужен `trigram`-токенизатор (не включён; пересборка FTS-таблицы). ## Стримы и треды (снимок) | Стрим | Сообщений | Тредов | |---|---|---| | personal | 111 534 | 95 | | master | 7 630 | 29 | | projects | 7 524 | 20 | | executor | 7 355 | 32 | | Zulip | 432 | 1 | | daily-brief | 182 | 12 | | general | 15 | 1 | | inbox | 14 | 3 | | journal | 1 | 1 | | focus | 1 | 1 | | Direct messages | 46 | 2 | Крупнейшие треды (personal): `Home assistant setup` 12 196 · `truenas jellyfin` 12 059 · `Балда/валера` 9 454 · `ZONT Config compiler` 6 585 · `✔ media toolbox setup` 6 198. ## 🔴 Ограничение — база не живая Файл — **снимок на 2026-09-29**. После сноса Zulip новые сообщения сюда не попадают. Живой поиск свежей переписки — только в Mattermost (`hermes-maintenance`, `a6nyh77eyjbn5c1owh1i8eebee`). Разделять явно: «историческое (до 29.09)» → SQLite; «что обсуждали вчера» → Mattermost. ## Как это было сделано (воспроизводимо) 1. **Экспорт из Postgres в CSV.** Ключ — `\copy` (не `COPY`), потому что `COPY` через `docker exec` требует прав на файл внутри контейнера, а `\copy` пишет в stdout. Колонки: `id, kind, stream, topic, dm_user_id, sender, sender_email, date_sent, content`. ```bash docker exec zulip-database-1 psql -U zulip -d zulip -c "\copy ( SELECT m.id, 'stream' AS kind, s.name AS stream, m.subject AS topic, 0 AS dm_user_id, u.full_name AS sender, u.email AS sender_email, m.date_sent, m.content FROM zerver_message m JOIN zerver_recipient r ON r.id = m.recipient_id JOIN zerver_stream s ON s.id = r.type_id JOIN zerver_userprofile u ON u.id = m.sender_id WHERE r.type = 2 ORDER BY m.id ) TO STDOUT WITH (FORMAT csv, HEADER false)" > /tmp/zulip-export/messages.csv ``` Личные сообщения (`r.type = 1`) — отдельным запросом в `dms.csv`, топик = `DM user #`. В БД лички в `zerver_recipient.type_id = 1`, у них нет `subject` — отсюда синтетический топик. 2. **Сборка SQLite** — скрипт `build_sqlite.py` (Python stdlib `csv` + `sqlite3`), создаёт таблицы, наполняет `streams`/`topics`/`messages`, строит индексы и наполняет FTS5. 3. **Верификация** — `PRAGMA integrity_check`, `PRAGMA foreign_key_check`, `count(*)` против Postgres, FTS-запрос по реальному слову. ### 🔴 Питфоллы, которые стоили времени - **TSV рвётся на многострочном контенте.** Первый заход был с `-A -F$'\t'` → вместо 134 688 строк получилось **486 751** (переносы строк внутри сообщений режут TSV). Решение: `WITH (FORMAT csv)` — кавычки экранируют переносы корректно. `wc -l` на CSV всё равно покажет завышенное число переносов — это ожидаемо, **считать строки нужно парсером, а не `wc`**. - **`rendered_content` — это HTML.** Для читаемой истории нужен `content` (markdown). HTML-дамп бесполезен без рендера. - **`content` может содержать что угодно** — включая NUL и гигантские блоки кода. В `build_sqlite.py` нужен `csv.field_size_limit(50MB)`, иначе падает на больших сообщениях. - **CSV-колонок было 9, а проверял на 8** — первая сборка дала 0 сообщений и 134 734 ошибки «bad field count». Читать позиционно по факту заголовка, не по памяти. ## Бэкапы | Что | Где | |---|---| | SQLite (история) | `~/Documents/zulip_message_history.sqlite` | | CSV-дампы | `/tmp/zulip-export/messages.csv` (56 MB), `dms.csv` (18 KB) | | Compose + env Zulip | `~/Developer/zulip/docker-compose.yml`, `.env.zulip` | ## Что НЕ выгружалось (осознанно, по указанию Alex) Только переписка + структура тредов. Вне выгрузки: - вложения/аплоады (`volume zulip_zulip-data`, 38.1 MB — `uploads/`, `certs/`) - аватары, реакции, подписки, метаданные организаций - настройки стримов, права, история изменений сообщений Если понадобится картинка из старого треда — её в SQLite нет, она была в volume. ## Снос Zulip — чек-лист ⚠️ **Пока не выполнено.** Файл сделан как страховка перед сносом. ```bash cd ~/Developer/zulip docker compose down docker volume rm zulip_zulip-data zulip_zulip-pgdata zulip_zulip-rabbitmq zulip_zulip-redis docker rmi zulip/docker-zulip:11.6-0 # ~3.84 GB ``` Стек: `zulip-zulip-1` (`zulip/docker-zulip:11.6-0`, порты 8000/8443), `zulip-database-1` (`zulip/zulip-postgresql:14`), `zulip-rabbitmq-1`, `zulip-memcached-1`, `zulip-redis-1`. Объём volumes ≈ 1.1 GB (pgdata 503 MB на диске, БД 439 MB; data 38 MB). ### ⚠️ Зависимости, которые сломаются | Компонент | Что будет | |---|---| | `zulip-router` (Go, `~/Developer/zulip-router/`) | мёртвый — поллит Zulip Events API. Убрать из compose, иначе restart-loop | | Bot webhook-роутинг (Eagle/Кит/Валера/Клавдий, `host.docker.internal:8644-8645`) | маршрутизация в Zulip перестанет работать | | Hermes `platforms: zulip` в `~/.hermes/config.yaml` | платформа отвалится; транспорт уже переехал на Mattermost | ℹ️ Скиллы на Zulip больше не ссылаются — этап 3 закрыт, битых инструкций после сноса не будет (кроме двух файлов `docker-balda-service`, см. §«Оставлено как есть»). ## Связанные документы - [[plans/zulip-decommission]] — план и статус вывода Zulip из эксплуатации (этап 3 = этот файл) - [[projects/zulip-router]] — архитектура event-роутера (Go), конфиг ботов - [[tech/hermes-whale-mattermost-transport]] — текущий транспорт Hermes - [[tech/hermes-self-improvement-review]] — фоновый review скиллов/памяти (в т.ч. как его выключить) - [[how-to/hermes-eagle-mac]] — Hermes на Mac, Zulip Docker стек, RabbitMQ/Khepri pitfall - [[tech/docker-mac-disk-issues]] — почему Zulip тормозил (переполнение диска VM) ## Как это используется агентами (скилл вместо скиллов) Три скилла, работавшие с Zulip Postgres, **снесены** — их место занял один: | Было | Стало | |---|---| | `zulip-db-forensics` | `message-history-search` | | `zulip-thread-handoff` | ↑ | | `zulip-bot-routing` | ↑ | `~/.hermes/skills/note-taking/message-history-search/SKILL.md` — единственная точка входа для «ищи в базе», «вспоминай», «читай историю». На неё ссылаются `SOUL.md` (оба файла!) и `USER.md`, поэтому агент находит её без подсказки. **Триггер в SOUL/USER:** «ищи в базе» / «чё сделано» → `sqlite3` СРАЗУ, без `session_search` и без теории. `session_search` индексирует файлы сессий Hermes, **не** историю чата — для старых тредов он пуст.