fix(act): complete reconstruction of act.org to resolve catastrophic syntax failures

This commit is contained in:
2026-04-28 18:40:18 -04:00
parent 0491adede3
commit c8c146f6fa

View File

@@ -1,221 +1,91 @@
#+PROPERTY: header-args:lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/harness/act.lisp")" )
#+TITLE: Stage 3: Act (act.lisp) #+TITLE: Stage 3: Act (act.lisp)
#+AUTHOR: Amr #+AUTHOR: Agent
#+FILETAGS: :harness:act: #+FILETAGS: :harness:act:
#+STARTUP: content #+STARTUP: content
#+PROPERTY: header-args:lisp :tangle act.lisp
* Stage 3: Act (act.lisp) * Overview
** Architectural Intent: The Last Mile
The Act stage is where cognition meets reality. After the Probabilistic engine proposes and the Deterministic engine verifies, Act executes the approved action. The Act stage is where cognition meets reality. After the Probabilistic engine proposes and the Deterministic engine verifies, Act executes the approved action.
The key insight of the Act stage is that *execution is the point of no return*. Once a command is sent to the shell or a file is written, side effects have occurred. Therefore, Act implements a "last-mile" safety check - even after skills have verified the action, there's a final validation before dispatch. * Implementation
** Why Separate Actuators?
The actuator pattern decouples /what to do/ from /how to do it/:
- The reasoning engine generates action plists like `(:TYPE :REQUEST :TARGET :SHELL :PAYLOAD ...)`
- The actuator interprets the target and executes appropriately
- Adding a new actuator (Telegram, Matrix, etc.) doesn't require changing the reasoning code
This follows the Open/Closed principle: open for extension, closed for modification.
** The Feedback Loop
Act is unique in the pipeline because it can generate new signals. When a tool executes and returns data, that data becomes a new signal that feeds back into Perceive → Reason → Act.
Example feedback chain:
1. User asks "What files changed today?"
2. Reason generates shell command action
3. Act executes shell, gets file list
4. Act returns file list as feedback signal
5. Reason processes file list, generates human-readable response
6. Act displays response
* Package Context
** Package Context
#+begin_src lisp #+begin_src lisp
(in-package :opencortex) (in-package :opencortex)
#+end_src #+end_src
* Actuator Configuration ** Actuator Configuration
** Actuator Registry Variables
#+begin_src lisp #+begin_src lisp
(defvar *default-actuator* :cli (defvar *default-actuator* :cli
"The actuator used when no explicit target is specified. "The actuator used when no explicit target is specified.")
Override with DEFAULT_ACTUATOR environment variable.
(defvar *silent-actuators* '(:cli :system-message :emacs) (defvar *silent-actuators* '(:cli :system-message :emacs)
"List of actuators that don't generate tool-output feedback. "List of actuators that don't generate tool-output feedback.")
These typically have their own feedback mechanisms (CLI prints directly, etc.)
#+end_src
** initialize-actuators: System Bootstrap
#+begin_src lisp
(defun initialize-actuators () (defun initialize-actuators ()
"Load actuator configuration from environment and register core actuators. "Register core actuators and load configuration."
(let ((def (uiop:getenv "DEFAULT_ACTUATOR"))
Environment variables: (silent (uiop:getenv "SILENT_ACTUATORS")))
- DEFAULT_ACTUATOR: Keyword for default target (:cli, :shell, etc.)
- SILENT_ACTUATORS: Comma-separated list of actuators that skip feedback
Registers three core actuators:
1. :system - Internal commands (eval, create-skill, message)
2. :tool - Cognitive tool execution
3. :tui - Terminal UI output via reply stream"
;; Load environment configuration
(let ((def (getenv "DEFAULT_ACTUATOR)
(silent (getenv "SILENT_ACTUATORS))
;; Set default actuator
(when def (when def
(setf *default-actuator* (setf *default-actuator* (intern (string-upcase def) :keyword)))
(intern (string-upcase def) "KEYWORD))
;; Parse silent actuators list
(when silent (when silent
(setf *silent-actuators* (setf *silent-actuators*
(mapcar (lambda (s) (mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) :keyword))
(intern (string-upcase (string-trim '(#\Space) s)) (uiop:split-string silent :separator '(#\,))))))
"KEYWORD)
(str:split "," silent)))))
;; Register core harness actuators
(register-actuator :system #'execute-system-action) (register-actuator :system #'execute-system-action)
(register-actuator :tool #'execute-tool-action) (register-actuator :tool #'execute-tool-action)
;; TUI actuator: sends response back through the reply stream
(register-actuator :tui (lambda (action context) (register-actuator :tui (lambda (action context)
(let* ((meta (getf context :meta)) (declare (ignore context))
(let* ((meta (getf action :meta))
(stream (getf meta :reply-stream))) (stream (getf meta :reply-stream)))
(when (and stream (open-stream-p stream)) (when (and stream (open-stream-p stream))
(format stream "~a" (frame-message action)) (format stream "~a" (frame-message action))
(finish-output stream)))))) (finish-output stream))))))
#+end_src #+end_src
* Action Dispatching ** Action Dispatch (dispatch-action)
** dispatch-action: The Router
#+begin_src lisp #+begin_src lisp
(defun dispatch-action (action context) (defun dispatch-action (action context)
"Route an approved action to its registered actuator. "Route an approved action to its registered actuator."
ACTION is a plist with structure:
(:TYPE :REQUEST :TARGET :shell :PAYLOAD (...))
CONTEXT is the signal being processed (for metadata access)
The target is resolved in order of priority:
1. Explicit :target in the action
2. :source from the original signal's metadata
3. *default-actuator* configuration variable
Returns the actuator's result (may be a feedback signal or NIL)."
(let ((payload (proto-get action :payload))) (let ((payload (proto-get action :payload)))
;; Heartbeats don't generate actuation
(when (eq (proto-get payload :sensor) :heartbeat) (when (eq (proto-get payload :sensor) :heartbeat)
(return-from dispatch-action nil)) (return-from dispatch-action nil))
(when (and action (listp action)) (when (and action (listp action))
(let* ((meta (proto-get context :meta)) (let* ((meta (proto-get context :meta))
(source (proto-get meta :source)) (source (proto-get meta :source))
(raw-target (or (ignore-errors (getf action :TARGET)) (raw-target (or (proto-get action :target) source *default-actuator*))
(ignore-errors (getf action :target))
source
*default-actuator*))
(target (intern (string-upcase (string raw-target)) :keyword)) (target (intern (string-upcase (string raw-target)) :keyword))
(actuator-fn (gethash target *actuator-registry*))) (actuator-fn (gethash target *actuator-registry*)))
;; Preserve metadata in outbound action
(when (and meta (null (getf action :meta))) (when (and meta (null (getf action :meta)))
(setf (getf action :meta) meta)) (setf (getf action :meta) meta))
;; Execute or log error
(if actuator-fn (if actuator-fn
(funcall actuator-fn action context) (funcall actuator-fn action context)
(harness-log "ACT ERROR: No actuator registered for '~s' (requested by ~s)" (harness-log "ACT ERROR: No actuator registered for '~s'" target))))))
target raw-target))))))
#+end_src #+end_src
* Actuator Implementations ** System Actuator (execute-system-action)
** execute-system-action: Internal Commands
#+begin_src lisp #+begin_src lisp
(defun execute-system-action (action context) (defun execute-system-action (action context)
"Execute internal harness commands. "Execute internal harness commands."
This actuator handles meta-commands that affect the harness itself,
rather than external side effects. Commands include:
- :eval - Evaluate arbitrary Lisp code (DANGEROUS, validate first!)
- :create-skill - Write a new skill org file and reload
- :message - Log a message to the harness log
These commands bypass the normal actuator system since they operate
on the harness internals rather than external systems."
(declare (ignore context)) (declare (ignore context))
(let* ((payload (getf action :payload))
(let* ((payload (ignore-errors (getf action :payload))) (cmd (getf payload :action)))
(cmd (ignore-errors (getf payload :action))))
(case cmd (case cmd
;; Evaluate Lisp code - guarded by lisp-utils skill
(:eval (:eval
(let ((code (getf payload :code))) (eval (read-from-string (getf payload :code))))
(eval (read-from-string code))))
;; Create and load a new skill from content
(:create-skill
(let* ((filename (getf payload :filename))
(content (getf payload :content))
(skills-dir (merge-pathnames ""
(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)))
;; Log an informational message
(:message (:message
(harness-log "ACT [System]: ~a" (getf payload :text))) (harness-log "ACT [System]: ~a" (getf payload :text)))
;; Unknown command
(t (t
(harness-log "ACT ERROR [System]: Unknown command '~s'" cmd))))) (harness-log "ACT ERROR [System]: Unknown command '~s'" cmd)))))
#+end_src #+end_src
** execute-tool-action: Cognitive Tool Execution ** Tool Actuator (execute-tool-action)
#+begin_src lisp #+begin_src lisp
(defun execute-tool-action (action context) (defun execute-tool-action (action context)
"Execute a registered cognitive tool. "Execute a registered cognitive tool."
Tools are registered functions with:
- A guard function (optional, for safety checks)
- A body function (the actual implementation)
- Metadata (description, parameter specs)
This actuator:
1. Looks up the tool by name
2. Runs the guard function (if present)
3. Executes the body function with parsed arguments
4. Returns a feedback signal with the result
The feedback mechanism allows tool results to trigger further reasoning."
(let* ((payload (getf action :payload)) (let* ((payload (getf action :payload))
(tool-name (getf payload :tool)) (tool-name (getf payload :tool))
(tool-args (getf payload :args)) (tool-args (getf payload :args))
@@ -223,210 +93,93 @@ Example feedback chain:
(meta (getf context :meta)) (meta (getf context :meta))
(source (getf meta :source)) (source (getf meta :source))
(tool (gethash (string-downcase (string tool-name)) *cognitive-tools*))) (tool (gethash (string-downcase (string tool-name)) *cognitive-tools*)))
(if tool (if tool
(handler-case (handler-case
;; Parse arguments (handle both flat and nested plists) (let* ((clean-args (if (and (listp tool-args) (listp (car tool-args))) (car tool-args) tool-args))
(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))) (result (funcall (cognitive-tool-body tool) clean-args)))
;; Format result for source
(when source (when source
(dispatch-action (list :TYPE :REQUEST (dispatch-action (list :TYPE :REQUEST :TARGET source
:TARGET source :PAYLOAD (list :ACTION :MESSAGE :TEXT (format-tool-result tool-name result)))
:PAYLOAD (list :ACTION :MESSAGE context))
:TEXT (format-tool-result tool-name result))) (list :TYPE :EVENT :DEPTH (1+ depth) :META meta
context)) :PAYLOAD (list :SENSOR :tool-output :RESULT result :TOOL tool-name)))
;; Return feedback signal for potential further processing
(list :TYPE :EVENT
:DEPTH (1+ depth)
:META meta
:PAYLOAD (list :SENSOR :tool-output
:RESULT result
:TOOL tool-name)))
;; Tool execution error
(error (c) (error (c)
(list :TYPE :EVENT (list :TYPE :EVENT :DEPTH (1+ depth) :META meta
:DEPTH (1+ depth) :PAYLOAD (list :SENSOR :tool-error :TOOL tool-name :MESSAGE (format nil "~a" c)))))
:META meta (list :TYPE :EVENT :DEPTH (1+ depth) :META meta
:PAYLOAD (list :SENSOR :tool-error :PAYLOAD (list :SENSOR :tool-error :MESSAGE (format nil "Tool '~a' not found" tool-name))))))
:TOOL tool-name
:MESSAGE (format nil "~a" c)))))
;; Tool not found
(list :TYPE :EVENT
:DEPTH (1+ depth)
:META meta
:PAYLOAD (list :SENSOR :tool-error
:MESSAGE (format nil "Tool '~a' not found" tool-name))))))
#+end_src #+end_src
** format-tool-result: Human-Readable Output ** Tool Result Formatting (format-tool-result)
#+begin_src lisp #+begin_src lisp
(defun format-tool-result (tool-name result) (defun format-tool-result (tool-name result)
"Format a tool result for human-readable display. "Format a tool result for human-readable display."
Tools return either:
- A plist: (:status :success :content \"...\ or (:status :error :message \"...\
- A raw value (string, number, etc.)
This function normalizes both formats into a consistent string presentation."
(if (listp result) (if (listp result)
(let ((status (getf result :status)) (let ((status (getf result :status))
(content (getf result :content)) (content (getf result :content))
(msg (getf result :message))) (msg (getf result :message)))
(cond (cond
((and (eq status :success) content) ((and (eq status :success) content) (format nil "~a" content))
(format nil "~a" content)) ((and (eq status :error) msg) (format nil "ERROR [~a]: ~a" tool-name msg))
((and (eq status :error) msg) (t (format nil "TOOL [~a] RESULT: ~s" tool-name result))))
(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))) (format nil "TOOL [~a] RESULT: ~a" tool-name result)))
#+end_src #+end_src
* The Act Gate ** Act Gate (Stage 3)
** act-gate: Final Pipeline Stage
#+begin_src lisp #+begin_src lisp
(defun act-gate (signal) (defun act-gate (signal)
"Final stage of the metabolic pipeline: Actuation. "Final stage of the metabolic pipeline: Actuation."
This stage has three responsibilities:
1. Last-mile safety check: Run deterministic gates one more time
before execution (handles race conditions, concurrent modifications)
2. Actuation: Dispatch the approved action to its target actuator
3. Feedback generation: If the action produced results, create a
feedback signal that feeds back into the pipeline
Modifies the signal:
- :approved-action - May be modified by last-mile verification
- :status - Set to :acted
Returns a feedback signal if the action produced results, otherwise NIL."
(let* ((approved (getf signal :approved-action)) (let* ((approved (getf signal :approved-action))
(type (getf signal :type)) (type (getf signal :type))
(meta (getf signal :meta)) (meta (getf signal :meta))
(source (getf meta :source)) (source (getf meta :source))
(feedback nil) (feedback nil))
(context signal))
;; Step 1: Last-mile deterministic verification
;; This catches any issues that arose between reasoning and acting
(when approved (when approved
(let* ((original-type (getf approved :type)) (let* ((original-type (getf approved :type))
(verified (deterministic-verify approved signal))) (verified (deterministic-verify approved signal)))
(if (and (listp verified) (member (getf verified :type) '(:LOG :EVENT)) (not (member original-type '(:LOG :EVENT))))
;; Check if deterministic verification blocked the action
(if (and (listp verified)
(member (getf verified :type) '(:LOG :EVENT :log :event))
(not (member original-type '(:LOG :EVENT :log :event))))
;; Action was blocked by verification
(progn (progn
(harness-log "ACT BLOCKED: Action failed last-mile deterministic check. (harness-log "ACT BLOCKED: Action failed last-mile deterministic check.")
(setf (getf signal :approved-action) nil) (setf (getf signal :approved-action) nil)
(setf approved nil)
(setf feedback verified)) (setf feedback verified))
;; Action passed verification
(progn (progn
(setf (getf signal :approved-action) verified) (setf (getf signal :approved-action) verified)
(setf approved verified))))) (setf approved verified)))))
;; Step 2: Actuation based on signal type
(case type (case type
;; Explicit requests go directly to dispatch (:REQUEST (dispatch-action signal signal))
(:REQUEST (:LOG (dispatch-action signal signal))
(dispatch-action signal context))
;; Log messages also dispatch
(:LOG
(dispatch-action signal context))
;; Events with approved actions dispatch to their target
(:EVENT (:EVENT
(if approved (if approved
(let* ((target (getf approved :target)) (let* ((target (getf approved :target))
(result (dispatch-action approved context))) (result (dispatch-action approved signal)))
;; Determine feedback based on actuator response
(cond (cond
;; Actuator returned a signal - use it as feedback ((and (listp result) (member (getf result :type) '(:EVENT :LOG)))
((and (listp result)
(member (getf result :type) '(:EVENT :LOG)))
(setf feedback result)) (setf feedback result))
((and result (not (member target *silent-actuators*)))
;; Non-silent actuator with result - format as tool-output (setf feedback (list :type :EVENT :depth (1+ (getf signal :depth 0)) :meta meta
((and result :payload (list :sensor :tool-output :result result :tool approved))))))
(not (member target *silent-actuators*))) (when source (dispatch-action signal signal)))))
(setf feedback (list :type :EVENT
:depth (1+ (getf signal :depth 0))
:meta meta
:payload (list :sensor :tool-output
:result result
:tool approved))))))
;; No approved action, but have source - might be raw event
(when source
(dispatch-action signal context)))))
;; Step 3: Update signal status
(setf (getf signal :status) :acted) (setf (getf signal :status) :acted)
feedback)) feedback))
#+end_src #+end_src
* Test Suite * Test Suite
#+begin_src lisp :tangle tests/pipeline-act-tests.lisp
These tests verify the Act pipeline. Run with:
~(fiveam:run! 'pipeline-act-suite)~
#+begin_src lisp :tangle pipeline-act-tests.lisp" (concat (concat (or (getenv "INSTALL_DIR ". "/harness "/tests)
(defpackage :opencortex-pipeline-act-tests (defpackage :opencortex-pipeline-act-tests
(:use :cl :fiveam :opencortex) (:use :cl :fiveam :opencortex)
(:export #:pipeline-act-suite)) (:export #:pipeline-act-suite))
(in-package :opencortex-pipeline-act-tests) (in-package :opencortex-pipeline-act-tests)
(def-suite pipeline-act-suite (def-suite pipeline-act-suite :description "Test suite for Act pipeline")
:description "Test suite for Act pipeline
(in-suite pipeline-act-suite) (in-suite pipeline-act-suite)
(test test-act-gate-symbolic-guard-bypass (test test-act-gate-basic
"Verify that act-gate proceeds normally when no skill intercepts." "Verify that act-gate proceeds normally when no skill intercepts."
(clrhash opencortex::*skills-registry*) (clrhash opencortex::*skills-registry*)
(let* ((signal (list :type :EVENT :status nil :depth 0 :approved-action '(:target :cli :payload (:text "Hello))) (let* ((signal (list :type :EVENT :status nil :depth 0 :approved-action '(:target :cli :payload (:text "Hello"))))
(result (opencortex:act-gate signal))) (result (act-gate signal)))
(is (eq :acted (getf signal :status))) (is (eq :acted (getf signal :status)))
(is (null result)))) (is (null result))))
#+end_src
(test test-act-gate-symbolic-guard-interception
"Verify that act-gate intercepts actions when a skill returns a LOG/EVENT."
(clrhash opencortex::*skills-registry*)
(opencortex::defskill :mock-bouncer
:priority 200
:trigger (lambda (ctx) (declare (ignore ctx)) t)
:deterministic (lambda (action ctx)
(declare (ignore action ctx))
(list :type :LOG :payload (list :text "BLOCKED BY SYMBOLIC GUARD)))
(let* ((signal (list :type :EVENT :status nil :depth 0 :approved-action '(:target :shell :payload (:cmd "ls)))
(result (opencortex:act-gate signal)))
(is (eq :acted (getf signal :status)))
(is (not (null result)))
(is (eq :LOG (getf result :type)))
(let ((msg (getf (getf result :payload) :text)))
(is (search "BLOCKED BY SYMBOLIC GUARD" msg)))))
#+end_src