Files
Maven/cmd/mavend/actions_note.go
T
claude 85a3397bf4 Cancel a reminder by voice, and honour a refusal (V-719)
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>
2026-08-15 17:19:25 +04:00

75 lines
3.0 KiB
Go

package main
import (
"context"
"log"
"strconv"
"github.com/kami/maven/internal/phraser"
"github.com/kami/maven/internal/router"
)
// nothingToCorrectReply — what she says to a correction that points at
// nothing. Filing it would put a sentence in his memory that reads as a fact.
const nothingToCorrectReply = "не поняла, что поправить. скажи целиком, и я запишу."
// actionNote handles router.IntentNote: embed the note, persist it, and
// index it for recall.
//
// The stored body comes only from dec.Utterance (V-576/V-721). An explicit
// leading capture frame is structurally removed; an unmarked note is otherwise
// byte-for-byte his utterance. It is never Slots.Text, phraser output or any
// other model string: a note is durable, the embedder indexes it, and it comes
// back later as recall in his own words. Phrasing belongs in the confirmation.
func (h *reactiveHandler) actionNote(ctx context.Context, dec router.Decision) string {
// A correction with no referent. Everything that could own one has already
// run by here: clarify, confirm and repair are all resolved before routing,
// so a fragment reaching the note path has nothing behind it (V-576).
if correctionFragment(dec.Utterance) {
return nothingToCorrectReply
}
// An utterance that explicitly files a task is work, not recall, and
// belongs in the task store (Vikunja #130). Checked before the embedding
// is paid for. Everything else is a note, exactly as before.
if reply, ok := h.captureTaskFromNote(ctx, dec); ok {
return reply
}
// A standing list is neither work nor recall (Vikunja #453). Checked here
// for the same reason and at the same cost: before the embedding is paid
// for, and it passes the turn straight back when no marker matches.
if reply, ok := h.captureListFromNote(ctx, dec); ok {
return reply
}
noteText := dec.Utterance
if body, explicit := router.ParseNoteCapture(dec.Utterance); explicit {
noteText = body
}
// embed the note text with the same model the classifier uses, persist
// via CoreAPI (source=tap:voice). Semantic recall lives in `notes`, not
// facts — no predicate reads it (spec's two-memory split).
vec, err := router.EmbedPassage(ctx, h.recall.embedder, noteText)
if err != nil {
log.Printf("voice: embed note: %v", err)
return phraser.Ack(phraser.FailNote, nil)
}
noteTs := h.now()
noteID, err := h.api.WriteNote(ctx, noteTs, noteText, vec, "tap:voice")
if err != nil {
log.Printf("voice: write note: %v", err)
return phraser.Ack(phraser.FailNote, nil)
}
// Insert into long-term memory (best-effort, must not fail the note write).
// text/ts in the meta make a Search hit self-describing (see bestRecall).
if h.recall.memStore != nil {
if err := h.recall.memStore.Insert(ctx, "note:"+strconv.FormatInt(noteID, 10), vec, map[string]string{
"source": "voice",
"type": "note",
"text": noteText,
"ts": strconv.FormatInt(noteTs.Unix(), 10),
}); err != nil {
log.Printf("voice: memory insert: %v", err)
}
}
return "" // replier phrases the "saved" reply
}