maven: scheduled routines + persistent long-term memory

Two additive proactive/recall features.

Routines (internal/routine): a third proactive class beside reminders
(user-stated) and care rules (world-state) — operator-declared clockwork.
config.routines[] (cron + literal RU body + severity) fire through the
normal dispatcher on schedule. Bodies are literal, not LLM-phrased (can't
hallucinate); rule name routine:<name> keeps them out of the care
autotuner; a cold-start guard seeds on first sight so a restart never
replays a missed schedule. Pure routine.Due + config validation, unit-
tested; the tick driver holds the last-fired map and calls fireRoutines.

Persistent memory (internal/store/memory.go): store.MemoryStore backs the
memory.Store interface with the SAME encrypted sqlite db — survives
restarts and recall text inherits at-rest encryption (no plaintext
sidecar). float32-blob vectors, brute-force cosine (ANN is a later swap
behind the interface), upsert-by-id. The daemon wires st.VectorMemory()
into wireVoice; the in-memory impl stays the test/no-store floor. Closes
the "in-memory only, lost on restart" gap (PROGRESS #8).

Gate green: gofmt/vet clean, -race across routine/config/store/mavend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2PNdwDj2Gt8YW294J7oSc
This commit is contained in:
kami
2026-07-06 12:37:28 +04:00
parent 186bbb960b
commit 59a4e06615
11 changed files with 590 additions and 13 deletions
+7 -3
View File
@@ -111,7 +111,7 @@ func (w *voiceWiring) close() {
//
// When voice is enabled, MUST wire a voicesink into the dispatcher's Voice
// slot using w.sessions (the caller does that — see main.go).
func wireVoice(cfg *config.Config, coreAPI ipc.CoreAPI, phr phraser.Phraser) (*voiceWiring, error) {
func wireVoice(cfg *config.Config, coreAPI ipc.CoreAPI, phr phraser.Phraser, memStore memory.Store) (*voiceWiring, error) {
if cfg.Voice == nil || !cfg.Voice.Enabled {
return nil, nil
}
@@ -203,8 +203,12 @@ 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)
// ----- memory (long-term vector storage, in-memory for now) -----
memStore := memory.NewInMemoryStore()
// ----- memory (long-term vector storage) -----
// Persistent (store-backed, survives restarts) when the daemon passes one;
// falls back to the in-memory floor otherwise (tests / no-store paths).
if memStore == nil {
memStore = memory.NewInMemoryStore()
}
// ----- dialogue (multi-turn slot carry-over; 2-min follow-up window) -----
dialogueSessions := dialogue.NewSessionStore(2 * time.Minute)