FEAT: Implement Lisp Repair Syntax Gate
This commit is contained in:
33
docs/rca/rca-lisp-repair.org
Normal file
33
docs/rca/rca-lisp-repair.org
Normal file
@@ -0,0 +1,33 @@
|
||||
#+TITLE: Root Cause Analysis: Implement Lisp Repair Syntax Gate
|
||||
#+DATE: 2026-04-11
|
||||
#+FILETAGS: :rca:lisp:repair:syntax:
|
||||
|
||||
* Executive Summary
|
||||
Implemented the `org-skill-lisp-repair` to address the issue of silent parser failures in System 1 (LLM) proposals. The system now attempts deterministic fixes (balancing parentheses) and neural fallback before discarding a thought.
|
||||
|
||||
* 1. Issue: Silent Parser Failures
|
||||
** Symptoms
|
||||
System 1 proposals containing minor syntax errors (like missing closing parens) were being discarded by the `think` function, leading to "Action rejected" or "Invalid output format" logs and wasted tokens.
|
||||
** Root Cause
|
||||
The `think` loop used `ignore-errors` around `read-from-string`, providing no path for recovery or diagnostics for malformed Lisp.
|
||||
** Resolution
|
||||
Replaced `ignore-errors` with a `handler-case` that invokes `repair-lisp-syntax`.
|
||||
|
||||
* 2. Design Decision: Two-Tiered Repair
|
||||
** Tier 1: Deterministic (Fast)
|
||||
- **Mechanism:** Count unclosed `(` and append `)`.
|
||||
- **Reasoning:** Most common error from LLMs under token limits or early termination. Cost: 0 tokens.
|
||||
** Tier 2: Neural (Deep)
|
||||
- **Mechanism:** specialized `ask-neuro` prompt with the error message and broken code.
|
||||
- **Reasoning:** Necessary for unbalanced strings or hallucinated syntax that deterministic rules can't fix.
|
||||
|
||||
* 3. PSF Mandate Alignment
|
||||
** Literate Programming
|
||||
- Created `org-skill-lisp-repair.org` as a Universal Literate Note.
|
||||
- Tangle targets established for `src/lisp-repair.lisp`.
|
||||
** High-Integrity Memory
|
||||
- Updated `neurosymbolic.org` documentation to reflect the new safety buffer in the `think` function.
|
||||
|
||||
* 4. Permanent Learnings
|
||||
- **Dynamic Skill Invocation:** Use `member :skill-name *loaded-skills*` and `fboundp` to ensure kernel functions remain decoupled from non-essential skills.
|
||||
- **Test Package Exporting:** Always remember to export the `fiveam` suite symbol in the `defpackage` of new test files to avoid `SIMPLE-READER-PACKAGE-ERROR`.
|
||||
@@ -175,7 +175,14 @@ To call a tool, you MUST use:
|
||||
(let ((regs (nth-value 1 (cl-ppcre:scan-to-strings "(?s)```(?:lisp)?\\n?(.*?)\\n?```" raw-thought))))
|
||||
(if (and regs (> (length regs) 0)) (elt regs 0) raw-thought))
|
||||
(string-trim '(#\Space #\Newline #\Tab) raw-thought))))
|
||||
(suggestion (ignore-errors (read-from-string cleaned-thought))))
|
||||
(suggestion (handler-case (read-from-string cleaned-thought)
|
||||
(error (c)
|
||||
(if (and (member :skill-lisp-repair *loaded-skills*)
|
||||
(fboundp 'repair-lisp-syntax))
|
||||
(repair-lisp-syntax cleaned-thought (format nil "~a" c))
|
||||
(progn
|
||||
(kernel-log "SYSTEM 1 ERROR: Invalid output format from LLM.~%")
|
||||
nil))))))
|
||||
(kernel-log "SYSTEM 1 Suggestion: ~a~%" cleaned-thought)
|
||||
(when (and suggestion (listp suggestion))
|
||||
(push suggestion suggestions))))
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
(:file "src/symbolic")
|
||||
(:file "src/safety-harness")
|
||||
(:file "src/self-fix")
|
||||
(:file "src/lisp-repair")
|
||||
(:file "src/core"))
|
||||
:build-operation "program-op"
|
||||
:build-pathname "org-agent-server"
|
||||
@@ -32,6 +33,7 @@
|
||||
(:file "tests/immune-system-tests")
|
||||
(:file "tests/task-orchestrator-tests")
|
||||
(:file "tests/self-fix-tests")
|
||||
(:file "tests/lisp-repair-tests")
|
||||
(:file "tests/chaos-qa"))
|
||||
:perform (test-op (o s)
|
||||
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :oacp-suite :org-agent-tests))
|
||||
@@ -43,4 +45,5 @@
|
||||
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :immune-suite :org-agent-immune-system-tests))
|
||||
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :task-orchestrator-suite :org-agent-task-orchestrator-tests))
|
||||
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :self-fix-suite :org-agent-self-fix-tests))
|
||||
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :lisp-repair-suite :org-agent-lisp-repair-tests))
|
||||
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :chaos-suite :org-agent-chaos-qa))))
|
||||
|
||||
71
skills/org-skill-lisp-repair.org
Normal file
71
skills/org-skill-lisp-repair.org
Normal file
@@ -0,0 +1,71 @@
|
||||
:PROPERTIES:
|
||||
:ID: 1e5a2c30-d3a9-4674-8db7-b08e7e1f44d1
|
||||
:CREATED: [2026-04-11 Sat 14:40]
|
||||
:END:
|
||||
#+TITLE: SKILL: Lisp Repair Syntax Gate
|
||||
#+STARTUP: content
|
||||
#+FILETAGS: :system:repair:syntax:lisp:psf:
|
||||
|
||||
* Overview
|
||||
The *Lisp Repair Syntax Gate* intercepts System 1 (LLM) proposals that fail the formal Lisp `read-from-string` parser due to syntax errors (e.g., hallucinated trailing text, unbalanced parentheses). It acts as a pre-eval System 2 safety buffer.
|
||||
|
||||
* Implementation
|
||||
|
||||
** Core Repair Logic
|
||||
#+begin_src lisp :tangle ../src/lisp-repair.lisp
|
||||
(in-package :org-agent)
|
||||
|
||||
(defun count-char (char string)
|
||||
(let ((count 0))
|
||||
(loop for c across string
|
||||
when (char= c char)
|
||||
do (incf count))
|
||||
count))
|
||||
|
||||
(defun deterministic-repair (code)
|
||||
"Attempts instant fixes on broken Lisp code."
|
||||
(let* ((open-parens (count-char #\( code))
|
||||
(close-parens (count-char #\) code))
|
||||
(diff (- open-parens close-parens)))
|
||||
(if (> diff 0)
|
||||
;; Append missing closing parens
|
||||
(concatenate 'string code (make-string diff :initial-element #\)))
|
||||
code)))
|
||||
|
||||
(defun neural-repair (code error-message)
|
||||
"Uses System 1 to deeply repair the syntax structure."
|
||||
(let ((prompt (format nil "The following Lisp code failed to parse.
|
||||
ERROR: ~a
|
||||
CODE: ~a
|
||||
MANDATE: Output EXACTLY ONE valid Common Lisp list. Do not explain. Do not use markdown blocks."
|
||||
error-message code))
|
||||
(system-prompt "You are a Lisp Syntax Repair Actuator. Return only valid, balanced Lisp code."))
|
||||
(let ((repaired (ask-neuro prompt :system-prompt system-prompt)))
|
||||
(string-trim '(#\Space #\Newline #\Tab) repaired))))
|
||||
|
||||
(defun repair-lisp-syntax (code error-message)
|
||||
"The entry point called by the neuro-gate when read-from-string fails."
|
||||
(kernel-log "SYNTAX GATE: Intercepted broken Lisp. Attempting deterministic repair...")
|
||||
(let ((fast-fix (deterministic-repair code)))
|
||||
(handler-case
|
||||
(read-from-string fast-fix)
|
||||
(error ()
|
||||
(kernel-log "SYNTAX GATE: Deterministic repair failed. Escalating to neural repair...")
|
||||
(let ((deep-fix (neural-repair code error-message)))
|
||||
(handler-case
|
||||
(read-from-string deep-fix)
|
||||
(error ()
|
||||
(kernel-log "SYNTAX GATE: Neural repair failed.")
|
||||
nil)))))))
|
||||
#+end_src
|
||||
|
||||
** Skill Definition
|
||||
Since this skill is a passive System 2 interceptor (called dynamically by `neuro.lisp`), it doesn't need to trigger on its own via the Event Bus.
|
||||
|
||||
#+begin_src lisp :tangle ../src/lisp-repair.lisp
|
||||
(defskill :skill-lisp-repair
|
||||
:priority 90
|
||||
:trigger (lambda (ctx) (declare (ignore ctx)) nil) ;; Passive interceptor
|
||||
:neuro nil
|
||||
:symbolic nil)
|
||||
#+end_src
|
||||
50
src/lisp-repair.lisp
Normal file
50
src/lisp-repair.lisp
Normal file
@@ -0,0 +1,50 @@
|
||||
(in-package :org-agent)
|
||||
|
||||
(defun count-char (char string)
|
||||
(let ((count 0))
|
||||
(loop for c across string
|
||||
when (char= c char)
|
||||
do (incf count))
|
||||
count))
|
||||
|
||||
(defun deterministic-repair (code)
|
||||
"Attempts instant fixes on broken Lisp code."
|
||||
(let* ((open-parens (count-char #\( code))
|
||||
(close-parens (count-char #\) code))
|
||||
(diff (- open-parens close-parens)))
|
||||
(if (> diff 0)
|
||||
;; Append missing closing parens
|
||||
(concatenate 'string code (make-string diff :initial-element #\)))
|
||||
code)))
|
||||
|
||||
(defun neural-repair (code error-message)
|
||||
"Uses System 1 to deeply repair the syntax structure."
|
||||
(let ((prompt (format nil "The following Lisp code failed to parse.
|
||||
ERROR: ~a
|
||||
CODE: ~a
|
||||
MANDATE: Output EXACTLY ONE valid Common Lisp list. Do not explain. Do not use markdown blocks."
|
||||
error-message code))
|
||||
(system-prompt "You are a Lisp Syntax Repair Actuator. Return only valid, balanced Lisp code."))
|
||||
(let ((repaired (ask-neuro prompt :system-prompt system-prompt)))
|
||||
(string-trim '(#\Space #\Newline #\Tab) repaired))))
|
||||
|
||||
(defun repair-lisp-syntax (code error-message)
|
||||
"The entry point called by the neuro-gate when read-from-string fails."
|
||||
(kernel-log "SYNTAX GATE: Intercepted broken Lisp. Attempting deterministic repair...")
|
||||
(let ((fast-fix (deterministic-repair code)))
|
||||
(handler-case
|
||||
(read-from-string fast-fix)
|
||||
(error ()
|
||||
(kernel-log "SYNTAX GATE: Deterministic repair failed. Escalating to neural repair...")
|
||||
(let ((deep-fix (neural-repair code error-message)))
|
||||
(handler-case
|
||||
(read-from-string deep-fix)
|
||||
(error ()
|
||||
(kernel-log "SYNTAX GATE: Neural repair failed.")
|
||||
nil)))))))
|
||||
|
||||
(defskill :skill-lisp-repair
|
||||
:priority 90
|
||||
:trigger (lambda (ctx) (declare (ignore ctx)) nil) ;; Passive interceptor
|
||||
:neuro nil
|
||||
:symbolic nil)
|
||||
@@ -129,7 +129,14 @@ To call a tool, you MUST use:
|
||||
(let ((regs (nth-value 1 (cl-ppcre:scan-to-strings "(?s)```(?:lisp)?\\n?(.*?)\\n?```" raw-thought))))
|
||||
(if (and regs (> (length regs) 0)) (elt regs 0) raw-thought))
|
||||
(string-trim '(#\Space #\Newline #\Tab) raw-thought))))
|
||||
(suggestion (ignore-errors (read-from-string cleaned-thought))))
|
||||
(suggestion (handler-case (read-from-string cleaned-thought)
|
||||
(error (c)
|
||||
(if (and (member :skill-lisp-repair *loaded-skills*)
|
||||
(fboundp 'repair-lisp-syntax))
|
||||
(repair-lisp-syntax cleaned-thought (format nil "~a" c))
|
||||
(progn
|
||||
(kernel-log "SYSTEM 1 ERROR: Invalid output format from LLM.~%")
|
||||
nil))))))
|
||||
(kernel-log "SYSTEM 1 Suggestion: ~a~%" cleaned-thought)
|
||||
(when (and suggestion (listp suggestion))
|
||||
(push suggestion suggestions))))
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
;; --- Self-Fix Agent ---
|
||||
#:self-fix-apply
|
||||
|
||||
;; --- Lisp Repair Syntax Gate ---
|
||||
#:repair-lisp-syntax
|
||||
#:deterministic-repair
|
||||
|
||||
;; --- Context API (Peripheral Vision) ---
|
||||
#:context-query-store
|
||||
#:context-get-active-projects
|
||||
|
||||
28
tests/lisp-repair-tests.lisp
Normal file
28
tests/lisp-repair-tests.lisp
Normal file
@@ -0,0 +1,28 @@
|
||||
(defpackage :org-agent-lisp-repair-tests
|
||||
(:use :cl :fiveam :org-agent)
|
||||
(:export #:lisp-repair-suite))
|
||||
(in-package :org-agent-lisp-repair-tests)
|
||||
|
||||
(def-suite lisp-repair-suite :description "Tests for Lisp Repair Syntax Gate.")
|
||||
(in-suite lisp-repair-suite)
|
||||
|
||||
(test test-deterministic-repair-balance
|
||||
"Verify that deterministic-repair balances parentheses."
|
||||
(let ((broken "(:type :REQUEST :target :emacs"))
|
||||
(is (equal "(:type :REQUEST :target :emacs)"
|
||||
(org-agent:deterministic-repair broken)))))
|
||||
|
||||
(test test-deterministic-repair-deep-balance
|
||||
"Verify that deterministic-repair balances multiple nested parentheses."
|
||||
(let ((broken "(list :a (list :b 1"))
|
||||
(is (equal "(list :a (list :b 1))"
|
||||
(org-agent:deterministic-repair broken)))))
|
||||
|
||||
(test test-repair-lisp-syntax-entry
|
||||
"Verify that repair-lisp-syntax successfully repairs and parses broken Lisp."
|
||||
(let ((broken "(:type :REQUEST :target :tool")
|
||||
(error-msg "End of file while reading"))
|
||||
(let ((result (org-agent:repair-lisp-syntax broken error-msg)))
|
||||
(is (listp result))
|
||||
(is (eq :REQUEST (getf result :type)))
|
||||
(is (eq :tool (getf result :target))))))
|
||||
Reference in New Issue
Block a user