Files
passepartout/harness/loop.org
Amr Gharbeia c0d3f066e8
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
Proactive doctor, setup wizard, and TUI fixes
BREAKING CHANGES / KNOWN ISSUES:
- 8 skills have syntax errors causing loader warnings:
  org-skill-bouncer, org-skill-config-manager, org-skill-credentials-vault,
  org-skill-engineering-standards, org-skill-gardener, org-skill-homoiconic-memory,
  org-skill-peripheral-vision, org-skill-policy
- These skills fail to load but don't block system operation
- TUI works despite these errors

FEATURES ADDED:

1. Proactive Doctor System
   - Doctor runs automatically on daemon startup
   - Health check runs before accepting connections
   - Adds /health endpoint for health status queries
   - *system-health* variable tracks: :healthy, :degraded, :unhealthy, :unknown

2. Error Handling (Option B - Debugger Hook)
   - TUI and CLI now run doctor diagnostics on errors
   - Shows "Run opencortex doctor" message on crash
   - Suggests repair commands after failures

3. Interactive Setup Wizard (org-skill-config-manager)
   - Full wizard implemented in config-manager skill:
     * LLM provider configuration (OpenAI, Anthropic, OpenRouter, Groq, Gemini, Ollama)
     * Gateway linking (Slack, Discord)
     * Memory settings (auto-save interval, history retention)
     * Network settings (timeout, proxy)
   - Saves to ~/.config/opencortex/.env (KEY=VALUE format)
   - CLI integration: opencortex setup, setup --add-provider, setup --link

4. CLI Enhancements
   - doctor --watch: Background health monitoring (60s interval)
   - doctor --fix: Interactive repair (falls back to full setup if core files missing)
   - setup command runs wizard or delegates to setup_system

5. TUI Fixes
   - Inlined message formatting to avoid dependency issues
   - Added error handling in handle-return
   - Cleaner error messages

6. Thin Harness Compliance
   - Removed doctor from harness (now in org-skill-diagnostics skill)
   - XDG directories: only .lisp in harness, .org kept in skills for loader
2026-04-29 12:58:09 -04:00

7.3 KiB

The Metabolic Loop (loop.lisp)

Overview

The Metabolic Loop is the fundamental rhythm of OpenCortex: the continuous processing of signals from perception through cognition to action.

Implementation

Package Context

(in-package :opencortex)

Global Variables (Thread-Safe)

(defvar *interrupt-flag* nil
  "Atomic flag set by signal handlers to trigger graceful shutdown.")

(defvar *interrupt-lock* (bt:make-lock "harness-interrupt-lock")
  "Mutex protecting *interrupt-flag* access.")

(defvar *heartbeat-thread* nil
  "Handle to the heartbeat thread.")

Core Engine (process-signal)

(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)))
        (when (> depth 10)
          (harness-log "METABOLISM ERROR: Max recursion depth reached.")
          (return nil))

        (when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
          (harness-log "METABOLISM: Interrupted by shutdown signal.")
          (return nil))

        (handler-case
            (progn
              (setf current-signal (perceive-gate current-signal))
              (setf current-signal (reason-gate current-signal))
              (let ((feedback (act-gate current-signal)))
                (if feedback
                    (progn
                      (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)
              (unless (member sensor '(:loop-error :tool-error :syntax-error))
                (harness-log "CRITICAL ERROR: Initiating Micro-Rollback.")
                (rollback-memory 0))
              (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)))))))))))

Heartbeat Mechanism

(defvar *auto-save-interval* 300)
(defvar *heartbeat-save-counter* 0)

(defun start-heartbeat ()
  "Starts the background heartbeat thread."
  (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
                 (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
           :name "opencortex-heartbeat"))))

Shutdown Flag

(defvar *shutdown-save-enabled* t)

Health Status

(defvar *system-health* :unknown
  "Current system health status: :healthy, :degraded, :unhealthy, or :unknown.")

(defvar *health-check-ran* nil
  "Flag indicating if initial health check has completed.")

Proactive Doctor

(defun run-startup-health-check ()
  "Runs the doctor diagnostics on startup. Returns health status."
  (format t "~%")
  (format t "==================================================~%")
  (format t " DOCTOR: Running Startup Health Check~%")
  (format t "==================================================~%")
  (handler-case
      (progn
        (when (fboundp 'doctor-run-all)
          (let ((result (doctor-run-all :auto-install nil)))
            (setf *health-check-ran* t)
            (if result
                (progn
                  (setf *system-health* :healthy)
                  (format t "DAEMON: Health check passed. Starting services.~%"))
                (progn
                  (setf *system-health* :degraded)
                  (format t "DAEMON: Health check found issues.~%")
                  (format t "         Run 'opencortex doctor --fix' to repair.~%")))))
        (setf *health-check-ran* t))
    (error (c)
      (format t "DOCTOR ERROR: ~a~%" c)
      (setf *system-health* :unhealthy)
      (setf *health-check-ran* t)))
  (format t "==================================================~%~%"))

Main Entry Point (main)

(defun main ()
  "Entry point for OpenCortex. Initializes the system and enters idle loop."
  (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)
  (initialize-actuators)
  (initialize-all-skills)
  
  ;; Run proactive doctor before starting services
  (run-startup-health-check)
  
  (start-heartbeat)
  (start-daemon)

  #+sbcl
  (sb-sys:enable-interrupt sb-unix:sigint
                            (lambda (sig code scp)
                              (declare (ignore sig code scp))
                              (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*)
        (harness-log "SHUTDOWN: Interrupt flag set. Saving memory...")
        (when *shutdown-save-enabled* (save-memory-to-disk))
        (return))
      (sleep sleep-interval))))

Test Suite

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload :fiveam :silent t))

(defpackage :opencortex-immune-system-tests
  (:use :cl :fiveam :opencortex)
  (:export #:immune-suite))

(in-package :opencortex-immune-system-tests)

(def-suite immune-suite :description "Verification of the Immune System (Core Error Hooks)")
(in-suite immune-suite)

(test loop-error-injection
  "Verify that a crash in think/decide triggers a :loop-error stimulus."
  (clrhash opencortex::*skills-registry*)
  (opencortex:defskill :evil-skill
    :priority 100
    :trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :user-input))
    :probabilistic (lambda (ctx) (declare (ignore ctx)) (error "CRITICAL BRAIN FAILURE"))
    :deterministic nil)
  (opencortex:process-signal '(:type :EVENT :payload (:sensor :user-input)))
  (let ((logs (opencortex:context-get-system-logs 20)))
    (is (not (null (find-if (lambda (line) (search "CRITICAL BRAIN FAILURE" line)) logs))))))