From cbd786e6b170e17d6e0a814841d422df04dab89a Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Wed, 22 Apr 2026 15:51:03 -0400 Subject: [PATCH] fix: Add normalize-plist-keywords and fix RESPONSE unbound bug - Add normalize-plist-keywords to convert LLM output TYPE -> :TYPE - Fix reason-gate to validate candidate is proper plist - Add debugging logs to trace LLM responses - Fix syntax error (extra paren) in think function --- harness/reason.org | 28 +++++++++++++++++++++------- library/reason.fasl | Bin 0 -> 18265 bytes library/reason.lisp | 35 +++++++++++++++++++++++++---------- 3 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 library/reason.fasl diff --git a/harness/reason.org b/harness/reason.org index c35cfa8..57b1f5f 100644 --- a/harness/reason.org +++ b/harness/reason.org @@ -58,6 +58,15 @@ The Reason stage is the cognitive engine of the OpenCortex. It bridges the gap b (string-trim '(#\Space #\Newline #\Tab) cleaned)) text)) +(defun normalize-plist-keywords (plist) + "Normalize all keys in a plist to keywords (e.g., TYPE -> :TYPE)." + (when (listp plist) + (loop for (k . rest) on plist by #'cddr + collect (if (and (symbolp k) (not (keywordp k))) + (intern (string k) :keyword) + k) + collect (car rest)))) + (defun think (context) "Generates a Lisp action proposal based on current context." (let* ((active-skill (find-triggered-skill context)) @@ -65,31 +74,33 @@ The Reason stage is the cognitive engine of the OpenCortex. It bridges the gap b (global-context (context-assemble-global-awareness)) (system-logs (context-get-system-logs)) (assistant-name (or (uiop:getenv "MEMEX_ASSISTANT") "Agent"))) - (let* ((prompt-generator (when active-skill (skill-probabilistic-prompt active-skill))) + (let* ((prompt-generator (when active-skill (skill-probabilistic-prompt active-skill)))) (raw-prompt (if prompt-generator (funcall prompt-generator context) (let ((p (proto-get (proto-get context :payload) :text))) (if (and p (stringp p)) p "Maintain metabolic stasis.")))) (system-prompt (format nil "IDENTITY: ~a. MANDATE: Respond with ONE Lisp plist. ~a ~a RECENT_LOGS: ~a IMPORTANT: To reply to the user, you MUST use: -(:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT "")) +(:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT \"\")) To call a tool, you MUST use: -(:TYPE :REQUEST :TARGET :TOOL :ACTION :CALL :TOOL "" :ARGS (:arg1 "val")) +(:TYPE :REQUEST :TARGET :TOOL :ACTION :CALL :TOOL \"\" :ARGS (:arg1 \"val\")) MANDATORY VALIDATION RULE: Before declaring any Lisp code edit complete, you MUST call the `:validate-lisp` tool with the proposed code. If the tool returns `:status :error`, read the `:reason` and `:failed` fields, fix the defect, and re-validate. You are strictly forbidden from relying on your own paren-balancing or syntax intuition. PROVIDER RULE: Always use the default cascade provider unless a specific model or capability is required for the task." - assistant-name global-context tool-belt system-logs))) + assistant-name global-context tool-belt system-logs))) (let* ((thought (probabilistic-call raw-prompt :system-prompt system-prompt :context context)) (cleaned (strip-markdown thought)) (meta (proto-get context :meta)) (source (proto-get meta :source))) + (harness-log "THINK: raw cleaned = ~a" (subseq cleaned 0 (min 100 (length cleaned)))) (if (and cleaned (stringp cleaned)) (let ((*read-eval* nil)) (if (and (> (length cleaned) 0) (char= (char cleaned 0) #\()) (handler-case (let ((parsed (read-from-string cleaned))) + (harness-log "THINK: parsed = ~a" parsed) (let ((type (proto-get parsed :TYPE)) (target (or (proto-get parsed :TARGET) (proto-get parsed :target)))) (cond ((member type '(:REQUEST :EVENT :STATUS :RESPONSE)) @@ -100,7 +111,7 @@ PROVIDER RULE: Always use the default cascade provider unless a specific model o (and (listp parsed) (listp (car parsed)) (keywordp (caar parsed)))) (list :TYPE :REQUEST :TARGET :TOOL :PAYLOAD parsed)) (t (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))))) - (error (c) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))) + (error (c) (harness-log "THINK ERROR: ~a" c) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))) thought))))) #+end_src @@ -139,9 +150,12 @@ PROVIDER RULE: Always use the default cascade provider unless a specific model o (unless (and (eq type :EVENT) (member sensor '(:user-input :chat-message))) (return-from reason-gate signal)) (let ((candidate (think signal))) - (if candidate + (harness-log "REASON: candidate = ~a" (type-of candidate)) + (if (and candidate (listp candidate) (keywordp (car candidate))) (setf (getf signal :approved-action) (deterministic-verify candidate signal)) - (setf (getf signal :approved-action) nil)) + (progn + (harness-log "REASON: Invalid candidate type ~a, dropping" (type-of candidate)) + (setf (getf signal :approved-action) nil))) (setf (getf signal :status) :reasoned) signal))) #+end_src diff --git a/library/reason.fasl b/library/reason.fasl new file mode 100644 index 0000000000000000000000000000000000000000..dc7947900ac0bdb909fbfdc9bb27a9ee45fed59b GIT binary patch literal 18265 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 literal 0 HcmV?d00001 diff --git a/library/reason.lisp b/library/reason.lisp index c946784..dfaecfe 100644 --- a/library/reason.lisp +++ b/library/reason.lisp @@ -36,6 +36,15 @@ (string-trim '(#\Space #\Newline #\Tab) cleaned)) text)) +(defun normalize-plist-keywords (plist) + "Normalize all keys in a plist to keywords (e.g., TYPE -> :TYPE)." + (when (listp plist) + (loop for (k . rest) on plist by #'cddr + collect (if (and (symbolp k) (not (keywordp k))) + (intern (string k) :keyword) + k) + collect (car rest)))) + (defun think (context) "Generates a Lisp action proposal based on current context." (let* ((active-skill (find-triggered-skill context)) @@ -50,35 +59,37 @@ (if (and p (stringp p)) p "Maintain metabolic stasis.")))) (system-prompt (format nil "IDENTITY: ~a. MANDATE: Respond with ONE Lisp plist. ~a ~a RECENT_LOGS: ~a IMPORTANT: To reply to the user, you MUST use: -(:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT "")) +(:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT \"\")) To call a tool, you MUST use: -(:TYPE :REQUEST :TARGET :TOOL :ACTION :CALL :TOOL "" :ARGS (:arg1 "val")) +(:TYPE :REQUEST :TARGET :TOOL :ACTION :CALL :TOOL \"\" :ARGS (:arg1 \"val\")) MANDATORY VALIDATION RULE: Before declaring any Lisp code edit complete, you MUST call the `:validate-lisp` tool with the proposed code. If the tool returns `:status :error`, read the `:reason` and `:failed` fields, fix the defect, and re-validate. You are strictly forbidden from relying on your own paren-balancing or syntax intuition. PROVIDER RULE: Always use the default cascade provider unless a specific model or capability is required for the task." - assistant-name global-context tool-belt system-logs))) + assistant-name global-context tool-belt system-logs))) (let* ((thought (probabilistic-call raw-prompt :system-prompt system-prompt :context context)) (cleaned (strip-markdown thought)) (meta (proto-get context :meta)) (source (proto-get meta :source))) + (harness-log "THINK: raw cleaned = ~a" (subseq cleaned 0 (min 100 (length cleaned)))) (if (and cleaned (stringp cleaned)) (let ((*read-eval* nil)) (if (and (> (length cleaned) 0) (char= (char cleaned 0) #\()) (handler-case (let ((parsed (read-from-string cleaned))) - (let ((type (proto-get parsed :TYPE)) + (harness-log "THINK: parsed = ~a" parsed) + (let ((parsed-normalized (normalize-plist-keywords parsed)) + (type (proto-get parsed :TYPE)) (target (or (proto-get parsed :TARGET) (proto-get parsed :target)))) (cond ((member type '(:REQUEST :EVENT :STATUS :RESPONSE)) (unless (proto-get parsed :target) (setf (getf parsed :target) (or source :CLI))) - parsed) - ;; Handle raw plists or lists of plists that look like tool calls or data + parsed-normalized) ((or (eq target :TOOL) (eq target :tool) (getf parsed :TOOL) (getf parsed :tool) (and (listp parsed) (listp (car parsed)) (keywordp (caar parsed)))) - (list :TYPE :REQUEST :TARGET :TOOL :PAYLOAD parsed)) + (list :TYPE :REQUEST :TARGET :TOOL :PAYLOAD (normalize-plist-keywords parsed))) (t (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))))) - (error (c) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))) + (error (c) (harness-log "THINK ERROR: ~a" c) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))) thought))))) @@ -111,8 +122,12 @@ PROVIDER RULE: Always use the default cascade provider unless a specific model o (unless (and (eq type :EVENT) (member sensor '(:user-input :chat-message))) (return-from reason-gate signal)) (let ((candidate (think signal))) - (if candidate + (harness-log "REASON: candidate = ~a" (type-of candidate)) + (if (and candidate (listp candidate) + (or (keywordp (car candidate)) (eq (car candidate) 'TYPE) (eq (car candidate) 'type))) (setf (getf signal :approved-action) (deterministic-verify candidate signal)) - (setf (getf signal :approved-action) nil)) + (progn + (harness-log "REASON: Invalid candidate type ~a, dropping" (type-of candidate)) + (setf (getf signal :approved-action) nil))) (setf (getf signal :status) :reasoned) signal)))