#+TITLE: Stage 1: Perceive (perceive.lisp) #+AUTHOR: Amr #+FILETAGS: :harness:perceive: #+STARTUP: content * Stage 1: Perceive (perceive.lisp) ** Architectural Intent: Sensory Ingestion The Perceive stage is the "sensory cortex" of the OpenCortex. Its primary responsibility is to take raw, unstructured stimuli from the outside world—whether from a TCP socket, a system interrupt, or a background heartbeat—and normalize them into high-fidelity internal **Signals**. Normalization is critical because it shields the subsequent reasoning and actuation stages from the messiness of different transport protocols. Whether a message arrives via a TUI, a Signal bot, or an internal timer, the core "Brain" perceives a consistent Lisp property list. ** Pipeline Initialization Ensuring we are in the correct namespace for sensory processing. #+begin_src lisp :tangle ../library/perceive.lisp (in-package :opencortex) #+end_src ** Sensory Concurrency (Async Sensors) To maintain the agent's responsiveness, we distinguish between "Fast" and "Slow" sensors. Sensors that require extensive processing or external API calls are routed to asynchronous threads to prevent blocking the main metabolic pipeline. #+begin_src lisp :tangle ../library/perceive.lisp (defvar *async-sensors* '(:chat-message :delegation :user-command) "List of sensors that should be processed asynchronously to avoid blocking gateways.") #+end_src ** Foveal Focus (User Context) The system tracks the user's current point of interaction (the "foveal focus"). This provides immediate situational awareness to the reasoning engine, allowing it to prioritize the data the human is currently looking at. #+begin_src lisp :tangle ../library/perceive.lisp (defvar *foveal-focus-id* nil "The Org ID of the node the user is currently interacting with.") #+end_src * Primary Ingress ** Stimulus Injection (inject-stimulus) The ~inject-stimulus~ function is the universal gateway into the OpenCortex mind. It performs two critical tasks: 1. *Envelope Wrapping:* Ensures that every raw message is wrapped in a ~:META~ envelope, preserving the source and session information. 2. *Dispatching:* Determines whether to run the metabolism synchronously or in a new thread. #+begin_src lisp :tangle ../library/perceive.lisp (defun inject-stimulus (raw-message &key stream (depth 0)) "Enqueues a raw message into the reactive signal pipeline." (let* ((payload (getf raw-message :payload)) (sensor (getf payload :sensor)) (meta (getf raw-message :meta)) (async-p (or (getf payload :async-p) (member sensor *async-sensors*)))) ;; Ensure META exists and contains the stream if provided (unless meta (setf meta (list :SOURCE :SYSTEM :SESSION-ID "internal"))) (when stream (setf (getf meta :reply-stream) stream)) (setf (getf raw-message :meta) meta) (if async-p (bt:make-thread (lambda () (restart-case (handler-bind ((error (lambda (c) (harness-log "ASYNC ERROR: ~a" c) (invoke-restart 'skip-event)))) (process-signal raw-message)) (skip-event () nil))) :name "opencortex-async-task") (restart-case (handler-bind ((error (lambda (c) (harness-log "SYSTEM ERROR: ~a" c) (invoke-restart 'skip-event)))) (process-signal raw-message)) (skip-event () (harness-log "SYSTEM RECOVERY: Stimulus dropped.~%")))))) #+end_src * The Perceive Stage ** Perception Gate (perceive-gate) The first official stage of the metabolic loop. It performs "Pre-Cognitive" work: 1. *Logging:* Recording the arrival of the signal. 2. *State Sync:* If the signal contains an AST update (e.g., from Emacs), it immediately updates the in-memory graph. 3. *Merkle Checkpointing:* Before modifying memory, it creates a snapshot to allow for emergency rollbacks. #+begin_src lisp :tangle ../library/perceive.lisp (defun perceive-gate (signal) "Initial processing: Normalizes raw stimuli and updates memory." (let* ((payload (getf signal :payload)) (type (getf signal :type)) (meta (getf signal :meta)) (sensor (getf payload :sensor))) (harness-log "GATE [Perceive]: ~a (~a) [Source: ~s]" type (or sensor "no-sensor") (getf meta :source)) (cond ((eq type :EVENT) (case sensor (:buffer-update (let ((ast (getf payload :ast))) (when ast (snapshot-memory) (ingest-ast ast)))) (:point-update (let ((element (getf payload :element))) (when element (snapshot-memory) (setf *foveal-focus-id* (ignore-errors (getf element :id))) (ingest-ast element)))) (:interrupt (bt:with-lock-held (*interrupt-lock*) (setf *interrupt-flag* t))))) ((eq type :RESPONSE) (harness-log "GATE [Perceive]: Act Result -> ~a" (getf payload :status)))) (setf (getf signal :status) :perceived) (setf (getf signal :foveal-focus) *foveal-focus-id*) signal)) #+end_src