# S02: Per-Stage LLM Model Routing + Think-Tag Stripping

**Goal:** Add per-stage LLM model/modality routing with think-tag stripping to the pipeline
**Demo:** After this: Pipeline stages use different models for mechanical (chat) vs reasoning (thinking) tasks; think-tag stripping handles reasoning model output

## Tasks
- [x] **T01: Added per-stage LLM model/modality config fields to Settings and _get_stage_config helper** — 1. Add 8 per-stage fields to Settings: llm_stage{2-5}_model (Optional[str], default None) and llm_stage{2-5}_modality (str, default 'chat')
2. Add _get_stage_config(stage_num) helper to stages.py
3. Update .env.example with new vars and comments
4. Verify Settings loads correctly with and without stage-specific vars
  - Estimate: 10min
  - Files: backend/config.py, backend/pipeline/stages.py, .env.example
  - Verify: cd backend && python3 -c "from config import Settings; s = Settings(); print(s.llm_stage2_model, s.llm_stage2_modality)"
- [x] **T02: LLMClient now supports chat/thinking modality with model override and think-tag stripping** — 1. Add strip_think_tags(text) utility function that removes <think>...</think> blocks
2. Handle multiline, multiple blocks, and no-block passthrough
3. Add modality parameter to LLMClient.complete() with model_override
4. For 'thinking' modality: skip response_format json_object, append JSON instructions to system prompt, strip think tags from response
5. For 'chat' modality: keep current behavior
6. Use model_override if provided, otherwise fall back to settings default
  - Estimate: 15min
  - Files: backend/pipeline/llm_client.py
  - Verify: cd backend && python3 -c "from pipeline.llm_client import strip_think_tags; assert strip_think_tags('<think>reasoning</think>{\"a\": 1}') == '{\"a\": 1}'; print('PASS')"
- [x] **T03: All 4 pipeline stages now use per-stage model/modality config with INFO logging** — 1. Update each stage (2-5) to call _get_stage_config and pass modality + model_override to llm.complete()
2. Add INFO log line at start of each stage showing which model/modality is being used
3. Update _safe_parse_llm_response to pass modality through
  - Estimate: 10min
  - Files: backend/pipeline/stages.py
  - Verify: cd backend && python3 -c "from pipeline.stages import _get_stage_config; m, mod = _get_stage_config(2); print(f'model={m}, modality={mod}')"
- [x] **T04: All 59 tests pass — existing mocks compatible, new think-tag stripping test added** — 1. Update _patch_llm_completions to handle new complete() signature (modality, model_override kwargs)
2. Update test_llm_fallback to account for new parameters
3. Add test_strip_think_tags with cases: single block, multiline, multiple blocks, no blocks, empty string
4. Run full test suite to verify nothing breaks
  - Estimate: 10min
  - Files: backend/tests/test_pipeline.py
  - Verify: cd backend && python3 -m pytest tests/test_pipeline.py -v --tb=short 2>&1 | tail -20
