Commit23afc2a7Recorded3 Apr 2026Repositoryminder
Fix schedule persistence: use read-modify-write instead of full rewrite
Message
save-config! was serializing the entire in-memory active-schedules list to minder.yaml. If active-schedules was empty or incomplete when this ran, all existing schedules in the file were clobbered.
Replace with add-schedule-to-config! that reads the current file, merges the single new/updated entry, and writes back — matching the pattern remove-schedule-from-config! already used.
Also: - One-time schedules no longer persist to config (the format can't represent the one-time flag, and they auto-remove after firing) - Extract shared YAML serialization into schedules->yaml-lines and write-schedules-config! helpers - Suppress config-watcher reload after our own writes to avoid redundant re-parsing
Changed
src/minder/scheduler.sgl | 83 +++++++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 45 insertions(+), 38 deletions(-)Diff
src/minder/scheduler.sglmodified
@@ -29,6 +29,9 @@
29
;; Mutable alist of (name handle cron prompt one-time?) 30
(define active-schedules '()) 31
+32
;; Flag to suppress config-watcher reload after our own writes.+33
(define suppress-reload? #f)+34
35
(define (get-active name) 36
(let ((pair (assoc name active-schedules))) 37
(and pair (cdr pair))))@@ -133,9 +136,12 @@
136
(when (file-exists? "minder.yaml") 137
(let ((current-mtime (file-mtime "minder.yaml"))) 138
(when (> current-mtime last-mtime)−136
(log-info "minder.yaml changed, reloading schedules")−137
(reload-schedules! server)−138
(set! last-mtime current-mtime))))+139
(set! last-mtime current-mtime)+140
(if suppress-reload?+141
(set! suppress-reload? #f)+142
(begin+143
(log-info "minder.yaml changed, reloading schedules")+144
(reload-schedules! server)))))) 145
(loop)))) 146
147
;; Reload schedules from minder.yaml, only touching changed ones.@@ -172,43 +178,43 @@
178
;; Config File Persistence 179
;; ============================================================ 180
−175
;; Write the current schedule state to minder.yaml.−176
(define (save-config!)−177
(let ((lines '()))−178
(when (pair? active-schedules)−179
(set! lines (list "schedules:"))−180
(for-each−181
(lambda (entry)−182
(let ((name (car entry))−183
(cron (caddr entry))−184
(desc (cadddr entry)))−185
(set! lines (append lines−186
(list (format " ~a:" name)−187
(format " cron: \"~a\"" cron)−188
(format " prompt: ~a" desc))))))−189
active-schedules))+181
;; Serialize schedule-config records to YAML lines.+182
(define (schedules->yaml-lines schedules)+183
(if (pair? schedules)+184
(cons "schedules:"+185
(apply append+186
(map (lambda (sched)+187
(list (format " ~a:" (schedule-config-name sched))+188
(format " cron: \"~a\"" (schedule-config-cron sched))+189
(format " prompt: ~a" (schedule-config-prompt sched))))+190
schedules)))+191
'()))+192
+193
;; Write a list of schedule-config records to minder.yaml.+194
(define (write-schedules-config! schedules)+195
(guard (e (else+196
(log-error (format "Failed to update config: ~a" e))))+197
(set! suppress-reload? #t) 198
(write-file-string "minder.yaml"−191
(string-append (string-join lines "\n") "\n"))))+199
(string-append (string-join (schedules->yaml-lines schedules) "\n") "\n"))))+200
+201
;; Add or update a single schedule in minder.yaml via read-modify-write.+202
(define (add-schedule-to-config! name cron desc)+203
(let* ((current (load-minder-config))+204
(existing (filter+205
(lambda (s) (not (equal? (schedule-config-name s) name)))+206
(minder-config-schedules current)))+207
(updated (append existing+208
(list (schedule-config name: name cron: cron prompt: desc)))))+209
(write-schedules-config! updated))) 210
211
;; Remove a single schedule from minder.yaml without touching others. 212
(define (remove-schedule-from-config! name)−195
(guard (e (else−196
(log-error (format "Failed to update config: ~a" e))))−197
(let* ((current (load-minder-config))−198
(remaining (filter−199
(lambda (s) (not (equal? (schedule-config-name s) name)))−200
(minder-config-schedules current)))−201
(lines (if (pair? remaining)−202
(cons "schedules:"−203
(apply append−204
(map (lambda (sched)−205
(list (format " ~a:" (schedule-config-name sched))−206
(format " cron: \"~a\"" (schedule-config-cron sched))−207
(format " prompt: ~a" (schedule-config-prompt sched))))−208
remaining)))−209
'())))−210
(write-file-string "minder.yaml"−211
(string-append (string-join lines "\n") "\n")))))+213
(let* ((current (load-minder-config))+214
(remaining (filter+215
(lambda (s) (not (equal? (schedule-config-name s) name)))+216
(minder-config-schedules current))))+217
(write-schedules-config! remaining))) 218
219
;; ============================================================ 220
;; MCP Tools@@ -258,8 +264,9 @@
264
(cancel-schedule! name)) 265
;; Register the schedule 266
(register-schedule! server name cron desc one-time?: one-time?)−261
;; Persist to config−262
(save-config!)+267
;; Persist to config (skip one-time schedules — they auto-remove)+268
(unless one-time?+269
(add-schedule-to-config! name cron desc)) 270
(format "Schedule '~a' added~a: ~a" name 271
(if one-time? " (one-time)" "") cron)))) 272