feat: ground Timmy chat answers in cited ledger records (#37) #78

Closed
rockachopa wants to merge 0 commits from timmy/37-grounded-chat-citations into main
pull from: timmy/37-grounded-chat-citations
Member

Closes #37

Outcome

Timmy chat now has to show its work: an answer that states a count, a date, or a visible form cites the confirmed records behind it, and following a citation opens exactly that log. A question the journal cannot answer gets boundary copy instead of reassurance.

What changed

  • src/grounded-chat.js — new module owning the citation contract: intent routing (frequency / recency / pattern / privacy / unsupported / summary), the sparse-window refusal, and resolution of Hermes citations against the ledger it was given.
  • app.js — local replies now come from answerFromLedger; Hermes replies pass through resolveAgentReply; citation chips render under the bubble and open a single-record sheet ([data-chat-citation], [data-chat-record-sheet]).
  • src/hermes-agent-service.js — each sanitized ledger record carries a positional ref (L1..Ln) and the prompt requires inline [entry:L<n>] citations for any journal fact. Refs stay positioned in the same order the browser sent, so the browser can resolve — and reject — them.
  • scripts/record_release_demo.mjs — the recording now shows the grounded local answer with four citation chips, opens one cited record, shows the refusal, then a Hermes reply whose refs render as sources. Every claim is asserted in the browser; the agent route is flipped off/on so local mode is a real state, not narration.
  • scripts/build_release.py — new on-camera caption gates and a grounded_chat_citations gate; test:grounded-chat added to the release check sequence.

Acceptance criteria

  • Answers cite dates/counts from the local ledger — frequency, recency, pattern, and summary answers each carry the records they used; out-of-window logs are neither counted nor cited.
  • Unsupported questions get boundary copy, not fabricated reassurance — food/restaurant/medicine/symptom-meaning questions get refusal copy with zero citations; a window below the minimum refuses to describe a trend.
  • Urgent symptom disclosures route to deterministic escalation — unchanged and still intercepted before Hermes; escalation bubbles carry no citations.

Evidence

  • node --test tests/grounded-chat.test.js — 10/10 (each behaviour written failing first; the empty-reply default and the L99 fabrication case were both caught by RED before implementation).
  • npm test — 185/185 pass.
  • npm run test:grounded-chat — PASS (real browser, synthetic logs, includes a Hermes reply citing a nonexistent record → labelled unverified, no citation rendered).
  • Full suite of acceptance runs: ui, photo, sleek, manual-fallback, provisional, followup, vision-refusal, pattern-summary, mobile-capture — all PASS.
  • npm run check:syntax and npm run check:dirty/check:diff clean.
  • scripts/record_release_demo.mjs recorded end-to-end (198s webm); OCR of frames confirms on-camera captions plus the visible "FROM YOUR LOG Oct 16 · T4 …" citation chips and the "4 confirmed logs" answer.

Privacy / safety disposition

  • No new data leaves the browser: citations are built from the ledger already in local storage; the Hermes path sends the same 20 text-only entries it already sent, now with positional refs and no photographs.
  • No new medical claim. The module refuses food/medicine/diagnosis questions and repeats the pattern summary's own boundary copy; the non-diagnostic boundary is unchanged.
  • No real medical images, model weights, credentials, or infrastructure changes. Screenshots committed are UI renders over fictional sample logs.

Notes

  • tests/staging-health.test.js ("malformed and escaping base paths") failed once inside a loaded full-suite run and passed in isolation and on the baseline worktree; it is a 800ms spawn-budget flake, unrelated to this change.
  • One test (tests/release-demo.test.js demo-caption gate) is a hold-out check written after the recording change; it did not produce a RED cycle.
Closes #37 ## Outcome Timmy chat now has to show its work: an answer that states a count, a date, or a visible form cites the confirmed records behind it, and following a citation opens exactly that log. A question the journal cannot answer gets boundary copy instead of reassurance. ## What changed - `src/grounded-chat.js` — new module owning the citation contract: intent routing (frequency / recency / pattern / privacy / unsupported / summary), the sparse-window refusal, and resolution of Hermes citations against the ledger it was given. - `app.js` — local replies now come from `answerFromLedger`; Hermes replies pass through `resolveAgentReply`; citation chips render under the bubble and open a single-record sheet (`[data-chat-citation]`, `[data-chat-record-sheet]`). - `src/hermes-agent-service.js` — each sanitized ledger record carries a positional `ref` (`L1..Ln`) and the prompt requires inline `[entry:L<n>]` citations for any journal fact. Refs stay positioned in the same order the browser sent, so the browser can resolve — and reject — them. - `scripts/record_release_demo.mjs` — the recording now shows the grounded local answer with four citation chips, opens one cited record, shows the refusal, then a Hermes reply whose refs render as sources. Every claim is asserted in the browser; the agent route is flipped off/on so local mode is a real state, not narration. - `scripts/build_release.py` — new on-camera caption gates and a `grounded_chat_citations` gate; `test:grounded-chat` added to the release check sequence. ## Acceptance criteria - [x] Answers cite dates/counts from the local ledger — frequency, recency, pattern, and summary answers each carry the records they used; out-of-window logs are neither counted nor cited. - [x] Unsupported questions get boundary copy, not fabricated reassurance — food/restaurant/medicine/symptom-meaning questions get refusal copy with zero citations; a window below the minimum refuses to describe a trend. - [x] Urgent symptom disclosures route to deterministic escalation — unchanged and still intercepted before Hermes; escalation bubbles carry no citations. ## Evidence - `node --test tests/grounded-chat.test.js` — 10/10 (each behaviour written failing first; the empty-reply default and the `L99` fabrication case were both caught by RED before implementation). - `npm test` — 185/185 pass. - `npm run test:grounded-chat` — PASS (real browser, synthetic logs, includes a Hermes reply citing a nonexistent record → labelled unverified, no citation rendered). - Full suite of acceptance runs: ui, photo, sleek, manual-fallback, provisional, followup, vision-refusal, pattern-summary, mobile-capture — all PASS. - `npm run check:syntax` and `npm run check:dirty`/`check:diff` clean. - `scripts/record_release_demo.mjs` recorded end-to-end (198s webm); OCR of frames confirms on-camera captions plus the visible "FROM YOUR LOG Oct 16 · T4 …" citation chips and the "4 confirmed logs" answer. ## Privacy / safety disposition - No new data leaves the browser: citations are built from the ledger already in local storage; the Hermes path sends the same 20 text-only entries it already sent, now with positional refs and no photographs. - No new medical claim. The module refuses food/medicine/diagnosis questions and repeats the pattern summary's own boundary copy; the non-diagnostic boundary is unchanged. - No real medical images, model weights, credentials, or infrastructure changes. Screenshots committed are UI renders over fictional sample logs. ## Notes - `tests/staging-health.test.js` ("malformed and escaping base paths") failed once inside a loaded full-suite run and passed in isolation and on the baseline worktree; it is a 800ms spawn-budget flake, unrelated to this change. - One test (`tests/release-demo.test.js` demo-caption gate) is a hold-out check written after the recording change; it did not produce a RED cycle.
rockachopa added 1 commit 2026-10-18 08:17:59 +00:00
feat: ground Timmy chat answers in cited ledger records (#37)
Quality gates / quality (pull_request) Failing after 2m50s
b838beb548
Chat now has to show its work. Every answer that states a count, a date, or a
visible form cites the confirmed records behind it, and following a citation
opens exactly that log. Questions the journal cannot answer (food safety,
medicine, what a symptom means) get boundary copy with no borrowed evidence,
and a sparse window refuses to describe a trend instead of naming one.

The Hermes path is held to the same rule: the server gives each ledger record a
positional ref, the prompt requires inline [entry:L<n>] citations, and the
browser resolves them against the ledger it sent. A ref that matches no record
is stripped and the answer is labelled unverified rather than rendered as a
source, so an invented citation cannot look like evidence.

The release recording now exercises the grounded local answer, its citation
sheet, the refusal, and a Hermes reply whose refs resolve, each asserted in the
browser with fictional logs.
rockachopa closed this pull request 2026-10-18 12:02:14 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.