nudge: add digest/batching mode for care nudges
When enabled, eligible nudges (sev ≤ ceiling) are queued in memory
instead of sent immediately. Every 'window' duration (or when
'max_items' reached), the queue is flushed as a single digest
notification with concatenated bodies.
Config:
digest:
enabled: true # default false
window: 30m # flush window (default 30m)
max_items: 5 # flush at this count (default 5)
severity_ceiling: 2 # max sev batched (default 2; sev3+ bypass)
Changes:
- config.go: add DigestConfig struct with defaults
- tick.go: QueuedNudge type, digest queue/flush in TickLoop,
shouldQueue/maybeFlush/flushDigest helpers
- main.go: pass cfg.Digest to newTickLoop
- tick_test.go: 6 new tests covering queue, flush, bypass, dedup
This commit is contained in:
@@ -115,6 +115,10 @@ type Config struct {
|
||||
// fact independently; both the schedule AND the toggle activate quiet.
|
||||
// nil ⇒ quiet hours only activate via the voice toggle.
|
||||
QuietHours *QuietHoursConfig `json:"quiet_hours,omitempty"`
|
||||
|
||||
// Digest — notification batching / digest mode. nil ⇒ digest disabled
|
||||
// (every nudge is sent as it fires — legacy behaviour).
|
||||
Digest *DigestConfig `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// QuietHoursConfig — a recurring daily quiet-window. Times are local to the
|
||||
@@ -185,6 +189,18 @@ type ToolConfig struct {
|
||||
Destructive bool `json:"destructive,omitempty"`
|
||||
}
|
||||
|
||||
// DigestConfig — notification batching / digest mode. When enabled, eligible
|
||||
// nudges (severity ≤ SeverityCeiling) are queued in memory instead of sent
|
||||
// immediately. Every Window duration (or when MaxItems reached), the queue is
|
||||
// flushed as a single digest notification. nil ⇒ digest disabled (legacy
|
||||
// behaviour — every nudge is sent as it fires).
|
||||
type DigestConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Window Duration `json:"window,omitempty"` // e.g. "30m"
|
||||
MaxItems int `json:"max_items,omitempty"` // flush at this count
|
||||
SeverityCeiling int `json:"severity_ceiling,omitempty"` // max sev batched
|
||||
}
|
||||
|
||||
// PhraserConfig — the LLM-backed phraser seam. The daemon spawns llama-server
|
||||
// as a managed subprocess and sends chat-completion requests to phrase nudge
|
||||
// and reminder messages. nil ⇒ the template-based Stub is used instead.
|
||||
@@ -315,6 +331,19 @@ func (c *Config) applyDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Digest == nil {
|
||||
c.Digest = &DigestConfig{Enabled: false}
|
||||
}
|
||||
if c.Digest.Window == 0 {
|
||||
c.Digest.Window = Duration(30 * time.Minute)
|
||||
}
|
||||
if c.Digest.MaxItems == 0 {
|
||||
c.Digest.MaxItems = 5
|
||||
}
|
||||
if c.Digest.SeverityCeiling == 0 {
|
||||
c.Digest.SeverityCeiling = 2
|
||||
}
|
||||
|
||||
if c.Voice != nil {
|
||||
if c.Voice.RouterThreshold <= 0 {
|
||||
c.Voice.RouterThreshold = DefaultRouterThreshold
|
||||
|
||||
Reference in New Issue
Block a user