# Narrow grounded two-stage coaching pipeline

## Status
Consensus draft for immediate Ultragoal execution. Fallback OpenAI models only; Kimi/Kimi3 is prohibited.

## RALPLAN-DR

### Principles
1. Code remains authoritative for facts, numbers, dates, decisions, actions, timing, safety, approval and delivery state.
2. Only approved doctrine and verified structured context may influence coaching prose.
3. Each LLM receives one bounded attempt; validation failure falls back without retry or invented text.
4. Preserve current Telegram routing and canonical copies exactly.
5. Keep the implementation narrow and observable without logging customer-sensitive prompts.

### Decision drivers
1. The user wants recognizable Choi-coach reasoning grounded in the curated corpus, not generic AI copy.
2. The existing `korean_humanizer.py` already provides locked prose slots and deterministic fallback.
3. `checkin_cli.coaching_grounding` already loads approved doctrine and bounded evidence; raw corpora and quarantined claims must stay out of runtime prompts.

### Options
- **Chosen: extend the existing guarded humanizer into two sequential validated stages.** Reuse the same slot/locked-line model, call the existing completion transport once for coaching and once for polishing, and emit a sanitized receipt. Smallest integration surface and preserves routing.
- **Rejected: create a new coaching service or general RAG subsystem.** It expands deployment, configuration, storage, and failure surfaces beyond the request.
- **Rejected: pass the 933-source corpus directly to the model.** It is unbounded, unsafe, and bypasses the approved 150-principle doctrine layer.

## Exact scope

### Product files
1. `/home/cube/projects/richard/hermes-agent/gateway/platforms/korean_humanizer.py`
   - Add frozen exact context/result/receipt types.
   - Add a bounded coaching-request builder that accepts only selected approved principles, verified memory facts, and one playbook descriptor.
   - Stage 1 returns replacements for existing editable prose slots plus referenced principle IDs; validate exact slot identity/order, locked numeric tokens, no internal codes, no unsupported principle IDs, and no added actions/timing/safety claims.
   - Stage 2 receives only the validated stage-1 prose and locked constraints; validate with the existing deterministic invariants.
   - Fallback ladder: coaching invalid/error → canonical; polishing invalid/error → validated coaching; no retries.
   - Receipt contains surface, outcome enum, selected principle IDs, playbook version, verified-memory keys, stage validation booleans, and output digest only. It contains no prompts, prose, customer identity, raw records, Telegram addresses, or credentials.
2. `/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py`
   - Extend only `_humanize_korean_copy` and a narrow helper that obtains a bounded `GroundedFeedbackContext` from existing profile `coaching_grounding` APIs.
   - Preserve all current daily/weekly/adaptive call sites and provider/routing behavior.
   - Make two sequential calls through the existing `_request_physique_coach_completion`; no parallel calls or retries.
   - If grounding is unavailable or invalid, return canonical without a model call.
3. `/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py` and its authoritative source counterpart only if required for import parity.
   - Add one exact bounded export for approved principle identifiers/text, verified memory facts already derived from finalized snapshots, and a playbook descriptor selected from an allowlist (`daily_checkin`, `weekly_report`, `adaptive_nutrition`).
   - Load only curated doctrine files; never load raw corpus/review queue/quarantined claims.
   - No persistence, customer-memory database, or new retrieval service.
4. `듀얼코치_사용설명서.md`
   - Keep the already recorded contract; only correct it if implementation names differ.

### Tests
1. `/home/cube/projects/richard/hermes-agent/tests/gateway/test_korean_humanizer.py`
   - Approved-principle allowlist, forged principle rejection, exact slot/number/action/timing/safety locks, stage-specific fallback ladder, exactly two calls on success, one call on coaching failure, no retry, sanitized receipt.
2. `/home/cube/projects/richard/hermes-agent/tests/gateway/test_telegram_physique_checkin.py`
   - Daily/weekly/adaptive integration preserves current routing and canonical fallback; grounding failure causes zero provider calls; success uses coach then polish in order.
3. Existing profile grounding tests, limited to the directly affected test file, prove raw/quarantined corpus exclusion and bounded context.

## Interfaces

```python
@dataclass(frozen=True, slots=True)
class CoachingGrounding:
    approved_principles: tuple[tuple[str, str], ...]  # 1..5
    verified_memory: tuple[tuple[str, str], ...]      # bounded allowlisted keys
    playbook_id: str
    playbook_version: str

@dataclass(frozen=True, slots=True)
class CoachingPipelineReceipt:
    surface: str
    outcome: Literal["canonical", "coached", "coached_and_polished"]
    principle_ids: tuple[str, ...]
    memory_keys: tuple[str, ...]
    playbook_id: str
    playbook_version: str
    coach_valid: bool
    polish_valid: bool
    output_sha256: str

def coach_and_polish(surface: str, canonical: str, grounding: CoachingGrounding,
                     request: Callable[[str, str], object]) -> tuple[str, CoachingPipelineReceipt]
```

The public integration may retain `humanize` as a compatibility wrapper, but production call sites use the explicit two-stage function. Direct mappings, duck-typed contexts, arbitrary principle text, raw customer records, and callable playbooks are rejected.

## Revision 2 — deterministic closure of review findings

This section supersedes any less-specific interface wording above.

