Observed live with granite3.3:8b on the question "what was the busiest hour 2 days ago?".
The scanner misses the whole <n> <unit> ago family
scanTimeExpressions only matches rolling spans with a leading past|last|previous (app/src/lib/assistant/timeframe-stage.ts:124). Nothing matches <n> <unit> ago:
scan("what was the busiest hour 2 days ago?") = []
scan("2 days ago") = []
scan("two days ago") = []
scan("3 hours ago") = []
scan("last 2 days") = ["last 2 days"] <- only the modifier form matches
An empty scan falls through to the default at timeframe-stage.ts:219, phrases: [today], so the system prompt told the model:
Timeframes for this question, already resolved (copy these exact phrases into when): "today".
for a question naming a different day. A model that follows that instruction answers about today using real data, which reads as correct — the exact failure whenNotFromQuestion exists to prevent, arriving through a door the scanner left open.
The when guard rejects the spelled-out form
whenWordTokens splits on [^\p{L}]+, so digits are dropped and number words are not:
guard("two days ago", "what was the busiest hour 2 days ago?") -> rejected
guard("2 days ago", "what was the busiest hour 2 days ago?") -> accepted
Identical phrases, different spelling of the number.
How they compound
The app resolved "today", the model correctly ignored that and called list_events with when: "two days ago", the app rejected it over the digit/word difference, and the model gave up and asked the user to restate the date. The turn produced nothing.
Test gap
time-eval-cases.ts carries 3 days ago as an INTERPRET case, so phrase-to-fields handles the family fine. There is no EXTRACT case for it, so nothing checked whether the scanner finds it in a question. The two stages disagree and only one is covered.
Fix
- A
<n> <unit> ago pattern in scanTimeExpressions, digits and spelled-out numbers.
- Fold number words to digits in the guard tokens so
two and 2 compare equal.
- Extract cases for the family, so the scanner stays covered.
Ships today with qwen3:8b, which likely survives it by copying the question words verbatim.
Observed live with
granite3.3:8bon the question "what was the busiest hour 2 days ago?".The scanner misses the whole
<n> <unit> agofamilyscanTimeExpressionsonly matches rolling spans with a leadingpast|last|previous(app/src/lib/assistant/timeframe-stage.ts:124). Nothing matches<n> <unit> ago:An empty scan falls through to the default at
timeframe-stage.ts:219,phrases: [today], so the system prompt told the model:for a question naming a different day. A model that follows that instruction answers about today using real data, which reads as correct — the exact failure
whenNotFromQuestionexists to prevent, arriving through a door the scanner left open.The
whenguard rejects the spelled-out formwhenWordTokenssplits on[^\p{L}]+, so digits are dropped and number words are not:Identical phrases, different spelling of the number.
How they compound
The app resolved "today", the model correctly ignored that and called
list_eventswithwhen: "two days ago", the app rejected it over the digit/word difference, and the model gave up and asked the user to restate the date. The turn produced nothing.Test gap
time-eval-cases.tscarries3 days agoas an INTERPRET case, so phrase-to-fields handles the family fine. There is no EXTRACT case for it, so nothing checked whether the scanner finds it in a question. The two stages disagree and only one is covered.Fix
<n> <unit> agopattern inscanTimeExpressions, digits and spelled-out numbers.twoand2compare equal.Ships today with
qwen3:8b, which likely survives it by copying the question words verbatim.