# 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.

## Revision 3 — final scope clarification and executable contracts

This section supersedes Revision 2 where they conflict.

1. **No-Kimi constraint is an execution-agent constraint, not a product-provider migration.** This goal's Planner/Architect/Critic/Executor agents must use fallback OpenAI models. Product runtime provider selection is preserved exactly as requested; no provider allowlist or migration is added. Tests use deterministic fake request callables and make no live provider request. The shared `_request_physique_coach_completion` and unrelated coach/conversation routes remain untouched.
2. **Exact frozen surface inputs.** Add `DailyGroundingInput.from_finalized_snapshot`, `WeeklyGroundingInput.from_summary`, and `AdaptiveGroundingInput.from_card`. Factories accept the existing code-owned source only at their named call site, select an explicit field allowlist, reject extras/wrong types, drop free-text notes, and return frozen values. `_humanize_korean_copy` accepts only their closed union; passing a mapping directly returns canonical with zero calls. Tests cover malformed and free-text-bearing mappings.
3. **Only soft explanatory atoms are selectable.** Decisions, action bullets, follow-up timing, safety, approval/delivery state, headings, and fact lines never become atoms and remain the existing locked canonical lines. For each editable prose slot, code derives the exact allowed observation/comparison/hypothesis/rationale atom IDs from the typed facts, selected approved principles, and one playbook. Stage 1 must return the exact offered hard identity plus a subset/order of only those offered soft atoms. Valid-but-unoffered atoms, changed order constraints, or any hard-semantic ID cause canonical fallback. Stage 2 chooses only among code-owned expression variants for each validated soft clause; it cannot emit text.
4. **Consent and enablement.** Before creating any typed input, each daily/weekly/adaptive call site reuses the current registered runtime/coordinator consent and `coaching_feedback_enabled` checks already available at that boundary. Missing/revoked consent or unavailable authority yields canonical and zero calls. This adds no new consent store or lifecycle.
5. **Budgets use UTF-8 bytes.** Each JSON response is capped at 1,024 UTF-8 bytes before parsing; provider request sets a 256 output-token cap through the existing request-options field if supported, otherwise the stage is disabled and canonical is returned. Tests cover exact ASCII and Korean byte boundaries and one-byte overflow. Existing ordinary 500-character routes are unchanged.
6. **Receipt remains narrow.** Log one receipt at the existing adapter logger with a code-derived revision binding digest and canonical/output SHA-256. It includes only selected IDs, stage booleans, outcome, surface, and playbook version; never prose or identity. If a call site lacks a revision binding digest, that surface stays canonical-only rather than inventing one.
7. **Profile authority and parity.** Modify the `physique-coach` authoritative `coaching_grounding.py`, then copy that exact file to `dualcoachtest`; a byte-parity test is required. No raw corpus, review queue, new memory storage, or deployment step 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.
