;
; persist.scm - AtomSpace persistence to disk
;
; Saves/loads knowledge between sessions using RocksDB or JSON fallback.
;
(use-modules (opencog))
(use-modules (ice-9 format))
(use-modules (ice-9 rdelim))
(use-modules (srfi srfi-1))
;; ═══════════════════════════════════════════════════════════════════
;; CONFIGURATION
;; ═══════════════════════════════════════════════════════════════════
(define *data-dir* (string-append (dirname (current-filename)) "/data"))
(define *words-file* (string-append *data-dir* "/words.scm"))
(define *pairs-file* (string-append *data-dir* "/pairs.scm"))
(define *counters-file* (string-append *data-dir* "/counters.scm"))
;; ═══════════════════════════════════════════════════════════════════
;; ENSURE DATA DIRECTORY
;; ═══════════════════════════════════════════════════════════════════
(define (ensure-data-dir)
(unless (file-exists? *data-dir*)
(mkdir *data-dir*)))
;; ═══════════════════════════════════════════════════════════════════
;; SAVE KNOWLEDGE
;; ═══════════════════════════════════════════════════════════════════
(define (save-knowledge)
"Save all knowledge to disk"
(ensure-data-dir)
(display "[persist] Saving knowledge...\n")
;; Save word counts
(call-with-output-file *words-file*
(lambda (port)
(display ";; Word counts\n" port)
(display "(define saved-words '(\n" port)
(for-each
(lambda (node)
(let ((name (cog-name node)))
(when (string-prefix? "word:" name)
(let ((word (substring name 5))
(count (get-word-count node)))
(format port " (~s . ~a)\n" word count)))))
(cog-get-atoms 'ConceptNode #t))
(display "))\n" port)))
;; Save pair counts
(call-with-output-file *pairs-file*
(lambda (port)
(display ";; Pair counts\n" port)
(display "(define saved-pairs '(\n" port)
(for-each
(lambda (edge)
(let ((outgoing (cog-outgoing-set edge)))
(when (= (length outgoing) 2)
(let ((bond (car outgoing))
(lst (cadr outgoing)))
(when (and (equal? (cog-name bond) "pair-bond")
(eq? (cog-type lst) 'ListLink))
(let* ((words (cog-outgoing-set lst))
(w1-name (cog-name (car words)))
(w2-name (cog-name (cadr words)))
(count (get-pair-count edge)))
(when (and (string-prefix? "word:" w1-name)
(string-prefix? "word:" w2-name))
(format port " ((~s . ~s) . ~a)\n"
(substring w1-name 5)
(substring w2-name 5)
count))))))))
(cog-get-atoms 'EdgeLink #t))
(display "))\n" port)))
;; Save global counters
(call-with-output-file *counters-file*
(lambda (port)
(format port ";; Global counters\n")
(format port "(define saved-total-words ~a)\n" *total-words*)
(format port "(define saved-total-pairs ~a)\n" *total-pairs*)))
(display "[persist] Saved.\n"))
;; ═══════════════════════════════════════════════════════════════════
;; LOAD KNOWLEDGE
;; ═══════════════════════════════════════════════════════════════════
(define (load-knowledge)
"Load knowledge from disk"
(display "[persist] Loading knowledge...\n")
(when (file-exists? *counters-file*)
(load *counters-file*)
(set! *total-words* saved-total-words)
(set! *total-pairs* saved-total-pairs)
(display (format #f "[persist] Counters: words=~a pairs=~a\n"
*total-words* *total-pairs*)))
(when (file-exists? *words-file*)
(load *words-file*)
(for-each
(lambda (entry)
(let ((word (car entry))
(count (cdr entry)))
(let ((node (cog-new-node 'ConceptNode
(string-append "word:" word))))
(cog-set-value! node (Predicate "count")
(FloatValue count)))))
saved-words)
(display (format #f "[persist] Loaded ~a words\n" (length saved-words))))
(when (file-exists? *pairs-file*)
(load *pairs-file*)
(for-each
(lambda (entry)
(let* ((pair-key (car entry))
(count (cdr entry))
(w1 (car pair-key))
(w2 (cdr pair-key)))
(let* ((n1 (cog-new-node 'ConceptNode (string-append "word:" w1)))
(n2 (cog-new-node 'ConceptNode (string-append "word:" w2)))
(bond (cog-new-node 'ConceptNode "pair-bond"))
(edge (cog-new-link 'EdgeLink bond
(cog-new-link 'ListLink n1 n2))))
(cog-set-value! edge (Predicate "count")
(FloatValue count)))))
saved-pairs)
(display (format #f "[persist] Loaded ~a pairs\n" (length saved-pairs))))
(display "[persist] Done.\n"))
;; ═══════════════════════════════════════════════════════════════════
;; AUTO-SAVE HOOK
;; ═══════════════════════════════════════════════════════════════════
(define *last-save-time* (current-time))
(define *save-interval* 300) ; Save every 5 minutes
(define *changes-since-save* 0)
(define *save-threshold* 50) ; Or after 50 changes
(define (maybe-auto-save)
"Save if enough time/changes have passed"
(set! *changes-since-save* (+ 1 *changes-since-save*))
(let ((now (current-time)))
(when (or (> *changes-since-save* *save-threshold*)
(> (- now *last-save-time*) *save-interval*))
(save-knowledge)
(set! *changes-since-save* 0)
(set! *last-save-time* now))))