Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
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
7.3 KiB
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))))))