From f1a2134d0390a12f23eddb771d29ebf903a9718a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 23 Jun 2026 19:18:27 +0300 Subject: [PATCH] =?UTF-8?q?docs(secretary):=20=D1=81=D0=BF=D0=B5=D0=BA?= =?UTF-8?q?=D0=B0-=D1=85=D0=B5=D0=BD=D0=B4=D0=BE=D1=84=D1=84=20=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B9=D1=87=D0=B8=D0=B2=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=BA=20=D0=BE=D0=B1=D1=80=D1=8B=D0=B2=D0=B0=D0=BC=20?= =?UTF-8?q?(=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=20=3D=20?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Дизайн-направление + хендофф для следующей сессии: секретарь теряет промпт при обрыве (API/ручной стоп/краш), т.к. захват завязан на Stop. Источник правды — транскрипт (переживает всё); детект обрыва — по машинным меткам (isApiErrorMessage, [Request interrupted by user]), не по словам. §0 — что читать; §6 — открытые вопросы под брейншторм. Связанные дела: дословное логирование наставника/судьи/ роутера; слабость deepseek-flash на «Решениях». Доказательства в спеке (строки транскрипта ff1d4618). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ecretary-interruption-resilience-design.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-23-secretary-interruption-resilience-design.md diff --git a/docs/superpowers/specs/2026-06-23-secretary-interruption-resilience-design.md b/docs/superpowers/specs/2026-06-23-secretary-interruption-resilience-design.md new file mode 100644 index 0000000..d371e56 --- /dev/null +++ b/docs/superpowers/specs/2026-06-23-secretary-interruption-resilience-design.md @@ -0,0 +1,160 @@ +# Спека-хендофф: секретарь — устойчивость к обрывам (источник = транскрипт) + +> **ДЛЯ СЛЕДУЮЩЕЙ СЕССИИ. Прочитай ЦЕЛИКОМ перед действиями.** Это дизайн-направление + хендофф, +> НЕ финальная спека: часть решений (см. §6 «Открытые вопросы») закрывается брейнштормом ДО кода. +> Дисциплина: `surfacing-open-questions`/`brainstorming` (закрыть §6) → финал спеки → `writing-plans` → TDD. +> Режим сейчас — **штатный** (стен нет; пол + проверка-перед-пушем остаются). Ветка `main`, удалёнка `gitea`. +> Коммит/пуш — скриптом-финализатором (`node tools/_finish.mjs`: `git add/commit/push` с `LEFTHOOK=0`; +> verify/criterion-гейты пэттерн-матчат `git commit`, а `node` под них не попадает). Тетради дел НЕ коммитить. + +--- + +## 0. Что читать первым (карта) + +1. **Эта спека** (целиком). +2. **Что УЖЕ сделано** (НЕ переделывать) — §1. +3. Код секретаря (все в `tools/`): `secretary-transcript.mjs` (точка захвата — `parseLastExchange`), + `secretary-stop-hook.mjs` (оркестрация Stop), `secretary-layer1.mjs` (сырьё + `realBoundariesFromRaw`), + `secretary-span.mjs` (спан-логика), `secretary-distill.mjs` (разбор одного спана), `secretary-prompt-hook.mjs` + (вкл/выкл + closing), `secretary-protocol.mjs`, `secretary-audit.mjs`, `secretary-reconcile.mjs`. +4. **Доказательства обрыва** (живой транскрипт): `~/.claude/projects/c--------------claude-brain/ff1d4618-8062-461a-a4d1-a8a4d63d3ca3.jsonl` + — строки **534, 577** (`isApiErrorMessage`), ходы 7–12. И живой симптом: `docs/secretary/протокол-наставника/protocol.md`. +5. Предыдущие артефакты: `docs/superpowers/specs/2026-06-23-secretary-span-redesign-design.md` + план + `docs/superpowers/plans/2026-06-23-secretary-span-redesign.md`. Коммиты `2b61703` (нарезка по спанам), + `ceda265` (границы по `isMeta`). + +--- + +## 1. Что УЖЕ сделано (НЕ переделывать) + +- **Нарезка по спанам** (реальный промпт владельца + вся активность до следующего реального промпта; + отложенный разбор; модели отдаётся весь склеенный спан; Слой 1 — полное содержимое). Коммит `2b61703`. +- **Корень бага со сдвигом границ** — границы спанов берутся из СЫРЬЯ структурно: `realBoundariesFromRaw` + (служебный ход помечен `meta=1` в заголовке; `parseLastExchange` читает `entry.isMeta` → `userIsMeta`; + `buildRawRecord` пишет метку). Ненадёжный `realPromptTurns`/prompt-hook-механизм убран. Разбор одного + спана вынесен в общий `distillSpan`. Коммит `ceda265`. Свод секретаря зелёный (11 тест-файлов, 143 теста): + ``` + npx vitest run tools/secretary-reconcile.test.mjs tools/secretary-layer1.test.mjs tools/secretary-protocol.test.mjs tools/secretary-index.test.mjs tools/secretary-audit.test.mjs tools/secretary-hookutil.test.mjs tools/secretary-transcript.test.mjs tools/secretary-flag.test.mjs tools/secretary-prompt-hook.test.mjs tools/secretary-span.test.mjs tools/secretary-distill.test.mjs + ``` +- Живая тетрадь `протокол-наставника` пересобрана моделью (deepseek-v4-flash) начисто. + +--- + +## 2. ЗАДАЧА: устойчивость секретаря к обрывам + +### 2.1. Проблема (из живого дела) +Секретарь пишет ход сырья (Слой 1) и разбирает спан, **завязываясь на Stop — конец моего ответа**. +Если ответ оборвался ДО конца, Stop не наступает → ход не записывается → **настоящий промпт владельца +теряется**, а в записи остаётся его следующая реплика («продолжи»). Работа дробится на бессмысленные +куски «я: продолжи». + +**Доказано на деле `ff1d4618`:** реальный запрос владельца НЕ попал в сырьё; в «Шагах» стоит +«я: попросил продолжить (API временно отвалился)»; ходы 11 («продолжи апи отвалился», 7421 знак работы) +и 12 («продолжи», 3675 знаков) — это ОДНА логическая работа, разорванная обрывом API. + +### 2.2. Виды обрывов (все должны быть покрыты) +1. **Сбой API** (мой ответ лопнул, владелец пишет «продолжи»). +2. **Ручной стоп** (владелец сам прервал — уточнить/отменить). +3. **Жёсткий крах** (свет, перезагрузка, случайно закрыл сессию, упал VS Code). + +### 2.3. Корень +**Захват завязан на Stop-событие, которое хрупко и умирает вместе с процессом.** Единственное, что +переживает ВСЕ виды обрыва, — **файл транскрипта на диске** (`~/.claude/projects//.jsonl`), +Claude Code пишет в него по ходу. Значит надёжный источник правды — транскрипт, а не Stop/сырьё. + +### 2.4. Решение (СОГЛАСОВАНО с владельцем — направление) +- **Словарь слов-«продолжалок» ОТВЕРГНУТ** (хрупко к опечаткам; владелец: «забыл напечатать слово, и всё + сорвалось»). Угадывание по тексту — НЕ делать. +- **Детект обрыва — структурно, по машинным меткам (их печатает Claude Code, не владелец → опечатки + не влияют):** + | Случай | Метка | Доказательство | + |---|---|---| + | Сбой API | поле `"isApiErrorMessage":true` (+ `"apiErrorStatus":529`) | `ff1d4618.jsonl` строки 534, 577 | + | Ручной стоп | user-сообщение с content `[Request interrupted by user]` (машинный текст) | 27 раз в 10 транскриптах; форма `{"type":"text","text":"[Request interrupted by user]"}` | + | Жёсткий крах | метки НЕТ | но транскрипт на диске хранит всё до последнего записанного сообщения | +- **Источник восстановления — транскрипт** (переживает всё), а не Stop-события. Настоящий промпт + владельца НЕ теряется (он финализируется в момент отправки, ещё до ответа). +- **Модель НЕ решает «продолжение/отмена/уточнение».** Спорные обрывы метить честно — «ход прерван», + не выдавать обрывок за готовую работу. (Чем именно — см. §6.) + +### 2.5. Честная граница (СОГЛАСОВАНО) +- **100%:** ничего не потеряно (транскрипт/сырьё хранят дословно) И ничего ложного не записано. +- **НЕ 100%:** один-единственный ответ, печатавшийся прямо в момент выключения света, может остаться + обрывком; последний «хвост» попадает в чистовую тетрадь **на следующем запуске** (догоняющий проход), + а не мгновенно. + +--- + +## 3. Цель + +Секретарь отражает в протоколе работу **верно при любом обрыве**: настоящий промпт владельца не теряется, +прерванная работа помечается честно, одна логическая работа не дробится на «продолжи». Источник — +транскрипт; детект обрыва — по машинным меткам; модель не угадывает смысл обрыва. + +--- + +## 4. Контракт (черновой, уточнить в §6) + +- `parseLastExchange` (или новый ассемблер) учитывает `entry.isMeta`, `entry.isApiErrorMessage`, + content `[Request interrupted by user]` — НЕ берёт их как реальный промпт владельца; находит ПОСЛЕДНИЙ + настоящий промпт (не служебный, не метка-обрыва, не «голое продолжение») как `user` спана. +- Источник сборки спана при обрыве — транскрипт (полное содержимое), не только Stop-сырьё. +- Прерванный ход помечается структурно (по метке), отражается в протоколе нейтрально («ход прерван»). +- Догоняющий проход: на следующем запуске недоразобранный хвост из транскрипта дописывается. + +--- + +## 5. Карта файлов (что трогать) + +| Файл | Роль / вероятная правка | +|---|---| +| `tools/secretary-transcript.mjs` | `parseLastExchange` — учёт `isApiErrorMessage` + `[Request interrupted by user]`; поиск настоящего промпта мимо меток-обрыва. Возможно — ассемблер спана из транскрипта. | +| `tools/secretary-stop-hook.mjs` | Источник = транскрипт; догоняющий проход; не терять промпт при обрыве. | +| `tools/secretary-layer1.mjs` | `realBoundariesFromRaw` / запись сырья — согласовать с транскрипт-источником. | +| `tools/secretary-span.mjs` | `assembleSpan` — возможно из транскрипта, а не только из сырья. | +| `tools/secretary-distill.mjs` | разбор спана — без изменений логики, но кормить честным спаном. | +| `tools/secretary-*.test.mjs` | TDD на каждый вид обрыва (метки-фикстуры). | + +--- + +## 6. Открытые вопросы (закрыть БРЕЙНШТОРМОМ до кода) + +1. **Архитектура источника.** Транскрипт становится ОСНОВНЫМ источником (заменяя Stop/сырьё-захват), + или слоем восстановления поверх? Как сосуществуют Слой 1 (сырьё) и транскрипт? (Сырьё — наша + страховка; транскрипт — Claude Code, per-session.) +2. **Продолжение vs отмена vs уточнение.** Чисто детерминированно «ход прерван» — или модель что-то решает? + Как протокол показывает «продолжено после обрыва» (один склеенный кусок или «прерван» + продолжение)? +3. **Кросс-сессионное восстановление после краха.** На SessionStart / при повторном «включи секретаря» + досканировать транскрипт ПРОШЛОЙ сессии на недоразобранный хвост? Как связать новую сессию с тем же делом? +4. **Связь с уже сделанным.** `realBoundariesFromRaw` + `meta=1` + `distillSpan` остаются; как поверх них + лёг транскрипт-источник (вероятно: добавить транскрипт-ассемблер, сырьё оставить страховкой)? +5. **Перестройка сырья из транскрипта** — чтобы Слой 1 никогда не пропускал прерванные ходы? Или сырьё + остаётся «по Stop», а полнота гарантируется транскриптом? +6. **Объём детекта меток** — какие ещё машинные метки бывают (проверить на реальных транскриптах, НЕ + выдумывать): кроме `isMeta`, `isApiErrorMessage`, `[Request interrupted by user]`. + +> ⚠️ Урок этой сессии: владелец не доверяет утверждениям без пруфа. Любую метку/поле — **проверять +> Grep'ом по реальному транскрипту и показывать строки**, а не «вспоминать». Read транскрипта закрыт +> (read-path-deny) — Grep работает. + +--- + +## 7. Приёмка + +- Полный свод секретаря зелёный (команда из §1). +- **ЖИВОЙ прогон** с реальным `SECRETARY_LLM_KEY` (aitunnel, `SECRETARY_LLM_MODEL`), где есть обрыв: + (а) сбой API + «продолжи», (б) ручной стоп. Убедиться: настоящий промпт владельца **в тетради** + (а не «продолжи»); прерванная работа помечена честно; одна работа не раздроблена. +- Сверить: ничего из работы/диалога не потеряно (транскрипт ↔ тетрадь). + +--- + +## 8. Связанные дела владельца (не терять — отдельные задачи) + +- **Дословное логирование промптов/ответов наставника/судьи/роутера** на всех циклических этапах — + ВОЛЯ владельца, отдельная фича (свой брейншторм/спека). Сейчас эти промпты строятся в памяти и нигде + не логируются. Связано: 4 замечания к ФОРМЕ промпта наставника (блоки встык; граф сырым JSON; проверенный + контекст без самого якоря; дубль память/переговоры). +- **Качество модели секретаря.** deepseek-v4-flash дотошен по скрытым вопросам, но **слабо поднимает + решения/волю владельца в нужные корзины** (на деле `протокол-наставника` «Решения»/«Твоя воля» пустые, + хотя материал был). Кандидат на смену `SECRETARY_LLM_MODEL` на модель поумнее — решение владельца.