Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
- New skill hooks into :syntax-error and :repair-request events - Deterministic paren balancing (fast fix) - Surgical file edits with memory rollback on failure - :self-edit and :balance-parens cognitive tools - 9 new tests, all 93 tests passing
205 lines
8.2 KiB
Org Mode
205 lines
8.2 KiB
Org Mode
:PROPERTIES:
|
|
:ID: self-edit-001
|
|
:END:
|
|
#+TITLE: SKILL: Self-Edit Agent
|
|
#+STARTUP: content
|
|
#+FILETAGS: :self-repair:autonomy:editing:
|
|
|
|
* Overview
|
|
The *Self-Edit Agent* enables the agent to modify its own code and files with safety guarantees. It handles:
|
|
1. Syntax errors - auto-balance parens, then LLM fix
|
|
2. File modifications - surgical edits with memory rollback on failure
|
|
3. Skill hot-reload - swap compiled skills without breaking the system
|
|
|
|
* Phase D: Build (Implementation)
|
|
|
|
** Package Context
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(in-package :opencortex)
|
|
#+end_src
|
|
|
|
** Deterministic Paren Repair
|
|
Fast paren balancing for syntax errors.
|
|
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(defun self-edit-count-char (char string)
|
|
"Counts occurrences of CHAR in STRING."
|
|
(loop for c across string count (char= c char)))
|
|
|
|
(defun self-edit-balance-parens (code)
|
|
"Balances parentheses in CODE."
|
|
(let ((opens (self-edit-count-char #\( code))
|
|
(closes (self-edit-count-char #\) code)))
|
|
(cond
|
|
((= opens closes) code)
|
|
((> opens closes)
|
|
(concatenate 'string code (make-string (- opens closes) :initial-element #\))))
|
|
((> closes opens)
|
|
(concatenate 'string (make-string (- closes opens) :initial-element #\() code)))))
|
|
#+end_src
|
|
|
|
** Parse Target Location
|
|
Extract file and line info from error context.
|
|
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(defun self-edit-parse-location (context)
|
|
"Extracts file and line from error context payload."
|
|
(let* ((payload (getf context :payload))
|
|
(message (getf payload :message ""))
|
|
(file (or (getf payload :file)
|
|
(when (search "file" message)
|
|
(car (cl-ppcre:all-matches-as-strings "[a-zA-Z0-9_/-]+\\.lisp" message)))))
|
|
(line (or (getf payload :line)
|
|
(let ((match (cl-ppcre:scan-to-strings "line.?(\\d+)" message)))
|
|
(when match (parse-integer (aref match 0)))))))
|
|
(list :file file :line line)))
|
|
#+end_src
|
|
|
|
** Apply Surgical Edit
|
|
Apply a find/replace to a file with rollback on failure.
|
|
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(defun self-edit-apply (target-file old-code new-code)
|
|
"Applies surgical edit to TARGET-FILE: replace OLD-CODE with NEW-CODE.
|
|
Returns list with :status and :message keys."
|
|
(unless (uiop:file-exists-p target-file)
|
|
(return-from self-edit-apply
|
|
(list :status :error :message (format nil "File not found: ~a" target-file))))
|
|
|
|
(snapshot-memory)
|
|
(harness-log "SELF-EDIT: Attempting surgical fix on ~a..." target-file)
|
|
|
|
(let ((original-content (uiop:read-file-string target-file)))
|
|
(handler-case
|
|
(if (search old-code original-content)
|
|
(let ((new-content (cl-ppcre:regex-replace-all
|
|
(cl-ppcre:quote-meta-chars old-code)
|
|
original-content
|
|
new-code)))
|
|
(with-open-file (out target-file :direction :output :if-exists :supersede)
|
|
(write-string new-content out))
|
|
(harness-log "SELF-EDIT: Edit applied successfully.")
|
|
(list :status :success :message "Edit applied."))
|
|
(progn
|
|
(harness-log "SELF-EDIT: Pattern not found in file.")
|
|
(list :status :error :message "Pattern not found in file.")))
|
|
(error (c)
|
|
(harness-log "SELF-EDIT: Edit failed: ~a" c)
|
|
(rollback-memory 0)
|
|
(list :status :error :message (format nil "Edit failed: ~a" c))))))
|
|
#+end_src
|
|
|
|
** Cognitive Tool: Edit File
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(def-cognitive-tool :self-edit
|
|
"Applies a surgical code modification to a file with automatic rollback on failure."
|
|
((:file :type :string :description "Path to the target file")
|
|
(:old :type :string :description "The code block to find")
|
|
(:new :type :string :description "The code block to replace with"))
|
|
:body (lambda (args)
|
|
(let* ((file (getf args :file))
|
|
(old (getf args :old))
|
|
(new (getf args :new)))
|
|
(self-edit-apply file old new))))
|
|
#+end_src
|
|
|
|
** Skill Definition
|
|
Hooks into syntax-error events for self-repair.
|
|
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(defskill :skill-self-edit
|
|
:priority 95
|
|
:trigger (lambda (ctx)
|
|
(let ((sensor (getf (getf ctx :payload) :sensor)))
|
|
(member sensor '(:syntax-error :repair-request :self-edit))))
|
|
:probabilistic (lambda (ctx)
|
|
(let ((sensor (getf (getf ctx :payload) :sensor)))
|
|
(cond
|
|
((eq sensor :syntax-error)
|
|
"You are the Self-Edit Agent. A syntax error occurred.
|
|
Provide a fixed version of the code as a lisp form.")
|
|
((eq sensor :repair-request)
|
|
"You are the Self-Edit Agent. Apply the surgical fix to the file.")
|
|
(t nil))))
|
|
:deterministic (lambda (action ctx)
|
|
(let* ((payload (getf ctx :payload))
|
|
(sensor (getf payload :sensor)))
|
|
(cond
|
|
((eq sensor :syntax-error)
|
|
(let ((code (getf payload :code)))
|
|
(harness-log "SELF-EDIT: Fast paren balancing...")
|
|
(let ((balanced (self-edit-balance-parens code)))
|
|
(handler-case
|
|
(progn
|
|
(read-from-string balanced)
|
|
(harness-log "SELF-EDIT: Fast fix SUCCESS.")
|
|
(list :status :success :repaired balanced))
|
|
(error ()
|
|
(harness-log "SELF-EDIT: Fast fix failed, need neural repair.")
|
|
(list :status :error :reason "needs-llm"))))))
|
|
((eq sensor :repair-request)
|
|
(let ((file (getf payload :file))
|
|
(old (getf payload :old))
|
|
(new (getf payload :new)))
|
|
(self-edit-apply file old new)))
|
|
(t nil)))))
|
|
#+end_src
|
|
|
|
** Tool: Quick Paren Fix
|
|
#+begin_src lisp :tangle ../library/gen/org-skill-self-edit.lisp
|
|
(def-cognitive-tool :balance-parens
|
|
"Balances parentheses in a code string."
|
|
((:code :type :string :description "The code to balance"))
|
|
:body (lambda (args)
|
|
(let ((code (getf args :code))
|
|
(balanced (self-edit-balance-parens code)))
|
|
(handler-case
|
|
(progn
|
|
(read-from-string balanced)
|
|
(list :status :success :repaired balanced))
|
|
(error (c)
|
|
(list :status :error :message (format nil "Could not repair: ~a" c)))))))
|
|
#+end_src
|
|
|
|
* Phase E: Verification
|
|
|
|
#+begin_src lisp :tangle ../tests/self-edit-tests.lisp
|
|
(defpackage :opencortex-self-edit-tests
|
|
(:use :cl :fiveam :opencortex)
|
|
(:export #:self-edit-suite))
|
|
|
|
(in-package :opencortex-self-edit-tests)
|
|
|
|
(def-suite self-edit-suite
|
|
:description "Tests for Self-Edit skill.")
|
|
|
|
(in-suite self-edit-suite)
|
|
|
|
(test balance-parens-balanced
|
|
(let ((result (opencortex:self-edit-balance-parens "(+ 1 2)")))
|
|
(is (string= result "(+ 1 2)"))
|
|
(is (not (null (read-from-string result))))))
|
|
|
|
(test balance-parens-missing-open
|
|
(let ((result (opencortex:self-edit-balance-parens "+ 1 2)")))
|
|
(is (string= result "(+ 1 2)"))
|
|
(is (not (null (read-from-string result))))))
|
|
|
|
(test balance-parens-missing-close
|
|
(let ((result (opencortex:self-edit-balance-parens "(+ 1 2")))
|
|
(is (string= result "(+ 1 2)"))
|
|
(is (not (null (read-from-string result))))))
|
|
|
|
(test balance-parens-deep
|
|
(let ((result (opencortex:self-edit-balance-parens "((lambda (x) (if x (+ 1 2) 3))")))
|
|
(is (string= result "((lambda (x) (if x (+ 1 2) 3)))"))
|
|
(is (not (null (read-from-string result))))))
|
|
|
|
(test balance-parens-empty
|
|
(let ((result (opencortex:self-edit-balance-parens "")))
|
|
(is (string= result ""))))
|
|
#+end_src
|
|
|
|
* See Also
|
|
- [[file:org-skill-lisp-utils.org][Lisp Utils]] - Validation and repair
|
|
- [[file:org-skill-self-fix.org][Self-Fix]] - File modification with rollback |