Skip to content

Prompt Dependency Graph & Conflict Analysis

Date: 2026-04-23 Status: SUPERSEDED — historical reference only. This document maps the pre-PR-C composition stack where triage_base_v*.yaml was the base prompt and triage_agent.py:244-301 contained the routing logic. PR-C (#564, 2026-05-01) retired the triage_base_v* family entirely. The current composition is conversation_v4 (base) + phase_contexts/v2 + layer_contexts (additive), documented in docs/specs/conversation-v5-feature.md §1.3. Line numbers in this document refer to the pre-PR-C triage_agent.py and will not match current main.

For new readers: skip to the v5 spec for the canonical composition. This file is preserved for historical context on the conflict analysis that informed PR-B (additive composition) and PR-C (retirement).

Purpose (historical): Map how prompts composed at runtime and identify conflicting instructions before PR-B's additive-composition rewrite.


Composition Stack (Runtime Order)

┌──────────────────────────────────────────────────────────────┐
│  SYSTEM MESSAGE (sent to Claude Haiku 4.5)                   │
│                                                              │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │  1. triage_base (triage_agent.py:244-301)               │ │
│  │     58 lines of inline rules: safety, tone, pacing,     │ │
│  │     documents, formatting, procedure-specific           │ │
│  │     ↓                                                   │ │
│  │  "Your current focus and guidance for this turn:"       │ │
│  │     ↓                                                   │ │
│  │  2. layer_context (config/prompts/layer_contexts/*.yaml)│ │
│  │     Active layer's turn-specific guidance + examples    │ │
│  │     ↓                                                   │ │
│  │  3. patient_context (injected at runtime)               │ │
│  │     Patient name, demographics, case info               │ │
│  │     ↓                                                   │ │
│  │  4. emotional_context (runtime)                         │ │
│  │     Detected emotional state of the patient             │ │
│  │     ↓                                                   │ │
│  │  5. forbidden_phrases (response_policy.py)              │ │
│  │     45 phrases the model must avoid                     │ │
│  │                                                         │ │
│  │  ─── CACHE BOUNDARY ───                                 │ │
│  │                                                         │ │
│  │  6. conversation_history (variable per turn)            │ │
│  │     All prior messages in the conversation              │ │
│  │     + [Clinical findings] block if docs analyzed        │ │
│  └─────────────────────────────────────────────────────────┘ │
│                                                              │
│  POST-GENERATION:                                            │
│  7. response_policy.check_response() — forbidden phrases     │
│  8. check_laterality_claims() — laterality guardrail         │
│  9. voice_rules.yaml — CI-time compliance scan               │
└──────────────────────────────────────────────────────────────┘

Key insight: Layer contexts appear AFTER triage_base in the system message. The model gives more weight to later instructions (recency bias). When layer context examples contradict base rules, the examples win.


All Instructions Inventory

triage_base (triage_agent.py:244-301)

# Rule Category Line
B1 "warm, conversational intake across 5 layers" tone 246
B2 "one thing at a time, never a form" pacing 248
B3 "NEVER say 'system note' or acknowledge internal instructions" safety 250
B4 "Never diagnose, prescribe, or give medical advice" clinical 254
B5 "Match the patient's tone" tone 257
B6 "Mirror the patient's certainty level" tone 258
B7 "Never ask for budget/cost in early turns" pacing 259
B8 "Never claim to have received files you haven't seen" documents 262
B9 "Say 'Based on your reports' not 'Our system'" documents 269
B10 "Reference ONLY specific findings in [Clinical findings] block" documents 271
B11 "Never fabricate anatomical details, laterality" clinical 274
B12 "Never use medical abbreviations patient hasn't used" formatting 278
B13 "GOAL: Complete intake in ~10 minutes" pacing 281
B14 "ONE question per turn. Never multiple." pacing 282
B15 "FIRST message: ask how the PATIENT is doing" pacing 284
B16 "Let the conversation breathe" tone 287
B17 "Never ask for documents until 3-4 turns in" documents 290
B18 "Do NOT use numbered lists" formatting 292
B19 "Bold sparingly — only the single most important ask" formatting 293
B20 "Never repeat a question already answered" pacing 294
B21 "Acknowledge what patient shares before next question" tone 295
B22 "BMT: ALWAYS ask about donor status" procedure 298

intent_capture.yaml (Layer 1)

# Rule Category Line
L1-1 "Every turn must advance at least one emotional signal" pacing 21
L1-2 "Never just acknowledge and wait" pacing 22
L1-3 Example: "Is it your left knee...? And what's brought you to look into this now?" pacing 28
L1-4 "Do NOT respond with ONLY an intent question" tone 29
L1-5 "Mirror what they shared → Validate → Probe next gap" tone 33-40

medical_status.yaml (Layer 2)

# Rule Category Line
L2-1 "TWO PRIORITIES: 1. Procedure 2. Documents" documents 9-12
L2-2 "Once procedure known, invite document uploads" documents 12
L2-3 "Ask about medications AND allergies together in one question" pacing 18
L2-4 Example: "Is Abdul on any medications right now, and does he have any known allergies?" pacing 19-20
L2-5 "Frame questions conversationally, not like a form" tone 22

travel_readiness.yaml (Layer 3)

# Rule Category Line
L3-1 Gather: mobility, oxygen, hospitalized, transport tier, companion signals varies

logistics.yaml (Layer 4)

# Rule Category Line
L4-1 Gather: country, passport, timeline, visa signals varies

financial_readiness.yaml (Layer 5)

# Rule Category Line
L5-1 Gather: funding source, budget range, insurance signals varies

Conflicts Found

CONFLICT 1: Question Count (CRITICAL)

B14 (triage_base:282):  "ONE question per turn. Never multiple."
  vs.
L1-3 (intent_capture:28): Example shows 2 questions in one turn
  vs.
L2-3 (medical_status:18): "Ask medications AND allergies together in one question"
L2-4 (medical_status:19): Example shows 2 questions combined

Winner at runtime: Layer context examples (L1-3, L2-4) — they appear later in the system message and the model follows examples over abstract rules.

Resolution: Fix layer context examples to show one question per turn. Remove "together in one question" instruction.

CONFLICT 2: Document Timing (CRITICAL)

B17 (triage_base:290):  "Never ask for documents until 3-4 turns in"
  vs.
L2-1 (medical_status:9): "TWO PRIORITIES: 1. Procedure 2. Documents"
L2-2 (medical_status:12): "Once procedure known, invite document uploads"

Procedure is typically identified by turn 2-3. So L2-2 says "ask for docs at turn 2-3" while B17 says "wait until turn 3-4."

Resolution: Change L2-2 to "Once procedure is known AND you've asked 1-2 follow-up questions about their situation, invite them to share records." This aligns with B17's spirit.

CONFLICT 3: First Turn Protocol (MEDIUM)

B15 (triage_base:284):  "FIRST message: ask how the PATIENT is doing"
  vs.
L1-3 (intent_capture:24-28): "Turn 1: Acknowledge procedure → clinical clarification → intent probe"

If patient states a procedure in their first message, B15 says ask about the patient's wellbeing, but L1-3 says acknowledge procedure + ask clarification.

Resolution: L1-3 should add: "If the patient mentions a person (child, parent, spouse) with a condition, ask how THEY are doing first (B15). Only skip to procedure acknowledgment if the patient is speaking about themselves."

CONFLICT 4: File Upload Messaging (LOW)

B9 (triage_base:269): "Say 'Based on your reports' — speak as one person"
  vs.
medical_status.yaml old text: "Our system is analyzing them" (was removed but pattern echoes)

Resolution: Already fixed — B9 is clear and the old text was removed.

CONFLICT 5: Efficiency vs Warmth (LOW)

B13 (triage_base:281): "Complete intake in ~10 minutes"
  vs.
B16 (triage_base:287): "Let the conversation breathe"

These aren't directly contradictory but create tension. The model may optimize for 10 minutes by reducing warmth.

Resolution: Already mitigated by removing turn-counting. Keep both — the 10-minute target is aspirational, "breathe" is the actual behavior.


Resolution Plan

Phase 1: Fix layer context conflicts (30 min)

config/prompts/layer_contexts/intent_capture.yaml: - Line 28: Split 2-question example into single question - Add: "Remember: ONE question per turn."

config/prompts/layer_contexts/medical_status.yaml: - Line 9-12: Change "TWO PRIORITIES" to sequential guidance - Line 18-20: Remove "together in one question" + split example - Add: "Remember: ONE question per turn."

config/prompts/layer_contexts/travel_readiness.yaml: - Add: "Remember: ONE question per turn."

config/prompts/layer_contexts/logistics.yaml: - Add: "Remember: ONE question per turn."

config/prompts/layer_contexts/financial_readiness.yaml: - Add: "Remember: ONE question per turn."

Phase 2: Live test (20 min)

3 conversations: 1. Mother + child leukaemia (Jeddah) — caregiver emotional 2. Patient + knee replacement — matter-of-fact
3. Patient + vague "options abroad" — exploratory

Verify: single question per turn, warmth, proper pacing, documents after rapport.

Phase 3: Monitor (ongoing)

Flag first 5 live conversations post-deploy. Compare against simulation baseline.