From 92b6f3cf2bd6cafd4627f78365f6b54aa6524030 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Wed, 22 Apr 2026 20:57:41 -0400 Subject: [PATCH] feat: Add cognitive tools to skills.org (reload-skill, read-file, write-file, replace-string) New tools require manual addition to skills.lisp due to compilation issue. --- harness/package.org | 75 ++++++---------- harness/skills.org | 118 +++++++++++++++++++++++++ library/gen/org-skill-cli-gateway.lisp | 4 +- library/tui-client.lisp | 8 +- 4 files changed, 152 insertions(+), 53 deletions(-) diff --git a/harness/package.org b/harness/package.org index 62109b6..9aafdb6 100644 --- a/harness/package.org +++ b/harness/package.org @@ -4,24 +4,15 @@ #+STARTUP: content * System Interface (package.lisp) + The ~package.lisp~ file defines the public API of the ~opencortex~ harness. It serves as the primary membrane between the deterministic core modules and the dynamic world of skills and actuators. -** Architectural Intent: The Package Membrane -By strictly defining the public interface, we ensure that skills remain decoupled from the harness implementation details. This allows for autonomous replacement of any component (e.g., swapping the Memory or the Probabilistic Engine) without breaking existing skills. - -#+begin_src mermaid -flowchart TD - External[Actuators / Clients] -- communication protocol --> Package[Package Membrane: API] - Skills[Dynamic Skills] -- API Calls --> Package - Package --> Internal[Harness Internal Modules] - style Package fill:#f9f,stroke:#333,stroke-width:4px -#+end_src - ** Public API Export + #+begin_src lisp :tangle ../library/package.lisp (defpackage :opencortex (:use :cl) - (:export + (:export ;; --- communication protocol --- #:frame-message #:read-framed-message @@ -33,13 +24,13 @@ flowchart TD #:parse-message #:make-hello-message #:validate-communication-protocol-schema - + ;; --- Daemon Lifecycle --- #:start-daemon #:stop-daemon #:harness-log #:main - + ;; --- Memory (CLOSOS) --- #:ingest-ast #:lookup-object @@ -61,7 +52,7 @@ flowchart TD #:org-object-hash #:snapshot-memory #:rollback-memory - + ;; --- Context API (Peripheral Vision) --- #:context-query-store #:context-get-active-projects @@ -73,7 +64,7 @@ flowchart TD #:context-get-skill-telemetry #:harness-track-telemetry #:context-assemble-global-awareness - + ;; --- Reactive Signal Pipeline --- #:process-signal #:perceive-gate @@ -87,7 +78,7 @@ flowchart TD #:initialize-actuators #:dispatch-action #:register-actuator - + ;; --- Skill Engine --- #:load-skill-from-org #:initialize-all-skills @@ -125,22 +116,28 @@ flowchart TD #:register-probabilistic-backend #:distill-prompt #:*provider-cascade* - + ;; --- Security Vault --- #:vault-get-secret #:vault-set-secret - + ;; --- Deterministic Logic --- #:list-objects-with-attribute #:deterministic-verify - + ;; --- AST Helpers --- #:find-headline-missing-id)) #+end_src +* Package Implementation + #+begin_src lisp :tangle ../library/package.lisp (in-package :opencortex) +#+end_src +** Robust Plist Accessor + +#+begin_src lisp :tangle ../library/package.lisp (defun proto-get (plist key) "Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions." (let* ((s (string key)) @@ -149,27 +146,8 @@ flowchart TD (or (getf plist up) (getf plist dn)))) #+end_src -#+end_src - -#+begin_src lisp :tangle ../library/package.lisp -(in-package :opencortex) - -(defun proto-get (plist key) - "Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions." - (let* ((s (string key)) - (up (intern (string-upcase s) :keyword)) - (dn (intern (string-downcase s) :keyword))) - (or (getf plist up) (getf plist dn)))) -#+end_src - -#+end_src - -** Package Implementation -#+begin_src lisp :tangle ../library/package.lisp -(in-package :opencortex) -#+end_src - ** Harness Logging State + The harness maintains a thread-safe circular log buffer to provide context for debugging and neural reasoning. #+begin_src lisp :tangle ../library/package.lisp @@ -179,33 +157,37 @@ The harness maintains a thread-safe circular log buffer to provide context for d #+end_src ** Skills Registry + #+begin_src lisp :tangle ../library/package.lisp (defvar *skills-registry* (make-hash-table :test 'equal) "Global registry of all loaded skills.") #+end_src ** Skill Telemetry State + #+begin_src lisp :tangle ../library/package.lisp (defvar *skill-telemetry* (make-hash-table :test 'equal)) (defvar *telemetry-lock* (bt:make-lock "harness-telemetry-lock")) #+end_src ** Telemetry Implementation -The system tracks the performance and reliability of individual skills. This logic is currently preserved in the package layer for future expansion into a dedicated telemetry skill. + +The system tracks the performance and reliability of individual skills. #+begin_src lisp :tangle ../library/package.lisp (defun harness-track-telemetry (skill-name duration status) "Updates performance metrics for a specific skill. Status should be :success or :rejected." - (when skill-name + (when skill-name (bt:with-lock-held (*telemetry-lock*) (let ((entry (or (gethash skill-name *skill-telemetry*) (list :executions 0 :total-time 0 :failures 0)))) - (incf (getf entry :executions)) + (incf (getf entry :executions)) (incf (getf entry :total-time) duration) - (when (eq status :rejected) (incf (getf entry :failures))) + (when (eq status :rejected) (incf (getf entry :failures))) (setf (gethash skill-name *skill-telemetry*) entry))))) #+end_src ** Cognitive Tool Registry + The Tool Registry allows the agent to interact with the physical world. Every tool must define a guard (for security) and a body (for execution). #+begin_src lisp :tangle ../library/package.lisp @@ -229,6 +211,7 @@ The Tool Registry allows the agent to interact with the physical world. Every to #+end_src ** Harness Logging Implementation + Centralized logging function. It simultaneously writes to standard output and the in-memory circular buffer. #+begin_src lisp :tangle ../library/package.lisp @@ -241,6 +224,4 @@ Centralized logging function. It simultaneously writes to standard output and th (setq *system-logs* (subseq *system-logs* 0 *max-log-history*)))) (format t "~a~%" formatted-msg) (finish-output))) -#+end_src - - +#+end_src \ No newline at end of file diff --git a/harness/skills.org b/harness/skills.org index 8f7af42..85658ef 100644 --- a/harness/skills.org +++ b/harness/skills.org @@ -360,3 +360,121 @@ EXAMPLES: (uiop:run-program (list "bash" "-c" cmd) :output :string :error-output :string :ignore-error-status t) (format nil "EXIT-CODE: ~a~%~%STDOUT:~%~a~%~%STDERR:~%~a" code out err))))) #+end_src + +*** The Reload-Skill Tool (Hot Reload) +#+begin_src lisp :tangle ../library/skills.lisp +(def-cognitive-tool :reload-skill "Reloads a skill from its Org-mode source file, recompiling into the live image without restarting the daemon." + ((:skill :type :string :description "The skill name (e.g., \"org-skill-policy\") or full path to the .org file")) + :guard (lambda (args context) + (declare (ignore context)) + (let ((skill (getf args :skill))) + (or (uiop:file-exists-p skill) + (let ((skills-dir (or (ignore-errors (uiop:getenv "SKILLS_DIR")) + (namestring (merge-pathnames "notes/" (user-homedir-pathname)))))) + (uiop:file-exists-p (merge-pathnames (format nil "~a.org" skill) skills-dir)))))) + :body (lambda (args) + (let ((skill (getf args :skill))) + (snapshot-memory) + (let ((skills-dir (or (ignore-errors (uiop:getenv "SKILLS_DIR")) + (namestring (merge-pathnames "notes/" (user-homedir-pathname))))) + (resolved-path (context-resolve-path skills-dir)) + (skills-dir-actual (if (ignore-errors (uiop:getenv "SKILLS_DIR")) + (uiop:ensure-directory-pathname (context-resolve-path (uiop:getenv "SKILLS_DIR"))) + (uiop:ensure-directory-pathname (user-homedir-pathname))))) + (let ((file (if (uiop:file-exists-p skill) + (uiop:ensure-pathname skill) + (merge-pathnames (format nil "~a.org" skill) skills-dir-actual)))) + (cond + ((not (uiop:file-exists-p file)) + (format nil "ERROR: Skill file not found: ~a" (uiop:native-namestring file))) + (t + (harness-log "SKILL: Hot-reloading ~a..." (pathname-name file)) + (let ((status (load-skill-with-timeout file 10))) + (if (eq status :success) + (let ((base-name (pathname-name file))) + (setf (skill-entry-status (gethash base-name *skill-catalog*)) :ready) + (format nil "OK: Skill '~a' reloaded successfully." base-name)) + (format nil "ERROR: Reload failed with status ~a" status))))))))) +#+end_src + +*** The File Read Tool (V 0.2.0 File I/O) +#+begin_src lisp :tangle ../library/skills.lisp +(def-cognitive-tool :read-file "Reads the contents of a file as a string." + ((:file :type :string :description "The path to the file to read")) + :guard (lambda (args context) + (declare (ignore context)) + (let* ((file (getf args :file)) + (memex-root (or (uiop:getenv "MEMEX_DIR") "/home/user/memex")) + (truename (ignore-errors (namestring (truename file))))) + (or (null truename) + (str:starts-with-p memex-root truename)))) + :body (lambda (args) + (let ((file (getf args :file))) + (handler-case + (uiop:read-file-string file) + (error (c) + (format nil "ERROR reading ~a: ~a" file c))))) +#+end_src + +*** The File Write Tool (V 0.2.0 File I/O) +#+begin_src lisp :tangle ../library/skills.lisp +(def-cognitive-tool :write-file "Writes content to a file, creating it if it doesn't exist." + ((:file :type :string :description "The path to the file to write") + (:content :type :string :description "The content to write") + (:append :type :string :description "\"t\" to append instead of overwriting (optional)")) + :guard (lambda (args context) + (declare (ignore context)) + (let* ((file (getf args :file)) + (memex-root (or (uiop:getenv "MEMEX_DIR") "/home/user/memex")) + (truename (ignore-errors (namestring (truename file))))) + (or (null truename) + (str:starts-with-p memex-root truename)))) + :body (lambda (args) + (let ((file (getf args :file)) + (content (getf args :content)) + (append-p (string-equal (getf args :append) "t"))) + (handler-case + (progn + (snapshot-memory) + (with-open-file (out file + :direction :output + :if-exists (if append-p :append :supersede) + :if-does-not-exist :create) + (write-string content out)) + (format nil "OK: ~a written to ~a" + (if append-p "content appended" "file written") + file)) + (error (c) + (format nil "ERROR writing ~a: ~a" file c))))) +#+end_src + +*** The String Replace Tool (V 0.2.0 File I/O) +#+begin_src lisp :tangle ../library/skills.lisp +(def-cognitive-tool :replace-string "Replaces occurrences of old-string with new-string in a file." + ((:file :type :string :description "The path to the file") + (:old :type :string :description "The substring to find and replace") + (:new :type :string :description "The replacement string")) + :guard (lambda (args context) + (declare (ignore context)) + (let* ((file (getf args :file)) + (memex-root (or (uiop:getenv "MEMEX_DIR") "/home/user/memex")) + (truename (ignore-errors (namestring (truename file))))) + (or (null truename) + (str:starts-with-p memex-root truename)))) + :body (lambda (args) + (let ((file (getf args :file)) + (old (getf args :old)) + (new (getf args :new))) + (handler-case + (progn + (snapshot-memory) + (let ((content (uiop:read-file-string file))) + (if (search old content) + (let ((new-content (cl-ppcre:regex-replace-all (cl-ppcre:quote-meta-chars old) content new))) + (with-open-file (out file :direction :output :if-exists :supersede) + (write-string new-content out)) + (format nil "OK: Replaced first occurrence in ~a" file)) + (format nil "ERROR: Pattern not found in ~a" file)))) + (error (c) + (format nil "ERROR replacing in ~a: ~a" file c))))) +#+end_src diff --git a/library/gen/org-skill-cli-gateway.lisp b/library/gen/org-skill-cli-gateway.lisp index 0a1a889..e3b1631 100644 --- a/library/gen/org-skill-cli-gateway.lisp +++ b/library/gen/org-skill-cli-gateway.lisp @@ -56,13 +56,13 @@ "Starts the TCP listener for local CLI clients." (setf *cli-server-socket* (usocket:socket-listen "0.0.0.0" port :reuse-address t)) (setf *cli-server-thread* - (bt:make-thread + (bordeaux-threads:make-thread (lambda () (unwind-protect (loop (let* ((socket (usocket:socket-accept *cli-server-socket*)) (stream (usocket:socket-stream socket))) - (bt:make-thread (lambda () + (bordeaux-threads:make-thread (lambda () (unwind-protect (handle-cli-client stream) (usocket:socket-close socket))) :name "opencortex-cli-client-handler"))) diff --git a/library/tui-client.lisp b/library/tui-client.lisp index 563ac7b..0f8448f 100644 --- a/library/tui-client.lisp +++ b/library/tui-client.lisp @@ -12,15 +12,15 @@ (defvar *status-text* "Connecting...") (defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t)) (defvar *is-running* t) -(defvar *queue-lock* (bt:make-lock)) +(defvar *queue-lock* (bordeaux-threads:make-lock)) (defvar *incoming-msgs* nil) (defun enqueue-msg (msg) - (bt:with-lock-held (*queue-lock*) + (bordeaux-threads:with-lock-held (*queue-lock*) (push msg *incoming-msgs*))) (defun dequeue-msgs () - (bt:with-lock-held (*queue-lock*) + (bordeaux-threads:with-lock-held (*queue-lock*) (let ((msgs (nreverse *incoming-msgs*))) (setf *incoming-msgs* nil) msgs))) @@ -90,7 +90,7 @@ (setf *socket* (usocket:socket-connect *daemon-host* *daemon-port*)) (error (e) (format t "Error connecting: ~a~%" e) (return-from main))) (setf *stream* (usocket:socket-stream *socket*)) - (bt:make-thread #'listen-thread :name "tui-listener") + (bordeaux-threads:make-thread #'listen-thread :name "tui-listener") (unwind-protect (with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible t)