Skip to content

feat(phase-d): conversational coaching layer — D1–D15 - #5

Open
simlirette wants to merge 17 commits into
mainfrom
feat/phase-d-d1-coordinator
Open

feat(phase-d): conversational coaching layer — D1–D15#5
simlirette wants to merge 17 commits into
mainfrom
feat/phase-d-d1-coordinator

Conversation

@simlirette

@simlirette simlirette commented Apr 26, 2026

Copy link
Copy Markdown
Owner

What changed

Phase D adds a full conversational coaching layer on top of the frozen V1 backend. 17 commits across 15 sessions deliver: intent classification (Haiku 4.5 gateway), a chat-turn graph with Head Coach direct + specialist chain routing (up to 3 specialists), clinical escalation, onboarding Q&A (6 HITL blocs), followup transition, recovery takeover overlay, a proactive monitoring service, frontend chat UI + onboarding coach flow, an E2E test suite, and an offline eval dataset for `classify_intent`.

Why

Phase D is the first user-facing conversational surface of Resilio+. It enables athletes to interact with their Head Coach via chat (free messages, injury reports, session logging), and guides new athletes through a structured 6-bloc onboarding. All changes are additive — zero V1-FROZEN backend files were modified.

Deliverables

Session Scope
D1 `CoordinatorService` skeleton + plan_generation state extension
D2 Pydantic schemas for chat layer (intent, clinical, head_coach_view)
D3 `classify_intent` service — Haiku 4.5 gateway, XML parser, observability
D4 `chat_turn` graph — `HEAD_COACH_DIRECT` + `SPECIALIST_TECHNICAL` + `handle_session_log`
D5 Specialist chain (max 3) + `CLINICAL_ESCALATION_IMMEDIATE` + `CLARIFICATION_NEEDED` + `CrossDisciplineInterferenceService`
D6 `run_injury_report()` — Recovery consulted, `escalate_to_takeover` detection (DEP-C3-001)
D7 Onboarding graph blocs 1–3 + HITL interrupts + `POST /onboarding/start
D8 Onboarding blocs 4–6 + handoff to `baseline_pending_confirmation` + mid-injury suspend/resume (DEP-C3-003)
D9 `followup_transition` graph — 2-step HITL, `adjust_objective` → `onboarding_reentry_active`
D10 `recovery_takeover` graph — overlay activation, 3-step flow, phase routing (DEP-C3-002)
D11 `MonitoringService` — HRV trend, energy patterns, proactive cap (≤2/week), baseline exit trigger; zero LLM calls
D12 E2E integration tests — 6 Phase D flows + 3 clinical escalation flows
D13 Frontend: `ChatBubble`, `TappableOptions`, `/chat` page, `createChatClient()` (7 Vitest tests)
D14 Frontend: `/onboarding/coach` 6-bloc stepper + resume detection, `createOnboardingClient()` (5 Vitest tests)
D15 `data/classify_intent_eval.jsonl` (24 cases) + `scripts/eval_classify_intent.py` + `docs/backend/PHASE-D-AUDIT.md`

How to verify

```bash

Backend unit tests

poetry run pytest tests/backend/ -q --tb=short

Phase D E2E flows (mocked LLM)

poetry run pytest tests/e2e/test_phase_d_flows.py tests/e2e/test_phase_d_clinical.py -v

Frontend tests + typecheck

pnpm --filter @resilio/web test --run
pnpm --filter @resilio/web typecheck

classify_intent offline eval (requires ANTHROPIC_API_KEY)

