# Guarded Korean expression-only LLM postprocessor — execution plan

## Summary
Replace the existing free-form daily feedback insertion with a deterministic, expression-only selection boundary. The canonical renderers remain authoritative: one bounded non-Kimi request may choose only a code-approved Korean tone variant; parse, schema, validation, configuration, provider, and timeout failures return the byte-identical canonical output with no retry.

The scope is deliberately narrow: daily saved-check-in copy and scheduled weekly-report copy may receive a validated expression variant; the adaptive-nutrition Topic 59 card contains no nonsemantic span, so it is passed through unchanged and never sent to the model. That exclusion is required to preserve its lifecycle, approval, delivery, and safety state.

## Claims and inspected evidence
- `/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:_nutrition_daily_text` builds the daily section/bullet layout deterministically, but `_nutrition_daily_interpretation` currently admits any two model lines except five blocked tokens (lines 5878–5898). `_saved_physique_coaching_feedback` calls the model after a completed save, and `_render_physique_callback_prompt` / `_render_physique_feedback_replay` inject it into the daily body.
- `TelegramAdapter._request_physique_coach_completion` already reuses the configured `model.provider` / `model.default` through `agent.auxiliary_client.resolve_provider_client`, invokes one completion with a timeout, and logs only an exception type. Reuse that provider path; do not introduce a new client, credential, vendor, or Kimi path.
- `TelegramAdapter._nutrition_report_text` renders weekly facts, judgment, actions, and rationale; `_send_nutrition_coaching_tick` calculates `template_digest`, reserves the immutable delivery row, then calls the strict-topic provider once. Any enhancement must occur before the digest/reservation and must never trigger another delivery request.
- `TelegramAdapter._handle_adaptive_review_callback` persists/publishes operator review-card payloads and its terminal copy reports delivery/approval state. `TelegramAdapter._recover_pending_adaptive_cards` republishes the durable text. Altering either body after persistence would break the card contract.
- `/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/adaptive_nutrition.py:render_operator_card` includes customer/date/weight/rate, judgment, recommendations, review requirements, operator note, and “not delivered” status. It already has an exact canonical digest path; `validate_explanation` and `render_explanation_fallback` demonstrate the local deterministic-fallback convention.
- The user-facing guide fixes daily, weekly, and adaptive-review canonical section layouts and declares safety, approval, delivery, and unknown delivery outcomes terminal. See `/home/cube/projects/richard/traning coach/듀얼코치_사용설명서.md` §§3–4 and §8.
- Focused regression seams already exist in `/home/cube/projects/richard/hermes-agent/tests/gateway/test_telegram_physique_checkin.py:TestNutritionCopySurfaces` and `/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_adaptive_nutrition.py` (`test_explanation_validator_rejects_adversarial_or_outage_text`, `test_explanation_provider_outage_uses_fallback_and_pins_customer_body`).

## Root cause
`_nutrition_daily_interpretation` treats an LLM answer as acceptable based on a tiny forbidden-token list, not a semantic contract. It can therefore publish fabricated values, recommendations, treatment claims, delivery state, or altered facts while looking syntactically normal. A free-form “rewrite this Korean body” response cannot be deterministically proven to preserve negation, modality, causal direction, or omitted facts. The fix is not a bigger deny-list or a second LLM critic; the model must lose authority to generate content.

## RALPLAN-DR

### Principles
1. **Code is authoritative.** Domain calculation, safety holds, lifecycle decisions, transport state, canonical body, and digest are rendered locally before any model call.
2. **Fail closed to the current canonical output.** Failure means the exact already-rendered string, not a softer default, a partial rewrite, a heuristic repair, or a retry.
3. **One attempt, no new delivery semantics.** One enhancement request at most per eligible render. It never retries and can neither alter strict-topic routing nor cause a second Telegram send.
4. **Privacy by non-observation.** No prompts, rendered bodies, model replies, customer keys, IDs, hashes of text, or exception strings enter logs/metrics.
5. **No Kimi.** This feature rejects a configured provider/model whose normalized identifier contains `kimi`; it has no fallback provider.
6. **Narrow eligibility beats fake coverage.** A surface with no demonstrably nonsemantic editable slot is preserved byte-for-byte and does not call the LLM.

