diff --git a/src/lib/ai/coach/snapshot.ts b/src/lib/ai/coach/snapshot.ts index ed57dc5cf..f8698b5fb 100644 --- a/src/lib/ai/coach/snapshot.ts +++ b/src/lib/ai/coach/snapshot.ts @@ -83,6 +83,16 @@ import type { export interface CoachSnapshotResult { snapshotJson: string; + /** + * v1.20.0 (F1) — the structured, post-degrade snapshot record `snapshotJson` + * is serialised from. Keyed by domain block (`bloodPressure`, `weight`, + * `pulse`, `mood`, `compliance`, `glucose`, `sleep`, `sleepRhythm`, `labs`, + * `illness`, `derived`, `dayStrain`, `trajectory`, `weeklyContext`, …) plus a + * `scope` block. The coach tool executor reads a single domain block out of + * this so an on-demand retrieval tool returns exactly the numbers the legacy + * snapshot-stuffing path would have shown — identical builder, gates, and I/O. + */ + sections: Record; /** * Provenance built from snapshot keys actually present. Stays in * sync with the SNAPSHOT block so the source-chip row mirrors what @@ -2352,6 +2362,13 @@ async function buildCoachSnapshotImpl( return { snapshotJson: JSON.stringify(compactSnapshot, null, 2), + // v1.20.0 (F1) — the structured, post-degrade snapshot record keyed by + // domain block (`bloodPressure`, `glucose`, `labs`, …). The coach tool + // executor slices a single domain block out of this so a retrieval tool + // returns exactly the numbers the legacy snapshot path would have shown — + // same builder, same gates, same I/O. `snapshotJson` is this record + // serialised; exposing the record avoids re-parsing it. + sections: compactSnapshot, provenance: { windows: Array.from(windows), metrics: Array.from(metrics), diff --git a/src/lib/ai/coach/types.ts b/src/lib/ai/coach/types.ts index 13c19b255..11862a186 100644 --- a/src/lib/ai/coach/types.ts +++ b/src/lib/ai/coach/types.ts @@ -338,6 +338,15 @@ export interface CoachProvenance { * on a conversation reload. Absent on turns that carry no suggestion. */ suggestion?: CoachSuggestion; + /** + * v1.20.0 (F1) — the retrieval-tool trace for this turn: which tools the + * Coach called and whether each found data (`present`). Metadata only — no + * raw values beyond what `keyValues` already persists. Lets a conversation + * reload show "what I looked at" and lets the hallucination audit replay + * grounding. Absent on the legacy snapshot path + on turns that called no + * tools. + */ + toolCalls?: ReadonlyArray<{ name: string; present: boolean }>; } /** diff --git a/src/lib/openapi/routes/coach.ts b/src/lib/openapi/routes/coach.ts index dcc6250c5..a52a4bcc4 100644 --- a/src/lib/openapi/routes/coach.ts +++ b/src/lib/openapi/routes/coach.ts @@ -277,6 +277,17 @@ const coachProvenanceSchema = z .describe( "Load-bearing numbers the assistant surfaced, rendered in the collapsible evidence block. Hard-capped at 8 entries.", ), + toolCalls: z + .array( + z.object({ + name: z.string(), + present: z.boolean(), + }), + ) + .optional() + .describe( + "v1.20.0 — the retrieval-tool trace for this turn: which tools the Coach called and whether each found data. Metadata only (no values). Absent on the legacy snapshot path and on turns that called no tools.", + ), }) .meta({ id: "CoachProvenance",