Files
passepartout/literate/loop.org
Amr Gharbeia dd3873cd5e
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
DOCS: Systematic overhaul of Literate source (Granularity & Technical Reasoning)
2026-04-21 11:49:58 -04:00

6.1 KiB

The Metabolic Loop (loop.lisp)

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

(in-package :opencortex)

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.

(defvar *interrupt-flag* nil
  "Thread-safe signal to halt the metabolic pipeline and daemon.")
(defvar *interrupt-lock* (bt:make-lock "harness-interrupt-lock")
  "Protects the interrupt flag from concurrent access.")

Heartbeat Thread Reference

(defvar *heartbeat-thread* nil
  "Reference to the background thread driving autonomous reflection.")

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.

(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)))))))))))

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.

(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"))))

Lifecycle Management

Main Daemon Entry Point (main)

Initializes the image, boots the gateways, and enters the primary idle loop.

(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))))