--- title: Session Memory 6 Jul 26 date: 2026-07-06 status: active --- ## Summary Sprint_1_2 clinical chat saw major LLM UX, streaming, and inference work (Jul 5–6). The highest-risk open issue is **frontend markdown rendering of generated assistant text** β€” it has caused tab freezes and catastrophic crashes during reasoning streams. Reasoning paths now use **plain text only** (`StreamingPlainText`) as a mitigation. **Beta functionality** (Planning mode + πŸ”₯ high reasoning level) must be completed **by end of this week** (target: **Friday 10 Jul 2026**). --- ## Change log (what was updated) ### Clinical chat UI & model lifecycle | Area | Change | Key files | |------|--------|-----------| | LLM loading bubble | Install vs load phases with distinct copy, progress bar, disabled composer | `ClinicalChatPanel.tsx`, `useClinicalChat.ts`, `modelLoadProgress.ts` | | Install vs load semantics | First OPFS download (~1.9 GB) vs cached checkpoint init into worker/GPU | `useClinicalChat.ts` (`ModelLoadPhase: 'installing' \| 'loading'`) | | Sidebar card switch | Both diagnosis + review layers stay mounted (CSS hide/show) so Gemma is not torn down on carousel switch | `SidebarLayerCarousel.tsx` | | OPFS persistence | Completed install survives reload; interrupted download not resumable (manifest only written on success) | `opfsModelStore.ts` | | Worker init progress | `init_progress` events wired through `LlmWorkerClient.init(onProgress)` | `llmWorkerClient.ts`, `llm.worker.ts` | ### Inference modes, reasoning levels & backends | Area | Change | Key files | |------|--------|-----------| | Unified chat modes | `ask` merged into `chat`; inference modes: **Chat**, **Planning** (beta), **Agent** | `clinicalChatModes.ts`, `analyzePromptComplexity.ts` | | Reasoning levels | 🧘 chill / πŸ€” moderate / πŸ”₯ high (beta); bar visibility persisted | `chatReasoningLevel.ts`, `ClinicalChatPanel.tsx` | | Edge vs server toggle | πŸ€” moderate: **MΓ‘y** (Gemma 4 E2B edge) vs **Server** (Gemma 4 E4B Modal Ollama `think:true`) | `reasoningModelBackend.ts`, `ollamaLlmClient.ts`, `inferenceBackend.ts` | | Ollama dev proxy | `VITE_OLLAMA_CHAT_URL=/api/ollama-chat/api/chat`, model `gemma4:e4b` | `.env.development`, `vite.config.ts` | | Agent mode | Uses Modal Ollama E4B when configured; tool loop unchanged | `clinicalChatModes.ts`, `runClinicalChatTurn.ts` | | OOM mitigation | Bootstrap at 2048 tokens; `releaseInference()` before reload; reuse engine when `configured.maxTokens >= required` | `clinicalChatConfig.ts`, `llm.worker.ts`, `llmModelBootstrap.ts` | | Qwen3 experiment | Dual-model (Qwen `.litertlm` for moderate) attempted then **disabled** β€” `usesQwenReasoningLevel()` returns `false`; moderate uses Gemma CoT again | `qwenOpfsModelStore.ts`, `reasoningLlmClient.ts`, `chatReasoningLevel.ts` | ### Streaming, CoT split & markdown | Area | Change | Key files | |------|--------|-----------| | CoT split | `splitGemmaThoughtOutput`, `isThoughtChannelComplete`; thought vs answer channels in message state | `prompts.ts`, `clinicalChat.ts`, `useClinicalChat.ts` | | Collapsible reasoning | `ClinicalChatThought` β€” expand while streaming, auto-collapse when thought completes | `ClinicalChatThought.tsx` | | Stream throttle | RAF-coalesced updates (~60/s) to reduce main-thread pressure | `streamUpdateThrottle.ts` | | Token-by-token Ollama | Imperative DOM via `clinicalChatStreamRegistry` + `StreamingPlainText` (bypasses React 18 batching) | `clinicalChatStreamRegistry.ts`, `StreamingPlainText.tsx`, `ollamaLlmClient.ts` | | Markdown renderer | Custom `ChatMarkdown` (bold, italic, code, lists, headings) β€” **deferred until stream ends** via `requestIdleCallback` + `startTransition` | `ChatMarkdown.tsx`, `ClinicalChatMessageBubble.tsx` | | Reasoning = plain text | When `tracksThought`, thought + answer use `StreamingPlainText` only β€” **no `ChatMarkdown`** | `ClinicalChatThought.tsx`, `ClinicalChatMessageBubble.tsx` | ### Agent tools & Modal testing | Area | Change | Key files | |------|--------|-----------| | Tool catalog doc | Walkthrough of Edge-LLM agent tools: `exa_search`, `supabase_query`, `escalate_medgemma` | session + `agent_tools_contract.md` | | 3-layer smoke harness | Layer 0 Modal Ollama, Layer 1 BFF routes, Layer 2 browser `ToolExecutor` | `ml/tests/agent_tools/` | | Python reference tests | Modal `/api/chat` streaming + thinking cases | `PILOT_PROJECT/tmp/test_endpoint.py`, `test_endpoint_img.py` | | Gemma4 E4B deploy script | Modal Ollama serverless for Gemma 4 E4B | `PILOT_PROJECT/tmp/GemmaE4B_ollama_deploy.py` | | gemma4_e2b lab | Tool smoke panel (no Gemma) for isolated tool calls | `ml/tests/gemma4_e2b/src/lib/toolSmoke.ts` | --- ## Critical issue: markdown rendering β†’ catastrophic crash ### Symptom Rendering **LLM-generated markdown** in the clinical chat UI (especially during or immediately after **reasoning / CoT streams**) has caused: - Main-thread freezes (composer unresponsive while tokens still arrive) - Tab crashes / OOM under combined **WebGPU model memory + large text buffers + markdown parse** ### Root cause (confirmed in debugging) 1. **Per-token full re-parse** β€” early implementation ran `ChatMarkdown` / `renderBlocks()` on the entire growing thought string every token β†’ hundreds of parses per second. 2. **React re-render storm** β€” `setMessages` on every token re-rendered the full message list. 3. **Post-stream markdown** β€” even with β€œparse after stream ends”, heavy `renderBlocks()` on long CoT + answer text still spikes CPU/memory. 4. **Dual large strings** β€” `rawAccumulator`, `thoughtContent`, and `content` can all hold 2048-token traces simultaneously at πŸ€” moderate. ### Mitigations applied (6 Jul) - Stream updates throttled (`createStreamUpdateThrottle`) - Plain text while `streaming === true` - `React.memo` on `ClinicalChatMessageBubble` - Reasoning paths (`tracksThought`) β†’ **`StreamingPlainText` only, markdown disabled** - Ollama path β†’ imperative DOM updates per token - `ChatMarkdown` uses idle callback + `startTransition` for post-stream formatting (chill / non-reasoning answers only) ### Current policy > **Be wary of re-enabling markdown in reasoning mode until a safe renderer is proven.** `ClinicalChatThought.tsx` comment: *"Reasoning panel β€” plain text only (markdown disabled while isolating stream crashes)."* ### Follow-up for later sprints - [ ] Reproduce crash with a minimal `ChatMarkdown` + long fixture (no LLM) β€” isolate `renderBlocks` vs React - [ ] Consider `react-markdown` with strict plugins OR server-side pre-render for final answer only - [ ] Cap thought trace length in UI (truncate + β€œshow more”) - [ ] Virtualize message list for long sessions - [ ] Re-test πŸ”₯ high mode and Planning mode with any new markdown path before removing plain-text guard - [ ] Audit whether chill-mode `ChatMarkdown` after stream is safe on 8 GB RAM devices --- ## Beta functionality β€” deadline end of week **Target: complete by Friday 10 Jul 2026** (end of sprint week). Features still marked `beta: true` (UI tab/button disabled until ready): | Feature | ID | Location | What β€œdone” means | |---------|-----|----------|-------------------| | **Planning mode** | `planning` | `clinicalChatModes.ts` | Checklist output stable; remove `beta` flag; selectable in mode tabs | | **High reasoning** | `high` πŸ”₯ | `chatReasoningLevel.ts` | 2048-token multi-turn works without crash; remove `beta` flag | Related work not yet beta-flagged but in scope: - Agent tool integration (Layer 3 after smoke tests pass) - Live BFF tools (`VITE_CLINICAL_CHAT_MOCK_TOOLS=false` + credentials) - Edge/server reasoning toggle polish (default server E4B when Modal up) --- ## Architecture snapshot (clinical LLM, 6 Jul EOD) ``` User message β”‚ β”œβ”€ Mode: chat ─┬─ 🧘 chill β†’ Gemma E2B edge (no CoT) β”‚ β”œβ”€ πŸ€” moderate β†’ MΓ‘y: Gemma E2B + CoT (plain text) β”‚ β”‚ Server: Gemma E4B Ollama think:true (plain text) β”‚ └─ πŸ”₯ high (BETA) β†’ disabled in UI β”‚ β”œβ”€ Mode: planning (BETA) β†’ disabled in UI β”‚ └─ Mode: agent β†’ Gemma E4B Ollama + tools (exa, supabase, medgemma) Display: tracksThought β†’ StreamingPlainText (thought panel + answer) else β†’ StreamingPlainText while streaming β†’ ChatMarkdown when done ``` --- ## Key env / endpoints (dev) ```env VITE_CLINICAL_CHAT_USE_LLM=true VITE_OLLAMA_CHAT_URL=/api/ollama-chat/api/chat VITE_OLLAMA_MODEL=gemma4:e4b VITE_CLINICAL_CHAT_MOCK_TOOLS=true # flip false for live agent tools ``` Modal Gemma E4B (reference): `PILOT_PROJECT/tmp/test_endpoint.py` β†’ `dtj-tran--ollama-gemma4-e4b-ollamaserver-web.modal.run` --- ## Session references - Prior frontend memory: `session_memory/27_jun_26/27_jun_26_frontend.md` - Agent tools smoke README: `CODEBASE/ml/tests/agent_tools/README.md` - Today's active dev: `npm run dev` on frontend implementation