package main import ( "context" "log" "strconv" "github.com/kami/maven/internal/ipc" "github.com/kami/maven/internal/router" ) // actionFact handles router.IntentFact: persist a tapped self-fact, index // it for recall, and let pattern detection propose a routine. func (h *reactiveHandler) actionFact(ctx context.Context, dec router.Decision) string { if !dec.Slots.HasKey { return "не разобрала, что записать — попробуй иначе." } now := h.now() req := ipc.WriteFactReq{ Ts: now, Kind: "self", Key: dec.Slots.Key, Value: dec.Slots.Value, Source: "tap:voice", Confidence: 1.0, // Subject: the key doubles as the entity-resolution candidate — // a voice-tapped fact's key is usually the thing/person it's // about ("espresso_machine", "kate"), so queueing it for Nexus // resolution costs one async lookup and is a no-op (not_found) // for the abstract self-state keys (mood, water) that aren't // entities at all. Subject: dec.Slots.Key, } factID, err := h.api.WriteFact(ctx, req) if err != nil { log.Printf("voice: write fact: %v", err) return "не получилось сохранить факт." } // Index the fact utterance in long-term memory (best-effort, must not // fail the fact write). Facts aren't in the notes table, so this is the // only recall path for them — "когда я пил воду?" reads back from here. if h.memStore != nil { if vec, err := router.EmbedPassage(ctx, h.embedder, dec.Utterance); err != nil { log.Printf("voice: embed fact for memory: %v", err) } else if err := h.memStore.Insert(ctx, "fact:"+dec.Slots.Key+":"+strconv.FormatInt(now.Unix(), 10), vec, map[string]string{ "source": "voice", "type": "fact", "text": dec.Utterance, "ts": strconv.FormatInt(now.Unix(), 10), }); err != nil { log.Printf("voice: memory insert fact: %v", err) } } // Event extraction + pattern detection (best-effort, must not fail the // fact write). If the fact describes a recognizable action, it becomes a // normalized event; if ≥3 events for the same action+object show stable // intervals, a proposed routine is created and parked for confirmation. if h.dataStore != nil { if phrase := h.detectPattern(ctx, factID, dec.Slots.Key, dec.Slots.Value, now); phrase != "" { return phrase // "ты заправляешь ... напоминать?" } } return "" // replier phrases the success reply }