maven: long-term memory vector-store interface (task 7)

- New internal/memory/ package: Store interface, InMemoryStore (cosine sim)
- Tests: insert→search roundtrip, topK truncation, empty store, cosine edges
- Wire into voice: memStore on reactiveHandler, insert note embedding after WriteNote
- Memory Insert is best-effort, log-and-continue on error

Co-Authored-By: opencode <opencode@anthropic.com>
This commit is contained in:
kami
2026-07-06 04:18:50 +04:00
parent 79eb43e9b9
commit 880715fad4
3 changed files with 194 additions and 2 deletions
+19 -2
View File
@@ -51,10 +51,12 @@ import (
"os"
"path/filepath"
"strings"
"strconv"
"sync"
"time"
"github.com/kami/maven/internal/audio"
"github.com/kami/maven/internal/memory"
"github.com/kami/maven/internal/config"
"github.com/kami/maven/internal/delivery"
"github.com/kami/maven/internal/delivery/voicesink"
@@ -200,7 +202,10 @@ func wireVoice(cfg *config.Config, coreAPI ipc.CoreAPI, phr phraser.Phraser) (*v
// ----- voice sink (proactive nudges: dispatcher → voicesink → tts → push to client) -----
w.voiceSink = voicesink.New(synthesizer, sessions)
// ----- the handler (the reactive path; closes over stt / tts / router / coreAPI) -----
// ----- memory (long-term vector storage, in-memory for now) -----
memStore := memory.NewInMemoryStore()
// ----- the handler (the reactive path; closes over stt / tts / router / coreAPI / memory) -----
h := &reactiveHandler{
stt: transcriber,
tts: synthesizer,
@@ -213,6 +218,7 @@ func wireVoice(cfg *config.Config, coreAPI ipc.CoreAPI, phr phraser.Phraser) (*v
now: time.Now,
weatherProvider: weatherProvider,
weatherLocation: weatherLocation,
memStore: memStore,
}
// ----- the server (TCP listener) -----
@@ -244,6 +250,8 @@ type reactiveHandler struct {
weatherProvider weather.Provider
weatherLocation string // default location for weather queries
memStore memory.Store
// pending destructive-act confirmation. A destructive act replies with a
// "выполнить X? да/нет" prompt and parks here; the NEXT utterance is read as
// the y/n answer. ponytail: single slot, single-user box — a second act
@@ -422,10 +430,19 @@ func (h *reactiveHandler) applyAction(ctx context.Context, dec router.Decision)
log.Printf("voice: embed note: %v", err)
return "не получилось сохранить заметку."
}
if _, err := h.api.WriteNote(ctx, h.now(), dec.Utterance, vec, "tap:voice"); err != nil {
noteID, err := h.api.WriteNote(ctx, h.now(), dec.Utterance, vec, "tap:voice")
if err != nil {
log.Printf("voice: write note: %v", err)
return "не получилось сохранить заметку."
}
// Insert into long-term memory (best-effort, must not fail the note write)
if h.memStore != nil {
if err := h.memStore.Insert(ctx, strconv.FormatInt(noteID, 10), vec, map[string]string{
"source": "voice",
}); err != nil {
log.Printf("voice: memory insert: %v", err)
}
}
return "" // replier phrases the "saved" reply
case router.IntentQuery: