I read this as a read-only forensic investigation into the customer summary formatter path in `/home/cube/projects/richard/hermes-agent`. Let me begin with parallel exploration.


This is a large codebase. Let me search for the key terms from the hypothesis in parallel.


The core formatter is `render_authoritative_customer_summary` in `nutrition_onboarding_reconciliation.py`. Let me read it and the runtime publication path in parallel.


The formatter is `render_authoritative_customer_summary`. Now let me find the `StructuredItem`/`StructuredItems` types (likely in `checkin_cli`), what `reconciliation_answers` returns, and the contract field definitions.


`StructuredItem` isn't in hermes-agent — it's in the external `checkin_cli` package. Let me locate it in the venv and examine the contract, the service `reconciliation_answers`, and the existing formatter tests.


The bash tool only allows read-only gh/curl — no arbitrary shell. Let me locate `checkin_cli` inside the venv directories and find where `reconciliation_answers` is defined.


`physique_checkin_cli` is an editable install. Let me read the editable finder to locate `checkin_cli` sources.


`checkin_cli` is an editable dependency at `/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli`. Let me explore it for the `StructuredItem`/`StructuredItems` types and `reconciliation_answers`.


This is the crux — the "structured" fields are `StructuredItems` with `status`/`items` or raw `{"status": "none", "items": []}` dicts. Let me read the models and find where `reconciliation_answers` is defined (the service feeding the formatter).


Now let me read `reconciliation_answers` in the service and the existing formatter tests to understand the exact return shape and test expectations.


I now have the core causal mechanism. Let me trace the clarification state machine (`current_index`, `record["state"]`) and the copy `card()` builder to pin the exact runtime path and card content.


Let me read the checkin_cli reconciliation record builder and the gateway clarification text renderer to complete the state-machine trace.


Let me examine the existing tests around the attestation/clarification publish to find the test seam and see whether `render_authoritative_customer_summary` is currently covered.


Let me pin exact line numbers in the formatter and confirm the clarification-answer re-publish handler that reaches `_publish` with `record["state"]=="resolved"` and `current_index>0`.


I have the complete causal path. Let me do one final cross-check for any direct unit test of the formatter so I can pinpoint the test gap.


I have the full causal chain. Here is the complete findings report.

<analysis>
**Literal Request**: Trace the exact runtime path that produced a screenshot card after a clarification, proving H1: the customer-summary formatter stringifies raw dicts/StructuredItems and misuses field names and truthiness, yielding `{'items': [], 'status': 'none'}`, `목표: 미입력`, activity text + `· 미입력`, and a false safety-health warning.

**Actual Need**: A file:line causal chain from collected answers → clarification → re-publication → rendered card, exact input dict, exact formatter output strings, and the smallest unit-test seam so the bug can be fixed and regression-tested.

**Success Looks Like**: Precise call chain with line numbers, the raw `answers` dict fed in, the four rendered output strings, and one pure-function seam (`render_authoritative_customer_summary`) that currently has zero direct tests.
</analysis>

<results>
<files>
- /home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_onboarding_reconciliation.py - The buggy formatter `render_authoritative_customer_summary` (lines 59-107). Root of all four symptoms.
- /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram_nutrition_onboarding_runtime_publication.py - Runtime publish path that calls the formatter after clarification (line 128) and wraps it via `render_reconciliation_text` (line 139).
- /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram_nutrition_onboarding_runtime_collection.py - Ingress handler for a clarification reply; calls `service.revise_reconciliation_answer` then `_publish`, re-issuing the card (lines 162-210).
- /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding.py - Service: `reconciliation_answers` (line 414, returns `answers`), `revise_reconciliation_answer` (line 529, advances reconciliation after a clarification), `submit_answer`/`attest_baseline`.
- /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_reconciliation.py - `build_reconciliation` (line 84, sets `current_index=0`, state `clarifying`/`resolved`) and `advance_reconciliation` (line 106, bumps `current_index`, sets `resolved` when all clarifications answered).
- /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_contract.py - Canonical field names (`QUESTION_FIELDS`, line 19) and `example_answer` (line 66) showing structured fields are `{"status": "none", "items": []}` dicts and belief fields are bools.
- /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_models.py - `StructuredItems`/`StructuredItemStatus` (lines 70, 90-121) proving structured values are dict-like objects serialized to `{"status": "none", "items": []}`.
- /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram_nutrition_onboarding_copy.py - `card()` (line ~52) and `PROMPTS`; the card base text and the `_publish` heading repurposed into the summary card.
- /home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_onboarding_reconciliation_copy.py - `render_clarification_text` (line 9), the pre-clarification card body.
- /home/cube/projects/richard/hermes-agent/tests/gateway/test_nutrition_onboarding_reconciliation.py - Existing reconciliation tests (line 207 renders `render_reconciliation_text`), confirming `render_authoritative_customer_summary` itself is never test-covered — the seam.
</files>

<answer>
H1 is confirmed. The formatter's bugs are a field-name mismatch against the canonical contract plus `str()` on structured dicts.