### Drivers / requirements
| Driver | Required design consequence |
|---|---|
| Facts, numeric values, dates, decisions, actions, follow-up, safety, approval/delivery status, and section boundaries are immutable | Build/lock them in code; model output has no body/text field and cannot address them. |
| Warm Korean expression only | Model selects a code-reviewed `variant_id` for a predeclared nonsemantic tone slot; it never writes a replacement. |
| Canonical current output is fallback | Render canonical first. Every non-success result returns that exact string object/content. |
| No prompt/body logging and no Kimi | Reuse only the existing configured client if its provider/model is permitted; emit bounded outcome counters only. |
| Existing delivery safety | Enhance weekly body before reservation/digest; do not mutate/republish adaptive durable cards; leave strict-topic one-call flow intact. |
| Operator cards must retain audit meaning | Give adaptive operator cards zero eligible slots in v1, record `no_eligible_slot`, make zero model calls. |

### Options considered
| Option | Result | Decision |
|---|---|---|
| Free-form whole-body Korean rewriter plus prompt instructions | Cannot deterministically prove it did not negate, add a medical claim, alter a number/date, or change a delivery state. A second LLM audit is another probabilistic failure path. | Reject. |
| Free-form replacement fields with protected-token/number matching | Protects literals but not omitted facts, modality, causality, action additions, or polarity; still unsafe. | Reject. |
| Model chooses only code-defined `variant_id` values; deterministic code renders the selected fixed variant around an eligible expression slot | Model cannot emit content. Schema and allow-list prove the only possible output is a reviewed local variant or exact canonical fallback. | Select. |
| Humanize every adaptive Topic 59 card | The card is durable audit/lifecycle state and every line carries a protected datum/status/action. Post-persistence mutation is unsafe. | Reject for v1; pass through with zero model call. |

## Targeted implementation plan

### 1. Establish the typed deterministic copy boundary
**Files/symbols:**
- Modify `/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/adaptive_nutrition.py` near `canonical_json`/`digest`, `render_operator_card`, `render_explanation_fallback`, and `validate_explanation`.
- Modify `/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py` at `TelegramAdapter.__init__`, `_nutrition_daily_text`, `_nutrition_report_text`, `_saved_physique_coaching_feedback`, `_render_physique_callback_prompt`, `_render_physique_feedback_replay`, `_request_physique_coach_completion`, `_send_nutrition_coaching_tick`, `_handle_adaptive_review_callback`, and `_recover_pending_adaptive_cards`.

Create a small shared, pure module-level contract in `adaptive_nutrition.py` (not a new persistence record and not a change to proposal/delivery schemas):

```json
// request document passed to the model, never logged
{
  "schema_version": "korean-expression-v1",
  "surface": "daily|weekly|adaptive_operator",
  "slots": [
    {
      "slot_id": "daily.acknowledgement",
      "allowed_variant_ids": ["canonical", "warm_ack_v1"]
    }
  ]
}

// the only accepted model response
{
  "schema_version": "korean-expression-v1",
  "surface": "daily|weekly|adaptive_operator",
  "selections": [
    {"slot_id": "daily.acknowledgement", "variant_id": "canonical|warm_ack_v1"}
  ]
}
```

Use frozen/slotted dataclasses or typed mappings for: `KoreanExpressionSurface`, `KoreanExpressionSlot`, `KoreanExpressionDocument`, `KoreanExpressionSelection`, and `KoreanExpressionResult`. The result carries `body`, bounded `outcome`, and optional bounded `reason_code`; it never retains/provider-logs the request/response body.

The validator must: strict-parse one JSON object (no markdown fences/prefix/suffix); require exact keys/types/schema version; require the requested surface; require exactly the same slot count/order/IDs; reject duplicates, missing/extra fields, unknown variants, nonstrings, or overlong output; and construct final text only by looking up a local immutable variant table. It must perform no “best effort” normalization except explicit NFC on local string constants and return the supplied canonical body for every rejection.

A `render_expression_variant(canonical_body, document, selections)` helper must splice only predeclared slot offsets into a canonical body. It must assert that each locked segment before/between/after slots matches exactly and that every protected section delimiter (`\n\n` layout, title, bullet order, headings) is unchanged. The model supplies no text and has no path to facts, values, dates, decisions, actions, follow-up, safety, approval/delivery state, or section labels.

Keep `NutritionProposal.operator_body`, `operator_body_digest`, `customer_body`, `customer_body_digest`, all `AdaptiveEventStore`/overlay records, and feature-epoch document shape unchanged. Do **not** add the humanizer flag to `_FEATURE_FLAG_NAMES`: those flags govern the digest-pinned adaptive lifecycle/delivery boundary, while this is a presentation-only adapter setting.