python scripts/eval_classify_intent.py --verbose
```

Notable decisions

  • In-memory thread state: Onboarding, followup, and recovery takeover use module-level `_thread_states` dicts (MemorySaver-equivalent). Sufficient for single-process dev; production path is `SqliteSaver` using the same thread_id → state contract.
  • `object.setattr()` for dynamic attributes: Fields not in SQLAlchemy mapped columns (`previous_journey_phase`, `suspended_onboarding_block`) are set via `object.setattr()` to satisfy mypy --strict without requiring a new DB migration.
  • Zero LLM calls in MonitoringService: All monitoring checks are pure flag evaluation (HRV strictly-decreasing test, `detect_energy_patterns()` wrapper). LLM interpretation happens on the next `CHAT_FREE_MESSAGE` only.
  • Dual onboarding pages: Existing `/onboarding` handles account creation (static form). New `/onboarding/coach` handles the conversational 6-bloc Q&A — called after login, guarded by `ProtectedRoute`.
  • Eval dataset not CI-gated: `eval_classify_intent.py` requires a live Anthropic API key and targets 75% accuracy; run manually before prompt changes to `docs/prompts/classify-intent.md`.

Audit reference

Full D1–D15 deliverable matrix with architecture decisions and known limitations: `docs/backend/PHASE-D-AUDIT.md`


🤖 Generated with Claude Code

Simon and others added 17 commits April 26, 2026 12:29
- AthleteCoachingState: generation_mode + active_plan (NotRequired fields, D1)
- AthleteModel: journey_phase, recovery/onboarding overlays, 3 thread_id columns
- Alembic 0011: 6 new columns on athletes table (journey_phase, overlays, thread IDs)
- CoordinatorService: dispatch() routing matrix (A2 §Matrice de routage), thread lifecycle
  (create/resume per PERSISTENT_THREAD_FIELDS), advance_journey_phase() with transition
  validation (A2 §Transitions valides)
- POST /coordinator/dispatch + GET /coordinator/state/{athlete_id} routes
- main.py: coordinator_router registered
- TDD: test_coordinator_service.py (routing matrix, thread lifecycle, transitions)
         test_coordinator.py (endpoint smoke tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-006, DEP-C9-008, DEP-C10-003)

- ClinicalContextFlag: Literal tca/red_s/ots_nfor type alias
- IntentClassification + IntentClassificationRequest (DEP-C10-003): intent routing contracts with specialist_chain max-3 validator, confidence bounds, language detection, clinical acknowledgement flag
- Recommendation + RecommendationMode (DEP-C4-006): B3 validators REC1 (INTERPRETATION mode field constraints) and REC2 (trigger↔mode mapping); adds INTERPRETATION mode for conditional chat consultations
- EnergyCoachView (DEP-C9-008 schema): aggregated energy + load view for Energy Coach — discipline loads, allostatic score, intensity cap, recent check-ins, pattern flags
- CrossDisciplineLoadV1 + V2 + DisciplineLoadDetail (DEP-C4-004): V1 session counts (backwards-compatible), V2 adds z-score, leg-impact index, session-day flags
- 49 new tests (all green), 0 mypy errors, 0 ruff violations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- classify_intent_service: gateway LLM (Haiku 4.5) with XML <contract_payload> parser,
  800ms timeout, 1 retry on APITimeoutError, track_agent_call("classify_intent")
- classify_intent_builder: build_classify_intent_request from AthleteModel — extracts
  athlete_id, journey_phase, sports (from JSON), clinical_flag, conversation context;
  truncates last_3_intents to 3
- System prompt loaded from docs/prompts/classify-intent.md with inline fallback
- 20 new tests (all green), 0 mypy errors, 0 ruff violations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…+ handle_session_log

- chat_turn.py: ephemeral conversational graph; run_chat_turn() orchestrates
  classify_intent → HeadCoach direct (1 LLM call) or specialist+synthesis (2 calls)
- handle_session_log (DEP-C4-001): Lifting consulted when RPE delta ≥ 1.5 or
  recent_elevated_rpe_count ≥ 2; else HEAD_COACH_DIRECT
- head_coach_view_builder: build_head_coach_view() from AthleteModel
- HeadCoachView schema (chat context snapshot, expandable in D6)
- ChatMessageModel + Alembic 0012: chat_messages table for history persistence
- POST /chat/message + GET /chat/history/{athlete_id} routes (ownership-gated)
- 13 new tests (all green), 0 mypy errors, 0 ruff violations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssDisciplineInterferenceService

- chat_turn: chain ≤3 specialists with prior_chain_specialist_notes (§10.1.2),
  CLINICAL_ESCALATION_IMMEDIATE (0 LLM, hardcoded resources), OUT_OF_SCOPE
  (HC bounded), CLARIFICATION_NEEDED (HC intro + tappable axes in result),
  clinical_context_active_acknowledged → flag injected in specialist payload (§10.1.4)
- CrossDisciplineInterferenceService (DEP-C4-004): compute_cross_discipline_load_v1
  counts sessions by discipline from active_plan dict; cycling/biking aliased
- 24 tests (all green), 0 mypy errors, 0 ruff violations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-001, DEP-C9-008)

- RecoveryCoachView schema + build_recovery_view() (mean_vs_prescribed_delta_7d, rpe_avg)
- EnergyCoachView builder build_energy_view() from EnergySnapshotModel (D2 schema)
- run_injury_report() in chat_turn: Recovery consulted, escalate_to_takeover detection,
  ValueError on missing injury_payload_draft (RA7)
- 9 tests: 5 view builder, 4 injury report

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- onboarding.py: run_onboarding_start() + run_onboarding_respond() with
  in-memory thread state, block progression, resume-after-abandon support
- Thread ID format: {athlete_id}:onboarding:{uuid4}, stored on AthleteModel
- Blocs 1-3: accueil, profil de base, objectif/horizon — HITL interrupt after each
- POST /onboarding/start + POST /onboarding/respond routes
- 12 tests: 8 graph (start, respond, resume), 4 API smoke tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-C3-003)

- Blocs 4-6: sport_history, methodology_preferences (DEP-C4-002), scope_confirmation
- Bloc 6 completion triggers journey_phase=baseline_pending_confirmation (handoff)
- suspend_onboarding_for_injury(): stores suspended_onboarding_block on athlete
- resume_onboarding_after_recovery(): resumes at suspended block post-takeover;
  returns None if no suspended block (non-onboarding injury path)
- Updated D7 test: bloc 3 response leaves 3 more blocks (in_progress, not completed)
- 7 D8 tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- run_followup_start(): creates thread, returns present_baseline step
- run_followup_respond(): 2-step HITL flow — feedback → confirm_first_plan → steady_state
- Objective adjustment path: onboarding_reentry_active=True flag returned
- POST /followup/start + POST /followup/respond routes
- 6 tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- run_recovery_takeover_start(): activates overlay, stores previous_journey_phase,
  suspends active plan, creates recovery thread at assess_injury step
- run_recovery_takeover_respond(): 3-step flow: assess_injury → monitor_recovery
  (loop) → evaluate_and_return (DEP-C3-002 fused node) → handoff
- Handoff: non-onboarding → baseline_pending_confirmation;
  onboarding → returns previous_journey_phase for CoordinatorService routing
- 7 tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- check_hrv_trend(): sets recovery_flag after 3 consecutive degraded days
- check_energy_patterns(): wraps detect_energy_patterns(), sets flags, 0 LLM
- check_proactive_message_allowed(): enforces ≤2/week cap
- check_baseline_exit_conditions(): triggers followup_transition at required weeks
- run_daily_checks(): APScheduler daily entry point
- 10 tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_phase_d_flows.py: 6 flows (HC direct, specialist chain, injury→takeover,
  onboarding 6 blocs, injury mid-onboarding suspend/resume, followup transition)
- test_phase_d_clinical.py: 3 clinical flows (tca escalation, self_harm, clinical
  flag injected into specialist chain)
- All LLM calls mocked; no real Anthropic API required
- 9 tests passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… api-client chat module

- packages/api-client/src/chat.ts: createChatClient() with sendMessage + getHistory
- packages/ui-web: ChatBubble (role/content/specialists/timestamp) + TappableOptions (disappears after selection)
- apps/web/src/app/chat/page.tsx: full chat page with ProtectedRoute, typing indicator, clarification axes
- 7 Vitest tests (ChatBubble ×4, TappableOptions ×3) — 33/33 passing
- Fixed pre-existing ThemeProvider.tsx unused @ts-expect-error directive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onboarding api-client

- packages/api-client/src/onboarding.ts: createOnboardingClient() with start() + respond()
- apps/web/src/app/onboarding/coach/page.tsx: 6-bloc stepper, resume detection, chat-style Q&A, completion → dashboard redirect
- 5 Vitest tests for createOnboardingClient — 38/38 passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- data/classify_intent_eval.jsonl: 24 eval cases (HEAD_COACH_DIRECT, SPECIALIST_TECHNICAL, CLINICAL, OUT_OF_SCOPE, CLARIFICATION_NEEDED, EN/mixed)
- scripts/eval_classify_intent.py: offline eval runner, 75% pass threshold, per-case PASS/FAIL + accuracy report
- docs/backend/PHASE-D-AUDIT.md: full D1-D15 deliverable audit, architecture decisions, known limitations
- .gitignore: allowlist eval dataset + eval script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@simlirette simlirette changed the title feat(D1): CoordinatorService skeleton + plan_generation state extension feat(phase-d): conversational coaching layer — D1–D15 Apr 26, 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.

1 participant