Files
passepartout/org/programming-repl.org
Amr Gharbeia e0a47575e9
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 3s
feat: REPL development tool + naming drift fixes + HITL gateways
REPL tool:
- ~/.opencode/bin/repl — connects to running daemon, evaluates Lisp forms,
  returns results. Usage: repl '(+ 1 2)' or via stdin.
- Server-side handler in programming-repl skill registers for :repl-eval
  sensor, bypasses LLM pipeline, writes result back through reply-stream.
- Core provides pre-reason-handler registry (register-pre-reason-handler)
  for skills to register custom sensors without modifying core code.

HITL gateway integration:
- hitl-handle-message: TUI, Telegram, and Signal gateways intercept
  approval/deny commands before they reach the LLM.
- hitl-create/hitl-approve/hitl-deny: in-memory HITL store with correlation
  tokens for gateway-agnostic approval.
- loop-gate-perceive detects HITL commands and blocks LLM processing.

Naming drift fixes (the complete batch):
- register-actuator vs actuator-register — fixed to register-actuator
- process-signal vs loop-process — alias added
- perceive-gate/reason-gate/act-gate vs loop-gate-* — aliases added
- initialize-actuators vs actuator-initialize — fixed to actuator-initialize
- initialize-all-skills vs skill-initialize-all — fixed to skill-initialize-all
- inject-stimulus alias added for backward compatibility
- All original gateway-manager inject-stimulus → stimulus-inject + HITL check
2026-05-03 13:46:32 -04:00

264 lines
8.6 KiB
Org Mode

