release/v1.11.1 — source-aware vitals + Coach long-term memory#252
Merged
Conversation
Rollup rows were grouped by (type, day) only, so two sources reporting the same standard vital for one day (e.g. WHOOP + Apple Watch resting heart rate) were AVG-blended into a single row instead of resolving to the source-priority ladder's canonical reading. Re-grain the rollup table to (type, day, source): the writer now groups by source and delete-then-inserts each affected partition (so a source that stops reporting a bucket cannot strand a stale row), and the read path will collapse overlapping sources to the canonical reading in a follow-up commit. Cumulative metrics (steps, energy) keep summing per source. Migration 0115 purges the legacy source-blind aggregates; the boot backfill + read-time self-heal re-mint per-source rows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
collapseRollupRowsBySource resolves the per-source rollup rows minted by the source-aware writer down to one row per bucket, using the user's source-priority ladder (the same ordering the raw-row picker uses). A metricKeyForType superset maps every overlapping non-cumulative vital (RHR, HRV-SDNN, SpO2, body/skin temperature, respiratory rate, weight, body fat, blood pressure, pulse, VO2max, recovery) plus the cumulative keys onto a SourcePriorityMetricKey. Unlisted sources fall back to the highest-count row so a bucket never doubles or goes dark. Wired into the rollup readers in the following commits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
readRollupBuckets and the WEEK/MONTH/YEAR readers now select the source column and run collapseRollupRowsBySource before composing, so every rollup consumer (dashboard sparkline, fast-paths, derived metrics, range deltas) sees one canonical row per bucket. loadUserSourcePriority loads the ladder blob once; readBestGranularityRollups threads it through each granularity probe so the per-floor walk never re-queries the user. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete the source-aware rollup read path: the slim summaries slice (rollup + live), the comprehensive aggregator (both paths, including the dailyByType correlation feed), and the /api/measurements route (rollup read + the live date_trunc fallback) now resolve overlapping sources to the source-priority ladder's canonical reading per (type, day) before aggregating. A dual-source vital (e.g. WHOOP + Apple Watch resting heart rate) surfaces one canonical series instead of a blend or a doubled count; cumulative metrics keep summing per canonical source. The collapse runs in SQL where the aggregate must stay a single round-trip (buildSourceRankCase emits a per-user ladder-rank CASE; canonicalMeasurementsFrom restricts a live read to canonical-source rows) and in application code (collapseRollupRowsBySource) for the row readers, both driven by the same resolved ladders so live and rollup agree. Also widens the enum-literal SQL whitelist to accept digits so VO2_MAX no longer throws on the typed write-hook recompute or the rank CASE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the AES-256-GCM Bytes codec from persistence into a shared bytes-codec module so the conversation summary and the new personal-fact store reuse one ArrayBuffer-backed implementation. Add the CoachFact table (migration 0116) for durable, user-scoped facts the Coach extracts from conversations — encrypted fact text, plain category/confidence/ source for rank-without-decrypt, soft-delete for a reversible forget. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add CoachConversation.summaryEncrypted and CoachFact.factEncrypted to the key-rotation registry, mirroring the CoachMessage.encryptedContent Bytes round-trip, so an operator dropping a legacy key re-encrypts the new Coach-memory columns instead of bricking them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the two background generators behind Coach long-term memory, both reusing the budget-aware provider chain (runStatusCompletion) with a local fallback and encrypting their output at rest: - conversation-summary: folds the turns elided past the history window into a rolling natural-language summary, merging into any prior summary; short-circuits cheaply until a conversation grows long. - facts: extracts durable, descriptive personal facts (preferences, stated conditions, goals, constraints, context), gated by a Zod schema + de-dup + per-user cap; buildCoachFactsBlock ranks the top facts for snapshot injection. Never diagnostic, never numeric. Both are pure/injectable for unit testing and annotate counts only, never content. Wiring into the queue, route, and snapshot follows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the coach-memory-refresh pg-boss queue: a fire-and-forget enqueue contract (coach-memory-shared) and a worker pipeline that runs the conversation-summary refresh then the fact extraction for one long conversation, each fault-isolated and budget-gated. Register the queue in allQueues + a boss.work handler in the worker, with a source-grep inventory guard so the queue can never ship unregistered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fetchConversationWithMessages now returns the decrypted rolling summary (fail-closed → null), and buildHistoryWindow substitutes it for the dead placeholder so a long conversation keeps memory of its elided turns. Once a conversation grows past the turn cap the chat turn fires a fire-and-forget coach-memory-refresh so the next long turn is fresh, without adding a provider round-trip to the interactive reply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
buildCoachMemoryBlock gains a third fault-isolated sub-source — the top-N durable facts — folded under snapshot.memory so they ride the existing lowest-priority registration and are shed first under the char budget. Add an EN + DE system-prompt paragraph instructing the Coach to use facts as the user's own stated, descriptive context — never as a diagnosis, never invented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the GDPR/trust surface for the durable Coach facts: GET lists the
caller's active facts decrypted, DELETE /[id] soft-deletes one, and a
bulk DELETE clears all — every verb apiHandler + requireAuth +
requireAssistantSurface("coach") + ownership-scoped, with an unknown or
cross-user id collapsing to an idempotent no-op that never leaks
existence. Registered in the OpenAPI contract.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- conversation-summary: fault-isolate the per-message decrypt in the fold loop so one undecryptable historic turn (e.g. mid key-rotation) no longer permanently stalls a conversation's summary refresh. - source collapse: unify the no-ladder tiebreak on alphabetically smallest source across the JS reader and the all-time rollup SQL, so a ranked vital whose day carries only off-ladder sources resolves to the same canonical source on the warm and the live-SQL read paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the two never-wired *_PROMPT_VERSION exports the dead-code gate flagged, and consume the summary-length target as a real backstop that caps the stored rolling summary so a misbehaving provider can't bloat the encrypted column or the next prompt's history window. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the three v1.11.0 follow-ups: cross-source vital de-duplication (measurement rollups resolve overlapping sources to the priority-ladder canonical reading across charts, summaries, insights, and the live fallback), the Coach's rolling encrypted conversation summary, and durable encrypted personal facts with a list/forget surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Closes the three v1.11.0 deferrals: cross-source vital de-duplication, Coach conversation-summary memory, and durable personal facts. Full suite green (6889), typecheck/lint/openapi clean, W10 QA reconciled (0 open Critical/High/Medium). Migrations 0115 (source-aware rollups) + 0116 (coach_facts), both additive/multi-tenant-safe.
🤖 Generated with Claude Code