# Два бота, которые делают музей: Archivessa и Chartessa

**Короткий ответ.** Да, я это соберу. И да — Chartessa-бот имеет смысл: он забирает у меня ровно ту половину работы,
которую я делаю хуже всего, а именно *интерфейс для человека*. Archivessa производит проверенное знание, Chartessa
делает из него личные инструменты, которыми можно пользоваться руками.

**Статус:** боты созданы 14.10.2026 — профили `archivessa` и `chartessa` (клон моей конфигурации: та же модель, те же скиллы),
у каждого свой SOUL.md с миссией и правилами, свои `assets/avatar.png` и заголовок роли. Аватарки и вид ботов выбраны
пользователем вручную, поэтому в `profile.yaml` стоит `ui_meta.hermes-bots.custom: true` — дефолтная генерация их не перезапишет.

Ниже — концепт, а не мечта: каждый шаг привязан к реальному примитиву Hermes (профили, cron-рутины, kanban, делегирование,
скиллы), а в конце — MVP с точными командами.

## 1. Почему два бота, а не один

| Критерий | Archivessa (контент) | Chartessa (интерфейс и инструменты) |
|---|---|---|
| Ритм | часовой/суточный, автономный | отзывчивый, по запросу человека |
| Тип работы | поиск, проверка, сведение данных | сборка материальных «вещей»: стеков, карточек, кнопок |
| Риск-профиль | самый опасный: выдуманный факт | самый мягкий: плохой интерфейс видно сразу |
| Память | источники, дедупликация, история проверок | предпочтения человека, шаблоны, привычные стеки |
| Отказ | должен молчать, если нечего сказать | должен отвечать всегда, даже частично |

Один бот с двумя характерами начнёт компромиссно делать и то и другое. Два профиля = две памяти, два набора скиллов,
два расписания и возможность работать параллельно. В Hermes каждый бот — это профиль (`hermes profile create archivessa`),
у него своя папка `~/.hermes/profiles/<bot>/`, свои скиллы, своя память, свои сессии и свои рутины (cron-задания,
которые видны как `[bot:<name>] <routine>` в `hermes cron list` и складывают результат прямо в чат этого бота).

## 2. Бот 1 — Archivessa: конвейер доказанных находок

### 2.1 Цикл из шести шагов

| # | Шаг | Что происходит | Примитив Hermes | Стоимость |
|---|---|---|---|---|
| 1 | Скан | забирает RSS/API/страницы из списка наблюдения, нормализует, считает хеши, отбрасывает уже виденное | cron с `script`-пре-раном (детерминированный Python, без модели) | ноль токенов |
| 2 | Триаж | оценивает кандидатов: это потерянная/недоразвитая идея? есть ли первоисточник? ново ли для архива? какой потенциальный «зазор»? | cron-рутина на дешёвой модели, порог отсекает шум | копейки |
| 3 | Проверка | на каждого прошедшего кандидата — карточка на kanban и воркер: читает первоисточники, обязан вернуть дословные цитаты с URL | kanban (durable SQLite) + `delegate_task` пачками (по умолчанию 3 ребёнка) | основная статья расходов |
| 4 | Сведение | один писатель вливает проверенное в `data/<slug>.json`, ставит `gap` и `pitch` (это суждение агента, оно так и помечается) | скилл `lost-tech-museum` | мало |
| 5 | Публикация | `python scripts/build.py` → коммит → push → Pages, затем проверка живой страницы (код 200, размер, отсутствие плейсхолдеров) | скилл `lost-tech-museum` | ноль токенов |
| 6 | Уведомление | сообщение человеку только при материальном изменении: что нового, ссылка, виджет в чат | cron-delivery + паттерн «молчать, если нечего сказать» | ноль |

Причина, по которой шаг 1 вынесен в скрипт: cron жёстко прерывает прогон через три минуты. Всё, что дольше одной
минуты — это уже не cron, а kanban-карточка, которую диспетчер поднимет отдельным воркером и не потеряет при
перезапуске: делегированные дети живут только внутри процесса родителя, kanban — нет.

### 2.2 Состояние конвейера (всё в репозитории, не в голове модели)

| Файл | Что хранит |
|---|---|
| `archivessa/watchlist.json` | источники: тип, URL, расписание, доверие, заметки |
| `archivessa/queue.json` | кандидаты после скана: статус, хеш, счётчики, причина отсева |
| `archivessa/seen.json` | дедупликация: хеши URL и нормализованных заголовков |
| `archivessa/evidence/<id>.json` | дословные цитаты + URL + скрин текста: доказательная база записи |
| `archivessa/runs.log` | журнал прогонов: когда, что нашли, что опубликовали |
| `data/<slug>.json` | единственный источник правды для сайта |

