fix(astra): stop dropping and collapsing habits when listing#236
Conversation
Astra dropped scattered habits and collapsed identical siblings (e.g. six "Water - 710ml" sub-habits rendered as one) when answering "what are my habits today". Root cause: the prompt has gpt-4.1-mini free-text reproduce the full nested habit index, where identical adjacent lines collapse under induction and mid-list items get lost. Stage 1 (backend hardening, server-deploys to help all clients today): - SiblingTitleDisambiguator numbers exact-duplicate-title siblings " (n of N)" in both the prompt index and query_habits output, so repeated habits become distinct, countable lines the model cannot silently merge. - One index instruction line to list every numbered instance. - Guard finish_reason == length in both AI round paths so a truncated list is logged, never silently rendered as complete. A later stage will render the habit list in-app so the model stops relaying the list as free text entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Solid fix. The SiblingTitleDisambiguator correctly scopes numbering per-sibling-group (applied at every recursion level in both ActiveHabitsSection and QueryHabitsTool), the ordinal case-sensitive matching is appropriate and tested, and the early-exit on no duplicates keeps the hot path clean. The finish_reason == length guard is a sensible observability improvement — logs at Warning with a stable EventId rather than silently swallowing truncated output. Test coverage is thorough: unit tests for the disambiguator (empty, no-dups, case-sensitive, multi-group), integration-level section/tool tests verifying the numbered suffix in rendered output, and buffered + streaming truncation tests with a recording logger. No DTO or client contract change, safe to deploy immediately.
|



The bug
Asked "quais meus habitos de hoje" (what are my habits today), Astra dropped scattered habits and collapsed identical siblings — e.g. a user with six identical
Water - 710mlsub-habits sawWaterlisted once, and mid-list items (Dinner, an overdueposthog, etc.) vanished entirely.Root cause
The prompt has
gpt-4.1-minifree-text reproduce the full nested habit index. Two well-documented small-LLM failure modes bite:Water - 710mllines are byte-identical except for a GUID the model is told never to emit).The index data itself is complete and uncapped; this is an enumeration/relay failure, not missing data or a token cap (
MaxOutputTokenCount = 8192, the answer ended cleanly).What this PR does (Stage 1 — backend hardening)
Server-side only, so it improves every client on the next deploy with no app update:
SiblingTitleDisambiguatornumbers exact-duplicate-title siblings(n of N)in both the prompt index (ActiveHabitsSection) andquery_habitsoutput. The six waters becomeWater - 710ml (1 of 6)…(6 of 6)— distinct, countable lines the model cannot silently merge.(n of N); list every numbered instance.finish_reason == lengthguard in both AI round paths (buffered + streaming): a truncated response is logged (Warning, EventId 8), never silently rendered as a complete list.Scope / what's deferred
This kills the duplicate-collapse half fully and hardens against silent truncation. The lost-in-the-middle half is only fully solved by having the app render the habit list so the model stops relaying it as free text — that's the next stage (cross-platform, needs the web + mobile chat surfaces). Deliberately not routing listing through a forced
query_habitscall here: it would add a round-trip per "what's today" without fixing enumeration (the model still relays), and that routing only pays off once the app renders the payload.Compatibility
Purely additive prompt/text formatting — no DTO or shared-contract change, no client change required. Safe to deploy any time.
Tests
SiblingTitleDisambiguatorunit tests, index + tool duplicate-numbering tests, and buffered/streaming truncation-guard tests. Full suite green: 3564 passed, 0 failed.🤖 Generated with Claude Code