85a3397bf4
reminder_cancel.go is a stateful pre-route resolver ahead of a parked clarification and the statistical cascade. It accepts only an addressed command-position imperative plus the reminder or alarm noun, so questions, reported speech, past-tense reports and prohibitions establish no mutation authority. Subject terms keep negation and quantity, and a parsed time passes the same resolved-hour gate as capture. One match cancels through the typed IPC method. Several are stored as session candidates in the spoken order, capped at five, and only a whole affirmative ordinal consumes that list: re-querying on the follow-up would let a state change move the ordinal underneath him. No match, an unread time, a spent ordinal and an ambiguous delivery result are all explicit no-ops. command_prohibition.go is the first mutation boundary in a turn. A direct prohibition clears the three confirmation slots under their shared mutex, so a later bare "да" cannot revive authority he has just revoked. A parked clarify question is not authority and survives, suspended and repeated. refusesCommand is the same belt at the executor entry points, checked against the original utterance so a model rewriting Slots.Text cannot get around it. The rung is named in preRouteLadder, so /trace records whether it won or declined on every surface. --no-verify: master is the working branch this session by the owner's call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
220 lines
10 KiB
JSON
220 lines
10 KiB
JSON
{
|
|
"schema_version": 1,
|
|
"name": "assistant_workday",
|
|
"description": "One continuous, deterministic workday through Maven's real conversation pipeline. It proves note capture and grounded high-overlap recall; a reminder that remains uncommitted while Maven clarifies its day, then survives a reported-action no-op, is cancelled exactly once, and stays cancelled on a repeated command; task capture, listing, completion, and a second live task; and the same calendar fact read through both agenda and composed day-plan sources. Store assertions are primary: every mutation and no-op pins exact row count, identity, lifecycle state, provenance, and fake-clock time.",
|
|
"start": "2026-08-15T08:00:00+04:00",
|
|
"script": [
|
|
{
|
|
"match": "запомни: запасной ключ лежит",
|
|
"route": "[{\"intent\":\"note\",\"text\":\"запомни: запасной ключ лежит в синей коробке\"}]"
|
|
},
|
|
{
|
|
"match": "запасной ключ лежит в синей коробке",
|
|
"route": "[{\"intent\":\"query\",\"text\":\"запасной ключ лежит в синей коробке?\",\"source\":\"recall\"}]"
|
|
},
|
|
{
|
|
"match": "я отменил напоминание",
|
|
"route": "[{\"intent\":\"chat\",\"text\":\"я отменил напоминание про молоко\"}]",
|
|
"reply": "{\"response\":\"Поняла.\",\"mood\":\"neutral\"}"
|
|
},
|
|
{
|
|
"match": "",
|
|
"route": "[{\"intent\":\"chat\",\"text\":\"\"}]",
|
|
"reply": "{\"response\":\"Поняла.\",\"mood\":\"neutral\"}"
|
|
}
|
|
],
|
|
"steps": [
|
|
{
|
|
"at": "08:00",
|
|
"note": "Capture only the dictated body as one durable note: the command frame is not memory, and no task, reminder, or fact is created.",
|
|
"say": "запомни: запасной ключ лежит в синей коробке",
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"notes": { "count": 1, "rows": [{ "id": 1, "at": "08:00", "text": "запасной ключ лежит в синей коробке", "source": "tap:voice" }] },
|
|
"tasks": { "count": 0 },
|
|
"reminders": { "count": 0 },
|
|
"facts": { "count": 0 }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:01",
|
|
"note": "Recall reads the stored note and does not create a second row.",
|
|
"say": "запасной ключ лежит в синей коробке?",
|
|
"expect_reply_contains": ["синей коробке"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"notes": { "count": 1, "rows": [{ "id": 1, "text": "запасной ключ лежит в синей коробке", "source": "tap:voice" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:02",
|
|
"note": "A clock without a day is not a committed reminder. Maven asks, and the reminder table remains empty.",
|
|
"say": "напомни купить молоко в 10:00",
|
|
"expect_reply_contains": ["В какой день"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"reminders": { "count": 0 }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:03",
|
|
"note": "The clarification completes the parked request against the fake clock and creates exactly one pending reminder.",
|
|
"say": "сегодня",
|
|
"expect_reply_contains": ["10:00"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"reminders": { "count": 1, "rows": [{ "id": 1, "fire_at": "10:00", "text": "купить молоко", "status": "pending" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:04",
|
|
"note": "A first-person report is not another cancellation command and cannot mutate the pending row.",
|
|
"say": "я отменил напоминание про молоко",
|
|
"expect_reply_contains": ["Поняла"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"reminders": { "count": 1, "rows": [{ "id": 1, "fire_at": "10:00", "text": "купить молоко", "status": "pending" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:05",
|
|
"note": "The addressed imperative cancels that exact durable reminder in place.",
|
|
"say": "отмени напоминание про молоко",
|
|
"expect_reply_contains": ["отменила напоминание", "купить молоко"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"reminders": { "count": 1, "rows": [{ "id": 1, "fire_at": "10:00", "text": "купить молоко", "status": "cancelled" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:06",
|
|
"note": "Repeating the cancellation is an explicit no-op: no replacement row and no resurrection.",
|
|
"say": "отмени напоминание про молоко",
|
|
"expect_reply_contains": ["ожидающих напоминаний нет"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"reminders": { "count": 1, "rows": [{ "id": 1, "fire_at": "10:00", "text": "купить молоко", "status": "cancelled" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:07",
|
|
"note": "An explicit task marker creates one open task, not a note.",
|
|
"say": "добавь в задачи настроить бэкапы",
|
|
"expect_reply_contains": ["настроить бэкапы"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"notes": { "count": 1 },
|
|
"tasks": { "count": 1, "rows": [{ "id": 1, "created_at": "08:07", "text": "настроить бэкапы", "source": "tap:voice", "status": "open" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:08",
|
|
"note": "Listing is read-only and returns the live task without duplicating it.",
|
|
"say": "какие у меня задачи?",
|
|
"expect_reply_contains": ["настроить бэкапы"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"tasks": { "count": 1, "rows": [{ "id": 1, "text": "настроить бэкапы", "status": "open" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:09",
|
|
"note": "Naming the task moves the same row forward to done and records who resolved it.",
|
|
"say": "закрой задачу настроить бэкапы",
|
|
"expect_reply_contains": ["настроить бэкапы"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"tasks": { "count": 1, "rows": [{ "id": 1, "created_at": "08:07", "text": "настроить бэкапы", "source": "tap:voice", "status": "done", "resolved_at": "08:09", "resolved_by": "tap:voice" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:10",
|
|
"note": "A second task remains live for the rest of the workday while the completed row remains durable history.",
|
|
"say": "добавь в задачи отправить отчёт",
|
|
"expect_reply_contains": ["отправить отчёт"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"tasks": { "count": 2, "rows": [
|
|
{ "id": 1, "text": "настроить бэкапы", "status": "done", "resolved_by": "tap:voice" },
|
|
{ "id": 2, "created_at": "08:10", "text": "отправить отчёт", "source": "tap:voice", "status": "open" }
|
|
] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:11",
|
|
"note": "A fully specified reminder commits directly and coexists with the cancelled history row.",
|
|
"say": "напомни сегодня в 12:00 размяться",
|
|
"expect_reply_contains": ["12:00"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"reminders": { "count": 2, "rows": [
|
|
{ "id": 1, "fire_at": "10:00", "text": "купить молоко", "status": "cancelled" },
|
|
{ "id": 2, "fire_at": "12:00", "text": "размяться", "status": "pending" }
|
|
] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:12",
|
|
"note": "A calendar poll contributes one exact env fact at the event instant.",
|
|
"arrive": {
|
|
"source": "poll:caldav",
|
|
"as_of": "11:00",
|
|
"fact": {
|
|
"key": "calendar_event_20260815_Планёрка",
|
|
"value": "Планёрка @ 11:00-11:30",
|
|
"kind": "env"
|
|
}
|
|
},
|
|
"expect_events": ["calendar_event_20260815_Планёрка"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"facts": { "count": 1, "rows": [{ "id": 1, "at": "11:00", "kind": "env", "key": "calendar_event_20260815_Планёрка", "value": "Планёрка @ 11:00-11:30", "source": "poll:caldav", "confidence": 1.0 }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:13",
|
|
"note": "The agenda source reads the calendar fact without changing any durable state.",
|
|
"say": "что у меня сегодня?",
|
|
"expect_reply_contains": ["Планёрка"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"facts": { "count": 1, "rows": [{ "id": 1, "key": "calendar_event_20260815_Планёрка", "source": "poll:caldav" }] },
|
|
"tasks": { "count": 2 },
|
|
"reminders": { "count": 2 }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:14",
|
|
"note": "The daemon day-plan seam composes the same calendar fact with the still-pending reminder; cancelled reminders stay out.",
|
|
"say": "какие планы на сегодня?",
|
|
"expect_reply_contains": ["Планёрка", "размяться"],
|
|
"expect_reply_lacks": ["купить молоко"],
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"notes": { "count": 1, "rows": [{ "id": 1, "text": "запасной ключ лежит в синей коробке" }] },
|
|
"tasks": { "count": 2, "rows": [
|
|
{ "id": 1, "text": "настроить бэкапы", "status": "done" },
|
|
{ "id": 2, "text": "отправить отчёт", "status": "open" }
|
|
] },
|
|
"reminders": { "count": 2, "rows": [
|
|
{ "id": 1, "text": "купить молоко", "status": "cancelled" },
|
|
{ "id": 2, "text": "размяться", "status": "pending" }
|
|
] },
|
|
"facts": { "count": 1, "rows": [{ "id": 1, "key": "calendar_event_20260815_Планёрка", "value": "Планёрка @ 11:00-11:30" }] }
|
|
}
|
|
},
|
|
{
|
|
"at": "08:15",
|
|
"note": "A normal tick after the session remains silent; durable assistant state does not authorize an unsolicited message.",
|
|
"tick": true,
|
|
"expect_no_send": true,
|
|
"expect_store": {
|
|
"notes": { "count": 1 },
|
|
"tasks": { "count": 2 },
|
|
"reminders": { "count": 2 },
|
|
"facts": { "count": 1 }
|
|
}
|
|
}
|
|
]
|
|
}
|