Skip to content

feat: team hub — one shared history index on your own server - #55

Merged
AbsoluteMode merged 7 commits into
mainfrom
claude/session-recall-server-6171d6
Aug 6, 2026
Merged

feat: team hub — one shared history index on your own server#55
AbsoluteMode merged 7 commits into
mainfrom
claude/session-recall-server-6171d6

Conversation

@AbsoluteMode

Copy link
Copy Markdown
Owner

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 name
  • hub/storage — layout plus the security boundary (traversal, symlinks), accepted bytes tracked separately from file size
  • hub/masking — masks Doppler values by variable name, auto-redaction limited to an explicit format allowlist
  • hub/client — resumable push, one unreadable transcript never aborts the run
  • hub/ask — plain-language question answered over the team index
  • sync — 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 mode

Fixes found on the first real corpus

  • masking by length/entropy shredded configuration (851 model-name mentions, 2429 hostnames) — now masked by variable name
  • a secret right after \n in JSON passed through untouched; hub remask applies the current map to already-stored files
  • service and indexer fought over the DB (sqlite-vec shadow tables) — the service now opens it read-only

Ships with a decision doc and a Russian team guide. 463 tests green.

🤖 Generated with Claude Code

Izzyleet and others added 7 commits August 6, 2026 00:31
Транскрипты участников уезжают на сервер команды, там векторизуются и
складываются в один индекс; любой участник ищет по всей истории и может
задать вопрос обычными словами.

Ключевое: хаб — это обычный 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>
@AbsoluteMode
AbsoluteMode merged commit 148fc82 into main Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants