fix: one open conversation per user, unkeyed from day_number#35
Merged
Conversation
Conversations were keyed on (owner, status, day_number), so every study day started a fresh conversation. Since chat history is scoped to one conversation, the LLM lost all context at each day boundary — observed on prod as an empty chat_history in the generation snapshot despite a week of messages. Conversations are now keyed only on (owner, status='open'): created on the user's first message and reused for all subsequent ones. The weekly history window is enforced by the existing since_timestamp filter, not by conversation boundaries. day_number remains in utterance metadata (daily prompt selection, generation snapshot). The migration merges each user's per-day open conversations into their earliest one (reassigning utterances, keeping max last_activity_at), strips per-request prompt keys from conversations.meta (now captured per-reply in the texet_generation snapshot), and replaces the day-scoped unique indexes with a single one-open-conversation-per-user index. Note: the no-day partial index had been silently cascade-dropped by d740e0090bdc when it removed the day_identifier column its predicate referenced, so the migration drops it conditionally. Also passes chat_history via the Kani constructor instead of attribute assignment, and removes the now-stale day/prompt fields from the conversation admin view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 11, 2026
1xabhay
added a commit
that referenced
this pull request
Jun 11, 2026
* feat: normalize Converse messages at the Bedrock engine boundary Merge consecutive same-role turns, prepend a placeholder user turn when history starts with an assistant message, and drop empty/None content. Converse requires user-first, strictly alternating, non-empty turns; owning that constraint in the engine lets build_chat_history stay a faithful transcript. No-op for current alternating histories. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: make chat history a faithful SMS transcript The LLM now sees exactly what was exchanged over SMS: hub opening messages (texet_hub_initial) stay in history as assistant turns, bot messages count only once delivered (sent) — dropping failed/queued — and moderated exchanges remain withheld on both sides. Previously only the first-ever opening was injected into the system prompt; since conversations merged to one-per-user (#35) every later daily opening was invisible to the model, which then hallucinated their content or denied having context. Remove the [Opening message] injection and get_opening_message entirely; Bedrock's user-first/ alternating constraint is owned by the engine-boundary normalization. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: day markers in chat history + history-conventions prompt section History spanning multiple days was an undifferentiated blob: the model could not map 'what we talked about this week' onto its context, and stale time references in old replies contradicted [User's Local Time]. - build_chat_history(annotate_days=True) prefixes the first message of each user-local calendar day with a [Tuesday, June 9] marker; the offset comes from per-utterance user_local_time meta (bot rows via their generation snapshot), backfilled for leading messages, UTC fallback. Only the LLM view is annotated — stored text and exports are untouched, and the moderation-email caller keeps the default. - compose_instruction_prompt always appends a code-owned [Conversation history conventions] section telling the model what its context actually is: a real SMS thread since Sunday, day-marked, with its own openings included and safety-withheld messages absent. - texet_generation snapshot version bumped to 2 (history semantics changed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: day-numbered activity label + CHARLA system prompt v2 doc compose_instruction_prompt takes day_number and labels the daily section [Today's Activity (Day N)] so the model can tie the curriculum to the study day. docs/prompts/charla-system-prompt-v2.md is the deployable base prompt (paste via admin console; latest row wins). It adds what v1 lacked: a memory self-knowledge section (the model sees this week's real SMS thread + last week's summary — never deny it, never invent beyond it), usage guidance for the activity/summary sections, SMS length and anti-repetition rules, stale-time handling, and instruction privacy decoupled from memory denial. Also recommends moving off Llama 4 Maverick 17B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: real-Kani e2e coverage + generation replay script The autouse kani_stub bypassed kani entirely, so nothing proved an assistant-first history survives a real chat round. Two new e2e tests restore the real _generate_reply: one drives a capture engine through the full Kani round (hub opening reaches the engine assistant-first, day-marked, reply persisted and sent), the other drives a stubbed BedrockEngine and asserts two back-to-back openings reach the Converse payload merged behind the placeholder user turn. scripts/replay_generation.py loads a bot utterance's texet_generation snapshot and prints unified diffs of the snapshot system prompt/history vs what current code would build — read-only, for replaying prod generations like eb02e4ed against context changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: clarify prompt v2 deploy ordering (code first, paste after) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Abhay Singh <abby@Abhays-MacBook-Pro.local> Co-authored-by: Claude Fable 5 <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.
Problem
Chat history sent to the LLM was empty at the start of every study day, observed on prod via the
texet_generationsnapshot (chat_history: []on a Wednesday despite a week of messages — the bot literally replied "I don't have access to past conversations").Root cause: conversations were keyed on
(owner, status, day_number), so each hubday_numbercreated a fresh conversation, whilebuild_chat_historyonly reads the current conversation. The intended weekly context window (since_timestamp=week_start) never had a chance to apply across days. Latent since #13; made visible by the #33 snapshots.Fix
(owner_speaker_id, status='open')— created on first message, reused forever. The weekly window stays enforced by the existingsince_timestampfilter.day_numberremoved from theConversationmodel; it remains in utterance metadata (daily prompt selection + generation snapshot).texet_instruction_prompt,texet_day_number,texet_user_local_time) — all captured per-reply in the bot utterance'stexet_generationsnapshot.chat_historynow passed via the Kani constructor.Migration (
9228797f839a)Runs automatically on deploy via the entrypoint's
alembic upgrade head:last_activity_at, deletes emptied rows) — restores full week context immediately on deploy.conversations.meta.ux_conversations_owner_open.Note:
ux_conversations_owner_open_no_dayhad been silently cascade-dropped byd740e0090bdc(its predicate referenced the droppedday_identifiercolumn), so prod had no uniqueness protection for no-day conversations; this migration drops it conditionally and the new index restores protection.Verification
test_response_single_conversation_across_day_numbersasserts one conversation acrossday_number1/2/none and that day-2 generation sees day-1 history.🤖 Generated with Claude Code