an alarm verb reaches stage 0, and the reminder grammar reads the lexicon (V-627)

reminder_verbs held five words and none named an alarm, and ReminderGrammar
did not read the set anyway — it carried the literal напомни|remind me. So no
part of the cascade recognised разбуди, and the three alarm cases in the
fixture went to fact and act at over 0.89.

The lexicon addition alone moved nothing, measured at 66/91. Every consumer
reads the set after a reminder route already exists. Building the grammar's
alternation from the set is what scored: 66/91 to 69/91, three cases gained,
none lost, and each alarm now carries its time slot.

Longest-first ordering in the alternation is load-bearing. Go's regexp
alternation is leftmost-first, so напомнить after напомни would never match.

Found while training the V-546 intent head, where the same three cases went
to system.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117tgnmbgZpHVV3XSNw8Qua
This commit is contained in:
2026-08-06 15:04:08 +04:00
parent e7537d032e
commit 1f8e9f21ce
3 changed files with 87 additions and 3 deletions
+3 -2
View File
@@ -173,10 +173,11 @@
]
},
"reminder_verbs": {
"note": "The imperatives that mean \"remind me\", in the forms he speaks. The same kind of set as capture_verbs and decided the same way: it is her vocabulary, not a discovery about Russian (Vikunja #530).",
"note": "The imperatives that mean \"remind me\", in the forms he speaks. The same kind of set as capture_verbs and decided the same way: it is her vocabulary, not a discovery about Russian (Vikunja #530). The alarm verbs joined them in V-627. \"разбуди меня в 6:30\" is a reminder that fires at the hour he gets up, and the set knew no form of it, so an alarm reached IntentReminder only by resembling one to the embedder.",
"words": [
"напомни", "напомните", "напомнить", "напоминай",
"remind"
"разбуди", "разбудите", "разбудить", "буди",
"remind", "wake"
]
},
"half_hour": {
+33 -1
View File
@@ -2,6 +2,7 @@ package router
import (
"regexp"
"sort"
"strings"
"unicode"
@@ -94,10 +95,41 @@ func DefaultGrammars(actMatcher ActMatcher) []Grammar {
// stage-0 decision too (fillMatchedSlots in router.go). Before that it did not,
// so "напомни в 11:00 позвонить маме" reached the daemon with HasTime false and
// was asked "Когда?" about an hour he had just said.
//
// The verb alternation is built from lexicon.ReminderVerbs rather than written
// out (V-627). The literal here knew "напомни" and "remind me" and nothing
// else, so "разбуди меня в 6:30" never reached stage 0 — and it does not reach
// IntentReminder further down either, where the classifier calls it fact at
// 0.918. An alarm is a reminder that fires at the hour he gets up, and the
// verb that names one is her vocabulary, so it belongs in the lexicon with the
// rest of it.
//
// Longest-first ordering matters: Go's regexp alternation is leftmost-first,
// not longest-match, so "напомнить" listed after "напомни" would never match.
var reminderVerbPattern = regexp.MustCompile(
`(?i)^\s*(?:` + longestFirstAlternation(lexicon.ReminderVerbs()) +
`)\s*(?:мне|меня|me)?[\s,:]+(.+)$`)
// longestFirstAlternation joins a word set into a regexp alternation, longest
// alternative first, with every member escaped.
func longestFirstAlternation(set []string) string {
out := make([]string, 0, len(set))
for _, w := range set {
out = append(out, regexp.QuoteMeta(w))
}
sort.Slice(out, func(i, j int) bool {
if len(out[i]) != len(out[j]) {
return len(out[i]) > len(out[j])
}
return out[i] < out[j]
})
return strings.Join(out, "|")
}
func ReminderGrammar() Grammar {
return Grammar{
Name: "reminder-wakeword",
Pattern: regexp.MustCompile(`(?i)^\s*(?:напомни|remind me)[\s,:]+(.+)$`),
Pattern: reminderVerbPattern,
Build: func(m []string) (Decision, bool) {
rest := strings.TrimSpace(m[1])
if rest == "" {