docs: add literate prose to naming-drift aliases and HITL gateway changes
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 3s

Adds prose sections before every code block to satisfy the
prose-before-code discipline. Each backward-compatibility alias
(process-signal, perceive-gate, reason-gate, act-gate, inject-stimulus)
now has its own subsection explaining why it exists and what new code
should use instead.

Also:
- Fixes double #+end_src in core-loop-perceive.org
- Renames inject-stimulus → stimulus-inject in heartbeat-start and
  client-handle-connection (both already had aliases)
- Adds HITL interception prose to gateway-manager.org telegram/signal
  sections
- Splits Pre-Reason Handler Registry into two code blocks (defvar + defun)
  for one-per-block compliance
This commit is contained in:
2026-05-03 13:58:08 -04:00
parent bbc5e4d8bf
commit 3f51a772d4
11 changed files with 109 additions and 47 deletions

View File

@@ -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))))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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)

View File

@@ -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))))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 ()