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:
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/kami/maven/internal/delivery/ntfysink"
|
||||
"github.com/kami/maven/internal/delivery/telegramsink"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
// Config — the daemon's whole config tree. Loaded once at startup.
|
||||
@@ -119,6 +120,25 @@ type Config struct {
|
||||
// Digest — notification batching / digest mode. nil ⇒ digest disabled
|
||||
// (every nudge is sent as it fires — legacy behaviour).
|
||||
Digest *DigestConfig `json:"digest,omitempty"`
|
||||
|
||||
// Routines — scheduled behaviors maven performs on a cron schedule (a
|
||||
// morning briefing, an evening wind-down), independent of any request or
|
||||
// care predicate. Each fires its Body through the dispatcher on its Cron
|
||||
// schedule. Empty ⇒ no routines. See internal/routine for the class
|
||||
// distinction from reminders (user-stated) and care rules (world-state).
|
||||
Routines []RoutineConfig `json:"routines,omitempty"`
|
||||
}
|
||||
|
||||
// RoutineConfig — one scheduled routine. Cron is a standard 5-field expression
|
||||
// ("0 8 * * *" = 08:00 daily). Body is the RU text delivered verbatim (routines
|
||||
// are not LLM-phrased). Severity (1-4, default 1) drives routing: care-class
|
||||
// (≤2) is suppressed by quiet hours and drops when away; ops-class reaches away
|
||||
// channels.
|
||||
type RoutineConfig struct {
|
||||
Name string `json:"name"`
|
||||
Cron string `json:"cron"`
|
||||
Body string `json:"body"`
|
||||
Severity int `json:"severity,omitempty"`
|
||||
}
|
||||
|
||||
// QuietHoursConfig — a recurring daily quiet-window. Times are local to the
|
||||
@@ -363,6 +383,14 @@ func (c *Config) applyDefaults() {
|
||||
c.Voice.ToolTimeout = Duration(DefaultToolTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// routines: default severity to care-class (1) — the safe floor: a
|
||||
// misconfigured routine can't blast an away channel at 3am.
|
||||
for i := range c.Routines {
|
||||
if c.Routines[i].Severity == 0 {
|
||||
c.Routines[i].Severity = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
@@ -382,6 +410,19 @@ func (c *Config) validate() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
// routines: name + body required, cron must parse. A typo here should fail
|
||||
// at startup, not silently never fire.
|
||||
for _, r := range c.Routines {
|
||||
if r.Name == "" {
|
||||
return errors.New("routine: name is required")
|
||||
}
|
||||
if r.Body == "" {
|
||||
return fmt.Errorf("routine %q: body is required", r.Name)
|
||||
}
|
||||
if _, err := cron.ParseStandard(r.Cron); err != nil {
|
||||
return fmt.Errorf("routine %q: bad cron %q: %w", r.Name, r.Cron, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user