Files
passepartout/skills/org-skill-tool-permissions.org

7.2 KiB

SKILL: Tool Permission Tiers

Overview

This skill implements tool permission tiers for security - controlling which cognitive tools can execute without user interaction.

Also provides vector embeddings via Ollama or llama.cpp.

The Three Tiers

Tier Behavior Use Case
:allow Executes immediately Trusted, safe tools
:deny Blocks before execution Dangerous tools
:ask Prompts user, pauses execution Sensitive tools

Embedding Providers

  • EMBEDDING_PROVIDER environment: "ollama" or "llama.cpp"
  • OLLAMA_HOST / LLAMA_HOST for the API endpoint
  • EMBEDDING_MODEL model name

Implementation

Tool permissions and embedding generation via multiple providers.

(in-package :opencortex)

(defvar *tool-permissions* (make-hash-table :test 'equal)
  "Hash table mapping tool names to :allow/:deny/:ask.")

(defun get-tool-permission (tool-name)
  (let ((key (string-downcase (string tool-name))))
    (or (gethash key *tool-permissions*) :allow)))

(defun set-tool-permission (tool-name tier)
  (setf (gethash (string-downcase (string tool-name)) *tool-permissions*) tier)
  (harness-log "TOOL PERMISSION: Set ~a = ~a" tool-name tier))

(defun check-tool-permission-gate (tool-name context)
  (declare (ignore context))
  (let ((perm (get-tool-permission tool-name)))
    (case perm
      (:allow :allow)
      (:deny :deny)
      (:ask (list :ask tool-name))
      (t :allow))))

(def-cognitive-tool :get-embedding
  "Generates vector embeddings via Ollama or llama.cpp API."
  ((:text :type :string :description "Text to embed."))
  :body (lambda (args)
          (let* ((text (getf args :text))
                 (provider (or (uiop:getenv "EMBEDDING_PROVIDER") "ollama"))
                 (model (or (uiop:getenv "EMBEDDING_MODEL") "nomic-embed-text"))
                 (embedding nil))
            (cond
              ((string= provider "ollama")
               (let* ((host (or (uiop:getenv "OLLAMA_HOST") "localhost:11434"))
                      (url (format nil "http://~a/api/embeddings" host))
                      (body (cl-json:encode-json-to-string `((model . ,model) (prompt . ,text)))))
                 (handler-case
                     (let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 30))
                            (json (cl-json:decode-json-from-string response))
                            (vec (cdr (assoc :embedding json))))
                       (when vec (setf embedding vec)))
                   (error (c) (harness-log "EMBEDDING: Ollama failed: ~a" c)))))
              ((string= provider "llama.cpp")
               (let* ((host (or (uiop:getenv "LLAMA_HOST") "localhost:8080"))
                      (url (format nil "http://~a/v1/embeddings" host))
                      (body (cl-json:encode-json-to-string `((model . ,model) (input . ,text)))))
                 (handler-case
                     (let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 30))
                            (json (cl-json:decode-json-from-string response))
                            (data (cdr (assoc :data json)))
                            (vec (when data (cdr (assoc :embedding (car data))))))
                       (when vec (setf embedding vec)))
                   (error (c) (harness-log "EMBEDDING: llama.cpp failed: ~a" c))))))
            (if embedding
                (list :status :success :vector embedding)
                (list :status :error :message "Embedding generation failed")))))

(def-cognitive-tool :tool-permissions
  "View or set tool permission tiers."
  ((:tool :type :string :description "Tool name")
   (:action :type :keyword :description "Action: :get, :set, :list" :default :get)
   (:tier :type :keyword :description "For :set: :allow/:deny/:ask"))
  :body (lambda (args)
          (let ((tool (getf args :tool))
                (action (getf args :action :get))
                (tier (getf args :tier)))
            (case action
              (:get (list :status :success :tool tool :permission (get-tool-permission tool)))
              (:set (progn (set-tool-permission tool tier)
                        (list :status :success :message (format nil "Set ~a = ~a" tool tier))))
              (:list (let ((r nil))
                       (maphash (lambda (k v) (push (list :tool k :permission v) r)) *tool-permissions*)
                       (list :status :success :tools r)))
              (t (list :status :error :message "Invalid action"))))))

;; Defaults
(set-tool-permission :shell :deny)
(set-tool-permission :delete-file :deny)
(set-tool-permission :eval :ask)
(set-tool-permission :write-file :ask)
(harness-log "TOOL PERMISSIONS: Initialized")

(defskill :skill-tool-permissions
  :priority 600
  ;; Trigger whenever there's a tool call
  :trigger (lambda (c) 
             (let* ((action (getf c :candidate))
                    (target (getf action :target)))
               (or (eq target :TOOL) (eq target :tool))))
  :deterministic (lambda (a c)
    (let ((tool (getf (getf a :payload) :tool)))
      (if tool
          (let ((perm (check-tool-permission-gate tool c)))
            (cond
              ((eq perm :deny)
               (list :type :LOG :payload (list :text (format nil "Tool '~a' execution denied by permission tiers." tool))))
              ((and (listp perm) (eq (car perm) :ask))
               (list :type :EVENT :status :suspended :reason :ask-permission :payload (list :tool tool :action a)))
              (t a)))
          a))))

Test Suite

These tests verify tool permissions. Run with: (fiveam:run! 'tool-permissions-suite)

(defpackage :opencortex-tool-permissions-tests
  (:use :cl :fiveam :opencortex)
  (:export #:tool-permissions-suite))

(in-package :opencortex-tool-permissions-tests)

(def-suite tool-permissions-suite
  :description "Tests for Tool Permissions skill")

(in-suite tool-permissions-suite)

(test default-permission-is-allow
  "Verify default permission is :allow."
  (is (eq (get-tool-permission "unknown-tool") :allow)))

(test set-and-get-permission
  "Verify setting and getting permissions."
  (set-tool-permission "test-tool-abc" :deny)
  (is (eq (get-tool-permission "test-tool-abc") :deny)))

(test permission-gate-allow
  "Verify :allow tier passes through."
  (set-tool-permission "gate-allow-tool" :allow)
  (is (eq (check-tool-permission-gate "gate-allow-tool" nil) :allow)))

(test permission-gate-deny
  "Verify :deny tier blocks."
  (set-tool-permission "gate-deny-tool" :deny)
  (is (eq (check-tool-permission-gate "gate-deny-tool" nil) :deny)))

(test permission-gate-ask
  "Verify :ask tier returns ask list."
  (set-tool-permission "gate-ask-tool" :ask)
  (is (listp (check-tool-permission-gate "gate-ask-tool" nil))))