FEAT: Implement asynchronous Lisp Repair (Microkernel Decoupled)

This commit is contained in:
2026-04-11 14:36:39 -04:00
parent a9a15b3d82
commit db5a941340
7 changed files with 210 additions and 2 deletions

View File

@@ -0,0 +1,33 @@
#+TITLE: Root Cause Analysis: Asynchronous Lisp Repair Syntax Gate
#+DATE: 2026-04-11
#+FILETAGS: :rca:lisp:repair:decoupling:architecture:psf:
* Executive Summary
Reimplemented the `org-skill-lisp-repair` to align with the "Sovereign Boundary" mandate. The previously synchronous, core-blocking repair logic has been replaced with an asynchronous, event-driven architecture using the Reactive Signal Pipeline.
* 1. Issue: Core Bloat & Synchronous Coupling
** Symptoms
The initial implementation of the Lisp Repair gate placed a `handler-case` and a dynamic function call (`repair-lisp-syntax`) directly inside the core `think` function (`neuro.lisp`). This forced the core to wait for repairs and made it "aware" of specific repair logic.
** Root Cause
Architectural shortcutting. By placing repair logic in the core execution path, we violated the microkernel principle which mandates that the core should be a "dumb" signal processor.
** Resolution
1. **Refactored Core:** `think` now only emits a `:syntax-error` stimulus if parsing fails. It no longer attempts to repair.
2. **Asynchronous Skill:** `skill-lisp-repair` now triggers on the `:syntax-error` event. It performs the repair and returns the corrected action, which is then dispatched by the pipeline.
* 2. Side-Issue: Nested Signal Payloads
** Symptoms
`TYPE-ERROR` during testing when extracting the broken code from the stimulus.
** Root Cause
Mismatched expectations of signal nesting. The skill expected the code at `(getf context :payload)`, but in the `decide-gate`, `context` is the full signal, and the error details were nested inside the `:candidate` field of that signal.
** Resolution
Updated the symbolic logic to correctly traverse the nested signal structure: `(getf (getf context :candidate) :payload)`.
* 3. PSF Mandate Alignment
** Sovereign Boundary
The core is now strictly a parser. Repair is an optional, user-space service.
** Reactive Signal Pipeline
Leveraged the pipeline's ability to re-inject `EVENT` signals to flatten the recursion of the repair loop.
* 4. Permanent Learnings
- **Emit, Don't Call:** In a microkernel, if a non-fatal error occurs, always emit a signal rather than calling a recovery function. This allows the system to remain asynchronous and modular.
- **Signal Inspection:** When writing symbolic gates, always verify the exact shape of the `context` signal being passed by the kernel to avoid nesting errors.

View File

@@ -175,7 +175,13 @@ 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)
;; EMIT ASYNCHRONOUS REPAIR STIMULUS
(list :type :EVENT :payload
(list :sensor :syntax-error
:code cleaned-thought
:error (format nil "~a" c)))))))
(kernel-log "SYSTEM 1 Suggestion: ~a~%" cleaned-thought)
(when (and suggestion (listp suggestion))
(push suggestion suggestions))))

View File

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

View File

