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)
#+AUTHOR: Amr
#+AUTHOR: Agent
#+FILETAGS: :harness:act:
#+STARTUP: content
#+PROPERTY: header-args:lisp :tangle act.lisp
* Stage 3: Act (act.lisp)
** Architectural Intent: The Last Mile
* Overview
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.
** 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
* Implementation
** Package Context
#+begin_src lisp
(in-package :opencortex)
#+end_src
* Actuator Configuration
** Actuator Registry Variables
** Actuator Configuration
#+begin_src lisp
(defvar *default-actuator* :cli
"The actuator used when no explicit target is specified.
Override with DEFAULT_ACTUATOR environment variable.
"The actuator used when no explicit target is specified.")
(defvar *silent-actuators* '(:cli :system-message :emacs)
"List of actuators that don't generate tool-output feedback.
These typically have their own feedback mechanisms (CLI prints directly, etc.)
#+end_src
"List of actuators that don't generate tool-output feedback.")
** initialize-actuators: System Bootstrap
#+begin_src lisp
(defun initialize-actuators ()
"Load actuator configuration from environment and register core actuators.
Environment variables:
- 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
"Register core actuators and load configuration."
(let ((def (uiop:getenv "DEFAULT_ACTUATOR"))
(silent (uiop:getenv "SILENT_ACTUATORS")))
(when def
(setf *default-actuator*
(intern (string-upcase def) "KEYWORD))
;; Parse silent actuators list
(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)))))
(mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) :keyword))
(uiop:split-string silent :separator '(#\,))))))
;; Register core harness actuators
(register-actuator :system #'execute-system-action)
(register-actuator :tool #'execute-tool-action)
;; TUI actuator: sends response back through the reply stream
(register-actuator :tui (lambda (action context)
(let* ((meta (getf context :meta))
(declare (ignore context))
(let* ((meta (getf action :meta))
(stream (getf meta :reply-stream)))
(when (and stream (open-stream-p stream))
(format stream "~a" (frame-message action))
(finish-output stream))))))
#+end_src
* Action Dispatching
** dispatch-action: The Router
** Action Dispatch (dispatch-action)
#+begin_src lisp
(defun dispatch-action (action context)
"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)."
"Route an approved action to its registered actuator."
(let ((payload (proto-get action :payload)))
;; Heartbeats don't generate actuation
(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*))
(raw-target (or (proto-get action :target) source *default-actuator*))
(target (intern (string-upcase (string raw-target)) :keyword))
(actuator-fn (gethash target *actuator-registry*)))
;; Preserve metadata in outbound action
(when (and meta (null (getf action :meta)))
(setf (getf action :meta) meta))
;; Execute or log error
(if actuator-fn
(funcall actuator-fn action context)
(harness-log "ACT ERROR: No actuator registered for '~s' (requested by ~s)"
target raw-target))))))
(harness-log "ACT ERROR: No actuator registered for '~s'" target))))))
#+end_src
* Actuator Implementations
** execute-system-action: Internal Commands
** System Actuator (execute-system-action)
#+begin_src lisp
(defun execute-system-action (action context)
"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."
"Execute internal harness commands."
(declare (ignore context))
(let* ((payload (ignore-errors (getf action :payload)))
(cmd (ignore-errors (getf payload :action))))
(let* ((payload (getf action :payload))
(cmd (getf payload :action)))
(case cmd
;; Evaluate Lisp code - guarded by lisp-utils skill
(:eval
(let ((code (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
(eval (read-from-string (getf payload :code))))
(:message
(harness-log "ACT [System]: ~a" (getf payload :text)))
;; Unknown command
(t
(harness-log "ACT ERROR [System]: Unknown command '~s'" cmd)))))
#+end_src
** execute-tool-action: Cognitive Tool Execution
** Tool Actuator (execute-tool-action)
#+begin_src lisp
(defun execute-tool-action (action context)
"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."
"Execute a registered cognitive tool."
(let* ((payload (getf action :payload))
(tool-name (getf payload :tool))
(tool-args (getf payload :args))
@@ -223,210 +93,93 @@ Example feedback chain:
(meta (getf context :meta))
(source (getf meta :source))
(tool (gethash (string-downcase (string tool-name)) *cognitive-tools*)))
(if tool
(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)))
;; Format result for source
(when source
(dispatch-action (list :TYPE :REQUEST
:TARGET source
:PAYLOAD (list :ACTION :MESSAGE
:TEXT (format-tool-result tool-name result)))
context))
;; 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
(dispatch-action (list :TYPE :REQUEST :TARGET source
:PAYLOAD (list :ACTION :MESSAGE :TEXT (format-tool-result tool-name result)))
context))
(list :TYPE :EVENT :DEPTH (1+ depth) :META meta
:PAYLOAD (list :SENSOR :tool-output :RESULT result :TOOL tool-name)))
(error (c)
(list :TYPE :EVENT
:DEPTH (1+ depth)
:META meta
:PAYLOAD (list :SENSOR :tool-error
: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))))))
(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 (format nil "Tool '~a' not found" tool-name))))))
#+end_src
** format-tool-result: Human-Readable Output
** Tool Result Formatting (format-tool-result)
#+begin_src lisp
(defun format-tool-result (tool-name result)
"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."
"Format a tool result for human-readable 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))))
((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
* The Act Gate
** act-gate: Final Pipeline Stage
** Act Gate (Stage 3)
#+begin_src lisp
(defun act-gate (signal)
"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."
"Final stage of the metabolic pipeline: Actuation."
(let* ((approved (getf signal :approved-action))
(type (getf signal :type))
(meta (getf signal :meta))
(source (getf meta :source))
(feedback nil)
(context signal))
;; Step 1: Last-mile deterministic verification
;; This catches any issues that arose between reasoning and acting
(feedback nil))
(when approved
(let* ((original-type (getf approved :type))
(verified (deterministic-verify approved signal)))
;; 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
(if (and (listp verified) (member (getf verified :type) '(:LOG :EVENT)) (not (member original-type '(:LOG :EVENT))))
(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 approved nil)
(setf feedback verified))
;; Action passed verification
(progn
(setf (getf signal :approved-action) verified)
(setf approved verified)))))
;; Step 2: Actuation based on signal type
(case type
;; Explicit requests go directly to dispatch
(:REQUEST
(dispatch-action signal context))
;; Log messages also dispatch
(:LOG
(dispatch-action signal context))
;; Events with approved actions dispatch to their target
(:REQUEST (dispatch-action signal signal))
(:LOG (dispatch-action signal signal))
(:EVENT
(if approved
(let* ((target (getf approved :target))
(result (dispatch-action approved context)))
;; Determine feedback based on actuator response
(result (dispatch-action approved signal)))
(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))
;; Non-silent actuator with result - format as tool-output
((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))))))
;; No approved action, but have source - might be raw event
(when source
(dispatch-action signal context)))))
;; Step 3: Update signal status
((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))))))
(when source (dispatch-action signal signal)))))
(setf (getf signal :status) :acted)
feedback))
#+end_src
* Test Suite
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)
#+begin_src lisp :tangle tests/pipeline-act-tests.lisp
(defpackage :opencortex-pipeline-act-tests
(:use :cl :fiveam :opencortex)
(:export #:pipeline-act-suite))
(in-package :opencortex-pipeline-act-tests)
(def-suite pipeline-act-suite
:description "Test suite for Act pipeline
(def-suite pipeline-act-suite :description "Test suite for Act pipeline")
(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."
(clrhash opencortex::*skills-registry*)
(let* ((signal (list :type :EVENT :status nil :depth 0 :approved-action '(:target :cli :payload (:text "Hello)))
(result (opencortex:act-gate signal)))
(let* ((signal (list :type :EVENT :status nil :depth 0 :approved-action '(:target :cli :payload (:text "Hello"))))
(result (act-gate signal)))
(is (eq :acted (getf signal :status)))
(is (null result))))
(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
#+end_src