# 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], ...]  # 3..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 3..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.

## Revision 4 — transport and consent closure

This section supersedes the conflicting Revision 3 clauses.

1. Add a separate `_request_physique_coaching_stage(system_prompt, user_content, *, max_output_tokens=256)` helper used only by the two new stages. It preserves the exact existing provider/model resolution but passes the provider-supported bounded output option and applies the 1,024 UTF-8-byte post-response cap. `_request_physique_coach_completion` and every ordinary caller remain unchanged. If the resolved client cannot accept a bounded output option, the stage returns no result and the pipeline follows its fallback ladder. Unit tests use a fake client to assert the cap and ordinary-helper non-regression.
2. Consent is surface-specific and reuses existing authority. Personal-owner daily check-in uses the existing explicit `PhysiqueCheckinConfig.coaching_feedback_enabled` enablement as its processing authority; no customer consent is asserted for that personal surface. Registered customer weekly/adaptive surfaces require the current coordinator/runtime AI-consent check already used by their customer delivery boundary before each stage. Missing/revoked customer consent means canonical and zero calls. Tests cover disabled personal feedback and revoked registered-customer consent separately.

## Revision 5 — adaptive typed source and processing authority

This final section supersedes earlier conflicting clauses and adds only one directly required integration file.

1. `/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py` is included solely for two narrow APIs. `AdaptiveOperatorService.coaching_facts_for_current_card(...) -> AdaptiveCoachingFacts` returns an exact frozen, allowlisted projection before rendering: evaluation day, goal mode/range, current/prior means and rate, decision, reason category IDs, target macros, carb-category targets, safety-held boolean, approval/delivery state, proposal/revision binding digest. It contains no rendered text, notes, paths, raw events, identities, destinations, or tokens. Both adaptive Telegram publication paths receive this typed projection alongside the existing rendered card; they never parse card mappings or text.
2. `NutritionCoachingCoordinator.coaching_processing_allowed(customer_key) -> bool` is a public read-only processing gate distinct from delivery. It refreshes the canonical registry and exact customer runtime, requires enabled state, current supported AI-consent/version, and no applicable processing/safety revocation. It does not inspect or authorize a destination and grants no delivery authority. Telegram invokes it immediately before the coach request and again immediately before the polish request for registered weekly/adaptive surfaces. Revocation between stages preserves the validated coach result only when policy permits processing already completed; otherwise canonical fallback is used, and no second request occurs. Tests cover false before stage 1 and revocation between stages for both surfaces.
3. This is not a general API expansion: no new storage, routing, provider, destination, lifecycle, or persistence is introduced. Only the two exact methods, typed dataclass, Telegram wiring, and directly related tests are in scope.

## Final closure applied for fresh consensus run

1. Any consent, safety, authority, enablement, or revision change before stage 1, between stages, or after stage 2 unconditionally discards all model output and returns the byte-identical canonical copy. A final gate runs immediately before existing publication/reservation. There is no policy exception and no coached-result retention after revocation.
2. The sanitized receipt includes only bounded opaque IDs: source-cluster IDs, excluded-risk category IDs, decision/soft-atom IDs, verified-memory keys, playbook/version, validation booleans, outcome, surface, revision/canonical/output digests. It contains no prose, identity, raw record, prompt, path, destination, or secret.
3. Grounding requires exactly 3..5 approved principles. Fewer than three or more than five yields canonical and zero model calls.

## Acceptance criteria
1. Success path performs exactly two ordered model calls: coach then polish.
2. Coaching uses 3..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.