### 2.3 Расписание (рутины каждого бота)

| Рутина | Когда | Что делает |
|---|---|---|
| `[bot:archivessa] scan` | каждые 6 часов | только скрипт: собрать кандидатов, обновить очередь |
| `[bot:archivessa] triage` | ежедневно 09:00 | оценить очередь, создать kanban-карточки на проверку |
| `[bot:archivessa] publish` | понедельник 10:00 | собрать и опубликовать то, что прошло проверку, уведомить человека |
| `[bot:archivessa] audit` | первое число месяца | битые ссылки, устаревшие «где это сейчас», здоровье источников |
| `[bot:chartessa] lint` | по воскресеньям | проверить целостность стеков (битые ссылки на карточки, пустые поля) |

### 2.4 Жёсткие правила Archivessa

1. **Нет цитаты — нет записи.** Каждая фактическая фраза проходит проверку дословной цитатой из скачанного текста; не прошла — запись не публикуется.
2. **Провенанс не размывается.** Источник, прочитанный целиком, помечается `r:1` (в интерфейсе ✓), источник уровня поискового индекса — `r:0` (↗).
3. **Архив не удаляет.** Устаревшая находка переписывается с пометкой, а не исчезает.
4. **Ничего из головы модели.** Не нашлось подтверждения — фраза не попадает в дайджест вообще (так уже выпала история про торговое давление США на BTRON).
5. **Суждение помечено суждением.** `gap` и «что внедрить» — вывод агента, и это написано и в интерфейсе, и в футере.
6. **Уважение к источникам.** Не больше одного запроса в секунду на хост, соблюдение robots.txt, никакой перепубликации чужих текстов — только ссылки и короткие цитаты.
7. **Бюджет.** Не больше N новых записей за цикл и не больше M воркеров одновременно: музей должен расти, а не сжигать лимиты.

### 2.5 Откуда берём находки (стартовый список наблюдения)

| Слой | Примеры источников | Что даёт |
|---|---|---|
| Стандарты | RFC Editor, IETF datatracker, W3C/ISO новости, ITU | протоколы и практики, умершие на полпути |
| Патенты | Google Patents, Espacenet, USPTO | идеи, описанные за 20–30 лет до реализации |
| Академия | arXiv (cs.OS, cs.PL, cs.DC), SIGPLAN/SOSP/OOPSLA/ICSE | системы, оставшиеся прототипами |
| Вендоры и музеи | архивные страницы Sun/IBM/Apple, Haiku/9legacy, компьютерные музеи | документы и даты |
| Сообщества | HN, retro-computing, mailing list archives | косвенные следы, требующие первоисточника |
| Каталоги | awesome-списки, Wiki старых проектов | карта охоты, а не доказательство |

## 3. Бот 2 — Chartessa: личные инструменты на базе архива

### 3.1 Метафора HyperCard (1987), перенесённая в LLM

| HyperCard (1987) | Что это теперь |
|---|---|
| карточка (card) | блок данных: заголовок, поля, ссылки, медиа, один вопрос |
| фон (background) | шаблон карточки: сравнение, таймлайн, «до/после», таблица источников |
| кнопка и скрипт | ограниченное действие, которое отправляет промпт с полями карточки как контекстом |
| стек (stack) | набор карточек = готовый инструмент («мой стек по агентным протоколам») |
| домашний стек (home) | витрина стека и переходы между стеками |
| режим browse и author | просмотр vs правка: человек правит словами, бот правит данные |

Ключевой момент: **скриптовый слой больше не пишут на HyperTalk**. Кнопка отправляет промпт, а инструменты,
которые агенту разрешены в этом действии, ограничены списком. Это тот же HyperCard, только действие описывается
словами, а безопасность обеспечивается белым списком инструментов, а не песочницей языка.

### 3.2 Формат стека

```
stacks/<slug>/stack.json     карточки, фоны, связи, действия
stacks/<slug>/index.html     результат сборки, руками не правится
stacks/<slug>/assets/        картинки и прочее (с проверкой лицензии)
```

Поля карточки: `id`, `title`, `background`, `fields` (свободные данные), `links` (двусторонние, как у Xanadu),
`actions` (промпты), `sources` (ссылки). Стили и разметка генерируются из шаблона `templates/stack.html`, как и у дайджестов:
человек и бот правят данные, а не HTML.

### 3.3 Что он умеет практически

