#+TITLE: Stage 3: Act (act.lisp) #+AUTHOR: Amr #+FILETAGS: :harness:act: #+STARTUP: content * Stage 3: Act (act.lisp) ** Architectural Intent: Actuation The Act stage performs the final physical side-effects of the metabolic pipeline. It takes an approved **Action** (the result of the Reasoning stage) and routes it to the correct physical **Actuator**. Actuators are the "hands" of the OpenCortex. They can be local (printing to a terminal), virtual (executing a shell command), or remote (sending a Matrix message). Crucially, the core microharness does not know *how* to talk to these services; it only knows how to *dispatch* to the registered actuator functions. ** Pipeline Initialization #+begin_src lisp :tangle ../library/act.lisp (in-package :opencortex) #+end_src * Actuator Configuration ** Default Actuator #+begin_src lisp :tangle ../library/act.lisp (defvar *default-actuator* :cli "The fallback actuator used if a signal has no source or target metadata.") #+end_src ** Silent Actuators To prevent infinite feedback loops, certain actuators are flagged as "silent." Results from these actuators are logged but do not trigger a fresh metabolic cycle. #+begin_src lisp :tangle ../library/act.lisp (defvar *silent-actuators* '(:cli :system-message :emacs) "List of actuators whose feedback should not re-enter the Reasoning stage.") #+end_src ** Initialization Logic (initialize-actuators) This function hydrates the actuator configuration from the environment and registers the core built-in actuators. #+begin_src lisp :tangle ../library/act.lisp (defun initialize-actuators () "Loads actuator routing defaults from environment variables and registers core harness actuators." (let ((def (uiop:getenv "DEFAULT_ACTUATOR")) (silent (uiop:getenv "SILENT_ACTUATORS"))) (when def (setf *default-actuator* (intern (string-upcase def) "KEYWORD"))) (when silent (setf *silent-actuators* (mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) "KEYWORD")) (str:split "," silent))))) ;; Register core harness actuators (register-actuator :system #'execute-system-action) (register-actuator :tool #'execute-tool-action) (register-actuator :tui (lambda (action context) (let* ((meta (getf context :meta)) (stream (getf meta :reply-stream))) (when (and stream (open-stream-p stream)) (format stream "~a" (frame-message action)) (finish-output stream)))))) #+end_src * Primary Routing ** Dispatching Logic (dispatch-action) The primary router. It identifies the target actuator based on the Signal's `:META` source or the Action's `:TARGET`. #+begin_src lisp :tangle ../library/act.lisp (defun dispatch-action (action context) "Routes an approved action to its registered physical actuator." (let ((payload (proto-get action :payload))) ;; Optimization: Heartbeats are system events, not actions. (when (eq (proto-get payload :sensor) :heartbeat) (return-from dispatch-action nil))) (when (and action (listp action)) (let* ((meta (proto-get context :meta)) (source (proto-get meta :source)) (raw-target (or (ignore-errors (getf action :TARGET)) (ignore-errors (getf action :target)) source *default-actuator*)) (target (intern (string-upcase (string raw-target)) :keyword)) (actuator-fn (gethash target *actuator-registry*))) ;; Propagation: Ensure outbound action inherits metadata (when (and meta (null (getf action :meta))) (setf (getf action :meta) meta)) (if actuator-fn (funcall actuator-fn action context) (harness-log "ACT ERROR: No actuator for ~s (from ~s)" target raw-target))))) #+end_src * Built-in Actuators ** System Actuator (execute-system-action) Handles meta-operations like hot-loading skills or evaluating raw Lisp within the image. #+begin_src lisp :tangle ../library/act.lisp (defun execute-system-action (action context) "Processes internal harness commands. (ACTUATOR)" (declare (ignore context)) (let* ((payload (ignore-errors (getf action :payload))) (cmd (ignore-errors (getf payload :action)))) (case cmd (:eval (let ((code (getf payload :code))) (eval (read-from-string code)))) (:create-skill (let* ((filename (getf payload :filename)) (content (getf payload :content)) (skills-dir (merge-pathnames "skills/" (asdf:system-source-directory :opencortex))) (full-path (merge-pathnames filename skills-dir))) (with-open-file (out full-path :direction :output :if-exists :supersede) (write-string content out)) (load-skill-from-org full-path))) (:message (harness-log "ACT [System]: ~a" (getf payload :text))) (t (harness-log "ACT ERROR [System]: Unknown command ~s" cmd))))) #+end_src ** Tool Result Formatting (format-tool-result) A UI helper that distills technical LLM responses into human-readable text. #+begin_src lisp :tangle ../library/act.lisp (defun format-tool-result (tool-name result) "Intelligently formats a tool result for user display." (if (listp result) (let ((status (getf result :status)) (content (getf result :content)) (msg (getf result :message))) (cond ((and (eq status :success) content) (format nil "~a" content)) ((and (eq status :error) msg) (format nil "ERROR [~a]: ~a" tool-name msg)) (t (format nil "TOOL [~a] RESULT: ~s" tool-name result)))) (format nil "TOOL [~a] RESULT: ~a" tool-name result))) #+end_src ** Tool Actuator (execute-tool-action) The engine for physical interaction. It executes a cognitive tool and generates feedback signals for the user. #+begin_src lisp :tangle ../library/act.lisp (defun execute-tool-action (action context) "Executes a registered cognitive tool and generates feedback signals. (ACTUATOR)" (let* ((payload (getf action :payload)) (tool-name (getf payload :tool)) (tool-args (getf payload :args)) (depth (getf context :depth 0)) (meta (getf context :meta)) (source (getf meta :source)) (tool (gethash (string-downcase (string tool-name)) *cognitive-tools*))) (if tool (handler-case (let* ((clean-args (if (and (listp tool-args) (listp (car tool-args))) (car tool-args) tool-args)) (result (funcall (cognitive-tool-body tool) clean-args))) (let ((feedback (list :TYPE :EVENT :DEPTH (1+ depth) :META meta :PAYLOAD (list :SENSOR :tool-output :RESULT result :TOOL tool-name)))) ;; UI Propagation: Send distilled text result back to the source client (when source (dispatch-action (list :TYPE :REQUEST :TARGET source :PAYLOAD (list :ACTION :MESSAGE :TEXT (format-tool-result tool-name result))) context)) feedback)) (error (c) (list :TYPE :EVENT :DEPTH (1+ depth) :META meta :PAYLOAD (list :SENSOR :tool-error :tool tool-name :message (format nil "~a" c))))) (list :TYPE :EVENT :DEPTH (1+ depth) :META meta :PAYLOAD (list :SENSOR :tool-error :message "Tool not found"))))) #+end_src * The Final Pipeline Stage ** Act Gate (act-gate) The exit point of the metabolic pipeline. It applies a last-mile safety check via the Deterministic Engine and dispatches the signal to the physical world. #+begin_src lisp :tangle ../library/act.lisp (defun act-gate (signal) "Final Stage: Actuation and feedback generation." (let* ((approved (getf signal :approved-action)) (type (getf signal :type)) (meta (getf signal :meta)) (source (getf meta :source)) (feedback nil) ;; context must keep internal objects for actuators to function (context signal)) ;; 1. Last-Mile Safety Check (The Bouncer & Deterministic Gates) (when approved (let* ((original-type (getf approved :type)) (verified (deterministic-verify approved signal))) (if (and (listp verified) (member (getf verified :type) '(:LOG :EVENT :log :event)) (not (member original-type '(:LOG :EVENT :log :event)))) (progn (harness-log "ACT BLOCKED: Action failed last-mile deterministic check.") (setf (getf signal :approved-action) nil) (setf approved nil) (setf feedback verified)) (progn (setf (getf signal :approved-action) verified) (setf approved verified))))) ;; 2. Actuation Logic (case type (:REQUEST (dispatch-action signal context)) (:LOG (dispatch-action signal context)) (:EVENT (if approved (let* ((target (getf approved :target)) (result (dispatch-action approved context))) (cond ((and (listp result) (member (getf result :type) '(:EVENT :LOG))) (setf feedback result)) ((and result (not (member target *silent-actuators*))) (setf feedback (list :type :EVENT :depth (1+ (getf signal :depth 0)) :meta meta :payload (list :sensor :tool-output :result result :tool approved)))))) ;; Fallback: route generic stimuli back to their origin (when source (dispatch-action signal context))))) (setf (getf signal :status) :acted) feedback)) #+end_src