feat: Add memory persistence functions (save/load-memory-to-disk)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
- Add save-memory-to-disk and load-memory-from-disk to memory.lisp
- Integrate auto-save into heartbeat (every N intervals)
- Load memory on daemon startup, save on graceful shutdown/SIGINT
- Add exports to package.lisp
NOTE: Hash table serialization requires object walker for complex structures.
Current implementation fails on load due to unreadable objects.
This commit is contained in:
@@ -65,14 +65,27 @@ The `process-signal` function is the core metabolic processor. It iterates throu
|
||||
The heartbeat ensures the agent remains "alive" even in the absence of external stimuli, allowing for latent reflection and periodic maintenance. The interval is externalized to the `HEARTBEAT_INTERVAL` environment variable.
|
||||
|
||||
#+begin_src lisp :tangle ../library/loop.lisp
|
||||
(defvar *auto-save-interval* 300
|
||||
"Save memory to disk every N seconds. Set from MEMORY_AUTO_SAVE_INTERVAL env.")
|
||||
|
||||
(defvar *heartbeat-save-counter* 0
|
||||
"Counter for auto-save triggers.")
|
||||
|
||||
(defun start-heartbeat ()
|
||||
"Starts the background heartbeat thread. Interval is loaded from HEARTBEAT_INTERVAL."
|
||||
(let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60)))
|
||||
(let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60))
|
||||
(auto-save (or (ignore-errors (parse-integer (uiop:getenv "MEMORY_AUTO_SAVE_INTERVAL"))) *auto-save-interval*)))
|
||||
(setf *auto-save-interval* auto-save)
|
||||
(setf *heartbeat-save-counter* 0)
|
||||
(setf *heartbeat-thread*
|
||||
(bt:make-thread
|
||||
(lambda ()
|
||||
(loop
|
||||
(sleep interval)
|
||||
(incf *heartbeat-save-counter*)
|
||||
(when (>= *heartbeat-save-counter* (/ *auto-save-interval* interval))
|
||||
(setf *heartbeat-save-counter* 0)
|
||||
(save-memory-to-disk))
|
||||
;; inject-stimulus is synchronous for heartbeats, preventing accumulation.
|
||||
(inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
|
||||
:name "opencortex-heartbeat"))))
|
||||
@@ -82,12 +95,18 @@ The heartbeat ensures the agent remains "alive" even in the absence of external
|
||||
The `main` function initializes the environment, loads skills, and starts the heartbeat. It now includes a graceful shutdown handler for `SIGINT` (Ctrl+C) and uses `DAEMON_SLEEP_INTERVAL` to control its idle rhythm.
|
||||
|
||||
#+begin_src lisp :tangle ../library/loop.lisp
|
||||
(defvar *shutdown-save-enabled* t
|
||||
"If non-nil, save memory to disk on graceful shutdown.")
|
||||
|
||||
(defun main ()
|
||||
"Entry point for the Skeleton MVP. Handles initialization and graceful shutdown."
|
||||
(let* ((home (uiop:getenv "HOME"))
|
||||
(env-file (uiop:merge-pathnames* ".local/share/opencortex/.env" (uiop:ensure-directory-pathname home))))
|
||||
(when (uiop:file-exists-p env-file) (cl-dotenv:load-env env-file)))
|
||||
|
||||
|
||||
;; Load memory from disk if a snapshot exists
|
||||
(load-memory-from-disk)
|
||||
|
||||
(initialize-actuators)
|
||||
(initialize-all-skills)
|
||||
|
||||
@@ -98,11 +117,15 @@ The `main` function initializes the environment, loads skills, and starts the he
|
||||
(sb-sys:enable-interrupt sb-unix:sigint
|
||||
(lambda (sig code scp)
|
||||
(declare (ignore sig code scp))
|
||||
(harness-log "SHUTDOWN: SIGINT received. Exiting...")
|
||||
(harness-log "SHUTDOWN: SIGINT received. Saving memory...")
|
||||
(when *shutdown-save-enabled* (save-memory-to-disk))
|
||||
(uiop:quit 0)))
|
||||
|
||||
(let ((sleep-interval (or (ignore-errors (parse-integer (uiop:getenv "DAEMON_SLEEP_INTERVAL"))) 3600)))
|
||||
(loop
|
||||
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*) (return))
|
||||
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
|
||||
(harness-log "SHUTDOWN: Interrupt flag set. Saving memory...")
|
||||
(when *shutdown-save-enabled* (save-memory-to-disk))
|
||||
(return))
|
||||
(sleep sleep-interval))))
|
||||
#+end_src
|
||||
|
||||
@@ -144,6 +144,47 @@ Restores the state of the Memex from one of the previous snapshots.
|
||||
(harness-log "MEMORY ERROR - Snapshot ~a not found." index))))
|
||||
#+end_src
|
||||
|
||||
** Disk Persistence (save-memory / load-memory)
|
||||
Essential for surviving crashes. Saves the in-memory hash tables to disk and loads them back on restart. The path is controlled by the `MEMORY_SNAPSHOT_PATH` environment variable.
|
||||
|
||||
#+begin_src lisp :tangle ../library/memory.lisp
|
||||
(defvar *memory-snapshot-path* nil
|
||||
"Path to the memory snapshot file. Set from MEMORY_SNAPSHOT_PATH env or default.")
|
||||
|
||||
(defun ensure-memory-snapshot-path ()
|
||||
"Initializes the snapshot path from environment or default location."
|
||||
(or *memory-snapshot-path*
|
||||
(let ((env-path (uiop:getenv "MEMORY_SNAPSHOT_PATH")))
|
||||
(setf *memory-snapshot-path*
|
||||
(or env-path
|
||||
(uiop:merge-pathnames* "memory.snap" (user-homedir-pathname)))))))
|
||||
|
||||
(defun save-memory-to-disk ()
|
||||
"Serializes *memory* and *history-store* to disk for crash recovery."
|
||||
(let ((path (ensure-memory-snapshot-path)))
|
||||
(with-open-file (stream path :direction :output :if-exists :supersede :if-does-not-exist :create)
|
||||
(format stream ";; OpenCortex Memory Snapshot~%")
|
||||
(format stream ";; Created: ~a~%~%" (format nil "~a" (get-universal-time)))
|
||||
(prin1 (list :memory *memory* :history-store *history-store*) stream))
|
||||
(harness-log "MEMORY - Saved to ~a" path)
|
||||
path))
|
||||
|
||||
(defun load-memory-from-disk ()
|
||||
"Loads *memory* and *history-store* from disk if the snapshot exists."
|
||||
(let ((path (ensure-memory-snapshot-path)))
|
||||
(when (uiop:file-exists-p path)
|
||||
(handler-case
|
||||
(with-open-file (stream path :direction :input)
|
||||
(let ((data (read stream nil)))
|
||||
(when data
|
||||
(setf *memory* (getf data :memory))
|
||||
(setf *history-store* (getf data :history-store))
|
||||
(harness-log "MEMORY - Loaded from ~a (~a objects)" path (hash-table-size *memory*)))))
|
||||
(error (c)
|
||||
(harness-log "MEMORY WARNING - Failed to load snapshot: ~a" c))))
|
||||
t))
|
||||
#+end_src
|
||||
|
||||
** Lookup Utilities
|
||||
Basic functions for retrieving objects by ID or type.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user