Summary
timer_request gates on text.contains("timer") || text.starts_with("remind me ") || text.starts_with("remind us ").
The timer arm is a substring test, so please set a timer for 10 minutes
routes fine. The reminder arms are prefix tests, so a leading please
pushes them off the anchor and every polite reminder falls through to the LLM.
The asymmetry is the bug: the same politeness token is harmless on one arm of
the same if and fatal on the other.
Repro (on main)
| utterance |
on main |
expected |
Please remind me to check the pasta in 5 minutes |
ABSTAIN |
set_timer{seconds: 300, label: check the pasta} |
Please remind me to take the trash out in 20 minutes |
ABSTAIN |
set_timer{seconds: 1200, label: take the trash out} |
Please remind us to leave in 10 minutes |
ABSTAIN |
set_timer{seconds: 600, label: leave} |
Please set a timer for 10 minutes |
set_timer{600, timer} |
unchanged (already works) |
Remind me to check the pasta in 5 minutes routes today.
Root cause
Two coupled anchors, not one:
- The entry gate
text.starts_with("remind me ") / ("remind us ") never
matches, so the function returns None immediately.
- Even if it did,
reminder_style is computed from the same two prefixes.
With please still attached it would be false, so reminder_label would
skip the task-first scan and the call would be labelled timer rather than
check the pasta — a silently degraded reminder instead of an absent one.
So stripping has to happen before both the gate and the reminder_style
computation, not just in front of the gate.
Proposed fix
Strip a single leading please at the top of timer_request, before the
gate and before reminder_style is derived, and tokenize the stripped text.
Mirrors the leading-please strips already in
scene_or_routine_activation_request, play_media_request, and
shopping_list_add_request. The trailing-please label trim already in
timer_request is unaffected.
text.starts_with("remind ") in the label fallback must read the stripped text
too, so a polite reminder with no extractable label still defaults to
reminder and not timer.
Acceptance
please remind me/us … emits the same set_timer seconds and label as
the bare form
- Existing
timer / trailing-please / compound-duration behavior unchanged
- Focused regression test fails on
main, passes with the fix
- No prompt growth; 4096 Jetson contract untouched
Labels / scope
Tool-dispatch correctness (deterministic quick-router).
Summary
timer_requestgates ontext.contains("timer") || text.starts_with("remind me ") || text.starts_with("remind us ").The
timerarm is a substring test, soplease set a timer for 10 minutesroutes fine. The reminder arms are prefix tests, so a leading
pleasepushes them off the anchor and every polite reminder falls through to the LLM.
The asymmetry is the bug: the same politeness token is harmless on one arm of
the same
ifand fatal on the other.Repro (on main)
mainPlease remind me to check the pasta in 5 minutesset_timer{seconds: 300, label: check the pasta}Please remind me to take the trash out in 20 minutesset_timer{seconds: 1200, label: take the trash out}Please remind us to leave in 10 minutesset_timer{seconds: 600, label: leave}Please set a timer for 10 minutesset_timer{600, timer}Remind me to check the pasta in 5 minutesroutes today.Root cause
Two coupled anchors, not one:
text.starts_with("remind me ")/("remind us ")nevermatches, so the function returns
Noneimmediately.reminder_styleis computed from the same two prefixes.With
pleasestill attached it would befalse, soreminder_labelwouldskip the task-first scan and the call would be labelled
timerrather thancheck the pasta— a silently degraded reminder instead of an absent one.So stripping has to happen before both the gate and the
reminder_stylecomputation, not just in front of the gate.
Proposed fix
Strip a single leading
pleaseat the top oftimer_request, before thegate and before
reminder_styleis derived, and tokenize the stripped text.Mirrors the leading-
pleasestrips already inscene_or_routine_activation_request,play_media_request, andshopping_list_add_request. The trailing-pleaselabel trim already intimer_requestis unaffected.text.starts_with("remind ")in the label fallback must read the stripped texttoo, so a polite reminder with no extractable label still defaults to
reminderand nottimer.Acceptance
please remind me/us …emits the sameset_timerseconds and label asthe bare form
timer/ trailing-please/ compound-duration behavior unchangedmain, passes with the fixLabels / scope
Tool-dispatch correctness (deterministic quick-router).