Add the day plan she can recite when asked (#128) #58
Closed
claude
wants to merge 1 commits from
overnight/day-plan into overnight/ambient-calendar
pull from: overnight/day-plan
merge into: kami:overnight/ambient-calendar
kami:master
kami:task/725-capability-ledger-and-empirical-baseline
kami:task/692-heads-path-may-equal-model-path-and-noth
kami:task/694-staticcheck-and-deadcode-are-still-not-i
kami:task/682-go-1-25-5-and-x-text-0-14-0-carry-20-rea
kami:task/674-caveats
kami:task/673-mavgpud-serves-the-model-to-the-whole-la
kami:task/487-capture-device-doc
kami:task/487-capture-device
kami:task/487-wake-word-deploy
kami:task/487-wake-word-threshold
kami:task/487-wake-word-stage-two
kami:task/671-mavwaked-registers-as-a-voice-consumer-i
kami:task/670-cut-claude-md-to-200-lines
kami:task/515-deploy-mavwaked-workpc
kami:task/669-prune-claude-md
kami:task/668-e4b-phrasing
kami:task/668-title-capital
kami:task/668-kiwix-answers-a-question-it-cannot-answe
kami:task/666-only-a-stage-0-grammar-may-take-the-pers
kami:task/487-mavwaked-has-no-wake-word-only-an-energy
kami:task/486-deploy-the-workstation-transcriber
kami:task/486-move-stt-and-tts-to-the-workstation-wher
kami:task/665-crisperwhisper-2-russian
kami:task/664-routing-heads-in-go
kami:task/662-usage-harness-source-badge
kami:task/661-post-merge-usage-rerun
kami:task/661-routing-heads-step-3-train-the-multi-hea
kami:task/660-router-prompt-destination
kami:task/659-destination-fixture
kami:task/655-query-source-is-a-routing-decision-made
kami:task/654-a-pending-clarify-has-no-way-out-neither
kami:task/654-week-of-usage-eval-docs
kami:task/649-needs-kami-telegram-is-the-only-reach-an
kami:task/643-memorystore-search-decodes-and-unmarshal
kami:task/641-two-maps-grow-for-the-process-lifetime-w
kami:task/644-mavcaldav-is-built-documented-as-running
kami:task/642-the-store-caps-sqlite-at-one-connection
kami:task/647-factenrichmentworker-walks-the-pending-q
kami:task/646-v-637-follow-up-telegram-intake-has-no-d
kami:task/638-no-deadline-survives-the-turn-path-from
kami:task/637-inbound-telegram-turns-and-corrections-f
kami:task/636-correcting-a-turn-from-telegram-and-from
kami:task/634-an-act-alias-resolves-the-verb-but-not-t
kami:task/630-one-gesture-correction-on-chat-v-628
kami:task/629-persist-the-routing-trace-and-record-it
kami:task/631-mode-inventory-written-from-the-handlers
kami:task/586-defaultfactparser-uses-hand-written-russ
kami:task/633-reconcile-the-seed-labels-with-the-handl
kami:task/627-reminder-verbs-has-no-alarm-verb-so-an-a
kami:task/626-the-classifier-seeds-teach-an-older-inte
kami:task/546-route-with-a-fine-tuned-e5-small-instead
kami:task/586-measure-the-fact-parser
kami:fix/gofmt-ecosystem-acts
kami:task/584-media-store-a-failed-write-leaks-its-bud
kami:task/518-no-write-path-for-a-backdated-event-so-t
kami:task/287-qa-voice-session-quality-polish
kami:task/492-qa-plan-reconcile
kami:task/530-sweep-tail-four-files-the-russian-sweep
kami:task/405-score-how-often-a-real-utterance-reaches
kami:task/529-money-and-list-pick-a-mechanism
kami:task/528-sweep-tail-the-three-files-on-467
kami:task/527-embedder-open-set-phrasings-stop-being-r
kami:task/526-morphology-a-dictionary-answers-the-gram
kami:task/525-lexicons-the-finite-russian-sets-move-to
kami:task/524-entity-reference-ask-nexus-about-every-l
kami:task/523-risk-tiers-take-hexis-s-tier-for-a-hexis
kami:task/521-review-pr-111-query-strings-declension-h
kami:task/491-llama-server-core-dumps-on-every-sigterm
kami:task/479-bug-an-unconfigured-capability-does-not
kami:task/467-bug-spoken-task-capture-is-dead-the-rout
kami:task/463-deploy-mavwaked-and-mavenclient-run-nowh
kami:task/480-hearing-no-shipped-client-can-start-a-re
kami:task/432-ambient-calendar-intake-is-fragile-and-p
kami:task/431-board-surface-maven-holds-the-work-board
kami:task/433-reactivehandler-has-30-fields-and-is-pas
kami:task/371-swap-the-embedder-for-an-asymmetric-retr
kami:task/408-review-31-07-split-the-30-method-coreapi
kami:task/410-review-31-07-hand-rolled-string-enums-st
kami:task/423-review-pr50-split-internal-ipc-server-go
kami:task/422-review-pr50-split-cmd-mavend-tick-go-860
kami:task/409-review-31-07-finish-moving-mavweb-markup
kami:task/482-ambient-ingest-reads-a-notification-s-ti
kami:task/444-kuma-a-fact-per-monitor-so-she-can-name
kami:task/452-capability-model-homelab-docker-restart
kami:task/449-destructive-confirm-policy-risk-tiers-no
kami:task/453-grocery-list-items-table-fourth-append-o
kami:task/399-run-the-persona-checks-inside-the-daemon
kami:task/448-bounded-follow-up-state-pending-candidat
kami:task/455-conversation-repair-name-the-misroute-co
kami:task/454-go-mod-tidy
kami:task/458-pronunciation-dictionary-for-piper
kami:task/456-command-history-read-only-query-over-exi
kami:task/457-clarification-templates-for-the-router-s
kami:task/474-query-source-ordering-feeds-and-calendar
kami:task/469-reminders-spelled-out-times-fail-the-bod
kami:task/475-bug-the-praxis-attention-capability-is-u
kami:task/481-bug-a-transient-complaint-is-stored-as-a
kami:task/476-bug-the-router-transliterates-latin-enti
kami:task/385-decide-whether-a-parked-clarify-question
kami:task/377-backfill-routines
kami:task/421-weather-geocoder
kami:task/390-no-read-path-for-delivery-attempts
kami:task/386-recall-fixture-filler-note-ids
kami:task/473-bug-morning-item-has-no-required-flag
kami:task/465-bug-make-simulate-routes-with-an-empty
kami:task/467-bug-spoken-task-capture-is-dead
kami:task/466-bug-a-pending-clarify-is-global-so-one-u
kami:task/468-bug-pattern-detect-has-no-minimum-interv
kami:task/462-bug-checkfeminine-flags-second-person-ma
kami:task/443-safekey-drops-cyrillic-so-russian-calend
kami:task/471-bug-agendaquerygrammars-covers-today-but
kami:task/383-slottext-in-clarify-answer-would-clobber
kami:task/323-qa-phraser-coverage-is-65-3-but-the-llam
kami:task/498-bug-and-x-reach-the-model-with-no-determ
kami:task/506-strings-family-6-summaries-and-reports-i
kami:task/504-strings-family-4-act-and-smart-home-repl
kami:task/503-strings-family-3-query-answers-and-gaps
kami:task/502-strings-family-2-capture-acknowledgement
kami:task/501-strings-family-1-phrasing-fallbacks-into
kami:task/397-phrasechat-and-phrasequery-hide-model-fa
kami:task/396-the-reply-path-can-t-be-tested-llmreplie
kami:task/496-recall-a-cross-language-question-loses-i
kami:task/495-bug-x-escapes-the-personal-boundary-and
kami:task/499-llama-server-holds-7-9gb-rss-for-a-1-1gb
kami:task/470-bug-a-question-writes-invented-knowledge
kami:task/493-bug-the-memory-index-stores-the-raw-utte
kami:task/490-name-the-gap-world-questions-through-the
kami:task/485-run-the-big-model-on-the-workstation-wit
kami:task/489-workstation-deploy-mavgpud-on-workpc-and
kami:task/488-workstation-a-supervisor-that-keeps-llam
kami:task/483-docs-offload-design
kami:task/483-design-offload-ml-to-the-workstation-kee
kami:task/459-docs-refresh-the-qa-plan-against-the-liv
kami:task/446-doc-reorg-tier-the-tree-retire-the-three
kami:fix/367-voice-parks-routine-accept
kami:task/365-dialogue-slots-and-router-slots-are-hand
kami:task/364-snooze-does-nothing-at-runtime-the-gate
kami:task/447-retire-progress-md-the-backlog-and-the-f
kami:task/445-session-workflow
kami:overnight/eco-versioned-traces
kami:overnight/eco-entity-refs
kami:overnight/eco-degraded-suite
kami:overnight/netscan
kami:overnight/smarthome
kami:overnight/replay-simulator
kami:overnight/event-envelope
kami:overnight/coldstart-unlock
kami:overnight/voice-barge-in
kami:overnight/stt-golden-audio
kami:overnight/senses-speaker
kami:overnight/senses-hearing
kami:overnight/senses-media-vision
kami:overnight/mcp-tools
kami:overnight/mcp-client
kami:overnight/self-update
kami:overnight/model-swap
kami:overnight/web-crawler
kami:overnight/rss-feeds
kami:overnight/email-poller
kami:overnight/email-extract
kami:overnight/email-imap
kami:overnight/money-zenmoney
kami:overnight/task-priority
kami:overnight/task-capture
kami:overnight/behavior-profile
kami:overnight/ambient-calendar
kami:overnight/local-calendar
kami:overnight/memory-eval
kami:overnight/proactive-proposals
kami:overnight/split-voice-quiet
kami:overnight/nginx-maven-block
kami:overnight/stepup-chat-surface
kami:integration/small-batch
kami:docs/fix-drift
kami:fix/ru-wording
kami:integration/jul31
kami:overnight/resident-1.7b
kami:overnight/nudge-templates
kami:overnight/kiwix-rewrite
kami:overnight/eval-writeup
kami:overnight/fix-truncation
kami:overnight/kiwix-client
kami:overnight/ru-prompts
kami:overnight/external-data
kami:overnight/phrasing-grammar
kami:overnight/talk-eval
kami:overnight/prompt-context
kami:overnight/prompt-address
kami:overnight/eval-label-kill
kami:overnight/delivery-boundary
kami:overnight/address-check
kami:overnight/system-replies-pr
kami:overnight/clock-intent-pr
kami:overnight/embedder-backfill-pr
kami:overnight/embedder-marker-pr
kami:overnight/note-recall-pr
kami:overnight/thinking-off-pr
kami:overnight/dialogue-persist-pr
kami:overnight/persona-2p-pr
kami:overnight/clarify-expiry-pr
kami:overnight/clarify-rework
kami:overnight/phrasing
kami:overnight/bakeoff
kami:overnight/recall-margin
kami:overnight/router-on
kami:overnight/slot-extract
kami:overnight/embedder-e5
kami:overnight/router-refusal
kami:overnight/eval-rerun
kami:overnight/eval-harnesses
kami:overnight/eval-rerun-base
kami:overnight/fmt-gate
kami:overnight/routines-fire
kami:overnight/router-prompt
kami:overnight/away-leak
kami:overnight/recall-eval
kami:overnight/snooze-works
kami:overnight/clarify-wiring
kami:overnight/delivery-tests
kami:overnight/routine-accept
kami:overnight/llm-router-flag
kami:overnight/clarify-data-layer
kami:overnight/loop-rule-tests
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "overnight/day-plan"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
The day plan: one ordered view of today, built only when asked.
internal/morning/plan.go—BuildPlan(pure) merges calendar events, pending reminders and one line per morning routine with items still outstanding, drops anything outside the clock's own day, and orders by time/kind/text.Plan.Afteris the rest-of-day view;Plan.FormatRUis the spoken rendering.internal/ipc— newday_planmethod withDayPlan/DayPlanItemDTOs. The store adapter refuses it (the plan needs the daemon's routine config), likeTickTraceandMorningStatus.cmd/mavend/tick.go—tickLoop.dayPlanis the impure half: readsCalendarEvents, pending reminders and the checklist facts, then calls the pure builder. Wired asdaemonAPI.getDayPlanat both construction sites.cmd/mavend/actions_query.go— aday-planquery source, gated byrouter.IsDayPlanQuery.cmd/mavweb— the plan renders on the existing/morningpage, above the checklist.Why
The plan belongs with the morning routine engine, not beside it. The routine knows what is missing from a window; the plan knows what the whole day holds. A parallel system would have to re-read the same facts and re-decide what "today" means.
Provenance from #126 travels all the way through: an event relayed off a phone notification is stored below full confidence, and the plan says "похоже, 14:00 — …" rather than reciting a guess as fact.
It is not a nag. Nothing in this change fires, schedules or announces — the plan is assembled only in response to a question, over IPC or on a page load. Unprompted delivery stays with the existing morning nudge and the dispatcher's policy.
Two ordering decisions worth reviewing:
day-plansource sits beforecalendarinquerySources. Both match "…на сегодня", and the plan's matcher is the more specific one (it requires a plan word), so the calendar listing would otherwise swallow it. Order in that table is load-bearing — see #373.IsDayPlanQuerymatches whole words, so "планёрка" (a meeting) is not read as a request for the plan, and it refuses any utterance naming another day, because the plan is built for the clock's own day only. Answering the wrong day is worse than answering more tersely.How verified
make buildandmake testboth exit 0.New tests: plan ordering, the checklist listing only what is left, other-day and blank-entry rejection, the exact RU rendering scanned against the persona rules (feminine, informal, no pet names, no exhortation), rest-of-day trimming with provenance preserved, the day-plan-before-calendar ordering, a core failure answering rather than falling through to RAG, and the matcher's refusals.
Vikunja #128
@@ -89,6 +95,46 @@ func (h *reactiveHandler) queryFactByKey(ctx context.Context, t *queryTurn) (strreturn "", false}// queryDayPlan — "какие планы на сегодня?", "что у меня по плану?", "чтоoh, you know what?
there's a thing I've tried yesterday, we can actually utilize it and it will be more comfortable and natural for me in terms of "day plans".
The split is right.
internal/morning/plan.gois pure and the daemon owns every read, so the plan is testable at a fixed clock. PuttingSpokenin the IPC payload means the voice reply and/morningcannot drift.IsDayPlanQuerytokenizes instead of substring-matching, and the comment about "планёрка" shows why that matters. The read-only claim holds: nothing intick.go:dayPlanwrites or dispatches.7f42cc7touchescmd/mavend/actions_query.gohere, but only the weather branch. Nothing in this PR is fixed on the tip.1. CalDAV events are recited at the wrong hour, and evening ones disappear.
parseDTreads a local DTSTART as UTC and says so in its own comment: "the busy gate only needs busy/not-busy to be right". The day plan is the first consumer that needs the wall clock to be right, and it does not get it.Walk a 13:00 Moscow event on a box at UTC+04.
parseDTstoresStart = 13:00Z.mavcaldavwrites the fact withTs = e.Start.BuildPlandoesat := e.At.In(now.Location())and gets 17:00. She says "план на 01.08.2026: 17:00 — Standup @ 13:00-13:30". The prefix and the value disagree by four hours in the same line.Now a 21:00 event.
Ts = 21:00Z, which is 01:00 the next day locally. The key prefix is stillcalendar_event_20260801, so the SQL returns it, and then theat.Before(dayStart) || !at.Before(dayEnd)filter drops it. Every evening event east of Greenwich is silently missing from the day she recites.Ambient events do not have this problem.
EventFromNotificationbuildsStartwithtime.Date(..., n.Posted.Location()), a real instant. So one plan mixes two clocks: relayed meetings land at the right time, CalDAV meetings are offset. The two sources are ordered against each other bysort.SliceStable, so the plan order is wrong too, not just the labels.The fix belongs in
parseDT, not here, but this PR is what makes it visible. At minimum the day plan should not ship claiming it recites the day.2. A rescheduled meeting is recited twice.
store.CalendarEventshas novoids_idexclusion and no latest-per-key filter. It returns every historical row in the key range, ordered by key.writeIfChangedinmavcaldavwrites a new row when the value changes. Move a standup from 14:00 to 16:00. The key is unchanged, sinceFactKeykeys on day plus summary. The store now holds two rows. The plan reads both: "14:00 — Standup @ 14:00-14:30; 16:00 — Standup @ 16:00-16:30". She reads back a meeting that no longer exists as if it were still on the day.queryCalendarhad the same exposure, but it prints onlyValuein a flat list, so a duplicate reads as noise. The plan prints a time per line, so a duplicate reads as a second appointment. Either dedupe by key at the top ofdayPlan, keeping the newest row, or fix the query.Related and cheap:
FactValuealready embeds15:04-15:04, so every event line prints its time twice. "14:00 — Standup @ 14:00-14:30" is what the formatter produces today.3. The doc says "any morning checklist still outstanding". The code says "active right now".
checklistEntriesskips a routine unlessst.Active.ipc.CoreAPI.DayPlanand thequeryDayPlancomment both promise outstanding items, unqualified.Ask "какие планы на сегодня?" at 14:00 with the morning routine unfinished. The window closed,
Activeis false, and the plan says nothing about it. The one thing the plan could tell him that the calendar cannot is exactly what he skipped. Either narrow the two doc comments to "checklist active now", or drop theActivetest and keep thelen(st.Missing) == 0one.The placement already makes the second option safe. The entry is timed at
NudgeAt. An unfinished routine sorts to the top of the day, not to the moment of asking.4.
otherDayWordsguards against the wrong days thatParseCalendarDateknows, not the wrong days he says.The list covers сегодня-adjacent words only. "какие планы на понедельник?" has no other-day token and does contain "планы", so
queryDayPlanclaims it, ahead ofqueryCalendarinquerySources. She answers today, stamped with today's date. Before this PR that utterance fell through to recall and got nothing, which is a better failure. Same for "планы на неделю" and "планы на выходные".Weekday names, "неделя" and "выходные" in
otherDayWordswould close it. That is a refusal, not a feature, and refusing is correct until the plan can build any day but the clock's own.Smaller notes:
isRestOfDayQueryusesstrings.ContainswhileIsDayPlanQuerytokenizes deliberately. The two should agree, and the substring form is the one with the false positives.Afterempties the plan andFormatRUsays "на 01.08.2026 ничего не запланировано". That is false. The day had things, they are done. The rest-of-day render needs its own empty string.dayPlanfilters reminders on the string literal"pending".7f42cc7introducedDigestStatusfor the same class of bug one table over.dayPlanMaxRemindersbounds the scan at 500 butListRemindersordering is not stated at the call site. If it is not ordered by fire time, the truncation drops arbitrary reminders rather than distant ones.safeKeystrips Cyrillic, so two Russian-named events on one day collapse to the same keycalendar_event_20260801_. The plan then shows one of them, or both as duplicates per finding 2. Pre-existing, but the plan is where he will notice it.On
cmd/mavend/actions_query.go: your comment about the thing you tried the day before is still unresolved, because you never named it. I am not proposing a day-plan redesign in its place. The question stays open and needs you to say what the thing was.Landed on master. The stack was one linear chain, so #84 carried every commit from #50 up, and master now contains this branch in full. Merging this PR on its own is an empty diff, so it is closed rather than merged. The review findings for it were fixed in the 2026-08-01 pass and are on master as commits on the stack tip, not on this branch.
Pull request closed