@@ -0,0 +1,75 @@
: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* asynchronously intercepts `:syntax-error` events emitted by the kernel when System 1 (LLM) proposals fail to parse. It performs deterministic or neural repairs and re-injects the corrected action into the pipeline.
* 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 (e.g. balancing parens)."
(let* ((open-parens (count-char #\( code))
(close-parens (count-char #\) code))
(diff (- open-parens close-parens)))
(if (> diff 0)
(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))))
#+end_src
** Skill Definition
Reacts to syntax error events and transforms them into repaired requests.
#+begin_src lisp :tangle ../src/lisp-repair.lisp
(defskill :skill-lisp-repair
:priority 90
:trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :syntax-error))
:neuro nil ;; Handled deterministically in symbolic or manually via ask-neuro
:symbolic (lambda (action context)
(declare (ignore action))
(let* ((payload (getf context :payload))
(code (getf payload :code))
(error-msg (getf payload :error)))
(kernel-log "SYNTAX GATE: Reacting to broken Lisp stimulus...")
(let ((fast-fix (deterministic-repair code)))
(handler-case
(let ((repaired (read-from-string fast-fix)))
(kernel-log "SYNTAX GATE: Deterministic repair SUCCESS.")
repaired)
(error ()
(kernel-log "SYNTAX GATE: Deterministic repair failed. Escalating...")
(let ((deep-fix (neural-repair code error-msg)))
(handler-case
(let ((repaired (read-from-string deep-fix)))
(kernel-log "SYNTAX GATE: Neural repair SUCCESS.")
repaired)
(error ()
(kernel-log "SYNTAX GATE: Neural repair failed.")
(list :type :LOG :payload (list :text "Lisp Repair Failed.")))))))))))
#+end_src

55
src/lisp-repair.lisp Normal file
View File

@@ -0,0 +1,55 @@
(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 (e.g. balancing parens)."
(let* ((open-parens (count-char #\( code))
(close-parens (count-char #\) code))
(diff (- open-parens close-parens)))
(if (> diff 0)
(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))))
(defskill :skill-lisp-repair
:priority 90
:trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :syntax-error))
:neuro nil
:symbolic (lambda (action context)
(declare (ignore action))
(let* ((stimulus (getf context :candidate))
(payload (getf stimulus :payload))
(code (getf payload :code))
(error-msg (getf payload :error)))
(kernel-log "SYNTAX GATE: Reacting to broken Lisp stimulus...")
(let ((fast-fix (deterministic-repair code)))
(handler-case
(let ((repaired (read-from-string fast-fix)))
(kernel-log "SYNTAX GATE: Deterministic repair SUCCESS.")
repaired)
(error ()
(kernel-log "SYNTAX GATE: Deterministic repair failed. Escalating...")
(let ((deep-fix (neural-repair code error-msg)))
(handler-case
(let ((repaired (read-from-string deep-fix)))
(kernel-log "SYNTAX GATE: Neural repair SUCCESS.")
repaired)
(error ()
(kernel-log "SYNTAX GATE: Neural repair failed.")
(list :type :LOG :payload (list :text "Lisp Repair Failed.")))))))))))

View File

@@ -129,7 +129,13 @@ 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)
;; EMIT ASYNCHRONOUS REPAIR STIMULUS
(list :type :EVENT :payload
(list :sensor :syntax-error
:code cleaned-thought
:error (format nil "~a" c)))))))
(kernel-log "SYSTEM 1 Suggestion: ~a~%" cleaned-thought)
(when (and suggestion (listp suggestion))
(push suggestion suggestions))))

View File

@@ -0,0 +1,30 @@
(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 Asynchronous 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"))
;; deterministic-repair will be defined in lisp-repair.lisp (user-space)
;; but for testing we expect it to be available in the org-agent package.
(is (equal "(:type :REQUEST :target :emacs)"
(org-agent::deterministic-repair broken)))))
(test test-async-repair-flow
"Verify that the pipeline correctly emits and reacts to syntax-error events."
(clrhash org-agent::*object-store*)
(let* ((broken-code "(:type :REQUEST :target :tool")
(error-msg "End of file")
;; 1. The Stimulus that caused the error
(stimulus `(:type :EVENT :payload (:sensor :syntax-error :code ,broken-code :error ,error-msg)))
;; 2. Simulate the decide-gate call for skill-lisp-repair
(result (org-agent:decide-gate (list :type :EVENT :candidate stimulus :payload '(:sensor :syntax-error)))))
(let ((approved (getf result :approved-action)))
;; The repair skill should have intercepted the EVENT and returned a repaired REQUEST
(is (eq :REQUEST (getf approved :type)))
(is (eq :tool (getf approved :target))))))