feat(coach): expose structured snapshot sections + tool-trace provenance

The Coach snapshot builder returns the structured, post-degrade record it
serialises to JSON alongside the JSON string, so a single domain block can be
read out without re-parsing. Add an optional tool-call trace to the provenance
envelope (which retrieval tools ran + whether each found data) and mirror it in
the OpenAPI provenance schema.

Groundwork for on-demand retrieval: the retrieval tools slice one section out
of this record, reusing the builder's gates and I/O verbatim.
This commit is contained in:
Marc-André Bombeck committed 2026-06-22 07:37:15 +02:00
1 parent b07fbcb6fa
commit ec77862286
3 files changed
+37

No files matched your search

+17
View File
@@ -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<string, unknown>;
/**
* 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),
+9
View File
@@ -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 }>;
}
/**
+11
View File
@@ -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",