Files
passepartout/skills/org-skill-repl.org
Amr Gharbeia 1080f0b873 feat(skills): add org-skill-repl for persistent Lisp evaluation
- NEW: org-skill-repl skill enables:
  * repl-eval: evaluate code with result+output+error separation
  * repl-inspect: inspect variables and functions
  * repl-list-vars: list all bound symbols in package
  * repl-load-file: load files into image
  * repl-set-package: switch default package
  * repl-help: show available commands

- Supports REPL-first workflow with literate reflection in org
- Priority 200 (after diagnostics, before utils-lisp)
- Follows same pattern as existing skills (in-package, defskill)
2026-04-30 10:54:05 -04:00

5.6 KiB

SKILL: REPL (org-skill-repl.org)

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

(defvar *repl-package* :opencortex
  "Default package for REPL evaluations.")

(defvar *repl-history* nil
  "History of evaluated forms for session continuity.")

(defvar *repl-variables* (make-hash-table :test #'eq)
  "Cache of bound variables for inspection.")

Core Evaluation

(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 :opencortex))))
    (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))))))

Variable Inspection

(defun repl-inspect (symbol-name &key (package *repl-package*))
  "Inspect a variable's value and structure."
  (let* ((pkg (or (find-package package) (find-package :opencortex)))
         (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)))))

List Bound Variables

(defun repl-list-vars (&key (package *repl-package*))
  "List all bound variables in the package."
  (let* ((pkg (or (find-package package) (find-package :opencortex)))
         (vars nil))
    (do-symbols (sym pkg)
      (when (boundp sym)
        (push (format nil "~a" sym) vars)))
    (sort vars #'string<)))

Load File into Image

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

Package Switching

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

Help/Info

(defun repl-help ()
  "Return available REPL commands."
  (format nil "~%
REPL Skill Commands:
-------------------
(repl-eval \"code\" :package :opencortex)
  - Evaluate Lisp code, returns (values result output error)

(repl-inspect \"symbol\" :package :opencortex)
  - Inspect a variable or function

(repl-list-vars :package :opencortex)
  - 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
"))

Phase D: Verification

Basic Evaluation Test

(test test-repl-eval-simple
  "Test basic arithmetic evaluation."
  (multiple-value-bind (result output error)
      (opencortex:repl-eval "(+ 1 2)")
    (is (string= result "3"))
    (is (null error))))

Error Handling Test

(test test-repl-eval-error
  "Test that errors are caught and returned."
  (multiple-value-bind (result output error)
      (opencortex:repl-eval "(+ 1 \"string\")")
    (is (null result))
    (is (not (null error)))))

Phase E: Lifecycle

The REPL skill loads at priority 200 (after diagnostics at 100, before utils-lisp at 400).

Skill Registration

(defskill :skill-repl
  :priority 200
  :trigger (lambda (ctx) (declare (ignore ctx)) nil)
  :deterministic (lambda (action ctx) (declare (ignore action ctx)) nil))