feat(v0.2.0): Self-Improvement & Structural Integrity
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 8s

- Fix critical paren balance issues across harness/skills.org, act.org,
  loop.org, memory.org, and skills/self-edit|emacs-edit.org
- Add :reload-skill cognitive tool for hot-reloading without restart
- Add :generate-embeddings tool and self-edit hot-reload infrastructure
- Wire all new skills (self-edit, emacs-edit, lisp-utils) into main ASDF
- Regenerate all .lisp tangled files via emacs --batch org-babel-tangle
- Add :opencortex/tests ASDF system with 14 test suites
- Fix test files to compile cleanly (self-edit-tests symbol vis, etc.)
This commit is contained in:
2026-04-27 07:30:01 -04:00
parent 1e202629ce
commit 43dbe3cf2d
29 changed files with 1980 additions and 590 deletions

View File

@@ -1,109 +1,258 @@
(in-package :opencortex)
(defun bouncer-scan-secrets (text)
"Returns the name of the secret found in TEXT, or NIL if clean."
"Scans TEXT for known secrets from the vault.
RETURNS: The name of the matched secret, or NIL if text is clean.
This prevents the catastrophic failure mode where the agent
accidentally echoes an API key in its response or log output.
The check uses substring matching (not regex) for reliability.
Only secrets longer than 5 characters are checked to avoid
false positives on common words."
(when (and text (stringp text))
(let ((found-secret nil))
(maphash (lambda (key val)
;; Only check secrets of meaningful length
(when (and val (stringp val) (> (length val) 5))
;; Search for secret value in action text
(when (search val text)
(setf found-secret key))))
opencortex::*vault-memory*)
found-secret)))
(defvar *bouncer-network-whitelist*
'("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")
"Domains that the Bouncer considers safe for outbound connections.
This whitelist should be minimal—only services explicitly configured
as gateways. All other outbound connections require approval.")
(defun bouncer-check-network-exfil (cmd)
"Returns T if the command appears to target an unwhitelisted external host."
"Detects if CMD attempts to contact an unwhitelisted external host.
Returns T if the command targets an unknown external host.
Returns NIL if the command is clean or only contacts whitelisted hosts.
The check looks for HTTP/HTTPS/FTP URLs and extracts the domain.
If the domain isn't in *bouncer-network-whitelist*, it's flagged."
(when (and cmd (stringp cmd))
;; Basic check for common data exfiltration tools being used with IPs/URLs
(let ((network-whitelist '("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")))
(when (cl-ppcre:scan "(http|https|ftp)://([\\w\\.-]+)" cmd)
(multiple-value-bind (match regs)
(cl-ppcre:scan-to-strings "(http|https|ftp)://([\\w\\.-]+)" cmd)
(declare (ignore match))
(let ((domain (aref regs 1)))
(not (some (lambda (safe) (search safe domain)) network-whitelist))))))))
;; Look for URL patterns in the command
(when (cl-ppcre:scan "(http|https|ftp)://([\\w\\.-]+)" cmd)
(multiple-value-bind (match regs)
(cl-ppcre:scan-to-strings "(http|https|ftp)://([\\w\\.-]+)" cmd)
(declare (ignore match))
(let ((domain (aref regs 1)))
;; Check if domain is whitelisted
(not (some (lambda (safe) (search safe domain))
*bouncer-network-whitelist*)))))))
(defun bouncer-check (action context)
"The 5-Vector security gate. Blocks or queues actions based on risk."
"The 5-Vector security gate for high-risk actions.
Evaluates an action against all security vectors and either:
- Returns the action unchanged (pass)
- Returns a blocking LOG event (hard block)
- Returns an approval-required EVENT (soft block)
Vector evaluation order:
1. Already approved actions pass immediately
2. Secret exposure → hard block
3. Network exfiltration → approval required
4. High-impact targets → approval required
The context parameter is not used directly but provided for
consistency with the skill gate signature."
(declare (ignore context))
(let* ((target (getf action :target))
(payload (getf action :payload))
(text (or (getf payload :text) (getf action :text)))
;; Extract cmd from direct shell or tool-mediated shell call
(cmd (or (getf payload :cmd)
(when (and (eq target :tool) (equal (getf payload :tool) "shell"))
(getf (getf payload :args) :cmd))))
(when (and (eq target :tool)
(equal (getf payload :tool) "shell"))
(getf (getf payload :args) :cmd))))
(approved (getf action :approved)))
(cond
;; 0. Bypass for already approved actions
(approved action)
;; 1. Secret Exposure Vector (Hard Block)
(cond
;; Vector 0: Already approved actions pass through
(approved
action)
;; Vector 1: Secret Exposure (Hard Block)
;; If any vault secret is found in the action text, block immediately
((and text (bouncer-scan-secrets text))
(let ((secret-name (bouncer-scan-secrets text)))
(harness-log "SECURITY VIOLATION: Blocked leak of secret ~a" secret-name)
`(:type :log :payload (:level :error :text ,(format nil "Action blocked: Potential exposure of ~a" secret-name)))))
(harness-log "SECURITY VIOLATION: Blocked potential leak of secret '~a'" secret-name)
(list :type :LOG
:payload (list :level :error
:text (format nil "Action blocked: Potential exposure of '~a'" secret-name)))))
;; 2. Network Exfiltration Vector (Authorization Required)
((and (or (eq target :shell)
(and (eq target :tool) (equal (getf payload :tool) "shell")))
;; Vector 2: Network Exfiltration (Soft Block)
;; Shell commands targeting unknown hosts require approval
((and (or (eq target :shell)
(and (eq target :tool)
(equal (getf payload :tool) "shell")))
(bouncer-check-network-exfil cmd))
(harness-log "SECURITY WARNING: External network call detected. Queuing for approval.")
`(:type :EVENT :payload (:sensor :approval-required :action ,action)))
;; 3. High-Impact Target Vector (Authorization Required)
(list :type :EVENT
:payload (list :sensor :approval-required
:action action)))
;; Vector 3: High-Impact Targets (Soft Block)
;; Shell execution, file repair, and eval require approval
((or (member target '(:shell))
(and (eq target :tool) (member (getf payload :tool) '("shell" "repair-file") :test #'string=))
(and (eq target :EMACS) (eq (getf payload :action) :eval)))
(harness-log "SECURITY: High-impact action ~a requires approval." (or (getf payload :tool) target))
`(:type :EVENT :payload (:sensor :approval-required :action ,action)))
(and (eq target :tool)
(member (getf payload :tool) '("shell" "repair-file") :test #'string=))
(and (eq target :emacs)
(eq (getf payload :action) :eval)))
;; 4. Default Pass
(t action))))
(harness-log "SECURITY: High-impact action requires approval: ~a"
(or (getf payload :tool) target))
(list :type :EVENT
:payload (list :sensor :approval-required
:action action)))
;; Vector 4: Default pass
(t
action))))
(defun bouncer-process-approvals ()
"Scans the object store for APPROVED flight plans and re-injects their actions."
"Scans the object store for APPROVED flight plans and re-injects them.
This function is called on every heartbeat, allowing the agent to
check for approvals without blocking the main signal pipeline.
Flight Plan format:
- Has TAGS including \"FLIGHT_PLAN\"
- Has TODO set to \"APPROVED\"
- Has ACTION containing the serialized action plist
When an approved flight plan is found:
1. Deserialize the action from the ACTION attribute
2. Mark the action as :approved = t (bypasses security gate)
3. Re-inject into the signal pipeline
4. Mark the flight plan as DONE
Returns T if any flight plans were processed."
(let ((approved-nodes (list-objects-with-attribute :TODO "APPROVED"))
(found-any nil))
(dolist (node approved-nodes)
(let* ((tags (getf (org-object-attributes node) :TAGS))
(action-str (getf (org-object-attributes node) :ACTION)))
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
(harness-log "BOUNCER: Found approved flight plan ~a. Re-injecting..." (org-object-id node))
;; Only process flight plans (not other APPROVED items)
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal)
action-str)
(harness-log "BOUNCER: Found approved flight plan '~a'. Re-injecting..."
(org-object-id node))
(let ((action (ignore-errors (read-from-string action-str))))
(when action
;; Mark as approved to bypass the gate
;; Mark as approved to bypass the security gate on re-injection
(setf (getf action :approved) t)
;; Re-inject the action into the signal pipeline
(inject-stimulus action)
;; Mark as DONE
;; Mark the flight plan as done
(setf (getf (org-object-attributes node) :TODO) "DONE")
(setq found-any t))))))
found-any))
(defun bouncer-create-flight-plan (blocked-action)
"Creates an Org node representing a pending flight plan for manual approval.
BLOCKED-ACTION is the action plist that was intercepted.
The flight plan node contains:
- A title describing the action
- TODO set to PLAN (awaiting approval)
- TAGS including FLIGHT_PLAN
- ACTION attribute containing the serialized action
The user reviews the flight plan and changes TODO to APPROVED.
On the next heartbeat, bouncer-process-approvals will detect
the approval and re-inject the action.
Returns the generated org-id for the flight plan."
(let ((id (org-id-new)))
(harness-log "BOUNCER: Creating flight plan node '~a'..." id)
;; Inject a node creation request
(list :type :REQUEST
:target :emacs
:payload (list :action :insert-node
:id id
:attributes (list
:TITLE "Flight Plan: High-Risk Action"
:TODO "PLAN"
:TAGS '("FLIGHT_PLAN")
:ACTION (format nil "~s" blocked-action)))))
(defun bouncer-deterministic-gate (action context)
"Main gate for the bouncer skill."
"Main deterministic gate for the Bouncer skill.
Handles three types of signals:
1. :approval-required - Create a flight plan for the blocked action
2. :heartbeat - Process any pending approvals
3. otherwise - Run security check on the action
The trigger is always true (bouncer evaluates all actions)
because security cannot be selective."
(let* ((payload (getf context :payload))
(sensor (getf payload :sensor)))
(case sensor
;; Signal type 1: Action was blocked, create flight plan
(:approval-required
(let* ((blocked-action (getf payload :action))
(id (org-id-new)))
(harness-log "BOUNCER: Creating flight plan node...")
;; Create the node in Emacs (or inbox)
(list :type :REQUEST :target :EMACS :action :insert-node
:id id :attributes `(:TITLE "Flight Plan: High-Risk Action"
:TODO "PLAN"
:TAGS ("FLIGHT_PLAN")
:ACTION ,(format nil "~s" blocked-action)))))
(let* ((blocked-action (getf payload :action)))
(bouncer-create-flight-plan blocked-action)))
;; Signal type 2: Heartbeat, check for approvals
(:heartbeat
;; Periodically check for approvals
(bouncer-process-approvals)
(if action (bouncer-check action context) action))
;; After processing approvals, still run the security check
(if action
(bouncer-check action context)
action))
;; Signal type 3: Normal action, run security check
(otherwise
(if action (bouncer-check action context) action)))))
(if action
(bouncer-check action context)
action)))))
(defskill :skill-bouncer
:priority 150
:trigger (lambda (ctx) t) ;; Bouncer evaluates all actions deterministically
:trigger (lambda (ctx) (declare (ignore ctx)) t)
:probabilistic nil
:deterministic #'bouncer-deterministic-gate)

View File

@@ -8,22 +8,40 @@
(:mentorship . 200)
(:sustainability . 100))
"Priority alist for policy invariant conflict resolution.
Higher numbers take precedence.")
Higher numbers take precedence.
When two invariants conflict, the higher priority wins.
Example: Modularity (250) takes precedence over Mentorship (200),
meaning a change that would fatten the harness is blocked
even if it would be educational.")
(defun policy-check-transparency (action context)
"Ensures the action is inspectable and user-facing actions carry an explanation.
Returns the action if clean, or a blocking LOG event if the action is opaque."
TRANSPARENCY CHECK:
1. Action must be a valid plist (not opaque data)
2. User-facing actions (:cli, :tui, :emacs) must include :explanation
3. Heartbeat and handshake messages are exempt (they're system status)
Returns the action if clean, or a blocking LOG event if violated."
(declare (ignore context))
;; Check 1: Action must be a valid plist
(unless (listp action)
(return-from policy-check-transparency
(list :type :LOG
:payload (list :level :error
:text "POLICY [Transparency]: Action is not a valid plist. Rejected."))))
(let* ((payload (getf action :payload))
(target (or (getf action :target) (getf action :TARGET)))
(explanation (or (getf payload :explanation) (getf payload :EXPLANATION)
(getf payload :rationale) (getf payload :RATIONALE))))
;; User-facing actions (CLI, TUI, Emacs) must explain themselves
(explanation (or (getf payload :explanation)
(getf payload :EXPLANATION)
(getf payload :rationale)
(getf payload :RATIONALE))))
;; Check 2: User-facing actions require explanation
(when (and (member target '(:cli :tui :emacs :EMACS :CLI :TUI))
(not explanation)
(not (member (getf payload :action)
@@ -31,172 +49,320 @@ Returns the action if clean, or a blocking LOG event if the action is opaque."
(return-from policy-check-transparency
(list :type :LOG
:payload (list :level :error
:text "POLICY [Transparency]: User-facing action missing :explanation. Blocked."))))
action))
:text "POLICY [Transparency]: User-facing action missing :explanation. Blocked.")))))
action))
(defvar *proprietary-domain-watchlist*
'("googleapis.com" "api.openai.com" "anthropic.com" "api.groq.com" "openrouter.ai")
"Domains that represent centralized, proprietary control.
Actions targeting these are logged as autonomy debt, not hard-blocked,
because tactical gateway usage is permitted under the strategic mandate.")
"Domains representing centralized, proprietary control.
Actions targeting these are logged as autonomy debt, not hard-blocked.
This is because tactical gateway usage (Telegram, Signal, OpenRouter)
is permitted under the strategic mandate for autonomy.
Strategic goal: Replace all proprietary APIs with local alternatives.
Tactical reality: Use what's available while building toward that goal.")
(defun policy-scan-proprietary-references (action)
"Scans ACTION text fields for proprietary domain references.
Returns the first matched domain, or NIL if clean."
Searches in:
- :text and :TEXT in payload
- :cmd and :CMD in payload
- :cmd in args (for shell tool calls)
Returns the first matched domain, or NIL if clean."
(let* ((payload (getf action :payload))
(text (or (getf payload :text) (getf payload :TEXT) ""))
(cmd (or (getf payload :cmd) (getf payload :CMD)
(when (equal (getf payload :tool) "shell")
(getf (getf payload :args) :cmd))
""))
(cmd (or (getf payload :cmd)
(getf payload :CMD)
(when (equal (getf payload :tool) "shell")
(getf (getf payload :args) :cmd))
""))
(haystack (concatenate 'string text cmd)))
(dolist (domain *proprietary-domain-watchlist* nil)
(when (search domain haystack)
(return domain)))))
(defun policy-check-autonomy (action context)
"Flags actions that reference proprietary domains. Returns the action
with an autonomy debt log appended, or the action itself if clean."
"Flags actions that reference proprietary domains.
Does NOT block the action—this is a warning, not a veto.
The agent can use proprietary services tactically, but must
be aware that each usage is a step away from full autonomy.
Returns a warning LOG if proprietary reference detected,
or the original action if clean."
(declare (ignore context))
(let ((domain (policy-scan-proprietary-references action)))
(if domain
(progn
(harness-log "POLICY [Autonomy]: Detected proprietary reference '~a'. Flagged for replacement." domain)
;; Return a side-effect log but DO NOT block the action
;; Return a warning log but DO NOT block the action
(list :type :LOG
:payload (list :level :warn
:text (format nil "Autonomy Debt: Action references proprietary domain '~a'. Consider a local alternative." domain)
:original-action action)))
action)))
(defvar *policy-max-skill-size-chars* 50000
"Maximum recommended size for a skill file tangled from an Org note.")
"Maximum recommended size for a skill file tangled from an Org note.
This is a soft limit—the check warns but does not block.
A large, well-documented skill is acceptable; a small, poorly-documented
one that adds unnecessary complexity is not.")
(defun policy-check-bloat (action context)
"Warns if a :create-skill action exceeds the bloat threshold.
Does not block, because size alone is not a proof of complexity."
Size alone is not proof of complexity—a 50KB skill that's well-designed
is better than a 5KB skill that's spaghetti. This check flags for review,
not automatic rejection.
Returns a warning LOG if threshold exceeded, or original action if clean."
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (getf payload :action))
(content (getf payload :content)))
(when (and (eq act :create-skill)
(stringp content)
(> (length content) *policy-max-skill-size-chars*))
(harness-log "POLICY [Bloat]: Proposed skill is ~a chars. Exceeds ~a char threshold."
(length content) *policy-max-skill-size-chars*)
(return-from policy-check-bloat
(list :type :LOG
:payload (list :level :warn
:text (format nil "Bloat Warning: Proposed skill (~a chars) exceeds ~a char threshold. Review for earned complexity."
(length content) *policy-max-skill-size-chars*)
(length content) *policy-max-skill-size-chars*)
:original-action action))))
action))
(defvar *mentorship-required-actions*
'(:create-skill :eval :modify-file :write-file :replace :rename-file :delete-file :shell :create-note)
"Actions that trigger the Mentorship invariant.")
(defun policy-check-mentorship (action context)
"Blocks high-impact actions that lack a mentorship note."
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (or (getf payload :action) (getf action :action)))
(note (or (getf payload :mentorship-note) (getf payload :MENTORSHIP-NOTE)))
(target (or (getf action :target) (getf action :TARGET)))
(tool (when (eq target :tool) (getf payload :tool))))
(when (or (member act *mentorship-required-actions*)
(member tool '("shell" "eval" "repair-file")))
(unless note
(return-from policy-check-mentorship
(list :type :LOG
:payload (list :level :error
:text "POLICY [Mentorship]: High-impact action missing :mentorship-note. Explain what you are doing and why. Blocked.")))))
action))
(defvar *cloud-only-backends* '(:openrouter :openai :anthropic :groq :gemini-api)
"Backends that require an internet connection and external infrastructure.")
(defun policy-check-sustainability (action context)
"Logs sustainability debt when the action relies on cloud-only infrastructure.
Does not block, because tactical cloud usage is permitted."
(let* ((payload (getf context :payload))
(backend (getf payload :backend))
(provider (getf payload :provider)))
(when (or (member backend *cloud-only-backends*)
(member provider *cloud-only-backends*))
(harness-log "POLICY [Sustainability]: Cloud provider '~a' used. Logged as sustainability debt."
(or backend provider))
(return-from policy-check-sustainability
(list :type :LOG
:payload (list :level :warn
:text (format nil "Sustainability Debt: Reliance on cloud provider '~a'. Consider Ollama or local inference."
(or backend provider))))))
action))
action))
(defvar *modularity-protected-paths*
'("harness/" "opencortex.asd")
"Paths that constitute the unbreakable core of the system.
Any action targeting these paths must include a :modularity-justification.
This list is project-specific and should be configured at boot time.")
Any action targeting these paths must include a :modularity-justification
explaining why the change cannot be implemented as a skill.
The Thin Harness principle: What belongs in the harness?
- Core signal processing (Perceive-Reason-Act loop)
- Memory and persistence primitives
- Protocol definition and validation
- Skills register and dispatch
What belongs in skills?
- Policy and security
- LLM integration
- Domain-specific functionality
- New actuators")
(defun policy-check-modularity (action context)
"Blocks modifications to the system's protected core unless justified."
"Blocks modifications to the system's protected core unless justified.
MODULARITY CHECK:
1. If the action targets a protected path
2. And no :modularity-justification is provided
3. Then block with an explanation
The justification should explain WHY the change cannot be a skill.
Common valid reasons:
- The change fixes a bug in the harness itself
- The change adds a primitive that skills cannot implement
- The change is required for security hardening
Invalid reasons:
- 'It's easier to modify the harness'
- 'Skills are too slow'
- 'I want to keep it all in one place'"
(declare (ignore context))
(let* ((payload (getf action :payload))
(target-file (or (getf payload :file) (getf payload :filename)))
(target-file (or (getf payload :file)
(getf payload :filename)))
(justification (or (getf payload :modularity-justification)
(getf payload :MODULARITY-JUSTIFICATION))))
(when (and target-file
(some (lambda (path) (search path target-file)) *modularity-protected-paths*)
(some (lambda (path) (search path target-file))
*modularity-protected-paths*)
(not justification))
(return-from policy-check-modularity
(list :type :LOG
:payload (list :level :error
:text "POLICY [Modularity]: Modification to protected core path blocked. Provide :modularity-justification explaining why this cannot be a skill."
:blocked-path target-file))))
action))
action))
(defvar *mentorship-required-actions*
'(:create-skill :eval :modify-file :write-file :replace
:rename-file :delete-file :shell :create-note)
"Actions that trigger the Mentorship invariant.
These are high-impact actions that should come with explanations
not just for the user, but for future debugging and maintenance.")
(defun policy-check-mentorship (action context)
"Blocks high-impact actions that lack a mentorship note.
MENTORSHIP CHECK:
1. If the action is in *mentorship-required-actions*
2. Or if the action calls shell/eval/repair-file tools
3. Then require :mentorship-note explaining what and why
The mentorship note should be:
- Concise (1-2 sentences)
- Educational (explain the principle, not just the action)
- Actionable (help the user understand the outcome)"
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (or (getf payload :action)
(getf action :action)))
(note (or (getf payload :mentorship-note)
(getf payload :MENTORSHIP-NOTE)))
(target (or (getf action :target)
(getf action :TARGET)))
(tool (when (eq target :tool)
(getf payload :tool))))
(when (or (member act *mentorship-required-actions*)
(member tool '("shell" "eval" "repair-file")))
(unless note
(return-from policy-check-mentorship
(list :type :LOG
:payload (list :level :error
:text "POLICY [Mentorship]: High-impact action missing :mentorship-note. Explain what you are doing and why. Blocked.")))))
action))
(defvar *cloud-only-backends* '(:openrouter :openai :anthropic :groq :gemini-api)
"Backends requiring internet connection and external infrastructure.
These are acceptable as fallbacks when local inference is unavailable,
but should be logged as sustainability debt for tracking purposes.")
(defun policy-check-sustainability (action context)
"Logs sustainability debt when action relies on cloud-only infrastructure.
Does NOT block—this is informational, not prohibitive.
Cloud usage is acceptable tactically (when local models fail),
but every cloud usage should be a conscious decision, not a default."
(let* ((payload (getf context :payload))
(backend (getf payload :backend))
(provider (getf payload :provider)))
(when (or (member backend *cloud-only-backends*)
(member provider *cloud-only-backends*))
(harness-log "POLICY [Sustainability]: Cloud provider '~a' used. Logged as sustainability debt."
(or backend provider))
(return-from policy-check-sustainability
(list :type :LOG
:payload (list :level :warn
:text (format nil "Sustainability Debt: Reliance on cloud provider '~a'. Consider Ollama or local inference."
(or backend provider))))))
action)))
(defun policy-explain (invariant-key message &optional original-action)
"Formats a policy decision into an auditable explanation plist.
INVARIANT-KEY is one of :transparency, :autonomy, :bloat, :modularity, :mentorship, :sustainability.
MESSAGE is a human-readable string.
ORIGINAL-ACTION is the action that was blocked or modified."
INVARIANT-KEY is one of:
:transparency, :autonomy, :bloat, :modularity, :mentorship, :sustainability
MESSAGE is a human-readable string explaining the decision.
ORIGINAL-ACTION is the action that was blocked or modified.
Returns a REQUEST plist addressed to the original source,
containing the explanation and original action for transparency."
(list :type :REQUEST
:target (or (ignore-errors (getf (getf original-action :meta) :source)) :cli)
:target (or (ignore-errors
(getf (getf original-action :meta) :source))
:cli)
:payload (list :action :message
:text (format nil "[POLICY ~a] ~a" invariant-key message)
:explanation (format nil "Invariant: ~a | Rationale: ~a" invariant-key message)
:explanation (format nil "Invariant: ~a | Rationale: ~a"
invariant-key message)
:original-action original-action)))
(defun policy-run-invariant-checks (action context)
"Runs all invariant checks in priority order. Returns the final action,
a blocking LOG event, or a warning wrapper."
"Runs all invariant checks in priority order.
Priority order (from *policy-invariant-priorities*):
1. Transparency (500) - blocks non-transparent actions
2. Autonomy (400) - warns on proprietary dependencies
3. Bloat (300) - warns on oversized skills
4. Modularity (250) - blocks unprotected core modifications
5. Mentorship (200) - blocks unexplained high-impact actions
6. Sustainability (100) - warns on cloud dependencies
Returns:
- The final action (possibly modified by checks)
- A blocking LOG event (if any check returned :error level)
- A warning wrapper (if checks returned :warn level but no blocks)"
(let ((checks '(policy-check-transparency
policy-check-autonomy
policy-check-bloat
policy-check-modularity
policy-check-mentorship
policy-check-sustainability)))
(dolist (check-fn checks action)
(let ((result (funcall check-fn action context)))
;; If the check returned a LOG event, treat it as a block/warning
;; If the check returned a LOG/EVENT, interpret it
(when (and (listp result)
(member (getf result :type) '(:LOG :EVENT)))
(let ((level (getf (getf result :payload) :level)))
(cond ((eq level :error)
;; Hard block: return the log event directly
(return-from policy-run-invariant-checks result))
(t
;; Warning: log it, but continue with the original action
(harness-log "~a" (getf (getf result :payload) :text))))))))))
(let ((level (getf (getf result :payload) :level)))
(cond
;; Hard block: error level stops processing immediately
((eq level :error)
(return-from policy-run-invariant-checks result))
;; Soft warning: log but continue with original action
(t
(harness-log "~a" (getf (getf result :payload) :text)))))))))
action))
(defun policy-find-engineering-standards-gate ()
"Searches for the Engineering Standards gate across known jailed package names.
Returns the function symbol, or NIL if unavailable."
The standards skill may be in opencortex-contrib submodule,
so we search multiple possible package names with graceful fallback.
Returns the function symbol, or NIL if unavailable."
(dolist (pkg-name '(:opencortex.skills.org-skill-engineering-standards
:opencortex.skills.org-skill-engineering
:opencortex.skills.engineering-standards)
nil)
(let ((pkg (find-package pkg-name)))
(when pkg
(let ((sym (find-symbol "ENGINEERING-STANDARDS-GATE" pkg)))
@@ -204,18 +370,31 @@ Returns the function symbol, or NIL if unavailable."
(return (symbol-function sym))))))))
(defun policy-deterministic-gate (action context)
"The main policy gate. Runs invariant checks, then delegates to engineering standards if available.
Never returns NIL silently; always returns an action or an auditable log event."
"The main policy gate entry point.
This function is registered as the deterministic-fn for the policy skill.
It runs invariant checks, then delegates to engineering standards if loaded.
IMPORTANT: Never returns NIL silently. Always returns either:
- An action (possibly modified)
- A blocking LOG event with explanation
- A warning wrapper with explanation"
;; Step 1: Run invariant checks
(let ((current-action (policy-run-invariant-checks action context)))
;; If an invariant returned a blocking log, do not proceed further
;; Step 2: If an invariant blocked the action, stop here
(when (and (listp current-action)
(member (getf current-action :type) '(:LOG :EVENT))
(eq (getf (getf current-action :payload) :level) :error))
(return-from policy-deterministic-gate current-action))
;; Delegate to Engineering Standards if loaded
;; Step 3: Delegate to Engineering Standards if loaded
(let ((eng-gate (policy-find-engineering-standards-gate)))
(when eng-gate
(setf current-action (funcall eng-gate current-action context))))
current-action))
(defskill :skill-policy

View File

@@ -116,3 +116,62 @@ Provide a fixed version of the code as a lisp form.")
(list :status :success :repaired balanced))
(error (c)
(list :status :error :message (format nil "Could not repair: ~a" c)))))))
(defvar *self-edit-skills-backup* nil
"Backup of skill registry before hot-reload.")
(defun self-edit-hot-reload-skill (skill-name gen-path)
"Reloads a skill from its compiled .lisp source.
Steps:
1. Backup current *skills-registry*
2. Compile the new skill file
3. Merge new skill into registry
4. Verify the skill loads without error
5. If error, rollback to backup
Returns (values :success t) or (values :error message)."
(unless *skills-registry*
(return-from self-edit-hot-reload-skill
(values :error "Skills engine not initialized")))
(unless (uiop:file-exists-p gen-path)
(return-from self-edit-hot-reload-skill
(values :error (format nil "Skill file not found: ~a" gen-path))))
;; Step 1: Backup registry
(setf *self-edit-skills-backup* (copy-hash-table *skills-registry*))
(handler-case
(progn
;; Step 2: Compile new skill
(let ((compiled (compile-file gen-path)))
(unless compiled
(error "Compilation returned nil")))
;; Step 3: Load the compiled skill
(load gen-path)
;; Step 4: Verify skill is in registry
(let ((skill (gethash (string skill-name) *skills-registry*)))
(if skill
(progn
(harness-log "SELF-EDIT: Hot-reloaded skill ~a from ~a"
skill-name gen-path)
(values :success t))
(error "Skill not registered after reload"))))
(error (e)
;; Step 5: Rollback
(when *self-edit-skills-backup*
(clrhash *skills-registry*)
(maphash (lambda (k v) (setf (gethash k *skills-registry*) v))
*self-edit-skills-backup*))
(harness-log "SELF-EDIT: Hot-reload FAILED for ~a: ~a" skill-name e)
(values :error (format nil "Hot-reload failed: ~a" e)))))
(def-cognitive-tool :reload-skill
"Hot-reloads a skill from its compiled source file without restarting the system."
((:skill-name :type :string :description "Name of the skill to reload (e.g. :skill-engineering-standards)")
(:gen-path :type :string :description "Absolute path to the compiled .lisp file"))
:body (lambda (args)
(let ((name (getf args :skill-name))
(path (getf args :gen-path)))
(multiple-value-bind (status message) (self-edit-hot-reload-skill name path)
(list :status status :message message)))))