Rank captured tasks by what he actually said (#129) #61
Closed
claude
wants to merge 1 commits from
overnight/task-priority into overnight/task-capture
pull from: overnight/task-priority
merge into: kami:overnight/task-capture
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-capture
kami:overnight/behavior-profile
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/task-priority"
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 changed
New pure package
internal/tasks(noipc, nostore, no cgo) that scores andorders captured tasks and renders the Russian recital:
Rank(items, now)— confirmed work first, then mail-derived candidates; withineach group by score desc, oldest-first on a tie (FIFO).
this week > later), stated urgency (
Weight, 3 rungs,MaxWeight), and agewith a cap so nothing rots at the bottom forever.
Ranked.Reasonnames the one signal that decided the row (просрочено на 12 дн.,сегодня,важно,давно в списке) and is empty when nothing distinguishedthe task.
FormatRU— one renderer for voice and web:сначала: … .for his work withreasons attached,
ещё я нашла, но ты не подтвердил: …for candidates listedbare,
задач нет.when empty, capped atSpokenLimit=5with an "и ещё N" tail.Wiring:
internal/router/task.go—ParseTaskCapturenow returnsTaskCapture{Text, Weight}and strips a leading/trailing
срочно/важно/urgent/importantout of the tasktext. Edges only: "позвонить в срочную помощь" keeps its wording and weight 0.
cmd/mavend/actions_task.go—queryTasksrecitestasks.FormatRU(tasks.Rank(...));capture passes the stated weight through.
cmd/mavweb— the live rows on/tasksare ordered by the same ranker, with a"why" column from
Ranked.Reason, and the add form gained an importance select(normal / важно / срочно). Out-of-range weights clamp instead of 400ing.
No new intent, no new IPC method, no config: this is ordering over the store that
landed in #130.
Why
The ordering is computed rather than asked of the model on purpose. A 1.7B would
answer "which of these matters most?" fluently and baselessly, and a confidently
wrong priority is worse than no priority — the same reasoning as the behaviour
profile in
internal/memory. Where he said nothing, the ranker says nothing: noreason string, score 0, position by age.
Candidates never outrank his own work whatever their due date, and a candidate is
listed without a reason, because its deadline is Maven's reading of a mail and not
something he stated.
Ranking is a READ. It sorts and renders; it never writes, schedules or announces.
Maven is not a nag — a task reaching the top of this list is not a reason to speak,
only the order she recites when asked.
Persona: recital is feminine and informal ("я нашла", "ты не подтвердил"), no pet
names; a test asserts the absence of "рад ", "понял ", "милый", "дорогой", "вам",
"ваши".
How verified
make buildexit 0,make testexit 0 (go vet+-raceacross./internal/..../cmd/...).internal/tasks/rank_test.go— deadline ordering, candidates never outrank openwork, weight lifts undated work but a deadline beats a weight, FIFO ties, no
invented reason, age cap, "due at 23:00 today is not overdue", overdue day
counting,
FormatRUreasons/candidate separation/persona/spoken cap/empty.internal/router/task_test.go— urgency stripped leading and trailing, mid-textstem left alone, bare marker files nothing.
cmd/mavend/actions_task_test.go— stated urgency reachesCaptureTaskReq.Weight,recital is in priority order with the reason named.
cmd/mavweb/tasks_test.go— open rows rendered in rank order with the reason,candidate reason hidden, weight passed through and clamped.
Vikunja #129
Ordering is computed, not generated. Asking a 1.7B which of his tasks matters most produces a fluent opinion with no basis in anything, and a confidently wrong priority is worse than none — same posture as the behaviour profile in internal/memory, which counts instead of summarising. internal/tasks is a pure package (no ipc, no store, no cgo) holding the score, the order and the Russian rendering, so the spoken list and the /tasks page cannot drift. Four signals, all of them things he stated: deadline (overdue > today > tomorrow > this week), stated urgency, age with a cap so nothing rots at the bottom, and confirmed work always ahead of mail-derived candidates. A task with no due date and no weight scores nothing and carries no reason string — inventing a "потому что" about a priority he never set is the failure mode this avoids. Capture now picks up urgency he says out loud ("добавь в задачи срочно оплатить интернет"), stripping the marker from the task text, and the web add form offers the same three rungs. Ranking is a read: it sorts and renders, never writes, schedules or announces.@@ -41,0 +50,4 @@// urgencyMarkers — the words that set a weight, strongest first. Only these// two rungs: "срочно" is a deadline he has not named, "важно" is a preference,// and a third shade of urgent would be a distinction he never makes out loud.var urgencyMarkers = []struct {again - those can be moved in a config file. kinda.
One renderer for the voice reply and the page is the right structure. The reason string is what makes the ranking auditable instead of a black box. Refusing to ask the model which task matters, on the same grounds as the counted behaviour profile, is consistent and correct. Blanking a candidate's reason so her reading of a mail is never shown as his priority is a good detail.
dayDeltain whole days rather than hours is right.7f42cc7moved the urgency markers intotask_phrases.jsonwith the same words and weights. The scoring, the reason strings and the ranker are untouched, so nothing below is fixed on the tip.1. Every due date is off by one day, and the page contradicts itself about it.
store.scanTaskbuildsDuethroughmillisToTime, which ends intime.UnixMilli(v.Int64).UTC(). So a due date always comes back in UTC.dayDeltathen takesloc := due.Location(), which is that UTC, and compares calendar days there.Walk the web form on a UTC+04 box. He adds a task due 2026-08-02.
applyTaskPostparses it withtime.ParseInLocation(..., time.Local), giving 2026-08-02T00:00+04, stored as 2026-08-01T20:00Z. He loads/taskson 1 August at 10:00 local, which is 06:00Z.dayDeltacomputesd= 1 August andn= 1 August, returns 0, and the row says "сегодня" for a task due tomorrow.The next morning is worse. On 2 August at 10:00 local,
n= 2 August andd= 1 August, sodays= -1. The row reads "просрочено на день" on the due date itself, and scores 105 instead of 60.The same row's due column says "02 Aug", because
fmtTaskDatecalls.Local()anddayDeltadoes not. One line of the table tells him two different things about one date.Taking
locfromdueis what breaks it. A date-only due value has no meaningful location once it has been through the store. Compare innow's location instead, or store due as a plain date string and stop pretending it is an instant.2. She downgrades "срочно" to "важно" when she reads the list back.
scoresetsreason = "важно"for any positive weight.urgencyMarkersmaps "срочно" to 3 and "важно" to 2, and both land on the same string.He says "добавь в задачи срочно оплатить интернет".
stripUrgencytakes the word out of the text and sets weight 3, correctly. Ask for the list and she says "сначала: оплатить интернет (важно)". The word he chose was stronger, the store holds the stronger value, and the read-back is the weaker one.The reason string is the one place the ranking explains itself. This is the one case where it reports a word he did not say. Two strings for the two rungs, keyed off the weight.
3. The page promises nothing is guessed, and then ranks by age.
The new hint promises "the deadlines and the urgency you gave, nothing guessed".
scoreaddsscorePerWeekOldper week since capture, up toscoreAgeCap. It stamps "давно в списке" on anything two weeks old with no other signal. Age is neither a deadline nor urgency he gave. It is the ranker's own opinion that old work should rise.I am not arguing against the signal. The package doc is honest about it. The hint on the page is not, and the hint is the part he reads.
There is a second-order effect worth naming. The package doc states the class ordering it guarantees: "overdue beats today beats this week beats undated". A task due in three weeks scores
scoreDueLater, 5. An undated task captured ten weeks ago scores the age cap, 10. The undated one wins, which is the stated ordering inverted. Either dropscoreDueLaterbelow the age floor, or stop claiming the class ordering.4.
stripUrgencymisses the urgency word as soon as anything sits next to it.The prefix arm requires the marker to start the text. The suffix arm requires exactly one space before it at the end.
"добавь в задачи оплатить интернет, срочно" ends in ", срочно". The suffix check tests for " срочно" and fails. Weight stays 0, and the word stays in the task text. The list then reads "оплатить интернет, срочно" with no reason and no score. That is the exact task he was trying to flag. A comma there is not exotic: whisper punctuates dictated Russian.
"очень срочно оплатить интернет" fails the prefix arm for the same reason. Both cases store the marker as part of the task text, so it also pollutes the dedupe key.
Trimming the edge tokens rather than matching a fixed string covers both. It also keeps the mid-sentence protection the comment argues for.
Smaller notes:
handleTasksranks withtime.Now()whilequeryTasksranks withh.now(). The daemon path is testable at a fixed clock and the page path is not.internal/tasksis pure, so the page has no reason to reach for the wall clock directly.applyTaskPostusesfmt.Sscanf(v, "%d", &wgt), which accepts "3junk" as 3. The values come from aselect, so this only matters for a hand-rolled POST, but the same call is used forid.joinRUappends "и ещё 5" with a bare number and no noun. Spoken, that trails off. "и ещё 5 задач" is one word longer and finishes the sentence.Rankputs every candidate below every open task, andFormatRUsplits them into separate sentences anyway. The cross-status comparator only has an effect if some future caller renders the flat slice. Not wrong, just currently unobservable.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