#+TITLE: The Metabolic Loop (loop.lisp) #+AUTHOR: Amr #+FILETAGS: :harness:loop: #+STARTUP: content * The Metabolic Loop (loop.lisp) ** Architectural Intent: The Heartbeat The Metabolic Loop is the high-level coordinator of the OpenCortex. It orchestrates the flow of energy (information) through the system by recursively calling the metabolic stages: Perceive, Reason, and Act. Inspired by biological metabolism, the loop ensures that every stimulus is processed until it reaches "stasis" (no further actions required) or an error occurs. This recursive design allows the agent to chain multiple thoughts and tool calls together into a single cohesive cognitive session. ** Pipeline Initialization #+begin_src lisp :tangle ../library/loop.lisp (in-package :opencortex) #+end_src * Concurrency and Interrupts ** Metabolic Interrupt Flag The harness must be able to stop gracefully. We use a thread-safe flag to signal the daemon to exit its primary loop. #+begin_src lisp :tangle ../library/loop.lisp (defvar *interrupt-flag* nil "Thread-safe signal to halt the metabolic pipeline and daemon.") #+end_src #+begin_src lisp :tangle ../library/loop.lisp (defvar *interrupt-lock* (bt:make-lock "harness-interrupt-lock") "Protects the interrupt flag from concurrent access.") #+end_src ** Heartbeat Thread Reference #+begin_src lisp :tangle ../library/loop.lisp (defvar *heartbeat-thread* nil "Reference to the background thread driving autonomous reflection.") #+end_src * The Metabolic Pipeline ** Signal Processor (process-signal) The primary cognitive processor. It takes a normalized signal and pushes it through the gates. If a gate generates "Feedback" (e.g., a tool result), the function recursively processes that feedback as a new stimulus. #+begin_src lisp :tangle ../library/loop.lisp (defun process-signal (signal) "The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act." (let ((current-signal signal)) (loop while current-signal do (let ((depth (getf current-signal :depth 0)) (meta (getf current-signal :meta))) ;; Safety: Prevent infinite cognitive recursion. (when (> depth 10) (harness-log "METABOLISM ERROR: Max depth reached.") (return nil)) ;; Check for graceful shutdown. (when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*) (harness-log "METABOLISM: Interrupted.") (bt:with-lock-held (*interrupt-lock*) (setf *interrupt-flag* nil)) (return nil)) (handler-case (progn ;; Stage 1: Ingest and Normalize (setf current-signal (perceive-gate current-signal)) ;; Stage 2: Cogitate and Verify (setf current-signal (reason-gate current-signal)) ;; Stage 3: Actuate and Generate Feedback (let ((feedback (act-gate current-signal))) (if feedback (progn ;; Inheritance: Metadata must persist across recursive cycles. (unless (getf feedback :meta) (setf (getf feedback :meta) meta)) (setf current-signal feedback)) (setf current-signal nil)))) (error (c) (let ((sensor (ignore-errors (getf (getf current-signal :payload) :sensor)))) (harness-log "METABOLISM CRASH [~a]: ~a" (or sensor :unknown) c) ;; Resilience: Only rollback on critical system errors. (unless (member sensor '(:loop-error :tool-error :syntax-error)) (harness-log "CRITICAL ERROR: Initiating Micro-Rollback.") (rollback-memory 0)) ;; If recursion is shallow, attempt to notify the user of the error. (if (or (> depth 2) (member sensor '(:loop-error :tool-error))) (setf current-signal nil) (setf current-signal (list :type :EVENT :depth (1+ depth) :meta meta :payload (list :sensor :loop-error :message (format nil "~a" c) :depth depth))))))))))) #+end_src * Autonomous Reflection ** Heartbeat Mechanism (start-heartbeat) The heartbeat ensures the agent remains "alive" even in the absence of external stimuli. It allows background workers like the Scribe and Gardener to trigger periodically. #+begin_src lisp :tangle ../library/loop.lisp (defun start-heartbeat () "Starts the background heartbeat thread. Interval is loaded from HEARTBEAT_INTERVAL (default: 60s)." (let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60))) (setf *heartbeat-thread* (bt:make-thread (lambda () (loop (sleep interval) ;; Note: inject-stimulus is synchronous for heartbeats to prevent task accumulation. (inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time)))))) :name "opencortex-heartbeat")))) #+end_src * Lifecycle Management ** Main Daemon Entry Point (main) Initializes the image, boots the gateways, and enters the primary idle loop. #+begin_src lisp :tangle ../library/loop.lisp (defun main () "Primary entry point for the OpenCortex daemon." ;; 1. Environment Hydration (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))) ;; 2. System Bootstrap (initialize-actuators) (initialize-all-skills) ;; 3. Wake up the heart. (start-heartbeat) ;; 4. OS Signal Handling (SBCL specific) #+sbcl (sb-sys:enable-interrupt sb-unix:sigint (lambda (sig code scp) (declare (ignore sig code scp)) (harness-log "SHUTDOWN: SIGINT received. Exiting...") (uiop:quit 0))) ;; 5. Primary Idle Loop (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)) (sleep sleep-interval)))) #+end_src