1. **Собрать стек по запросу:** «сделай карточки по агентным протоколам 90-х и сравни с MCP» → стек с полями и источниками.
2. **Сравнивать:** карточка-фон «до/после» для пары «что было — что стало».
3. **Действовать:** кнопка на карточке запускает ограниченный промпт («найди современный аналог», «проверь, жив ли проект», «добавь источник»).
4. **Связывать:** двусторонние ссылки между карточками и записями архива (идея Xanadu, которая так и не прижилась в вебе).
5. **Собирать инструмент, а не читать:** стек — это не отчёт, а рабочая вещь, которую можно листать и которой можно пользоваться.

## 4. Как они работают в паре

| Сценарий | Кто ведущий | Канал |
|---|---|---|
| Найден новый кластер находок | Archivessa | публикует дайджест, ставит задачу на витрину |
| Нужна новая витрина/сравнение по архиву | Chartessa | читает `data/*.json`, собирает стек |
| Нужны источники под новый стек | Chartessa → Archivessa | прямое сообщение между ботами (Bot Mode) либо kanban-карточка |
| Человек просит «покажи, что нашлось за неделю» | Archivessa | уведомление с ссылкой и виджетом |
| Человек хочет свой инструмент | Chartessa | стек в репозитории |
| Правка факта в архиве | только Archivessa | единственный писатель `data/*.json` |

Разделение писателей — главная защита от конфликтов: **в `data/` пишет только Archivessa, в `stacks/` — только Chartessa.**
Оба публикуют через один и тот же `scripts/build.py`, поэтому сайт остаётся согласованным.

## 5. MVP: три итерации до работающей системы

**Итерация 1 — фундамент (полдня).**
Боты созданы (см. «Статус» выше); остаётся скриптовая часть:

```
hermes profile create archivessa
hermes profile create chartessa
hermes profile describe archivessa "Производит проверенные дайджесты для музея потерянных идей"
hermes profile describe chartessa "Собирает личные стеки-карточки поверх архива музея"
```

В репозиторий добавить `archivessa/watchlist.json`, `scripts/scan.py` (только stdlib: RSS/Atom, HN Algolia, arXiv API),
`scripts/queue.py` (дедупликация и статусы). Проверка готовности: `python scripts/scan.py` кладёт кандидатов в очередь
и повторный запуск не даёт дублей.

**Итерация 2 — цикл публикации.**
Скилл `lost-tech-museum` скопировать в профиль Archivessa, добавить рутины:

```
hermes cron add "0 */6 * * *" --name "[bot:archivessa] scan"  --script scripts/scan.py --no-agent
hermes cron add "0 9 * * *"   --name "[bot:archivessa] triage" --skill lost-tech-museum,grounded-citations
hermes cron add "0 10 * * 1"  --name "[bot:archivessa] publish"
```

Проверка готовности: ручной прогон триажа создаёт kanban-карточки, прогон публикации обновляет сайт и уведомляет
человека ровно один раз.

**Итерация 3 — Chartessa.**
`templates/stack.html`, скилл `stack-author`, поддержка `stacks/` в `scripts/build.py`, домашний стек-витрина.
Проверка готовности: по одной фразе человека появляется стек с карточками, источниками и работающими кнопками,
и он открывается как обычная страница сайта.

## 6. Риски и что с ними делаем

| Риск | Ответ |
|---|---|
| Выдуманный факт | доказательный шлюз: запись без дословной цитаты не публикуется |
| Раздувание архива мусором | порог триажа + бюджет новых записей за цикл |
| Смерть источника | рутина `audit`: битые ссылки, замена первоисточника, пометка «источник недоступен» |
| Конфликты правок | один писатель на каталог; HTML всегда генерируется из данных |
| Расходы | дешёвая модель на триаж, дорогая — только на сведение и скелет статей; скан бесплатный |
| Юридическое | только ссылки и короткие цитаты, проверка лицензий на медиа, уважение robots.txt |
| «Музей ради музея» | каждый дайджест обязан заканчиваться списком «что внедрить»: архив без питчей — это склад |

## 7. Что считаем успехом

1. За месяц архив вырос не менее чем на 10 проверенных записей, и ни одна не отозвана как выдуманная.
2. Каждая запись имеет минимум один прочитанный целиком источник.
3. Человек получает уведомление только тогда, когда действительно есть что смотреть.
4. Хотя бы один стек Chartessa используется повторно через неделю после создания.
5. Новый дайджест от идеи до живой страницы — без ручных команд в HTML.

## 8. Что нужно решить вам