1. **OpenAI-only fail-closed gate.** Add a dedicated `_request_physique_coaching_stage` transport in `telegram.py`. Before client resolution it requires provider identity in the existing OpenAI family (`openai`, `openai-codex`) and rejects provider/model strings containing `kimi` or `moonshot`. A rejected or missing configuration returns canonical with zero client requests. Both stage prompts use this gate. Tests cover Kimi provider, Moonshot alias, Kimi model slug, missing config, and allowed OpenAI Codex.
2. **Typed inputs at all render boundaries.** `_humanize_korean_copy` becomes `(surface, canonical, grounding_input)` where `grounding_input` is an exact frozen gateway type. Daily call sites pass the finalized snapshot mapping; weekly passes a code-built bounded summary type; adaptive passes a code-built bounded proposal/card type. No grounding is parsed back from rendered text. Missing/wrong typed context means canonical and zero calls.
3. **Finite semantic atoms, not unconstrained generated instructions.** The grounding export returns 1..5 exact approved principle IDs plus an allowlisted playbook. Each playbook contains code-owned `CoachingAtom` values for observations, comparisons, hypotheses, judgments, actions, timing, and safety. Stage 1 returns only ordered `principle_ids` and `atom_ids`; code renders the validated coaching draft from those atoms. It cannot return free-form prose. Stage 2 returns only one approved `variant_id` for each editable coaching clause; code owns the Korean variants. Both stages therefore participate, but neither can invent an action, relative time, or safety claim.
4. **Bounded structured transport.** The new stage transport has a separate 4,096-character response ceiling and rejects overflow before JSON parsing. Existing ordinary completion behavior and its 500-character limit stay unchanged. The finite ID-only schema is additionally capped at 1,024 characters; a maximum valid response and one-byte overflow are tested.
5. **Receipt binding and sink.** `_humanize_korean_copy` receives a non-sensitive `revision_binding_digest` already derived at the call site from canonical code-owned state. It logs exactly one structured bounded receipt through the existing logger: surface, revision binding digest, canonical/output digests, selected principle/atom/variant IDs, stage booleans, outcome. It never logs prompts, rendered prose, customer keys/display names, raw records, Telegram identifiers, or credentials. Tests capture the logger and assert both binding and absence of sensitive content.
6. **Grounding source authority.** The authoritative profile source is `/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli/checkin_cli/coaching_grounding.py`; the `dualcoachtest` copy must remain byte-identical for this module after the change. A parity test imports both supported profile copies or compares their bytes. Both load only curated `choi-coach-public-doctrine.md` and `nutrition-doctrine.md`; raw corpus and review queue paths are forbidden.
7. **Narrow playbooks.** Implement only three playbooks matching current surfaces: `daily_checkin_v1`, `weekly_report_v1`, `adaptive_nutrition_v1`. Persistent memory storage is out of scope. “Verified memory” means only bounded facts already present in the finalized typed input (previous comparison, recent committed adjustment, and next code-issued check time); no new database or conversation history is added.

## Acceptance criteria
1. Success path performs exactly two ordered model calls: coach then polish.
2. Coaching uses 1..5 approved principles and an allowlisted playbook; unavailable grounding causes canonical output and zero calls.
3. Neither stage can alter locked lines, numeric/date tokens, decision, actions, follow-up timing, safety, approval/delivery state, or section ordering.
4. Coaching invalid/error returns canonical; polishing invalid/error returns validated coaching; neither retries.
5. Receipt is bounded and non-sensitive and never contains source prose or customer identifiers.
6. Existing daily, weekly, adaptive and generic Telegram routing/provider behavior remains unchanged.
7. Canonical copy remains byte-identical fallback.

## Pre-mortem
1. **Generic or unsafe knowledge enters prompt.** Prevention: exact typed grounding from approved doctrine only; forged ID/text test; raw corpus paths never opened.
2. **Second model silently changes a locked instruction.** Prevention: apply both stages through the same slot document and locked-line/number/action validation; adversarial tests for timing and safety weakening.
3. **Two calls duplicate delivery or introduce retries.** Prevention: both calls occur before existing publication/reservation boundaries through the current completion helper; tests assert call count/order and no Telegram send changes.

## Verification
- Unit: focused humanizer and grounding tests, including malformed JSON, partial slots, extra numbers, internal codes, forged principles, unsafe claims, and receipt privacy.
- Integration: existing Telegram physique-checkin tests for daily, weekly, adaptive call sites and fallback behavior.
- Regression: focused gateway suites for `test_korean_humanizer.py`, `test_telegram_physique_checkin.py`, and `test_adaptive_nutrition.py`; directly affected profile grounding tests.
- Compile: `py_compile` for modified Python files.
- No live Telegram/provider deployment, service restart, synthetic data mutation, credential access, or unrelated cleanup.

## ADR
### Decision
Extend the existing guarded humanizer and approved grounding boundary; do not introduce a new service or generic RAG system.
### Drivers
Narrow scope, deterministic invariants, existing curated knowledge boundary, preserved routing.
### Alternatives considered
New service and raw-corpus prompting were rejected for scope and safety reasons.
### Consequences
Two local model calls add latency, while deterministic fallback preserves availability and correctness. Receipt visibility improves auditability without sensitive logs.
### Follow-ups
None required for this scope; persistent customer-memory products remain explicitly out of scope.

## Intent reconciliation
The user explicitly requires the combined Choi-coach plus polishing pipeline, wants the curated 900+ source effort used through approved principles, prohibits Kimi/Kimi3, requests Ultragoal execution, and forbids scope expansion. This plan implements exactly that boundary.
