diff --git a/lisp/core-communication.lisp b/lisp/core-communication.lisp index b713381..72ba3d3 100644 --- a/lisp/core-communication.lisp +++ b/lisp/core-communication.lisp @@ -71,7 +71,7 @@ nil)))) (format stream "~a" (frame-message health-msg)) (finish-output stream))) - (t (inject-stimulus msg :stream stream)))))) + (t (stimulus-inject msg :stream stream)))))) (error (c) (log-message "CLIENT ERROR: ~a" c))) (ignore-errors (usocket:socket-close socket)))) diff --git a/lisp/core-loop-act.lisp b/lisp/core-loop-act.lisp index e221653..348b8e1 100644 --- a/lisp/core-loop-act.lisp +++ b/lisp/core-loop-act.lisp @@ -96,10 +96,6 @@ (t (format nil "TOOL [~a] RESULT: ~s" tool-name result)))) (format nil "TOOL [~a] RESULT: ~a" tool-name result))) -;; Alias: act-gate → loop-gate-act -(defun act-gate (signal) - (loop-gate-act signal)) - (defun loop-gate-act (signal) "Final stage of the metabolic pipeline: Actuation. For approval-required actions, creates a Flight Plan instead of executing." @@ -155,6 +151,9 @@ For approval-required actions, creates a Flight Plan instead of executing." (setf (getf signal :status) :acted) feedback)) +(defun act-gate (signal) + (loop-gate-act signal)) + (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/lisp/core-loop-perceive.lisp b/lisp/core-loop-perceive.lisp index 7ff13f9..0bd1a59 100644 --- a/lisp/core-loop-perceive.lisp +++ b/lisp/core-loop-perceive.lisp @@ -16,9 +16,7 @@ FN receives (signal) and returns T if consumed, nil to continue." (setf (gethash sensor *pre-reason-handlers*) fn)) -;; Alias for backward compatibility (defun inject-stimulus (raw-message &key stream (depth 0)) - "Alias for stimulus-inject." (stimulus-inject raw-message :stream stream :depth depth)) (defun stimulus-inject (raw-message &key stream (depth 0)) @@ -53,10 +51,6 @@ FN receives (signal) and returns T if consumed, nil to continue." (skip-event () (log-message "SYSTEM RECOVERY: Stimulus dropped.")))))) -;; Alias: perceive-gate → loop-gate-perceive -(defun perceive-gate (signal) - (loop-gate-perceive signal)) - (defun loop-gate-perceive (signal) "Stage 1 of the metabolic pipeline: Normalize sensory input." (let* ((payload (getf signal :payload)) @@ -110,6 +104,9 @@ FN receives (signal) and returns T if consumed, nil to continue." (setf (getf signal :foveal-focus) *loop-focus-id*) signal)) +(defun perceive-gate (signal) + (loop-gate-perceive signal)) + (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/lisp/core-loop-reason.lisp b/lisp/core-loop-reason.lisp index 4678b5a..5c9cd56 100644 --- a/lisp/core-loop-reason.lisp +++ b/lisp/core-loop-reason.lisp @@ -121,10 +121,6 @@ modified action (for approval-required or pass)." :action approval-action)) current-action))) -;; Alias: reason-gate → loop-gate-reason -(defun reason-gate (signal) - (loop-gate-reason signal)) - (defun loop-gate-reason (signal) (let* ((type (proto-get signal :type)) (payload (proto-get signal :payload)) @@ -162,6 +158,9 @@ modified action (for approval-required or pass)." (setf (getf signal :status) :reasoned) (return signal)))))))) +(defun reason-gate (signal) + (loop-gate-reason signal)) + (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/lisp/core-loop.lisp b/lisp/core-loop.lisp index d40a6dd..a9703b9 100644 --- a/lisp/core-loop.lisp +++ b/lisp/core-loop.lisp @@ -9,10 +9,6 @@ (defvar *heartbeat-thread* nil "Handle to the heartbeat thread.") -;; Alias: process-signal → loop-process (backward compatibility) -(defun process-signal (signal) - (loop-process signal)) - (defun loop-process (signal) "The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act." (let ((current-signal signal)) @@ -49,6 +45,9 @@ (list :type :EVENT :depth (1+ depth) :meta meta :payload (list :sensor :loop-error :message (format nil "~a" c) :depth depth))))))))))) +(defun process-signal (signal) + (loop-process signal)) + (defvar *memory-auto-save-interval* 300) (defvar *heartbeat-save-counter* 0) @@ -69,8 +68,8 @@ (when (>= *heartbeat-save-counter* (/ *memory-auto-save-interval* interval)) (setf *heartbeat-save-counter* 0) (save-memory-to-disk)) - (inject-stimulus - (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time)))))) + (stimulus-inject + (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time)))))) :name "passepartout-heartbeat")))) (defvar *shutdown-save-enabled* t) diff --git a/org/core-communication.org b/org/core-communication.org index 112ad28..5875dca 100644 --- a/org/core-communication.org +++ b/org/core-communication.org @@ -38,7 +38,7 @@ The 6-character hex length supports messages up to ~16MB (0xFFFFFF bytes). This ** Actuator Registry -The global registry mapping target keywords (~:cli~, ~:telegram~, ~:signal~, etc.) to their physical actuator functions. Extensible at runtime — skills can register new actuators via ~actuator-register~. +The global registry mapping target keywords (~:cli~, ~:telegram~, ~:signal~, etc.) to their physical actuator functions. Extensible at runtime — skills can register new actuators via ~register-actuator~. #+begin_src lisp (defvar *actuator-registry* (make-hash-table :test 'equalp) @@ -115,7 +115,7 @@ Reads a complete framed message from a TCP stream. Handles leading whitespace be The TCP server that accepts connections from CLI and TUI clients. Each connection gets a dedicated thread (~client-handle-connection~). -The daemon sends a handshake message on connection, then enters a read loop, injecting each received message into the metabolic loop via ~inject-stimulus~. The ~:health-check~ message type is handled inline (not sent to the cognitive loop) so that health checks work even when the agent is busy. +The daemon sends a handshake message on connection, then enters a read loop, injecting each received message into the metabolic loop via ~stimulus-inject~. The ~:health-check~ message type is handled inline (not sent to the cognitive loop) so that health checks work even when the agent is busy. #+begin_src lisp (defvar *daemon-socket* nil) @@ -142,7 +142,7 @@ The daemon sends a handshake message on connection, then enters a read loop, inj nil)))) (format stream "~a" (frame-message health-msg)) (finish-output stream))) - (t (inject-stimulus msg :stream stream)))))) + (t (stimulus-inject msg :stream stream)))))) (error (c) (log-message "CLIENT ERROR: ~a" c))) (ignore-errors (usocket:socket-close socket)))) diff --git a/org/core-loop-act.org b/org/core-loop-act.org index 7ff3f9d..b3ede27 100644 --- a/org/core-loop-act.org +++ b/org/core-loop-act.org @@ -193,12 +193,12 @@ The gate runs a last-mile deterministic check on the approved action before exec After dispatch, the gate captures any feedback produced by the actuation (tool output, error events) and returns it to the loop for the next cognitive cycle. +*** loop-gate-act + +The main act pipeline stage. + ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp -;; Alias: act-gate → loop-gate-act -(defun act-gate (signal) - (loop-gate-act signal)) - (defun loop-gate-act (signal) "Final stage of the metabolic pipeline: Actuation. For approval-required actions, creates a Flight Plan instead of executing." @@ -255,6 +255,18 @@ For approval-required actions, creates a Flight Plan instead of executing." feedback)) #+end_src +*** act-gate (backward-compatibility alias) + +The pipeline gate was originally named ~act-gate~. Code that still +uses the old name can call this alias. New code should call +~loop-gate-act~. + +;; REPL-VERIFIED: 2026-05-03T13:00:00 +#+begin_src lisp +(defun act-gate (signal) + (loop-gate-act signal)) +#+end_src + * Test Suite Verifies that the act gate correctly processes an approved action and sets the signal status to ~:acted~. #+begin_src lisp :tangle ../lisp/core-loop-act.lisp diff --git a/org/core-loop-perceive.org b/org/core-loop-perceive.org index 724eb4a..6015194 100644 --- a/org/core-loop-perceive.org +++ b/org/core-loop-perceive.org @@ -70,21 +70,34 @@ before the signal reaches the LLM. The handler receives the full signal and returns T if the signal was consumed (don't continue to reason) or nil if processing should proceed normally. +*** Pre-Reason Handler Hash Table + ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *pre-reason-handlers* (make-hash-table :test 'eq) "Pre-reason handler registry: sensor keyword → handler function.") +#+end_src +*** register-pre-reason-handler + +;; REPL-VERIFIED: 2026-05-03T13:00:00 +#+begin_src lisp (defun register-pre-reason-handler (sensor fn) "Registers FN to handle signals with SENSOR in the perceive gate. FN receives (signal) and returns T if consumed, nil to continue." (setf (gethash sensor *pre-reason-handlers*) fn)) - -;; Alias for backward compatibility -(defun inject-stimulus (raw-message &key stream (depth 0)) - "Alias for stimulus-inject." - (stimulus-inject raw-message :stream stream :depth depth)) #+end_src + +** inject-stimulus backward-compatibility alias + +Skills and external code that still call ~inject-stimulus~ (the previous +name for the pipeline injection function) can use this alias. New code +should call ~stimulus-inject~ directly. + +;; REPL-VERIFIED: 2026-05-03T13:00:00 +#+begin_src lisp +(defun inject-stimulus (raw-message &key stream (depth 0)) + (stimulus-inject raw-message :stream stream :depth depth)) #+end_src ** Stimulus Injection (stimulus-inject) @@ -139,12 +152,12 @@ The perceive gate is the first stage of the metabolic pipeline. It receives a no All signals get tagged with their processing stage (`:status :perceived`) and the current foveal focus before being passed to the Reason stage. +*** loop-gate-perceive + +The main perceive pipeline stage. + ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp -;; Alias: perceive-gate → loop-gate-perceive -(defun perceive-gate (signal) - (loop-gate-perceive signal)) - (defun loop-gate-perceive (signal) "Stage 1 of the metabolic pipeline: Normalize sensory input." (let* ((payload (getf signal :payload)) @@ -199,6 +212,18 @@ All signals get tagged with their processing stage (`:status :perceived`) and th signal)) #+end_src +*** perceive-gate (backward-compatibility alias) + +The pipeline gate was originally named ~perceive-gate~. Code that still +uses the old name can call this alias. New code should call +~loop-gate-perceive~. + +;; REPL-VERIFIED: 2026-05-03T13:00:00 +#+begin_src lisp +(defun perceive-gate (signal) + (loop-gate-perceive signal)) +#+end_src + * Test Suite Verifies that the perceive gate correctly ingests AST nodes into memory and that the depth limiter prevents runaway recursive signals. #+begin_src lisp :tangle ../lisp/core-loop-perceive.lisp diff --git a/org/core-loop-reason.org b/org/core-loop-reason.org index ad13868..d0dadf4 100644 --- a/org/core-loop-reason.org +++ b/org/core-loop-reason.org @@ -279,12 +279,10 @@ The loop has retry logic: up to 3 attempts. If the deterministic engine rejects The retry limit prevents infinite loops. If the LLM cannot produce a passable proposal within 3 attempts, the last rejection reason is attached to the signal and the acted pipeline sees a failed reasoning cycle. +*** loop-gate-reason + ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp -;; Alias: reason-gate → loop-gate-reason -(defun reason-gate (signal) - (loop-gate-reason signal)) - (defun loop-gate-reason (signal) (let* ((type (proto-get signal :type)) (payload (proto-get signal :payload)) @@ -323,6 +321,18 @@ The retry limit prevents infinite loops. If the LLM cannot produce a passable pr (return signal)))))))) #+end_src +*** reason-gate (backward-compatibility alias) + +The pipeline gate was originally named ~reason-gate~. Code that still +uses the old name can call this alias. New code should call +~loop-gate-reason~. + +;; REPL-VERIFIED: 2026-05-03T13:00:00 +#+begin_src lisp +(defun reason-gate (signal) + (loop-gate-reason signal)) +#+end_src + * Test Suite Verifies that the deterministic engine correctly rejects unsafe actions (like ~rm -rf /~) while allowing safe ones. #+begin_src lisp :tangle ../lisp/core-loop-reason.lisp diff --git a/org/core-loop.org b/org/core-loop.org index e090eb8..795505a 100644 --- a/org/core-loop.org +++ b/org/core-loop.org @@ -78,12 +78,12 @@ The function handles four failure modes: - High-depth errors (depth > 2) → dropped (avoids cascading failures) - **Unhandled error**: the handler-case catches everything, preventing any single bad signal from crashing the agent +*** loop-process + +The main pipeline entry point. + ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp -;; Alias: process-signal → loop-process (backward compatibility) -(defun process-signal (signal) - (loop-process signal)) - (defun loop-process (signal) "The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act." (let ((current-signal signal)) @@ -121,6 +121,18 @@ The function handles four failure modes: :payload (list :sensor :loop-error :message (format nil "~a" c) :depth depth))))))))))) #+end_src +*** process-signal (backward-compatibility alias) + +The pipeline entry point was originally named ~process-signal~. Code +that still uses the old name can call this alias. New code should call +~loop-process~. + +;; REPL-VERIFIED: 2026-05-03T13:00:00 +#+begin_src lisp +(defun process-signal (signal) + (loop-process signal)) +#+end_src + ** Heartbeat Mechanism The heartbeat is a background thread that fires every N seconds (configurable via ~HEARTBEAT_INTERVAL~ env var, default 60). On each tick, it: @@ -159,8 +171,8 @@ The heartbeat signal is how background skills (Gardener, Scribe) get triggered w (when (>= *heartbeat-save-counter* (/ *memory-auto-save-interval* interval)) (setf *heartbeat-save-counter* 0) (save-memory-to-disk)) - (inject-stimulus - (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time)))))) + (stimulus-inject + (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time)))))) :name "passepartout-heartbeat")))) #+end_src #+end_src diff --git a/org/gateway-manager.org b/org/gateway-manager.org index 868e4fb..6879ab2 100644 --- a/org/gateway-manager.org +++ b/org/gateway-manager.org @@ -33,6 +33,11 @@ Registration of available gateway implementations: each platform registers its p #+end_src ** Telegram Implementation + +When a Telegram message arrives, the gateway first checks whether it is a +HITL approval/denial command via ~hitl-handle-message~. If consumed, +the message never enters the cognitive pipeline. Otherwise, it is injected +as a normal ~:user-input~ event via ~stimulus-inject~. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun telegram-get-token () @@ -94,6 +99,10 @@ Registration of available gateway implementations: each platform registers its p #+end_src ** Signal Implementation + +Signal messages follow the same pattern as Telegram: ~hitl-handle-message~ +is called first, and only non-HITL messages are injected into the pipeline. + ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun signal-get-account ()