### 2. Define eligible slots and canonical semantics
**Daily:** change `_nutrition_daily_text` to first create the current deterministic canonical body without free-form `feedback`; the fact bullets, interpretation fallback, “오늘 할 일” heading, and actions remain code-generated. Retire the raw post-save feedback insertion as a display source: `_saved_physique_coaching_feedback` must no longer return arbitrary prose for `_nutrition_daily_interpretation`, and `_render_physique_callback_prompt` / `_render_physique_feedback_replay` must start from the canonical body. The only v1 daily slot is a reviewed, non-medical acknowledgement/tone line whose variants are entirely local and cannot contain a target, diagnosis, prescription, decision, or follow-up. If the stable current layout cannot accommodate a no-op-safe slot without adding a section boundary, declare daily `slots=()` rather than inventing copy.

**Weekly:** `_nutrition_report_text` remains the sole calculator/formatter for averages, percent, check-in rate, goal range, interpretation, judgment, actions, and rationale. Only a preallocated static courtesy slot may be eligible, and only when its predicate is entirely deterministic (for example, a nonzero recorded check-in rate). Never use `summary.interpretation`, `actions`, `rationale`, `display_name`, or text derived from an event as an editable model field. The final selected/canonical body is computed before `template_digest` and `reserve_customer_task_delivery` in `_send_nutrition_coaching_tick`.

**Adaptive operator:** feed `render_operator_card(proposal)` through `KoreanExpressionDocument(surface="adaptive_operator", slots=())`, which deterministically returns it unchanged without a provider request. Do not apply the postprocessor in `_handle_adaptive_review_callback` or `_recover_pending_adaptive_cards`; their texts are durable cards/terminal state and must remain exactly the service payload. This is intentional v1 coverage: the pipeline classifies the surface, proves it ineligible, and preserves all operator facts/statuses/digests.

### 3. Provider reuse, bounded attempt, and error behavior
Add a private adapter method such as `_maybe_apply_korean_expression_variant(surface, canonical_body)` plus a synchronous worker `_request_korean_expression_selection(document, timeout_seconds)`. It dynamically imports the profile-local `checkin_cli` contract only after the feature gate is valid, builds the small JSON selection prompt, and invokes the existing configured provider resolution path. Refactor `_request_physique_coach_completion` only enough to accept an explicit `timeout_seconds` and maximum returned character count while preserving existing callers’ 30-second/default behavior.

For this feature only: normalize provider/model labels; if either contains `kimi`, return canonical with `provider_disallowed` before client resolution. Reuse `load_config()` + `resolve_provider_client(provider, model)` and existing credentials/model, but pass a tight validated timeout (default 3 seconds, permitted 1–5 seconds) and no tools. The prompt states: Korean honorific/professional warmth; choose one listed `variant_id` only; never infer or add facts/medical advice; output the exact JSON schema only. Do not use `response_format` so the existing provider-neutral client remains compatible; strict local JSON validation is the enforcement point.

Execute exactly one `asyncio.to_thread` request. Wrap it in one outer deadline slightly above the client timeout to bound the Telegram coroutine; cancellation may leave a blocking thread completing in the background, but its result is ignored and there is never a second call. On disabled/invalid configuration, Kimi, unavailable provider, exception, deadline, empty response, JSON failure, or validator failure: record a safe outcome and return the canonical body. Never retry inside the helper, after a schedule tick, or after a provider/send failure.

For weekly copy, a humanizer failure still proceeds immediately with canonical `body`, reservation, strict-topic send, and existing one-call receipt protocol. A Telegram timeout remains `unknown` exactly as current code does; it cannot re-enter enhancement or delivery. For daily copy, record persistence/save occurs before enhancement and is never rolled back by an enhancement failure.

### 4. Configuration, rollout, and privacy-safe observability
Parse a strict local `config.extra["korean_expression_postprocessor"]` mapping during `TelegramAdapter.__init__`. Defaults and invalid/missing/unknown keys are disabled. Required keys when enabled: `enabled: true`, `daily: bool`, `weekly: bool`, `adaptive_operator: bool`, `timeout_seconds: int`; enforce `1 <= timeout_seconds <= 5`. `adaptive_operator: true` is accepted but yields no eligible slots/no provider call until a separately designed safe slot version exists. This mapping contains no provider/model override, no secret, no destination, and no mutable delivery flag.

