Answer "что я обычно делаю по вторникам?" by counting, not guessing (#254) #59
Closed
claude
wants to merge 1 commits from
overnight/behavior-profile into overnight/day-plan
pull from: overnight/behavior-profile
merge into: kami:overnight/day-plan
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/day-plan
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/behavior-profile"
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
Behavioural memory, narrowed to the half that can be true.
internal/memory/behavior.go—BuildProfilecounts habits out of self-facts: distinct days per weekday (MinHabitDays = 2), a median time of day, andFormatWeekdayRU/FormatOverallRUfor the spoken answer.internal/router/habit.go—ParseHabitQuery: requires a habit marker ("обычно", "каждую", "привычки", …) and resolves the weekday deterministically.cmd/mavend/actions_query.go— ahabitsquery source, placed beforecalendar.docs/plans/09-behavioral-memory.md— a status section recording what shipped and what was refused.No new IPC method: the counted profile reads
RecentFactsover the existing surface.Why this is smaller than the plan doc
Three of the doc's steps are deliberately not implemented, and I would argue against implementing them as written:
internal/pattern/detector.goproposes a routine and the owner accepts it on/routines. It goes through him.Still open if wanted:
MethodGetBehaviorProfile+ a/dashpanel (step 7). That is a display concern only.Judgement calls worth reviewing
cooldown:/quietkeys are excluded explicitly.How verified
make buildandmake testboth exit 0.New tests: weekday counting and ordering, the one-off rejection, env/config/machinery-key exclusion, median-vs-outlier, future-row rejection, the exact RU rendering scanned word-by-word against the persona rules, verbatim fallback for unknown keys, the matcher's yes/no set, and the query source's claim/pass behaviour plus its ordering before
calendar.Vikunja #254
@@ -0,0 +200,4 @@// activityRU glosses the loop's known fact keys. An unknown key is read back// verbatim: it is what the store holds, and inventing a Russian phrase for a key// maven does not recognise would be putting words in his mouth.var activityRU = map[string]string{what?
"Maven, what do I do on Saturdays? - You drink water." - is this how it's supposed to work?
and here's another thing which my eyes caught - a lot of the strings can either be moved somewhere like a constants store. or even be extracted in separate files: yaml, toml, json, whatever.
Counting instead of generating is the right call, and the header comment argues it well. Refusing step 4 of
docs/plans/09-behavioral-memory.mdin the same file that implements the rest is the strongest thing in this PR. Distinct days rather than raw count is the correct habit unit. The median time keeps one 03:00 outlier out of the answer.ParseHabitQueryrequiring a habit marker before it claims the turn is the right seam againstqueryCalendar.7f42cc7already answers your everyday-habits comment.Profile.Everyday,EverydaySpan, and the reworkedFormatWeekdayRUare on the tip, and the RU tables are inbehavior_ru.json. Nothing below is fixed there.1. The 2000-fact window is a row budget over all kinds, and machine rows eat it.
RecentFactsisORDER BY ts DESC LIMIT ?with no kind filter.BuildProfilethen throws away everything that is notkind=self, but the discarding happens after the limit, not before. So the size of the behavioural window depends on how noisy the other writers are.mavpollpolls every 60s and writeswg_handshakewhenever the handshake timestamp is newer than the stored one. WireGuard rehandshakes about every two minutes per active peer. That is roughly 700 env rows a day from one peer, before netdata, kuma, ambient events or telegram.At that rate 2000 rows is under three days. A weekday habit needs
MinHabitDaysdistinct Tuesdays, so at least eight days of history. The window can never contain two Tuesdays.FormatWeekdayRUanswers "по вторникам у меня пока нет ничего постоянного" forever, on a store that holds a year of taps.The comment calls the bound "the cost control", but it is not costing what it thinks. Add
WHERE kind = 'self'to the query behind this call, or bound the window by time and let the row count be whatever it is. A self-fact-only scan over 90 days is small. Self facts come from voice taps, and he does not tap 700 times a day.2. The counting unit is a key the model invented.
Activity.KeyisSlots.Key, which comes straight out of the LLM router.routeSystemshows exactly one keyed example,{"intent":"fact","key":"water","value":"выпил"}, and the GBNF constrains field names only. There is no key allowlist and no normalization anywhere in the tree.Two consequences, both visible in the answer. First, counts split. "я выпил воду" on Monday and "попил воды" on Tuesday can land as
waterandводу. Each has one day, each is belowMinHabitDays, and the habit disappears. Second,activityRUglosses eight English keys, and everything else is recited verbatim. A key ofвыпил_водыproduces "обычно ты выпил_воды около 09:00". That is a snake-cased past-tense verb read aloud mid-sentence.internal/patternalready solved this one layer over: it extracts a normalized action plus object rather than trusting the key. The profile should count those same normalized events. Failing that, the fact write should map the key through a canonical table.3. Any habit question phrased with "планы" is swallowed by the day plan.
querySourcesputsday-planbeforehabits.IsDayPlanQueryfires on the token "планы" and itsotherDayWordslist does not know weekday names."какие у меня обычно планы по вторникам?" has a habit marker and a weekday.
queryDayPlanclaims it first and answers today's calendar, stamped with today's date.queryHabitsnever runs. The comment you added toquerySourcessays the habit question is "the more specific one", and the ordering says the opposite.Either move
habitsaboveday-plan, or makeIsDayPlanQueryreturn false whenParseHabitQuerymatches. The second is better: one matcher should not have to know the other's position in a slice.4.
medianIntis a median of minutes-since-midnight, so it breaks on anything that straddles midnight.sleepis in the gloss table. Bedtimes of 23:40, 23:50, 00:10 and 00:20 give minute values of 1420, 1430, 10 and 20. The median is 720. She reports "обычно ты спишь около 12:00".This is the exact class of error the median was chosen to avoid. It hits the one activity most likely to cross the boundary. A circular median, or a per-activity day-cut offset, would fix it. Refusing to report a time when the values span more than twelve hours would also be honest.
Smaller notes:
RecentFactsincludes voided rows, by its own doc comment: "you want to SEE a correction, not have it hidden". The profile counts both the retracted row and the correction. A fact he explicitly reverted still shapes what she says he usually does.Profile.Sinceis computed and never spoken. She says "обычно" without ever saying over what period. Given finding 1 that period may be three days. One clause on the end ofFormatOverallRUwould make the claim checkable.FormatWeekdayRUstill says "по средам у меня пока нет ничего постоянного". "У меня" is her, and the question was about him. The tip changed the other two branches and left this one.nonBehaviouralKeyPrefixesholds"quiet"as a bare prefix, not"quiet_"or an exact match. Any future self-fact key starting with those five letters is silently dropped from behaviour.weekdayWordshas no "воскресенье" oblique forms beyond the dative plural, and no "по выходным". "что я обычно делаю по выходным?" matches the marker and finds no weekday. She answers with the whole-week profile instead.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