a spoken status change reaches the board at stage 0 (V-512)

Step 4 of the board build (docs/plans/15-board-surface.md). Naming a task
instead of its position reached nothing: "закрой задачу купить молоко" routed
act, found no allowlisted fn, and the gate asked "Что сделать?". The position
path already worked through resolveCandidate, but only in the two turns after
she read the list out.

TaskStatusGrammar is the same shape TaskCaptureGrammar uses — matches broadly,
decides in Build, no eighth intent — and fills the fn slot with task_status,
which is neither a Hexis capability nor a Praxis one. Three conditions, all
required: the board noun, so no ordinary sentence claims a turn; exactly one
status class, since "готово, убери" names two and asking beats picking; and a
status word matched as an imperative exactly or a stative by lemma. So a bare
"готово" and a bare "закрой" are not this rule's, and the second belongs to
Praxis, which claims it already.

Two lexicon sets rather than one with a value. The store records which of the
two transitions happened and /tasks shows it: work he chose to stop is not work
he did.

Measured on the fixture, two new cases (ru-act-020, ru-act-021). Classifier +
ONNX 62/89 (69.7%) → 64/91 (70.3%); cascade+llm 67/89 (75.3%) → 69/91 (75.8%,
80.2% intent-only) at p50 1.225s. Both new cases claimed at stage 0, no case
regressed, clarify counts unchanged at 3 false / 1 missed.

The task's own warning stands: every such grammar runs its parser ahead of the
resident model on every turn, so this is the last one that is free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoL7EBdYC5Mhz3DJd49GJy
This commit is contained in:
2026-08-05 19:53:20 +04:00
parent 8f2377d27d
commit c259ed6c73
7 changed files with 281 additions and 1 deletions
+12 -1
View File
@@ -64,7 +64,7 @@ func mustLoad() lexiconFile {
"interrogatives", "capture_verbs", "narrative_requests", "cardinals", "ordinals",
"day_offsets", "weekdays", "months_genitive", "hours_spoken",
"not_place_after_v", "parts_of_day", "reminder_verbs", "half_hour",
"filler_particles",
"filler_particles", "task_done_words", "task_drop_words",
} {
s, ok := f.Sets[name]
if !ok || (len(s.Words) == 0 && len(s.Values) == 0) {
@@ -113,6 +113,17 @@ func PartsOfDay() []string { return words("parts_of_day") }
// ReminderVerbs returns the imperatives that open a reminder.
func ReminderVerbs() []string { return words("reminder_verbs") }
// TaskDoneWords returns the words that finish a task, and TaskDropWords the
// words that abandon one. Two sets rather than one with a value, because the
// store records which of the two happened and the caller has to say so.
//
// Both mix moods on purpose, and the caller must match them the way the sets'
// notes say: an imperative exactly, a stative by lemma.
func TaskDoneWords() []string { return words("task_done_words") }
// TaskDropWords — see TaskDoneWords.
func TaskDropWords() []string { return words("task_drop_words") }
// IsFillerParticle reports whether a word can never be the subject of a
// request: a particle, a politeness word, or the first-person object. See the
// set's own note for why this is not a stopword list.
+16
View File
@@ -181,6 +181,22 @@
"давай", "давай-ка", "а", "и", "бы", "мне", "меня", "мной",
"please", "just", "hey", "me"
]
},
"task_done_words": {
"note": "The ways he says a task is finished, split by mood the way the Praxis lifecycle words are (Vikunja #512). The imperatives are addressed to her and are matched exactly, because morph.SameWord makes \"закрой\" and \"закрыл\" one word and only one of them is an instruction. The statives report his own day and are matched by lemma, since \"сделано\", \"сделана\" and \"сделанную\" are one state. Closed because these are her vocabulary for one transition, not a discovery about Russian.",
"words": [
"закрой", "закройте", "закрыть", "заверши", "завершить", "close", "finish",
"сделано", "сделал", "сделала", "выполнено", "выполнил", "выполнила",
"готово", "готова", "закрыл", "закрыла", "done", "finished"
]
},
"task_drop_words": {
"note": "The ways he abandons a task rather than finishing it (Vikunja #512). Same two moods as task_done_words and the same matching rule. Separate from the done words because the store records which of the two happened and /tasks shows it: dropped work he chose to stop is not work he did.",
"words": [
"убери", "уберите", "убрать", "удали", "удалить", "отмени", "отменить",
"drop", "remove", "cancel",
"передумал", "передумала", "неактуально"
]
}
}
}