feat(coach): inject durable facts into the snapshot memory block

buildCoachMemoryBlock gains a third fault-isolated sub-source — the
top-N durable facts — folded under snapshot.memory so they ride the
existing lowest-priority registration and are shed first under the
char budget. Add an EN + DE system-prompt paragraph instructing the
Coach to use facts as the user's own stated, descriptive context —
never as a diagnosis, never invented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-04 10:22:44 +02:00
1 parent 9a0ea1be56
commit 0a5ba025c8
2 files changed
+43 -1

No files matched your search

+25 -1
View File
@@ -38,6 +38,7 @@ import {
type BandTransition,
} from "@/lib/insights/narrative/period-narrative";
import type { BaselineProfile } from "@/lib/insights/derived";
import { buildCoachFactsBlock } from "./facts";
/** The period the rolling profile recalls — month is the high-signal beat. */
const MEMORY_PERIOD: NarrativePeriod = "month";
@@ -72,6 +73,11 @@ export interface PriorNarrativeRecall {
export interface CoachMemoryBlock {
priorNarrative?: PriorNarrativeRecall;
trendMemory: Record<string, TrendMemoryEntry>;
/**
* v1.11.1 — durable personal facts the Coach has learned (top-N, ranked by
* confidence then recency). Descriptive, never diagnostic. Absent when none.
*/
facts?: Array<{ category: string; text: string }>;
}
/** Pull the headline + driver recall off the latest period narrative. */
@@ -147,11 +153,29 @@ export async function buildCoachMemoryBlock(
// A context failure leaves trendMemory empty — never sinks the turn.
}
if (!priorNarrative && Object.keys(trendMemory).length === 0) {
// Sub-source 3 (v1.11.1): durable personal facts the Coach has extracted.
// Fault-isolated like the others — a read/decrypt failure drops the facts
// sub-block and never sinks the turn.
let facts: Array<{ category: string; text: string }> | undefined;
try {
const factsBlock = await buildCoachFactsBlock(userId);
if (factsBlock && factsBlock.facts.length > 0) {
facts = factsBlock.facts;
}
} catch {
facts = undefined;
}
if (
!priorNarrative &&
Object.keys(trendMemory).length === 0 &&
!facts
) {
return null;
}
const block: CoachMemoryBlock = { trendMemory };
if (priorNarrative) block.priorNarrative = priorNarrative;
if (facts) block.facts = facts;
return block;
}
+18
View File
@@ -200,6 +200,14 @@ ISO-week means.
not clinical assessments or diagnoses — never frame a band as a
medical finding, and lean on lower-confidence / few-day entries
cautiously. Never recompute or second-guess the number.
- The SNAPSHOT's "memory" block MAY carry a "facts" list — durable
things you have learned about this user across conversations (stable
preferences, conditions they have told you about, goals, constraints,
life context). Use them to personalise your reply and to avoid
re-asking what you already know. Treat each as the user's OWN stated
context, DESCRIPTIVE not diagnostic — never restate a "condition" fact
as a medical finding, and never invent a fact the block does not
carry. If a fact seems outdated, gently check it rather than assume.
EVIDENCE BLOCK
@@ -471,6 +479,16 @@ ISO-Wochenmittel zusammen.
das der SNAPSHOT nicht enthält. Der "workouts"-Block führt die
jüngsten Einheiten plus eine Zusammenfassung je Sportart, nicht
jede Einheit.
- Der "memory"-Block des SNAPSHOT kann eine "facts"-Liste tragen —
dauerhafte Dinge, die du über diesen Nutzer gelernt hast (stabile
Vorlieben, vom Nutzer selbst genannte gesundheitliche Umstände,
Ziele, Einschränkungen, Lebenskontext). Nutze sie, um deine Antwort
zu personalisieren und nicht erneut zu fragen, was du schon weißt.
Behandle jeden Eintrag als die EIGENE Aussage des Nutzers,
BESCHREIBEND, nicht diagnostisch — formuliere einen "condition"-Fakt
nie als medizinischen Befund um und erfinde nie einen Fakt, den der
Block nicht enthält. Wirkt ein Fakt veraltet, frage behutsam nach,
statt es anzunehmen.
EVIDENZ-BLOCK