Files
passepartout/harness/skills.org

11 KiB

The Skill Engine (skills.lisp)

Overview

The opencortex Skill Engine enables Late-Binding Intelligence, allowing the system to discover and integrate new cognitive capabilities at runtime.

Implementation

Package Context

(in-package :opencortex)

Global Skill Registry

(defun COSINE-SIMILARITY (v1 v2)
  "Computes cosine similarity between two vectors."
  (let* ((len1 (length v1)) (len2 (length v2)))
    (if (or (zerop len1) (zerop len2))
        0.0
        (let* ((dot 0.0d0) (n1 0.0d0) (n2 0.0d0))
          (dotimes (i (min len1 len2))
            (let* ((x (coerce (elt v1 i) 'double-float)) (y (coerce (elt v2 i) 'double-float)))
              (incf dot (* x y)) (incf n1 (* x x)) (incf n2 (* y y))))
          (if (or (zerop n1) (zerop n2)) 0.0 (/ dot (sqrt (* n1 n2))))))))

(defun VAULT-MASK-STRING (s) (declare (ignore s)) "[MASKED]")
(defvar *VAULT-MEMORY* (make-hash-table :test 'equal))

(defstruct skill name priority dependencies trigger-fn probabilistic-prompt deterministic-fn)
(defvar *skill-catalog* (make-hash-table :test 'equal)
  "A stateful tracking table for all skill files discovered in the environment.")

(defstruct skill-entry filename (status :discovered) error-log (load-time 0))

(defun find-triggered-skill (context)
  "Returns the highest priority skill whose trigger matches context."
  (let ((triggered nil))
    (maphash (lambda (name skill) 
               (declare (ignore name)) 
               (when (and (skill-probabilistic-prompt skill)
                          (ignore-errors (funcall (skill-trigger-fn skill) context)))
                 (push skill triggered))) 
             *skills-registry*)
    (first (sort triggered #'> :key #'skill-priority))))

(defmacro defskill (name &key priority dependencies trigger probabilistic deterministic)
  "Registers a new skill into the global registry."
  `(setf (gethash (string-downcase (string ,name)) *skills-registry*)
         (make-skill :name (string-downcase (string ,name)) 
                    :priority (or ,priority 10) 
                    :dependencies ',dependencies
                    :trigger-fn ,trigger 
                    :probabilistic-prompt ,probabilistic 
                    :deterministic-fn ,deterministic)))

(defun resolve-skill-dependencies (skill-name)
  "Recursively resolves dependencies for a given skill name."
  (let ((resolved nil) (seen nil))
    (labels ((visit (name) 
               (unless (member name seen :test #'equal) 
                 (push name seen)
                 (let ((skill (gethash (string-downcase (string name)) *skills-registry*)))
                   (when skill 
                     (dolist (dep (skill-dependencies skill)) (visit dep))))
                 (push name resolved))))
      (visit skill-name) 
      (nreverse resolved))))

Skill File Analysis (parse-skill-metadata)

(defun parse-skill-metadata (filepath)
  "Extracts ID and DEPENDS_ON tags from org file."
  (let ((dependencies nil) (id nil) (content (uiop:read-file-string filepath)))
    (let ((id-start (search ":ID:" content)))
      (when id-start
        (let ((id-end (position #\Newline content :start id-start)))
          (when id-end (setf id (string-trim " " (subseq content (+ id-start 4) id-end)))))))
    (let ((pos 0))
      (loop while (setf pos (search "#+DEPENDS_ON:" content :start2 pos))
            do (let ((end (position #\Newline content :start pos)))
              (when end
                (let ((line (string-trim " " (subseq content (+ pos 13) end))))
                  (dolist (d (uiop:split-string line :separator '(#\Space #\Tab)))
                    (unless (string= d "") (push d dependencies))))
                (setf pos end)))))
    (values id (reverse dependencies))))

Dependency Resolution (topological-sort-skills)

(defun topological-sort-skills (skills-dir)
  "Returns a list of skill filepaths sorted by dependency."
  (let ((files (uiop:directory-files skills-dir "org-skill-*.org"))
        (adj (make-hash-table :test 'equal))
        (name-to-file (make-hash-table :test 'equal))
        (id-to-file (make-hash-table :test 'equal))
        (result nil)
        (visited (make-hash-table :test 'equal))
        (stack (make-hash-table :test 'equal)))
    (dolist (file files)
      (let ((filename (pathname-name file)))
        (multiple-value-bind (id deps) (parse-skill-metadata file)
          (setf (gethash (string-downcase filename) name-to-file) file)
          (when id (setf (gethash (string-downcase id) id-to-file) file))
          (setf (gethash (string-downcase filename) adj) deps))))
    (labels ((visit (file)
               (let* ((filename (pathname-name file))
                      (node-key (string-downcase filename)))
                 (unless (gethash node-key visited)
                   (setf (gethash node-key stack) t)
                   (dolist (dep (gethash node-key adj))
                     (let* ((is-id-p (uiop:string-prefix-p "id:" (string-downcase dep)))
                            (dep-key (string-downcase (if is-id-p (subseq dep 3) dep)))
                            (dep-file (if is-id-p 
                                          (gethash dep-key id-to-file)
                                          (or (gethash dep-key id-to-file)
                                              (gethash dep-key name-to-file)))))
                       (when dep-file
                         (let ((dep-filename (pathname-name dep-file)))
                           (if (gethash (string-downcase dep-filename) stack)
                               (error "Circular dependency detected")
                               (visit dep-file))))))
                   (setf (gethash node-key stack) nil)
                   (setf (gethash node-key visited) t)
                   (push file result)))))
      (let ((filenames (sort (mapcar #'pathname-name files) #'string<)))
        (dolist (name filenames)
          (let ((file (gethash (string-downcase name) name-to-file)))
            (when file (visit file)))))
      (nreverse result))))

Jailed Loading (load-skill-from-org)

(defun validate-lisp-syntax (code-string)
  "Checks if a string contains valid Common Lisp forms."
  (handler-case
      (let ((*read-eval* nil))
        (with-input-from-string (s (format nil "(progn ~a)" code-string))
          (loop for form = (read s nil :eof) until (eq form :eof)))
        (values t nil))
    (error (c) (values nil (format nil "~a" c)))))

(defun extract-tangle-target (line)
  "Extracts the value of the :tangle header."
  (let ((pos (search ":tangle" line)))
    (when pos
      (let ((rest (string-trim '(#\Space #\Tab) (subseq line (+ pos 7)))))
        (let ((end (position #\Space rest)))
          (if end (subseq rest 0 end) rest))))))

(defun load-skill-from-org (filepath)
  "Parses and evaluates Lisp blocks from an Org file."
  (let* ((skill-base-name (pathname-name filepath))
         (entry (or (gethash skill-base-name *skill-catalog*) (make-skill-entry :filename skill-base-name))))
    (setf (skill-entry-status entry) :loading)
    (handler-case
        (let* ((content (uiop:read-file-string filepath))
               (lines (uiop:split-string content :separator '(#\Newline)))
               (in-lisp-block nil) (collect-this-block nil) (lisp-code "")
               (pkg-name (intern (string-upcase (format nil "OPENCORTEX.SKILLS.~a" skill-base-name)) :keyword)))
          (dolist (line lines)
            (let ((clean-line (string-trim '(#\Space #\Tab #\Return) line)))
              (cond
                ((uiop:string-prefix-p "#+begin_src lisp" clean-line)
                 (setf in-lisp-block t)
                 (let ((target (extract-tangle-target clean-line)))
                   (setf collect-this-block (and target (not (search "/tests" target))))))
                ((uiop:string-prefix-p "#+end_src" clean-line)
                 (setf in-lisp-block nil) (setf collect-this-block nil))
                ((and in-lisp-block collect-this-block)
                 (unless (or (uiop:string-prefix-p ":" clean-line))
                   (setf lisp-code (concatenate 'string lisp-code line (string #\Newline))))))))
          (if (= (length lisp-code) 0)
              (setf (skill-entry-status entry) :ready)
              (progn
                (multiple-value-bind (valid-p err) (validate-lisp-syntax lisp-code)
                  (unless valid-p (error err)))
                (unless (find-package pkg-name)
                  (let ((new-pkg (make-package pkg-name :use '(:cl)))) (use-package :opencortex new-pkg)))
                (let ((*read-eval* nil) (*package* (find-package pkg-name)))
                  (eval (read-from-string (format nil "(progn ~a)" lisp-code))))
                (setf (skill-entry-status entry) :ready)))
          t)
      (error (c)
        (harness-log "LOADER ERROR in skill '~a': ~a" skill-base-name c)
        (setf (skill-entry-status entry) :failed) nil))))

Initialize (initialize-all-skills)

(defun initialize-all-skills ()
  "Initializes all skills from SKILLS_DIR."
  (let* ((env-path (uiop:getenv "SKILLS_DIR"))
         (skills-dir (uiop:ensure-directory-pathname (or env-path (namestring (merge-pathnames "notes/" (user-homedir-pathname)))))))
    (unless (uiop:directory-exists-p skills-dir) (return-from initialize-all-skills nil))
    (let ((sorted-files (topological-sort-skills skills-dir)))
      (harness-log "LOADER: Initializing ~a skills..." (length sorted-files))
      (dolist (file sorted-files)
        (load-skill-from-org file))
      (harness-log "LOADER: Boot Complete."))))

Test Suite

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

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

(in-package :opencortex-boot-tests)

(def-suite boot-suite :description "Verification of the Skill Engine loader")
(in-suite boot-suite)

(test test-topological-sort-basic
  (let ((tmp-dir "/tmp/opencortex-boot-test/"))
    (uiop:ensure-all-directories-exist (list tmp-dir))
    (with-open-file (out (merge-pathnames "org-skill-a.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out "#+DEPENDS_ON: skill-b-id~%"))
    (with-open-file (out (merge-pathnames "org-skill-b.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out ":PROPERTIES:~%:ID: skill-b-id~%:END:~%"))
    (unwind-protect
         (let ((sorted (opencortex::topological-sort-skills tmp-dir)))
           (let ((pos-a (position "org-skill-a" sorted :key #'pathname-name :test #'string-equal))
                 (pos-b (position "org-skill-b" sorted :key #'pathname-name :test #'string-equal)))
             (is (< pos-b pos-a))))
       (uiop:delete-directory-tree (uiop:ensure-directory-pathname tmp-dir) :validate t))))