Files
HealthLog/src/lib/ai/coach/coach-memory-shared.ts
T
Marc-André Bombeck e4ec0eec49 fix(insights): stop sending German period narratives to fr/es/it/pl readers
The narrative read route narrowed every non-English locale to German, so a
French, Spanish, Italian or Polish account read the German narrative row and
warmed it from the German prompt body. Carry the resolved locale through
instead: the row is keyed (user, period, locale) across the full UI locale
union, and the four locales now compose the English instruction body with
their language named and the locale's own reply-language directive appended
last. The German and English prompts are unchanged apart from the version
string.

The nightly warm keeps the user's stored locale for the same reason — it has
to write the key the read route reads — and the coach and dashboard narrative
recalls read that same key. Coach memory stays de/en: it is model-facing
context, not user-facing prose, so the four locales compose and key English
rather than German.

NARRATIVE_PROMPT_VERSION 1.13.0 -> 1.14.0.
2026-07-18 23:25:44 +02:00

65 lines
2.3 KiB
TypeScript

/**
* v1.11.1 — generator-free contract for the Coach long-term-memory refresh
* queue. The chat route enqueues a single-conversation refresh here without
* importing the concrete generators (which would pull the provider chain into
* the route bundle). The worker handler (`runCoachMemoryRefresh`) calls the
* summary + fact generators; it re-uses the queue name from here so there is
* one source of truth.
*
* One combined queue does BOTH conversation-summary refresh and durable-fact
* extraction in a single worker job: when a long conversation crosses the
* history-window cap, both are usually due together, so one job + one
* singleton window avoids a second queue and a redundant wake-up.
*
* Mirrors `period-narrative-shared.ts`: queue name + payload type + enqueue
* helper here, the concrete dispatch in the worker.
*/
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { annotate } from "@/lib/logging/context";
export const COACH_MEMORY_REFRESH_QUEUE = "coach-memory-refresh";
export interface CoachMemoryRefreshPayload {
conversationId: string;
userId: string;
/** Locale to compose the summary / facts prose in; defaults to "en". */
locale?: "de" | "en";
}
/**
* Fire-and-forget enqueue from the chat turn once a conversation has grown past
* the history-window cap. A `singletonKey` per conversation collapses repeated
* turns within a short window into one queued job. No-ops cleanly when the
* global boss is unavailable (a web process without an embedded worker) — the
* memory simply stays as-is until the next eligible turn.
*/
export async function enqueueCoachMemoryRefresh(payload: {
conversationId: string;
userId: string;
locale: "de" | "en";
}): Promise<void> {
const boss = getGlobalBoss();
if (!boss) return;
try {
await boss.send(
COACH_MEMORY_REFRESH_QUEUE,
{
conversationId: payload.conversationId,
userId: payload.userId,
locale: payload.locale,
} satisfies CoachMemoryRefreshPayload,
{
singletonKey: `refresh:${payload.conversationId}`,
singletonSeconds: 120,
},
);
annotate({
action: { name: "coach.memory.refresh.enqueued" },
meta: { locale: payload.locale },
});
} catch {
// Best-effort — a failure just leaves the Coach memory unchanged until the
// next eligible turn re-enqueues.
}
}