Roll out only in the isolated test profile described by the guide: (1) disabled/default regression baseline; (2) `analytics_shadow`-style observation in which the selection is validated but discarded and customer text stays canonical; (3) daily-only test-profile enablement; (4) weekly test-profile enablement only after delivery ledger tests pass; (5) keep adaptive operator pass-through. No production customer enablement, delivery enablement, or Gate-D claim follows from these tests. Roll back by setting `enabled: false`; existing canonical rendering and ledger behavior immediately remain.

Emit one bounded structured telemetry event/counter per attempted/classified render with only: contract version, surface enum, outcome enum (`disabled`, `no_eligible_slot`, `success`, `provider_disallowed`, `provider_error`, `timeout`, `invalid_response`, `validation_rejected`), and coarse latency bucket. Do not emit prompt, canonical/enhanced body, response, exception text, customer/key/chat/topic/message IDs, operator name/note, digest/text hash, numeric values, or raw length. Ensure exception logging uses only exception class where unavoidable. Add a metrics seam/sink injectable in tests; do not create an audit/event row for the cosmetic choice.

### 5. Attribution and operator documentation
Update `/home/cube/projects/richard/traning coach/듀얼코치_사용설명서.md` with a non-executable “한국어 표현 후처리” section: default-disabled/test-only status; code-authoritative immutable fields; no-Kimi/provider reuse; exactly-one attempt and canonical fallback; no content logging; adaptive Topic 59 pass-through; bounded metrics fields; and the rollout/rollback boundary. Keep the document’s statement that actual customers, automatic delivery, and Gate-D are not implied.