#+TITLE: SKILL: REPL (org-skill-repl.org)
#+AUTHOR: Agent
#+FILETAGS: :system:repl:interactive:debug:
#+PROPERTY: header-args:lisp :tangle ../lisp/programming-repl.lisp
* Overview
The *REPL Skill* provides persistent Lisp evaluation, inspection, and debugging capabilities. This enables the agent to verify behavior at runtime rather than just at the text level.
* Phase A: Demand (Thinking)
** Why a REPL?
The utils-lisp-eval function provides one-shot evaluation but:
- No state persistence between calls
- No variable inspection
- No debugging capabilities
The REPL skill fills this gap by:
- Maintaining evaluation state across turns
- Supporting variable inspection
- Providing debugging commands
- Optionally connecting to external Swank servers
** Success Criteria
- Code evaluation returns result + stdout/stderr separately
- Variables can be inspected
- Can load code into image
- Optional: connect to external SLIME/Swank session
* Phase B: Protocol (Spec)
- `repl-eval` returns: (values result output error)
- `repl-inspect` returns: structured description
- `repl-list-vars` returns: list of bound symbols
- `repl-load-file` returns: t on success, error on failure
* Phase C: Implementation
** Global State
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defvar *repl-package* :passepartout
"Default package for REPL evaluations.")
#+end_src
** *repl-history*
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defvar *repl-history* nil
"History of evaluated forms for session continuity.")
#+end_src
** *repl-variables*
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defvar *repl-variables* (make-hash-table :test #'eq)
"Cache of bound variables for inspection.")
#+end_src
#+end_src
** Core Evaluation
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-eval (code-string &key (package *repl-package*))
"Evaluate Lisp code and return (values result output error).
- result: the return value as string
- output: captured stdout
- error: error message or nil on success"
(let ((out (make-string-output-stream))
(err (make-string-output-stream))
(pkg (or (find-package package) (find-package :passepartout))))
(handler-case
(let* ((*standard-output* out)
(*error-output* err)
(*package* pkg)
(*read-eval* nil)
(result nil))
(with-input-from-string (s code-string)
(loop for form = (read s nil :eof) until (eq form :eof)
do (setf result (eval form))))
(push code-string *repl-history*)
(values
(format nil "~a" result)
(get-output-stream-string out)
nil))
(error (c)
(values
nil
(get-output-stream-string out)
(format nil "~a" c))))))
#+end_src
** Variable Inspection
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-inspect (symbol-name &key (package *repl-package*))
"Inspect a variable's value and structure."
(let* ((pkg (or (find-package package) (find-package :passepartout)))
(sym (find-symbol (string-upcase symbol-name) pkg)))
(cond
((null sym)
(format nil "Symbol ~a not found in package ~a" symbol-name package))
((boundp sym)
(let ((val (symbol-value sym)))
(format nil "~a = ~a~%Type: ~a~%~%"
sym val (type-of val))))
((fboundp sym)
(format nil "~a is a function~%Args: ~a~%"
sym (documentation sym 'function)))
(t
(format nil "~a is unbound" symbol-name)))))
#+end_src
** List Bound Variables
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-list-vars (&key (package *repl-package*))
"List all bound variables in the package."
(let* ((pkg (or (find-package package) (find-package :passepartout)))
(vars nil))
(do-symbols (sym pkg)
(when (boundp sym)
(push (format nil "~a" sym) vars)))
(sort vars #'string<)))
#+end_src
** Load File into Image
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-load-file (filepath)
"Load a Lisp file into the current image."
(handler-case
(progn
(load filepath)
(format nil "Loaded ~a" filepath))
(error (c)
(format nil "Error loading ~a: ~a" filepath c))))
#+end_src
** Package Switching
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-set-package (package-name)
"Set the default package for REPL evaluations."
(let ((pkg (find-package (string-upcase package-name))))
(if pkg
(setf *repl-package* pkg)
(format nil "Package ~a not found" package-name))))
#+end_src
** Help/Info
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-help ()
"Return available REPL commands."
(format nil "~%
REPL Skill Commands:
-------------------
(repl-eval \"code\" :package :passepartout)
- Evaluate Lisp code, returns (values result output error)
(repl-inspect \"symbol\" :package :passepartout)
- Inspect a variable or function
(repl-list-vars :package :passepartout)
- List all bound variables
(repl-load-file \"/path/to/file.lisp\")
- Load a file into the image
(repl-set-package :package-name)
- Switch default package
(repl-help)
- Show this message
"))
#+end_src
* Phase D: Verification
** Basic Evaluation Test
#+begin_src lisp :tangle no
(test test-repl-eval-simple
"Test basic arithmetic evaluation."
(multiple-value-bind (result output error)
(passepartout:repl-eval "(+ 1 2)")
(is (string= result "3"))
(is (null error))))
#+end_src
** Error Handling Test
#+begin_src lisp :tangle no
(test test-repl-eval-error
"Test that errors are caught and returned."
(multiple-value-bind (result output error)
(passepartout:repl-eval "(+ 1 \"string\")")
(is (null result))
(is (not (null error)))))
#+end_src
** REPL-EVAL Pre-Reason Handler
Registers a handler for =:repl-eval= sensor signals. When the daemon
receives a framed message with =:sensor :repl-eval=, this handler
evaluates the Lisp code directly and writes the result back through
the reply-stream, bypassing the LLM pipeline entirely.
Since this handler is registered via =register-pre-reason-handler=,
the perceive gate calls it before any LLM reasoning occurs. The
handler returns T (consumed), so the signal never reaches Reason.
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-handle (signal)
"Pre-reason handler for :repl-eval sensor. Evaluates code and
writes the result back through the reply-stream."
(let* ((payload (getf signal :payload))
(code (getf payload :code))
(stream (getf (getf signal :meta) :reply-stream))
(result (multiple-value-bind (val out err)
(repl-eval code)
(if err
(list :status :error :message err)
(list :status :success :value (or val ""))))))
(when stream
(handler-case
(progn
(write-sequence (frame-message result) stream)
(finish-output stream))
(error (c)
(log-message "REPL-EVAL: Failed to write response: ~a" c))))
;; Return T to signal the message was consumed
t))
;; Register the handler at load time
(register-pre-reason-handler :repl-eval #'repl-handle)
#+end_src
* Phase E: Lifecycle
The REPL skill loads at priority 200 (after diagnostics at 100, before utils-lisp at 400).
** System Prompt Augment (repl-mandate)
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun repl-mandate (context)
"Returns REPL-first engineering mandate when context involves code editing."
(let ((raw (or (proto-get (proto-get context :payload) :text) "")))
(when (or (search "org-skill-" raw :test #'char-equal)
(and (search ".org" raw :test #'char-equal)
(or (search "defun" raw :test #'char-equal)
(search "tangle" raw :test #'char-equal)
(search "write-file" raw :test #'char-equal)
(search "lisp" raw :test #'char-equal)))
(search "defun " raw :test #'char-equal)
(search "repl-eval" raw :test #'char-equal)
(search "validate" raw :test #'char-equal))
(format nil "~%REPL-FIRST MANDATE:~%Before writing any defun to an Org file, prototype it in the REPL first. Set :repl-verified t on the write action. On rejection, fix the error and retry.~%"))))
#+end_src
** Skill Registration
#+begin_src lisp
(defskill :passepartout-programming-repl
:priority 200
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
:deterministic (lambda (action ctx) (declare (ignore action ctx)) nil)
:system-prompt-augment #'repl-mandate)
#+end_src