**Exact runtime path (after clarification)**
1. Customer fills onboarding; collected answers stored raw in the session document (`nutrition_onboarding.py:157 submit_answer`). Structured fields are stored as their JSON form `{"status": "none", "items": []}`; belief fields as bools (see `contract.py:66 example_answer`).
2. On `customer_attestation`, `_publish` calls `service.reconciliation_answers()` (`publication.py:71-72`, def `nutrition_onboarding.py:414`) -> deepcopy of `document["answers"]`, raw JSON dicts. LLM reconciliation yields clarifications; record created with `current_index=0`, state `clarifying` (`reconciliation.py:84 build_reconciliation`).
3. Each clarification reply is routed by `runtime_collection.py:162-176` (`clarification_field` in the publication payload, set at `publication.py:166`) to `service.revise_reconciliation_answer` (`nutrition_onboarding.py:529`), which stores the revised answer and calls `advance_reconciliation` (`nutrition_onboarding_reconciliation.py:106`), incrementing `current_index`. It re-publishes afterward (`runtime_collection.py:210`).
4. After the last clarification, `advance_reconciliation` sets `state="resolved"` and `current_index = N` (> 0). The re-published `customer_attestation` then hits the `else` branch: `if current_index:` is truthy, so `summary_ko = render_authoritative_customer_summary(answers)` (`publication.py:116,126-129`). `render_reconciliation_text` wraps that string into the card body (`publication.py:138-141`, join at `nutrition_onboarding_reconciliation.py:34-58`). This is exactly the "card after clarification."

**Exact inputs** (example answers dict, confirmed by `contract.py:66` and `models.py`):
- `goal_type="maintain"` (no `goal` key)
- `activity_category="moderate"`, `activity_rationale="주 3회 근력 운동"` (no `training_details` key)
- `allergies` / `intolerances` / `dietary_preferences` / `religious_ethical_exclusions` / `conditions` / `medications` = `{"status": "none", "items": []}`
- `pregnancy_breastfeeding=False`, `eating_disorder_risk=False`
- `meal_count=3`, `cooking_access="기본 조리 가능"`, `budget_band="보통"`, `schedule_constraints="없음"`

**Why each symptom happens** (`nutrition_onboarding_reconciliation.py`)
- `value()` (lines 62-68) only short-circuits on `None`/`""`/`[]`; it never checks `{}` and falls to `str(raw)` (line 68). Non-empty dicts `{"status":"none","items":[]}` stringify to `"{'status': 'none', 'items': []}"`. This literal appears in the restriction line (and would appear in any stringified structured field).
- `- 목표: {value('goal')}` (line 93) reads `goal`, but the contract field is `goal_type` -> `answers.get('goal')` is `None` -> `미입력`. (No `goal` key exists anywhere.)
- `- 활동: {value('activity_category')} · {value('training_details')}` (lines 94-97): `activity_category` renders "moderate", but `training_details` does not exist (the real field is `activity_rationale`) -> `미입력` -> `활동: moderate · 미입력`.
- False health warning (lines 70-80): `health_fields` uses `medical_conditions` and `pregnancy_lactation` (real fields: `conditions`, `pregnancy_breastfeeding`) so those read `None`; but `medications` is the dict string `"{'status': 'none', 'items': []}"` and `eating_disorder_risk` is bool `False` -> `str(False)="False"`. Neither is in `{"없음","해당 없음","아니요"}` -> `any(...)` is True -> `검토할 입력이 있습니다.` even though nothing meaningful was provided.
- Restriction line (lines 82-90) uses `religious_restrictions` (real: `religious_ethical_exclusions`), so 3 of 4 fields stringify as dicts: `... · ... · ... · 없음` when the customer simply reported none.

**Exact formatter output** (what appeared in the card's summary block):
```
- 목표: 미입력
- 활동: moderate · 미입력
- 식사: 하루 3끼
- 조리·예산: 기본 조리 가능 · 보통
- 식사 시간: 없음
- 알레르기·불내증·식품 제한: {'status': 'none', 'items': []} · {'status': 'none', 'items': []} · {'status': 'none', 'items': []} · 없음
- 건강 관련 입력: 검토할 입력이 있습니다.
```
(Dict repr key order is whatever was parsed from JSON; the H1 `{'items': [], 'status': 'none'}` matches this stringified dict.)

**Minimal test seam**
`render_authoritative_customer_summary` (pure function, `nutrition_onboarding_reconciliation.py:59`) is imported by `_publish` and `runtime_collection` but has zero direct tests — it is not in any import list in `tests/gateway/test_nutrition_onboarding_reconciliation.py` (lines 17-27). Add a focused unit test there that calls it with a canonical `answers` dict (all 22 contract keys, structured fields as `{"status": "none", "items": []}`, goal as `goal_type`, activity rationale as `activity_rationale`) and asserts both the current (buggy) output and the corrected output (goal rendered, no `· 미입력` via `activity_rationale`, empty restriction set, `별도 검토 입력이 없습니다.`). This isolates all four bugs without touching the service or Telegram path.

**Root causes (fix direction, not applied):** (a) formatter field names must match `QUESTION_FIELDS`: `goal`→`goal_type`, `training_details`→`activity_rationale`, `medical_conditions`→`conditions`, `pregnancy_lactation`→`pregnancy_breastfeeding`, `religious_restrictions`→`religious_ethical_exclusions`; (b) `value()` must treat empty/`NONE`-status dicts as absent instead of `str()`-ing them; (c) health review must interpret `StructuredItems(status="none")` and bool `False` as "no review needed."
</answer>

<next_steps>
- Add the seam test for `render_authoritative_customer_summary` in /home/cube/projects/richard/hermes-agent/tests/gateway/test_nutrition_onboarding_reconciliation.py before changing behavior, so the false-positive output is pinned first.
- Then align the formatter's field names with `QUESTION_FIELDS` (`contract.py:19`) and add an "is empty/none" predicate for structured dicts and `False`.
- Optionally add one `_publish` integration assert (mirroring `tests/gateway/test_telegram_nutrition_onboarding.py:1330-1370`) with a resolved record `current_index=1` to confirm the card text no longer contains `미입력` or the dict repr.
- Ready to proceed — the causal chain and inputs are fully pinned; no further discovery needed.
</next_steps>
</results>

Read-only investigation complete. No files, services, network, or evidence were modified.