Include a third-party notice in that guide (or an adjacent NOTICE subsection within the allowed target) with the complete MIT copyright/permission notice required for copied/substantial rules: **Copyright (c) 2026 Taehan Kim; Copyright (c) 2026 epoko77-ai (Original work — https://github.com/epoko77-ai/im-not-ai)**, source **https://github.com/Gaeduck-0908/im-not-ai-kiro**, MIT License. Attribute only the selected principles, not its filesystem-run workflow: (a) meaning/facts/numbers/dates/proper nouns/quotes preserved, (b) finding-local rather than global change, (c) tone/genre retained, (d) excessive polishing avoided, and (e) doubtful semantic edits roll back. Do not copy its `_workspace` persistence, multi-agent/retry pipeline, or raw input/output artifact logging because each violates this system’s privacy and one-attempt constraints.

## Test and verification plan

### Unit — `checkin_cli/tests/test_adaptive_nutrition.py`
1. Add contract tests for exact accepted selection schema and rendering; verify every accepted variant uses a local fixed string and preserves every locked fragment, all facts/numbers/dates, decisions, action bullets, safety/approval/delivery text, section headings, blank-line boundaries, and canonical digests.
2. Parameterize failures: non-JSON/fenced JSON, unknown/missing/extra key, bad version/surface, reordered/missing/duplicate slot, unknown variant, oversized output, empty output, and `None`. Each returns exactly `canonical_body`.
3. Exercise normalized Korean/NFC only where defined; test no arbitrary model-provided text is retained in a result object or exception/log payload.
4. Assert `render_operator_card` produces an adaptive operator document with zero eligible slots and exact card/digest equality; no proposal revision/event/feature-epoch value changes.
5. Preserve/extend `test_explanation_validator_rejects_adversarial_or_outage_text` and `test_explanation_provider_outage_uses_fallback_and_pins_customer_body` so existing deterministic explanation behavior remains intact.

### Integration — `tests/gateway/test_telegram_physique_checkin.py`
1. Extend `TestNutritionCopySurfaces`: feature disabled output is byte-for-byte the current expected daily/weekly fixture; enabled valid selection changes only the preapproved tone slot, while every printed fact/bullet/heading/action remains exact.
2. Mock the provider boundary and assert one call for an eligible daily or weekly render, zero calls for disabled, Kimi, invalid config, adaptive operator, and no-eligible documents. Assert configured provider/model is reused and no alternate provider is resolved.
3. Make timeout, client exception, malformed JSON, and validator rejection return canonical output; assert no second enhancement request and no generic-ingress call.
4. Update the saved-checkin/replay tests so raw `_generate_physique_coaching_feedback` cannot be appended as customer copy. Verify a successful immutable save is still confirmed even if expression selection fails.
5. Extend `TestNutritionScheduleDelivery`: with weekly selection success, reserve/template digest contains the selected final string and strict-topic provider is called once; with enhancement timeout/rejection, reservation contains canonical text and the send remains once; a delivery timeout/restart remains unknown and does not invoke the humanizer or provider again.
6. Exercise `_handle_adaptive_review_callback` and `_recover_pending_adaptive_cards` with a model mock that fails if called; assert card payload/text/buttons/terminal status remain exactly durable canonical content.
7. Capture telemetry and assert enum-only fields, no prompt/body/response/customer identifiers/digests/exception message leakage, and bounded latency/outcome labels.

### End-to-end — isolated profile only
1. Daily: save a finalized check-in under the dedicated test profile; fake a valid non-Kimi selection; assert the rendered Telegram edit retains all exact fields and has at most one selection call. Repeat with a deadline failure and compare the body byte-for-byte to the canonical renderer.
2. Weekly: run one due weekly task through ledger reservation, selection, strict-topic fake Telegram provider, and audit; assert one enhancement maximum, one provider delivery maximum, final ledger template digest matches the actual selected/canonical body, and restart does not resend.
3. Adaptive Topic 59: exact review identity publishes, approves, and views a card; assert no enhancement call, exact `render_operator_card`/terminal text, opaque buttons intact, and recover-pending publication reuses persisted bytes. Verify foreign/wrong-topic input is still denied.

### Observability acceptance
- Dashboard/test fixture can count outcomes by surface and version without a content sample or text-derived identifier.
- Alert only on aggregate timeout/validation/provider-disallowed rates; inspect local code/config rather than captured prompts/bodies.
- Release gate: zero semantic-lock regressions, zero duplicate Telegram sends in P2/P3/P4-style delivery tests, zero content-bearing telemetry fields, and no invocation when model/provider contains Kimi.

## Three-scenario pre-mortem
| Failure scenario | Earliest signal | Preventive design | Containment / rollback |
|---|---|---|---|
| Provider returns prose, malformed JSON, or a prompt-injected instruction that changes calories or safety advice | `invalid_response`/`validation_rejected` rises; golden semantic-lock test fails | The response has no text field; strict exact schema and local variant lookup reject it | Render canonical body immediately, no retry; keep delivery path unchanged and investigate aggregate metric only. |
| Slow/unavailable provider blocks a scheduled report and tempts a duplicate send | `timeout` bucket rises; scheduler latency grows | One tight client timeout + one outer deadline; canonical body continues into existing reservation; no attempt counter/retry branch | Existing ledger records one send/unknown result; disable flag returns all copy to canonical renderer. |
| Cosmetic postprocessing mutates durable Topic 59 review/terminal cards or leaks customer copy into telemetry | Card text/digest differs on recovery or logs contain text-like fields | Classify adaptive operator as zero-slot pass-through; never touch `_handle_adaptive_review_callback`/`_recover_pending_adaptive_cards`; enum-only telemetry contract tests | Disable feature; preserve/recover existing durable card bytes and audit configuration/log sink. |

## Findings
- **HIGH — `/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:5878-5898`:** `_nutrition_daily_interpretation` currently inserts arbitrary model text after a minimal forbidden-token scan. It violates the required immutable semantic contract. Replace it as described rather than expanding the deny-list.

## Recommendations
1. Implement the finite-variant typed selection contract before enabling any model request.
2. Remove free-form feedback from the daily customer rendering route; do not paper over it with a prompt or LLM critic.
3. Preserve adaptive review cards as no-op classified surface in v1; design any future semantic-free slot separately with durable-card compatibility proof.
4. Keep copy configuration outside adaptive feature-epoch flags and default it disabled; gate releases by the verification plan.
5. Add the MIT notice and selected-rule attribution while excluding the source project’s artifact-writing/retry workflow.

## Architectural Status
BLOCK — the current daily free-form insertion is not semantically safe. This plan is implementable without changing domain calculations, lifecycle records, or transport semantics, but the feature must not be enabled until the typed contract and tests land.

## Code Review Recommendation
REQUEST CHANGES

## Tradeoffs
- The selected finite-variant design intentionally gives up arbitrary LLM prose. It trades maximal stylistic novelty for deterministic proof that code-authoritative meaning and delivery state cannot change.
- V1 does not cosmetically rewrite adaptive operator cards because their content is all operationally meaningful and durable. This is less broad but prevents audit/card-recovery inconsistency.
- Provider-neutral prompt JSON instead of provider-specific structured-output APIs has less server-side enforcement but keeps existing provider reuse; strict local validation remains the authority.
