982c25118a
turnIsAbout scored t.vec, and t.vec was set in one place: queryEmbed, the source at actions_query.go:125. Every topic source sits above it — attention, list, feeds, home, network, weather. So best was handed an empty slice on every deployed turn, returned ok=false, and all six recognisers ran on their keyword floors. The seeds have decided nothing outside the tests since the mechanism landed. TestONNXTopics passes because it embeds each utterance itself and calls best directly. That is the shape that hid this for a month: it measures the scorer and never the wiring. Found on the box instead — "что мне нужно купить" was answered from an old note about a monitor, and the seeds place it as the list by 0.0841. turnVector computes the vector on first ask and caches it on the turn; queryEmbed returns early when it is already set. Chosen over moving the embed source up the list, because the cost is then paid only by turns that ask a topic source, and the order of querySources keeps meaning what its comments argue for. One scenario assertion moved, and it is a behaviour change rather than a bent test. morning_missed step 5 pinned "не знаю" for "что я пропустил?" with an unresolved Praxis item on the board. isAttentionQuery does not match that phrasing and the topicAttend seeds carry "что важное я пропустил" almost verbatim, so she reads the item back now. Reading a surfaced item aloud is not inventing a morning summary, so the floor that step exists for still holds; what moved is which source answers.
98 lines
4.9 KiB
JSON
98 lines
4.9 KiB
JSON
{
|
|
"schema_version": 1,
|
|
"name": "morning_missed",
|
|
"description": "The scenario from Vikunja #284's description, replayed. He appears at 08:30, things arrive through the morning while he is at the desk, and at 08:50 he asks what he missed. The assertions are as much about what did NOT happen — nothing was sent at him unprompted — as about what she said.",
|
|
"start": "2026-08-01T08:30:00+03:00",
|
|
"praxis_attention": "[{\"id\":\"item_1\",\"title\":\"medicine not taken\",\"importance\":3.0,\"rule\":\"morning_medicine\"}]",
|
|
"script": [
|
|
{
|
|
"match": "выпил воды",
|
|
"route": "[{\"intent\":\"fact\",\"key\":\"water\",\"value\":\"выпил\"}]"
|
|
},
|
|
{
|
|
"match": "записала факт: water",
|
|
"reply": "{\"response\":\"Записала, что ты выпил воды.\",\"mood\":\"neutral\"}"
|
|
},
|
|
{
|
|
"match": "что я пропустил",
|
|
"route": "[{\"intent\":\"query\",\"text\":\"что я пропустил\"}]"
|
|
},
|
|
{
|
|
"match": "",
|
|
"route": "[{\"intent\":\"chat\",\"text\":\"привет\"}]",
|
|
"reply": "{\"response\":\"Я рада тебя слышать.\",\"mood\":\"happy\"}"
|
|
}
|
|
],
|
|
"steps": [
|
|
{
|
|
"at": "08:30",
|
|
"note": "he appears at the desk",
|
|
"signal": { "key": "desk_active", "value": "true", "source": "infer:hyprland" },
|
|
"expect_events": ["infer:hyprland"],
|
|
"expect_no_send": true
|
|
},
|
|
{
|
|
"at": "08:32",
|
|
"note": "a feed item arrives, published half an hour ago",
|
|
"arrive": {
|
|
"source": "rss:tech",
|
|
"as_of": "08:02",
|
|
"note": { "text": "Вышло ядро 6.19 [tech]\nкраткое содержание\nhttps://example.org/a" }
|
|
},
|
|
"expect_events": ["rss:tech"],
|
|
"expect_no_send": true
|
|
},
|
|
{
|
|
"at": "08:35",
|
|
"note": "the mail reader extracts a candidate — a candidate is never spoken",
|
|
"arrive": {
|
|
"source": "email:inbox",
|
|
"task": { "text": "продлить домен", "evidence": "Домен истекает через 7 дней" }
|
|
},
|
|
"expect_events": ["email:inbox", "продлить домен"],
|
|
"expect_no_send": true
|
|
},
|
|
{
|
|
"at": "08:40",
|
|
"note": "the work calendar signal — a relayed notification, at the ambient path's own 0.6 rather than an observation she made herself. That is the branch factPriority takes, so the journal must file it low.",
|
|
"arrive": {
|
|
"source": "ambient:notif",
|
|
"fact": {
|
|
"key": "calendar_event_20260801_планёрка",
|
|
"value": "10:00-11:00 планёрка",
|
|
"confidence": 0.6
|
|
}
|
|
},
|
|
"expect_events": ["планёрка", "ambient:notif/fact pri=low"],
|
|
"expect_no_send": true
|
|
},
|
|
{
|
|
"at": "08:45",
|
|
"note": "a tick with him present and nothing wrong must stay silent",
|
|
"tick": true,
|
|
"expect_no_send": true
|
|
},
|
|
{
|
|
"at": "08:50",
|
|
"note": "he asks what he missed, and Praxis holds one unresolved item — the morning medicine — so she reads that back. This step pinned \"не знаю\" until 05-08-2026, and that was the keyword floor's blind spot rather than a rule: isAttentionQuery does not match \"что я пропустил\", while the topicAttend seeds carry \"что важное я пропустил\" almost verbatim. The seeds only started deciding when turnVector fixed the empty query vector every topic source was reading (V-547). Reading a surfaced item aloud is not inventing a morning summary, so the no-hallucination floor still holds; what moved is which source answers. Note what the persona check here is and is not: the reply is a constant in the Go source, so expect_reply_lacks pins that constant, not anything the model wrote. The step below is the one that reads model output.",
|
|
"say": "что я пропустил?",
|
|
"expect_reply_contains": ["требует внимания", "morning_medicine"],
|
|
"expect_reply_lacks": ["рад ", "милый", "ваш"]
|
|
},
|
|
{
|
|
"at": "08:55",
|
|
"note": "stating a fact writes it and says so, in the feminine. This reply comes back through the replier from the scripted model, so the persona check is against generated text rather than a constant. The masculine forms are listed with their following character — \"записал \" and \"записал,\" — because \"записала\" contains \"записал\", and the earlier check on the comma alone passed on \"записал что ты выпил воды\".",
|
|
"say": "я выпил воды",
|
|
"expect_reply_contains": ["записала"],
|
|
"expect_reply_lacks": ["записал ", "записал,", "записал.", "милый"],
|
|
"expect_events": ["water"]
|
|
},
|
|
{
|
|
"at": "09:00",
|
|
"note": "a second tick, still nothing unprompted",
|
|
"tick": true,
|
|
"expect_no_send": true
|
|
}
|
|
]
|
|
}
|