docs(secretary): спека-хендофф устойчивости к обрывам (источник = транскрипт)
Дизайн-направление + хендофф для следующей сессии: секретарь теряет промпт при обрыве (API/ручной стоп/краш), т.к. захват завязан на Stop. Источник правды — транскрипт (переживает всё); детект обрыва — по машинным меткам (isApiErrorMessage, [Request interrupted by user]), не по словам. §0 — что читать; §6 — открытые вопросы под брейншторм. Связанные дела: дословное логирование наставника/судьи/ роутера; слабость deepseek-flash на «Решениях». Доказательства в спеке (строки транскрипта ff1d4618). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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/<proj>/<session>.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` на модель поумнее — решение владельца.
|
||||
Reference in New Issue
Block a user