Files
memex/notes/skill-emacs-bridge.org

3.6 KiB

#+TITLE - Emacs Actuator Bridge Skill #+AUTHOR - org-agent #+SKILL_NAME - skill-emacs-bridge

This skill provides the sensor (TCP Socket) and actuator (OACP Dispatch) for the Emacs interface. It abstracts the I/O layer away from the core `org-agent` kernel.

Sensor & State (TCP Socket Listener)

We start a TCP server that listens for incoming connections from `org-agent.el`.

(defvar *emacs-server-thread* nil)
(defvar *emacs-server-socket* nil)
(defvar *active-emacs-clients* nil "List of active Emacs socket streams.")
(defvar *emacs-clients-lock* (bt:make-lock "emacs-clients-lock"))

(defun handle-emacs-client (stream)
  "Handle a single OACP connection from Emacs."
  (bt:with-lock-held (*emacs-clients-lock*)
    (push stream *active-emacs-clients*))
  (unwind-protect
      (handler-case
          (loop
            (let* ((len-buf (make-string 6))
                   (read-len (read-sequence len-buf stream)))
              (when (zerop read-len) (return))
              (let* ((msg-len (parse-integer len-buf :radix 16))
                     (msg-buf (make-string msg-len)))
                (read-sequence msg-buf stream)
                (let ((plist (read-from-string msg-buf)))
                  (org-agent:kernel-log "BRIDGE: Received message type ~a" (getf plist :type))
                  ;; PROCESS: Send the message through the 4-stage cognitive loop
                  (org-agent:cognitive-loop plist)))))
        (error (c) (org-agent:kernel-log "BRIDGE ERROR: ~a" c)))
    (bt:with-lock-held (*emacs-clients-lock*)
      (setf *active-emacs-clients* (remove stream *active-emacs-clients*)))
    (close stream)))

(defun start-emacs-server (&key (port 9105))
  "Start the OACP listener for Emacs."
  (setf *emacs-server-socket* (usocket:socket-listen "0.0.0.0" port :reuse-address t))
  (setf *emacs-server-thread* 
        (bt:make-thread 
         (lambda ()
           (loop
             (let ((conn (usocket:socket-accept *emacs-server-socket*)))
               (bt:make-thread (lambda () (handle-emacs-client (usocket:socket-stream conn)))
                               :name "org-agent-emacs-handler"))))
         :name "org-agent-emacs-daemon"))
  (org-agent:kernel-log "BRIDGE: Listening on port ~a" port))

Actuator (Outbound Dispatcher)

When the core `cognitive-loop` decides on an action targeting `:emacs`, it delegates to this function.

(defun broadcast-to-emacs (action-plist)
  "Translate an action into OACP framing and send to all connected Emacs clients."
  (let ((action-msg (org-agent:frame-message (prin1-to-string action-plist))))
    (bt:with-lock-held (*emacs-clients-lock*)
      (dolist (client *active-emacs-clients*)
        (ignore-errors 
         (write-string action-msg client)
         (force-output client))))))

Skill Registration

Register the skill. We don't use `trigger`, `neuro`, or `symbolic` because this is an I/O skill, not a cognitive skill. We just use the file evaluation to bootstrap the server and register the actuator.

;; Register the actuator with the core Event Bus
(org-agent:register-actuator :emacs #'broadcast-to-emacs)

;; Register as a skill so it appears on the dashboard
(defskill :skill-emacs-bridge
  :priority 100
  :trigger (lambda (context) nil)
  :neuro (lambda (context) nil)
  :symbolic (lambda (action context) action))

;; Start the socket server when this skill is loaded by the daemon
(let* ((env-port (uiop:getenv "ORG_AGENT_DAEMON_PORT"))
       (port (if env-port (parse-integer env-port :junk-allowed t) 9105)))
  (unless *emacs-server-thread*
    (start-emacs-server :port port)))