Answer history questions from the records, and ask one question while the family is still there - #4
Draft
Laolex wants to merge 4 commits into
Draft
Answer history questions from the records, and ask one question while the family is still there#4Laolex wants to merge 4 commits into
Laolex wants to merge 4 commits into
Conversation
Lifts the grounding vocabulary and its checks out of the eval script into a module both callers share, so the rule that scores a saved handover is the same code that will gate an answer. ungroundedWords takes an extra allowlist so a caller can widen its own vocabulary without moving what anyone else measures: the eval passes none, and its published numbers are unchanged.
Replaces the canned keyword sentence with a bounded tool-use loop over four read-only tools scoped to one circle. The model never sees the database and never names a circle, so an instruction injected into a family email can change what it says but not which family it can read. An answer is served only if it cited a record, reads as no medical advice, and every content word traces back to what the tools returned. Otherwise the keyword answer stands: it is computed first every time, so a slow, down, or wrong provider still leaves the family an answer with evidence attached. Tool calls are persisted so any answer can be audited afterwards.
Capture opens a spoken realtime session alongside the recorder, so a missing concrete detail - who agreed to do a thing, and when - can be asked for once, aloud, while it is still remembered. The reply lands in the same recording. The session deliberately does not produce the transcript. A transcript from the browser cannot be checked against the audio, and the eval, the history agent, and the promise that a handover holds only what the family said all rest on the transcript being faithful to the recording. So the audio is still recorded locally and still transcribed server side, and the question is passed to the drafting model as context, marked as something the app said. The browser never holds our API key: it is given a short-lived client secret, restricted to circle members and rate limited, with the model and the safety instructions pinned at mint time. The recorder starts first and never waits on the network, so a refused mint, a browser without WebRTC, or a provider outage costs the family nothing but the question.
Three small guards, one theme. The eval reported 100.0% on a database with no handovers in it. That is the confident wrong number the harness exists to catch, and it caught us: the repo .env points DATABASE_PATH at a file the deployment does not use. It now exits 2 and names the path it read. A realtime session bills for as long as it stays open, and a carer may leave a recording running. The listening window is capped server side, default three minutes, and the recording is untouched by it. Reading the spoken question out of a realtime event is the code most exposed to the provider renaming a field. It moves to capture-events.js, apart from app.js and free of any DOM, and is now tested against shapes it should read and shapes it should ignore - including the family own speech, which must never be mistaken for the question.
Laolex
marked this pull request as draft
July 29, 2026 08:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #2 (
fix/week1-debt) so the diff shows only the new work. Base moves tomainonce #2 merges.Not for merge or deploy yet — judging is live. Production still runs
docs/backups-erasure-and-hostnamesand was not touched by any of this.What this adds
An agent that answers from the records.
agent.jsruns a bounded tool-use loop (4 steps, 6 rows, 25s) over four read-only tools —search_sources,get_topic_thread,list_commitments,get_handover. Every query is parameterised and scoped by acircle_idthe server supplies; the model never chooses which circle it reads. Voice notes and inbound email are untrusted text, so the isolation lives in SQL, not in prompt wording.Three gates sit between the model and the family:
A discarded answer falls back to the deterministic path, and the mode is recorded (
agentic/deterministic/deterministic-after-rejection) so a silently-degrading model is visible rather than invisible. Tool calls are persisted tohistory_tool_calls.One grounding rule, shared.
grounding.jsextracts the check the eval already used, so the agent is held to the same rule the published numbers were measured against.extraAllowedlets the agent widen its vocabulary without moving the eval's baseline.Guided recording. The app can now ask one short question while the family is still standing there, via the Realtime API. Ephemeral client secrets — the browser never sees the server key. The recorder starts first and never waits on the network, so if the realtime session fails to open, capture is unaffected and the endpoint returns
fallback: 'record'. Sessions are capped (REALTIME_MAX_SECONDS, default 180) because a realtime session bills for as long as it stays open; the cap closes the listener, never the recording. The question reaches the handover as context, explicitly marked as not something the family said.Three fail-loudly guards. The eval used to report
100.0%on an empty database — the exact confident-wrong number it exists to catch, and it caught us this week when.envpointed at a database the deployment does not use. It now exits2and names the path it read.spokenQuestionmoved intocapture-events.js, DOM-free and directly tested — including that auser-role transcript returns'', so the family's own speech can never be mistaken for the question the app asked.Verification
37/37 passing. Each new guard was mutation-tested: disabling the advice gate, the grounding gate, the
circle_idpredicate, and the key redaction each fails a specific test, and every restore was verified byte-identical.Known gaps
getUserMedia,RTCPeerConnection, the SDP exchange — has never run against the live Realtime API. Not coverable without a real browser.main, and the deployed branch has 3 commitsmaindoes not. A three-way reconcile is needed before any deploy.