feat(coach): give the turn the memory block, its recalled facts and a pending proposal

Build the memory block for the providers the turn sends to, send it on
both paths, keep the facts it recalled in the owner's trail, and offer a
health fact the background found as the answer's one note. Every turn
enqueues the memory refresh; the job's debounce and singleton key decide
when it runs. A remembered fact points back to the stored user message.
This commit is contained in:
Marc-André Bombeck committed 2026-10-06 19:48:55 +02:00
1 parent f46e0960fb
commit de5b315999
10 files changed
+176 -53

No files matched your search

@@ -19,6 +19,9 @@ exports[`coach chat golden transcripts > 01a tool mode, two rounds, present data
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -50,7 +53,7 @@ exports[`coach chat golden transcripts > 01a tool mode, two rounds, present data
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -265,6 +268,9 @@ exports[`coach chat golden transcripts > 01b tool mode, present data, one figure
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -296,7 +302,7 @@ exports[`coach chat golden transcripts > 01b tool mode, present data, one figure
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -523,6 +529,9 @@ exports[`coach chat golden transcripts > 02 tool mode, every tool missed, fabric
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -554,7 +563,7 @@ exports[`coach chat golden transcripts > 02 tool mode, every tool missed, fabric
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -889,6 +898,9 @@ exports[`coach chat golden transcripts > 03 no-tools path with the full snapshot
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1339,6 +1351,9 @@ exports[`coach chat golden transcripts > 06 budget exceeded 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1390,6 +1405,9 @@ exports[`coach chat golden transcripts > 07a all providers failed (plain) 1`] =
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1421,7 +1439,7 @@ exports[`coach chat golden transcripts > 07a all providers failed (plain) 1`] =
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -1479,6 +1497,9 @@ exports[`coach chat golden transcripts > 07b all providers failed (every hop rat
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1510,7 +1531,7 @@ exports[`coach chat golden transcripts > 07b all providers failed (every hop rat
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -1568,6 +1589,9 @@ exports[`coach chat golden transcripts > 07c all providers failed (primary crede
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1599,7 +1623,7 @@ exports[`coach chat golden transcripts > 07c all providers failed (primary crede
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -1657,6 +1681,9 @@ exports[`coach chat golden transcripts > 08 an unwrapped tagged provider error 1
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1688,7 +1715,7 @@ exports[`coach chat golden transcripts > 08 an unwrapped tagged provider error 1
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -1747,6 +1774,9 @@ exports[`coach chat golden transcripts > 09 an aborted request leaves a cancelle
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1778,7 +1808,7 @@ exports[`coach chat golden transcripts > 09 an aborted request leaves a cancelle
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -1843,6 +1873,9 @@ exports[`coach chat golden transcripts > 10 outbound block replaces the reply an
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -1874,7 +1907,7 @@ exports[`coach chat golden transcripts > 10 outbound block replaces the reply an
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -2219,6 +2252,9 @@ exports[`coach chat golden transcripts > 11a KEYVALUES valid 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -2250,7 +2286,7 @@ exports[`coach chat golden transcripts > 11a KEYVALUES valid 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -2469,6 +2505,9 @@ exports[`coach chat golden transcripts > 11b KEYVALUES partial 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -2500,7 +2539,7 @@ exports[`coach chat golden transcripts > 11b KEYVALUES partial 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -2717,6 +2756,9 @@ exports[`coach chat golden transcripts > 11c KEYVALUES malformed (no closing mar
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -2748,7 +2790,7 @@ exports[`coach chat golden transcripts > 11c KEYVALUES malformed (no closing mar
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -2949,6 +2991,9 @@ exports[`coach chat golden transcripts > 11d sentinel-only reply is an empty rep
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -2980,7 +3025,7 @@ exports[`coach chat golden transcripts > 11d sentinel-only reply is an empty rep
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -3049,6 +3094,9 @@ exports[`coach chat golden transcripts > 11e empty provider reply 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -3080,7 +3128,7 @@ exports[`coach chat golden transcripts > 11e empty provider reply 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -3140,6 +3188,9 @@ exports[`coach chat golden transcripts > 12a SUGGEST-REMINDER surfaced 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -3171,7 +3222,7 @@ exports[`coach chat golden transcripts > 12a SUGGEST-REMINDER surfaced 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -3410,6 +3461,9 @@ exports[`coach chat golden transcripts > 12b SUGGEST-REMINDER suppressed 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -3441,7 +3495,7 @@ exports[`coach chat golden transcripts > 12b SUGGEST-REMINDER suppressed 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -3644,6 +3698,9 @@ exports[`coach chat golden transcripts > 13 SUGGEST-ACTION 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -3675,7 +3732,7 @@ exports[`coach chat golden transcripts > 13 SUGGEST-ACTION 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -3918,6 +3975,9 @@ exports[`coach chat golden transcripts > 14a REMEMBER valid 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -3949,7 +4009,7 @@ exports[`coach chat golden transcripts > 14a REMEMBER valid 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -4172,6 +4232,9 @@ exports[`coach chat golden transcripts > 14b REMEMBER malformed 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -4203,7 +4266,7 @@ exports[`coach chat golden transcripts > 14b REMEMBER malformed 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -4421,6 +4484,9 @@ exports[`coach chat golden transcripts > 15 an unknown learn link is scrubbed, a
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -4452,7 +4518,7 @@ exports[`coach chat golden transcripts > 15 an unknown learn link is scrubbed, a
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -4668,6 +4734,9 @@ exports[`coach chat golden transcripts > 16a coach.provider.none via an empty ch
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "annotate",
"metaKeys": [],
@@ -4707,6 +4776,9 @@ exports[`coach chat golden transcripts > 16b an empty chain served by the legacy
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -5064,6 +5136,9 @@ exports[`coach chat golden transcripts > 16d coach.provider.none from the capabi
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -5109,6 +5184,9 @@ exports[`coach chat golden transcripts > 17a a workout-scoped first turn 1`] = `
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "isModuleEnabled",
"module": "workouts",
@@ -5153,7 +5231,7 @@ exports[`coach chat golden transcripts > 17a a workout-scoped first turn 1`] = `
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -5368,6 +5446,9 @@ exports[`coach chat golden transcripts > 17b a workout-scoped first turn with th
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "annotate",
"metaKeys": [
@@ -5408,7 +5489,7 @@ exports[`coach chat golden transcripts > 17b a workout-scoped first turn with th
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -5611,6 +5692,9 @@ exports[`coach chat golden transcripts > 18 a persistence throw after the provid
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -5642,7 +5726,7 @@ exports[`coach chat golden transcripts > 18 a persistence throw after the provid
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -5757,7 +5841,7 @@ exports[`coach chat golden transcripts > 19 an existing conversation with prior
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -5984,6 +6068,9 @@ exports[`coach chat golden transcripts > 20 a guided-question turn with a scope
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -6015,7 +6102,7 @@ exports[`coach chat golden transcripts > 20 a guided-question turn with a scope
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 30000,
"tools": 6,
@@ -6198,6 +6285,9 @@ exports[`coach chat golden transcripts > 21 the German locale with a stored self
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -6229,7 +6319,7 @@ exports[`coach chat golden transcripts > 21 the German locale with a stored self
"window": "last30days",
},
"signal": true,
"system": "ea823e5e510a:4068",
"system": "7c188ee61f98:5016",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -6448,6 +6538,9 @@ exports[`coach chat golden transcripts > 22 tool mode with live steps: found, em
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
{
"call": "assertConsentForChain",
"chain": [
@@ -6479,7 +6572,7 @@ exports[`coach chat golden transcripts > 22 tool mode with live steps: found, em
"window": "last30days",
},
"signal": true,
"system": "d46e2a89426f:3975",
"system": "37f58b1a7608:4923",
"temperature": 0.4,
"timeoutMs": 180000,
"tools": 6,
@@ -7064,6 +7157,9 @@ exports[`coach chat golden transcripts — refusals before the stream > R6 conse
{
"call": "storeDeterministicFacts",
},
{
"call": "enqueueCoachMemoryRefresh",
},
],
"contentType": null,
"frames": [],
+5
View File
@@ -31,6 +31,8 @@ import {
import {
PLAN_REVIEW_DAYS,
REMEMBER_FACT_MAX_CHARS,
} from "@/lib/ai/coach/memory/shared";
import {
proposePlanFromTool,
rememberFactFromTool,
} from "@/lib/ai/coach/memory/contract";
@@ -200,6 +202,8 @@ export interface DialogToolContext {
locale: Locale;
/** The person's current message: the only text a fact may come from. */
userMessage: string;
/** The stored id of that message, which a remembered fact points back to. */
userMessageId?: string;
/** What the record holds; null on the no-tools path. */
inventory: InventoryEntry[] | null;
/**
@@ -319,6 +323,7 @@ export async function runDialogTool(args: {
userId: ctx.userId,
conversationId: ctx.conversationId,
userMessage: ctx.userMessage,
...(ctx.userMessageId ? { userMessageId: ctx.userMessageId } : {}),
call: parsed.data,
});
if (outcome.kind === "declined") {
+7 -1
View File
@@ -228,6 +228,12 @@ export async function runCoachToolLoop(args: {
locale?: Locale;
/** v1.41 — what the dialog tools need. Absent: they are declined. */
dialog?: DialogToolContext;
/**
* v1.41 — a note the answer already carries before any round (a health
* fact the background found, offered once). It is the answer's one note:
* `remember_fact` is declined beside it.
*/
initialMemoryNote?: CoachMemoryNote;
/** v1.41 — the figures read so far, for screening a checkpoint. */
checkpointScreen?: { userMessage: string };
}): Promise<CoachToolLoopResult> {
@@ -276,7 +282,7 @@ export async function runCoachToolLoop(args: {
const toolTrace: CoachToolTrace[] = [];
const toolResults: CoachToolResult[] = [];
const declinedClarifications: CoachClarification[] = [];
let memoryNote: CoachMemoryNote | undefined;
let memoryNote: CoachMemoryNote | undefined = args.initialMemoryNote;
let planProposal: CoachPlanProposal | undefined;
let callCount = 0;
let stopReason: CoachStopReason | null = null;
@@ -131,6 +131,7 @@ function persistedProvenance(): CoachProvenance {
beforeEach(() => {
for (const fn of Object.values(m)) fn.mockReset();
m.persistUserTurn.mockResolvedValue({ messageId: "m-user" });
m.resolveTurnConversation.mockResolvedValue({
conversation: {
conversationId: "c1",
@@ -238,6 +238,7 @@ function modelPath(prefs = DEFAULT_COACH_PREFS) {
beforeEach(() => {
for (const fn of Object.values(m)) fn.mockReset();
m.persistUserTurn.mockResolvedValue({ messageId: "m-user" });
m.readFollowUpHistory.mockResolvedValue({
today: "2026-09-27",
firstDate: { bp: "2024-01-01" },
+13 -12
View File
@@ -177,18 +177,19 @@ export async function assembleTurnContext(args: {
// Fact capture must never break the chat turn; the >TURN_CAP LLM
// extraction remains as the catch-all on long conversations.
});
if (turnContext.historyElided) {
void enqueueCoachMemoryRefresh({
conversationId,
userId,
// Coach memory prose is composed in de/en only — it is MODEL-FACING
// context (a rolling conversation summary + extracted durable facts),
// not user-facing prose, so English is the correct target for every
// locale without a reviewed body. The former `=== "en" ? "en" : "de"`
// binary composed and keyed a French account's memory in German.
locale: instructionLocale(locale),
});
}
// v1.41 — every turn enqueues the refresh; the job waits for the
// conversation to go quiet and runs once per conversation (its debounce
// and singleton key), so a short chat is remembered too.
void enqueueCoachMemoryRefresh({
conversationId,
userId,
// Coach memory prose is composed in de/en only — it is MODEL-FACING
// context (a rolling conversation summary + extracted durable facts),
// not user-facing prose, so English is the correct target for every
// locale without a reviewed body. The former `=== "en" ? "en" : "de"`
// binary composed and keyed a French account's memory in German.
locale: instructionLocale(locale),
});
// A workout launch is an optional narrowing of Coach, not permission to
// bypass the workouts module. Disabled modules contribute no read and no
+4 -2
View File
@@ -163,10 +163,12 @@ export async function resolveTurnConversation(args: {
export async function persistUserTurn(
conversationId: string,
message: string,
): Promise<void> {
await appendMessage({
): Promise<{ messageId: string }> {
const stored = await appendMessage({
conversationId,
role: "user",
content: message,
});
// v1.41 — the id a remembered fact points back to.
return { messageId: stored.id };
}
+15 -10
View File
@@ -282,6 +282,8 @@ export async function runTurnModel(args: {
priorResults?: PriorResultTurn[];
/** v1.41 — the person's message, the only source of a remembered fact. */
message: string;
/** v1.41 — that message's stored id. */
userMessageId?: string;
/** v1.41 — the reasoning the turn asks for, already resolved. */
reasoning?: TurnReasoning;
/** v1.41 — who pays: fixes the turn's budget. */
@@ -333,8 +335,13 @@ export async function runTurnModel(args: {
userId,
conversationId,
locale,
// The block re-runs the wire egress check for exactly these.
providerTypes: chain.map((entry) => entry.providerType),
}).catch(() => null);
if (memory) {
recorder.setRecalled(memory.recalled);
}
if (memory && memory.factIds.length + memory.planIds.length > 0) {
const count = memory.factIds.length + memory.planIds.length;
const id = activity.start({
phase: "memory",
@@ -365,12 +372,8 @@ export async function runTurnModel(args: {
reach: ctx.reach,
prior: args.priorResults ?? [],
});
const requestWith = (tableRules: boolean) => {
// `memoryBlock` is the memory package's parameter; held in a
// variable so the request builds whether or not it is declared yet.
const request: Parameters<typeof buildCoachToolRequest>[0] & {
memoryBlock?: string;
} = {
const requestWith = (tableRules: boolean) =>
buildCoachToolRequest({
systemPrompt: ctx.systemPrompt,
toolModeAddendum: appendBlocks(buildToolModeAddendum(locale), [
buildDialogAddenda(locale, { tableRules }),
@@ -384,9 +387,7 @@ export async function runTurnModel(args: {
transcript: turnContext.transcript,
languageName: LANGUAGE_NAMES[locale],
...(memory ? { memoryBlock: memory.text } : {}),
};
return buildCoachToolRequest(request);
};
});
// The recheck and follow-up rules are about tables: they ride the
// prompt once the conversation holds one, or from the round after this
// turn produced its first.
@@ -445,11 +446,15 @@ export async function runTurnModel(args: {
...(args.reasoning ? { reasoning: args.reasoning } : {}),
activity,
locale,
...(memory?.pendingProposal
? { initialMemoryNote: memory.pendingProposal }
: {}),
dialog: {
userId,
conversationId,
locale,
userMessage: args.message,
...(args.userMessageId ? { userMessageId: args.userMessageId } : {}),
inventory: inventory.entries,
conversationWindowSet: args.conversationWindowSet,
},
@@ -698,7 +703,7 @@ export async function runTurnModel(args: {
activity: recorder,
toolClarification: null,
declinedClarifications: [],
memoryNote: null,
memoryNote: memory?.pendingProposal ?? null,
planProposal: null,
interimSent: false,
};
+5 -1
View File
@@ -149,7 +149,10 @@ async function runTurn(
...(clarifiedLine ? [clarifiedLine] : []),
];
await persistUserTurn(workingConversationId, message);
const { messageId: userMessageId } = await persistUserTurn(
workingConversationId,
message,
);
const ctx = await assembleTurnContext({
userId,
@@ -219,6 +222,7 @@ async function runTurn(
turnHints,
priorResults: conversation.priorResults,
message,
userMessageId,
reasoning,
payer,
conversationWindowSet: input.scope?.window !== undefined,
+2
View File
@@ -49,6 +49,8 @@ import { createSseStream } from "@/lib/sse/create-stream";
import {
COACH_MEMORY_KEYS,
COACH_PLAN_KEYS,
} from "@/lib/ai/coach/memory/shared";
import {
decideFactProposal,
decidePlanProposal,
} from "@/lib/ai/coach/memory/contract";