1. **Имена ботов.** Решено: профили `archivessa` (Хранительница архива) и `chartessa` (Собирательница стеков) уже созданы, аватарки выбраны вами.
2. **Агрессивность.** Сколько новых записей в неделю считаем нормой: спокойно (3–5) или настойчиво (10+)?
3. **Уведомления.** Куда присылать дайджест-алерты: только в этот чат, или ещё в мессенджер (Telegram и т.д.)?

Скажете «поехали» — и я начну с итерации 1: два профиля, скрипт скана и список наблюдения.

## 9. Рутина конвейера: scan → очередь → триаж → публикация

Ниже — фактический порядок от 14.10.2026, проверенный запуском (не проект, а то, что реально работает).
Сканер — `hubd` (`/home/lain/.hermes/profiles/libraressa/workspace/hubd`), музей читается только на чтение.

**1. Список наблюдения собирается из архива, а не пишется руками.**

```bash
cd /home/lain/.hermes/profiles/libraressa/workspace/hubd
python3 -m hubd watchlist --museum ../lost-tech-museum --out watchlist.json
# источников: 47 (уникальных адресов: 47) → watchlist.json
```

В список попадают статусы и ленты изменений RFC, на которые ссылается архив, первоисточники записей
и сам сайт музея. Хосты вне «архивного» списка (вики, новости, копии докладов с учебных кафедр)
пропускаются — поэтому мёртвые ссылки из записей сюда не попадают (см. п. 7).

**2. Обход и очередь кандидатов.**

```bash
python3 -m hubd serve --store data/hub.json --port 8765 --retry-interval 5   # отдельным терминалом
python3 -m hubd scan --watchlist watchlist.json --state data/scan.json \
    --queue data/candidates.jsonl --hub http://127.0.0.1:8765 --once        # или --every 300
python3 -m hubd candidates --file data/candidates.jsonl --status new --show-sample
```

Кандидат — это (тема, адрес, отпечаток содержимого). Первый обход источника — знакомство, а не новость:
событие появляется только когда отпечаток реально изменился. Возврат источника к уже разобранной
версии публикуется подписчикам, но дубля в очереди не создаёт.

**3. Триаж — работа хранителя, не скрипта.** Для каждого кандидата: прочитать первоисточник, выписать
дословные цитаты с URL и решить — это правка записи, дубликат или отказ.

```bash
python3 -m hubd candidates --file data/candidates.jsonl --accept <ключ кандидата> --reason "«…» подтверждено цитатой"
python3 -m hubd candidates --file data/candidates.jsonl --reject <ключ кандидата> --reason "наша же публикация"
```

Оговорка, проверенная запуском (состояние на 14.10.2026): в первой версии CLI опции `--reason` не было —
теперь она есть (hubd `adfc8d9`): `--accept/--reject <ключ> --reason "…"`
возвращает код 0 и кладёт причину в поле `note` строки очереди вместе с отметкой времени.
Одиночный `--reason` без решения по-прежнему отвергается кодом 2 — причина обязана объяснять решение.
Причина отказа также ведётся в журнале `docs/triage-log.md`.
Отказ не удаляет кандидата: статус `rejected` + причина — принцип «архив не удаляет» действует и здесь.

**4. Правка архива.** `data/*.json` — единственный писатель Archivessa; у каждого источника, на который
опирается утверждение, поле `q` с дословной цитатой. Если источник заменён — в тексте записи остаётся
пометка, что источник менялся.

**5. Проверки перед публикацией (все три обязаны быть зелёными).**

```bash
python3 scripts/quotes.py --fetch      # каждая цитата против скачанного первоисточника; код 1 — публиковать нельзя
python3 scripts/build.py               # сборка сайта + проверка страниц
python3 scripts/lint_stacks.py         # целостность стеков
```

**6. Публикация.**

```bash
git add -A && git commit -m "digest: …" && git push     # Pages обновляется сам
```

**7. Сторож и замкнутый контур.** Среди 47 источников есть сам сайт музея. После публикации его ETag
меняется, сканер кладёт кандидата `museum/site`, и триаж отклоняет его с причиной «наша же публикация,
находки нет». Это не шум, а подтверждение, что робот действительно видит наш сайт.

**Чего рутина не делает (и что заменяет пробел).** Сканер ловит только *изменения* источников. Утверждение
без цитаты и мёртвая ссылка изменениями не являются — их ловит аудит записей: обход адресов из `data/*.json`
(включая те, что не попали в список наблюдения) с `--fail-on-error`, чтобы cron получал код 1.

```bash
python3 -m hubd check --watchlist watchlist-audit.json --state data/scan-audit.json --fail-on-error
```

**Cron не заведён.** Расписание из §2.3 остаётся проектом: постоянные рутины не создаются без отдельного
подтверждения владельца.
