Files
obsidian-vault/personal/projects/psychologist-app/session-design.md
T

288 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Session Design — AI Psychologist App
aliases:
- Дизайн сессии
- Session design
- архитектура сессий
created: '2026-05-19'
updated: '2026-06-02'
tags:
- project
- app
- psychology
- ux
- architecture
related:
- '[[personal/projects/psychologist-app/overview]]'
- '[[personal/projects/psychologist-app/character-design]]'
- '[[personal/projects/psychologist-app/onboarding-ux]]'
---
# Session Design — AI Psychologist App
> Детальный дизайн сессии: UX-флоу, механика вопросов, открытие/закрытие.
> Связан с: [[overview]] (раздел 5-6 — базовая структура и архитектура ботов)
---
## 1. Механика вопросов (Question Delivery)
### Pipeline вопросов
```
Бот 2 (Analyst / "Психолог")
→ генерирует 1-2 вопроса в 3-м лице ("пользователь избегает...")
Бот 1 (Narrator / "Посредник")
→ переформулирует в 1-е лицо ("ты избегаешь?")
→ на выходе: структурированный массив вопросов для UI
UI алгоритм (не агент!)
→ показывает вопросы по одному в карточках
→ ждёт ответа → следующий
```
**Ключевой принцип**: раздача вопросов — детерминированный алгоритм, не LLM-вызов.
Агент генерирует батч вопросов один раз → посредник переформулирует → UI сам управляет порядком.
**Narrator детектирует 3-е лицо**: через prompt rule ("переформулируй в 1-е лицо") — регекс не нужен.
### Завершение батча вопросов → подбивка
Когда все вопросы в батче отвечены, UI передаёт агенту нумерованную подбивку ответов.
Это входные данные для следующей итерации (следующий батч или инсайт).
---
## 2. Открытие сессии
### Структура intro
1. **Персонаж-активация** (анимация, 1-2 сек)
2. **Короткое введение** — 1-2 предложения, сразу к делу, без "Давай начнём с..."
3. **"Крючки-активаторы"** — max 2 из profile.md / последних сессий. Если нет истории — пропустить.
4. **Переход к теме** — предложение агента или выбор пользователя
### Что получает Analyst при старте
- `profile.md` — паттерны пользователя
- последние 2 сессии из `observations/`
- задача: intro + max 2 крючка
---
## 3. Закрытие сессии
### Последовательность (3 этапа)
#### Этап 1 — Structured session close (backend, не видно пользователю)
Analyst генерирует `SessionCloseResult`:
- `insight` — ключевое наблюдение (1-2 предложения, 3-е лицо)
- `sessionSummary` — аналитическое резюме всей сессии (не первые реплики)
- `profileUpdates` — новые паттерны для добавления в `profile.md`
- `promptUpdates` — обновления для `prompts.md` (если применимо)
Приложение раскладывает результат по файлам через `ProfileManager`.
> **Статус**: частично реализован. `insight` генерируется (Sonnet + tools + полный диалог).
> `sessionSummary`, `profileUpdates`, `promptUpdates` — НЕ реализованы (см. §12.2.G).
#### Этап 2 — Посредник: человечный итог (видно пользователю)
- Тёплая живая фраза, без психолого-языка
- Не повторять аналитика, не хвалить избыточно
- Упомянуть дату следующей сессии если есть расписание
#### Этап 3 — "One last thing" + завершение
- Открытый вопрос: "Хочешь что-то добавить?"
- Маленький текстовый input, неакцентная кнопка "Завершить"
- Если написал → агент добавляет в заметки сессии, отвечает коротко (не перезапускает сессию)
- Если упомянул перенос → сценарий планировщика (EventKit)
---
## 4. Флоу сессии целиком
```
ОТКРЫТИЕ
├── Анимация персонажа (1-2 сек)
├── Intro: 1-2 предложения + max 2 крючка из истории
└── Выбор темы
ДИАЛОГ
├── Analyst генерирует 1-2 вопроса (3-е лицо)
├── Narrator переформулирует → карточки (1-е лицо)
├── UI раздаёт по одному (one-card UX)
├── Пользователь отвечает → нумерованная подбивка
└── Следующий батч / инсайт
└── session_complete: true → карточка "Завершить / Продолжить"
└── hard limit: 8 ходов → принудительный readyToClose
ЗАКРЫТИЕ
├── [backend] Analyst: insight + sessionSummary + profileUpdates
├── Посредник: тёплая фраза + дата следующей сессии
├── "One last thing"
│ ├── Написал что-то → в заметки, короткий ответ
│ ├── Упомянул перенос → сценарий планировщика
│ └── Завершить (кнопка) → сессия закрыта
```
---
## 5. State Persistence & Session Recovery
### Текущая реализация (актуально)
`resumeOrStart()` при открытии SessionView:
- Проверяет незавершённую сессию (`detectIncompleteSession()`)
- `.resumed` → восстанавливает карточки из `session.messages` (видимые `.analyst`)
- `.startedAfterPartialSummary` → была стale сессия >20h → показывает partial summary, затем чистый старт
- `.started` → обычный старт
`IncompleteSessionState.isFresh` — сессия считается свежей если < 20 часов.
Stale → `generatePartialSummary()``completed_partial`.
### Кнопка "Начать заново" при resume
При восстановлении сессии (`.resumed`) показывать кнопку **"Начать заново"** рядом с первой карточкой.
- Кнопка исчезает после первого отправленного ответа пользователя
- Нажатие → `cancelSession()` + `startNewSession()` (чистый старт)
- Реализовать как `SessionViewModel.showRestartButton: Bool`, сбрасывается в `send(text:)`
### Что не реализовано (Stage 2)
Retention protocol: при N пропущенных сессиях подряд → реактивационный флоу с push-уведомлением.
---
## 6. Что реализовано сейчас
> Актуально на 2026-06-02, коммит `53c1d2cb`.
### Пейсинг сессии
- `AnalystJSON.sessionComplete: Bool` (backward-compatible)
- `ProcessingResult.readyToClose([String]?, observation:)`
- `SessionManager.userTurnCount`, `consecutiveShortAnswerCount`
- Hard limit: 8 ходов → принудительный `.readyToClose`
- `continueSession()` — сброс флага после "Продолжить"
- Пейсинг-правила и директивы усталости в systemPrompt AnalystBot
### Session Recovery
- `detectIncompleteSession()`, `resumeOrStart()`, `generatePartialSummary()`
- `FatigueContext`, `IncompleteSessionState`, `SessionResumeResult`
- SessionViewModel: восстановление карточек при `.resumed`
### Завершение сессии
- `endSession()``generateSessionInsight()` (Sonnet + tools + полный диалог, без обрезки)
- Агент вызывает `search_sessions`, `read_session`, `read_profile` для сравнения с историей
- `finalInsight``ProfileManager.addInsight()` немедленно
- `suggestedInsight` по ходу диалога → `addInsight()` немедленно (не теряется при X)
### Агент / промпты
- NarratorBot: legit-маркер, injection guard, off-topic директива, детекция усталости/настроения
- AnalystBot: turnCount/duration/messageCount/fatigueCtx в user message; no-filepath директива
### UX
- One-card UX: `CardData.isActive`, `appendCard()`, `activeCard`, `historyCards`
- История — dimmed compact cards (read-only)
- `TextEditor` с placeholder, auto-grow (36→160pt), кнопка dismiss клавиатуры
- `CardData.isCompletion` → quickReply "Завершить" → `endSession()`, "Продолжить" → `continueAfterCompletion()`
### Тесты
- 202 теста, 0 фейлов
- Покрыто: пейсинг, усталость, injection guard, crisis, session recovery, one-card UX, insights, snapshot prompts
---
## 7. Что не реализовано (план)
### 7.1 Structured session close [ПРИОРИТЕТ]
**Что в плане** (§3, §4): Analyst возвращает структурированный объект — insight + sessionSummary + profileUpdates.
**Что в коде**: `generateSessionInsight()` возвращает только одну строку-инсайт.
- `profile.md` после сессии не обновляется новыми паттернами
- `prompts.md` не обновляется
- `sessions/YYYY-MM-DD.md` заполняется через `saveSessionToProfile()` где summary = **первые 3 реплики пользователя**, не аналитическое резюме
**Решение**: `SessionCloseResult { insight: String, sessionSummary: String, profileUpdates: [String], promptUpdates: [String]? }`.
Агент возвращает структуру → приложение раскладывает по файлам.
### 7.2 mood_delta → Orb visual feedback
`AnalystJSON` → добавить `moodDelta: "declining" | "stable" | "improving"`.
UI: declining = холодный синий/медленный пульс, stable = нейтральный, improving = amber/живой.
### 7.3 Планировщик как tool
EventKit-интеграция как tool у Narrator — аналогично `search_sessions` / `read_profile`.
Дизайн-решение отложено.
### 7.4 Retention protocol `[STAGE 2 — not in MVP]`
При N пропущенных сессиях подряд → реактивационный флоу (§5).
Отложено до после релиза MVP. Не входит в текущий спринт.
### 7.5 Android: TextEditor в Skip Fuse
Проверить поддержку `TextEditor` + `fixedSize(horizontal:vertical:)` в Skip Fuse на Android.
При необходимости заменить на `TextField(axis: .vertical)`.
`.scrollContentBackground(.hidden)` уже огорожен `#if os(iOS)`.
---
## 8. Открытые вопросы
- [ ] Планировщик — tool у Narrator (EventKit). Когда и как реализовывать?
- [ ] mood_delta — 3 значений достаточно? (declining/stable/improving)
- [ ] Android: `TextEditor` в Skip Fuse — нужна проверка на устройстве
---
## 9. Протокол агента — действующие правила
### NarratorBot systemPrompt содержит:
- Legit-маркер (защита от injection false positive)
- Injection guard директива
- Off-topic директива (мета-вопросы → вернуть фокус)
- Детекция усталости → передать в metadata для Analyst
- Детекция настроения → `sentimentObservation` в metadata
### AnalystBot systemPrompt содержит:
- Пейсинг-правила (turn_count, 4+ оборотов → оценка exhaustion, 7+ → принудительно)
- Директива при усталости: `session_complete: true`, `questions: []`
- Директива при мета-вопросах: не объяснять архитектуру, вернуть фокус
- No-filepath директива: не упоминать пути файлов
### AnalystBot tools (read-only):
- `search_sessions` — поиск по архиву
- `read_session` — читать конкретную сессию
- `get_index` — индекс всех сессий
- `read_profile` — профиль пользователя
- `list_files` / `read_file` — дополнительные файлы профиля
---
## 10. Хранение данных (on-device)
Всё хранится через `ProfileManager` в `Documents/UserProfile/`:
| Файл | Что содержит | Кто пишет |
|------|-------------|-----------|
| `profile.md` | Паттерны пользователя, наблюдения | `updateProfile()` — пока не вызывается автоматически после сессии |
| `insights.md` | Инсайты по сессиям | `addInsight()` — пишется немедленно |
| `prompts.md` | Промпты/темы пользователя | не обновляется автоматически |
| `observations/Www.md` | Запись сессии за неделю | `appendSession()` |
| `index.md` | Индекс сессий | `updateSessionIndex()` |
**Расписание**: через системный календарь (EventKit), не отдельный файл.
---
## Связанные заметки
- [[personal/projects/psychologist-app/overview]]
- [[personal/projects/psychologist-app/character-design]]
- [[personal/projects/psychologist-app/onboarding-ux]]