feat: team hub — one shared history index on your own server - #55
Merged
Conversation
Транскрипты участников уезжают на сервер команды, там векторизуются и складываются в один индекс; любой участник ищет по всей истории и может задать вопрос обычными словами. Ключевое: хаб — это обычный session-recall, у которого транскрипты приходят по сети. Сервер хранит их в той же раскладке каталогов, что и ноутбук, и запускает штатный index_corpus. Никакого серверного экстрактора и второго пути поиска. - hub/auth: ключ на участника, на диске только хеш, отзыв по имени - hub/storage: раскладка + граница безопасности (traversal, симлинки), учёт принятых байт отдельно от размера файла - hub/masking: маскировка значений из Doppler именем переменной, хранятся хеши под солью, фильтр по длине и энтропии - hub/client: join с экраном согласия, инкрементальный push, redact - hub/ask: вопрос → поиск → ответ Codex по подписке (gpt-5.6-terra) - hub/remote: те же пять MCP-инструментов, но против сервера - share/compose: композер через codex exec, --ephemeral обязателен Solo-путь не меняется: без hub-конфига всё работает как раньше. WHY: docs/decisions/2026-08-06-team-hub-central-index.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Обе правки — с живого деплоя на Netcup. - CHUNK_BYTES 256 KB → 4 MB: на 256 KB история шла ~0.1 МБ/с, потому что каждый чанк это отдельное соединение с TLS-рукопожатием и fsync. Стало ~60 МБ/мин, 3.5 ГБ укладываются в час вместо девяти. - ask считает when_human сам: поле добавляется при сериализации анкора, поэтому дайджест печатал "session-recall ()" вместо даты. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Первый боевой корпус показал, что оба слоя резали живой текст.
Маскировка по Doppler отбирала значения по длине и энтропии — но форма
не отличает `claude-opus-4` от токена. В итоге по всей истории команды
замаскировались 851 упоминание имени модели, 2429 hostname и 1536 IP:
читать такую историю тяжело, а искать «какую модель брали» невозможно.
Теперь решает ИМЯ переменной: PASSWORD/TOKEN/SECRET/KEY — креды,
*_MODEL/*_HOSTNAME/*_IP — конфигурация. Из 650 секретов Doppler
маскируется 200 вместо 348.
Авто-редакт на клиенте выключен для password-assignment: паттерн
\w*(token|secret|key)\s*[=:]\s*\S{8,} ловит любое поле JSON и любую фразу
вида "token: …" — 27324 срабатывания против ~1900 у всех настоящих
форматов ключей вместе взятых. В scan() он остаётся: там находку смотрит
человек, и ложное срабатывание ничего не стоит.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три вещи, невидимые на фикстурах: скорость заливки, маскировка конфигурации вместо кредов и авто-редакт, съедающий живой текст. Общий вывод — у флагующего и у режущего детектора разная цена ошибки, один набор паттернов не может обслуживать оба. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Транскрипты — JSON, поэтому перенос строки внутри строки это два символа: \ и n. Бэкслеш не входит ни в один алфавит токенизации и обрывает токен, а буква n входит — и приклеивается к тому, что идёт следом. Ключ, записанный сразу после переноса, токенизировался как n<ключ> и не совпадал ни с одним хешем. Поймано на боевом корпусе: один настоящий VOYAGE_API_KEY уцелел в сообщении вида "вот замени в doppler...\n\n<ключ>". Плюс `hub remask` — применить текущую карту к уже сохранённым транскриптам. Маскировка работает на ингесте, поэтому исправленная позже карта иначе требовала бы перезаливки гигабайтов; ledger при этом не трогается (он считает байты файла КЛИЕНТА, а они не меняются). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
На хабе два процесса делят одну базу: HTTP-сервис ищет, таймер индексирует. sqlite-vec держит векторы в shadow-таблицах, и живое read-write соединение сервиса ломало запись индексатора — "could not write to vector blob". Молча: за один боевой прогон так потерялись 63 транскрипта, а проверить это можно было только сравнив счётчики. WAL не помогает, потому что проблема не в блокировках, а в том, что писателей оказалось двое. Сервис теперь открывает базу read-only: один писатель, много читателей. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Members' transcripts travel to the team's server, get vectorized there, and land in a single index: anyone can search the whole team's history, or ask a question in plain words.
The hub is just session-recall whose transcripts arrive over the network. The server stores them in the same directory layout as a laptop and runs the stock
index_corpus— no server-side extractor, no second search path.What's in it
hub/auth— one key per member, only the hash on disk, revoke by namehub/storage— layout plus the security boundary (traversal, symlinks), accepted bytes tracked separately from file sizehub/masking— masks Doppler values by variable name, auto-redaction limited to an explicit format allowlisthub/client— resumable push, one unreadable transcript never aborts the runhub/ask— plain-language question answered over the team indexsync— the one command the SessionStart hook calls; means "keep the local index fresh" on a solo install and "push to the hub" once joined, so the hook never has to know the modeFixes found on the first real corpus
\nin JSON passed through untouched;hub remaskapplies the current map to already-stored filesShips with a decision doc and a Russian team guide. 463 tests green.
🤖 Generated with Claude Code