#+TITLE: SKILL: Diagnostics (org-skill-diagnostics.org) #+AUTHOR: Agent #+FILETAGS: :system:diagnostics:doctor: #+PROPERTY: header-args:lisp :tangle ../lisp/system-diagnostics.lisp * Why a Doctor? The Diagnostics skill is the self-knowledge of Passepartout. It answers "Is everything working?" by checking dependencies, environment variables, and LLM connectivity. Unlike the harness-level Doctor (which runs at boot and on CLI demand), this skill provides the Lisp-level diagnostic functions — defining what "healthy" means: which binaries must be present, which directories must exist, which API keys should be configured. * Phase A: Demand (Thinking) ** Why a Doctor? The Doctor transforms opaque startup failures into actionable engineering reports. It ensures the Brain never attempts to boot in a compromised state. ** Detection Invariant Binary detection must use shell probing (`which`) to account for varying `$PATH` inheritance between interactive and headless sessions. * Phase B: Protocol (Success Criteria) - Dependency check passes when all required binaries are found - Environment check passes when XDG directories exist and are accessible - LLM check passes when at least one provider is configured or Ollama is running locally * Phase C: Implementation (Build) ** Package Context #+begin_src lisp (in-package :passepartout) #+end_src ** Global Configuration ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *diagnostics-binaries* '("sbcl" "emacs" "git" "socat" "nc") "List of external binaries required for full system operation.") #+end_src ** *diagnostics-package-map* ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *diagnostics-package-map* '(("sbcl" . "sbcl") ("emacs" . "emacs") ("git" . "git") ("socat" . "socat") ("nc" . "netcat-openbsd") ("curl" . "curl") ("rlwrap" . "rlwrap")) "Map binary names to apt package names.") #+end_src ** *doctor-missing-deps* ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *doctor-missing-deps* nil "List of missing dependencies populated by diagnostics-dependencies-check.") #+end_src ** *doctor-auto-install* ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *doctor-auto-install* t "When T, doctor will attempt to install missing dependencies automatically.") #+end_src #+end_src ** Dependency Verification ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun diagnostics-dependencies-check () "Verifies that required external binaries are available in the PATH via shell probe." (setf *doctor-missing-deps* nil) (let ((all-ok t)) (format t "DOCTOR: Checking system dependencies...~%") (dolist (dep *diagnostics-binaries*) (let ((path (ignore-errors (uiop:run-program (list "which" dep) :output :string :ignore-error-status t)))) (if (and path (> (length path) 0)) (format t " [OK] Found ~a~%" dep) (progn (format t " [FAIL] Missing binary: ~a~%" dep) (push dep *doctor-missing-deps*) (setf all-ok nil))))) (when (and all-ok (null *doctor-missing-deps*)) (format t "DOCTOR: All dependencies satisfied.~%")) all-ok)) #+end_src ** Auto-Install Dependencies ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun diagnostics-dependencies-install () "Attempts to install missing system dependencies via apt." (when (null *doctor-missing-deps*) (format t "DOCTOR: No missing dependencies to install.~%") (return-from diagnostics-dependencies-install t)) (format t "DOCTOR: Attempting to install ~a missing dependencies...~%" (length *doctor-missing-deps*)) (let ((packages (remove-duplicates (mapcar (lambda (dep) (or (cdr (assoc dep *diagnostics-package-map* :test #'string=)) dep)) *doctor-missing-deps*) :test #'string=))) (format t "DOCTOR: Packages to install: ~a~%" packages) (let ((cmd (format nil "apt-get install -y ~{~a~^ ~}" packages))) (format t "DOCTOR: Running: ~a~%" cmd) (handler-case (let ((output (uiop:run-program cmd :output :string :error-output :string :external-format :utf-8))) (if (zerop (uiop:run-program (format nil "which ~a" (car *doctor-missing-deps*)) :ignore-error-status t)) (progn (format t "DOCTOR: Dependencies installed successfully.~%") (setf *doctor-missing-deps* nil) t) (progn (format t "DOCTOR: Installation failed. Output: ~a~%" output) nil))) (error (c) (format t "DOCTOR: Installation error: ~a~%" c) nil))))) #+end_src ** XDG Environment Validation ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun diagnostics-env-check () "Validates XDG directories and environment configuration." (format t "DOCTOR: Checking XDG environment...~%") (let ((all-ok t) (config-dir (uiop:getenv "PASSEPARTOUT_CONFIG_DIR")) (data-dir (uiop:getenv "PASSEPARTOUT_DATA_DIR")) (state-dir (uiop:getenv "PASSEPARTOUT_STATE_DIR")) (memex-dir (uiop:getenv "MEMEX_DIR"))) (flet ((check-dir (name path critical) (if (and path (> (length path) 0)) (if (uiop:directory-exists-p path) (format t " [OK] ~a: ~a~%" name path) (progn (format t " [FAIL] ~a directory missing: ~a~%" name path) (when critical (setf all-ok nil)))) (progn (format t " [FAIL] ~a variable not set.~%" name) (when critical (setf all-ok nil)))))) (check-dir "Config (PASSEPARTOUT_CONFIG_DIR)" config-dir t) (check-dir "Data (PASSEPARTOUT_DATA_DIR)" data-dir t) (check-dir "State (PASSEPARTOUT_STATE_DIR)" state-dir t) (check-dir "Memex (MEMEX_DIR)" memex-dir t)) all-ok)) #+end_src ** LLM Connectivity The doctor checks all supported LLM providers and detects local Ollama instances. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun diagnostics-llm-check () "Tests connectivity to LLM providers. Returns T if at least one provider is configured." (format t "DOCTOR: Checking LLM connectivity...~%") (let ((providers '((:openrouter . "OPENROUTER_API_KEY") (:anthropic . "ANTHROPIC_API_KEY") (:openai . "OPENAI_API_KEY") (:groq . "GROQ_API_KEY") (:gemini . "GEMINI_API_KEY") (:deepseek . "DEEPSEEK_API_KEY") (:nvidia . "NVIDIA_API_KEY") (:ollama . "OLLAMA_URL"))) (configured nil)) (dolist (p providers) (let ((env-val (uiop:getenv (cdr p)))) (cond ((and env-val (> (length env-val) 0)) (format t " [OK] ~a configured~%" (car p)) (setf configured t)) ((eq (car p) :ollama) (let ((ollama-check (ignore-errors (uiop:run-program '("curl" "-s" "http://localhost:11434/api/tags") :output :string :ignore-error-status t)))) (when (and ollama-check (search "\"models\"" ollama-check)) (format t " [OK] Ollama local model server detected~%") (setf configured t))))))) (if configured (progn (format t " [OK] LLM provider(s) available~%") t) (progn (format t " [WARN] No LLM provider configured.~%") (format t " Run 'passepartout configure' to configure a provider.~%") t)))) #+end_src ** Orchestration ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun diagnostics-run-all (&key (auto-install t)) "Executes the full diagnostic suite and returns T if system is healthy." (format t "==================================================~%") (format t " PASSEPARTOUT DOCTOR: Commencing Health Check~%") (format t "==================================================~%") (let ((dep-ok (diagnostics-dependencies-check))) (when (and (not dep-ok) auto-install *doctor-auto-install*) (format t "DOCTOR: Attempting automatic installation...~%") (setf dep-ok (diagnostics-dependencies-install)) (when dep-ok (setf dep-ok (diagnostics-dependencies-check)))) (let ((env-ok (diagnostics-env-check)) (llm-ok (diagnostics-llm-check))) (format t "==================================================~%") (if (and dep-ok env-ok) (progn (format t " ✓ SYSTEM HEALTHY: Ready for ignition.~%") t) ;; Explicitly return T (progn (format t "==================================================~%") (format t " ISSUES FOUND:~%") (when (not dep-ok) (format t " - Missing system dependencies~%")) (when (not llm-ok) (format t " - No LLM provider configured~%")) (format t "~%") (format t " RECOMMENDED ACTIONS:~%") (format t " 1. Run 'passepartout configure' to configure everything~%") (format t " 2. Or run 'passepartout doctor --fix' for auto-repair~%") (format t "==================================================~%") nil))))) ;; Return nil when issues found #+end_src ** CLI Entry Point ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun diagnostics-main () "Entry point for the 'doctor' CLI command." (if (diagnostics-run-all) (uiop:quit 0) (uiop:quit 1))) #+end_src * Phase D: Verification (Testing) #+begin_src lisp (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) (defpackage :passepartout-diagnostics-tests (:use :cl :fiveam :passepartout) (:export #:diagnostics-suite)) (in-package :passepartout-diagnostics-tests) (def-suite diagnostics-suite :description "Verification of the System Diagnostics logic") (in-suite diagnostics-suite) (test test-diagnostics-dependency-fail "Verify that missing binaries are correctly identified as failures." (let ((passepartout::*diagnostics-binaries* '("non-existent-binary-123"))) (is (null (diagnostics-dependencies-check))))) (test test-diagnostics-env-fail "Verify that an invalid MEMEX_DIR triggers a critical failure." (let ((old-m (uiop:getenv "MEMEX_DIR")) (old-d (uiop:getenv "PASSEPARTOUT_DATA_DIR"))) (unwind-protect (progn (setf (uiop:getenv "MEMEX_DIR") "/non/existent/path/999") (is (null (diagnostics-env-check)))) (setf (uiop:getenv "MEMEX_DIR") (or old-m "")) (setf (uiop:getenv "PASSEPARTOUT_DATA_DIR") (or old-d ""))))) #+end_src * Phase E: Lifecycle The doctor skill should be loaded early (priority 100) to validate system health before other skills initialize. ** Skill Registration #+begin_src lisp (defskill :passepartout-system-diagnostics :priority 100 :trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :heartbeat)) :deterministic (lambda (action ctx) (declare (ignore action ctx)) nil)) #+end_src