Files
passepartout/harness/setup.org
Amr Gharbeia 41e25d091e
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
refactor(standard): granularity, XDG compliance, and literate thinking medium
2026-04-27 19:53:45 -04:00

9.3 KiB

Zero-to-One Setup (setup.org)

Zero-to-One Setup (setup.org)

The setup.org file defines the automated installation and initialization sequence for the OpenCortex.

Phase A: Demand (Thinking)

The Agnostic LLM Provider Registry

To fulfill the mandate of sovereignty and extensibility, the setup process must move away from a single hardcoded LLM provider (like OpenRouter).

Design Goals:

  1. Modular Adapters: Each provider (Ollama, Groq, OpenAI, etc.) is a data-driven structure defining its required fields (API_KEY, BASE_URL) and its "ping" validation logic.
  2. Interactive Selection: The user should be presented with a multi-select list of providers.
  3. Local-First Default: If no cloud keys are provided, the system must default to a local Ollama/llama.cpp configuration.
  4. State Persistence: Configuration is saved to `providers.lisp` in the XDG Config directory.
  5. Secret Splitting: Sensitive keys go to `.env`, while metadata (models, URLs) lives in `state/providers.lisp`.

Phase B: Protocol (Success Criteria)

Test Suite Context

(defpackage :opencortex-setup-tests
  (:use :cl :fiveam :opencortex)
  (:export #:setup-suite))
(in-package :opencortex-setup-tests)
(def-suite setup-suite :description "Verification of the Lisp Setup Wizard")
(in-suite setup-suite)

Persistence Tests

(test test-provider-registry-persistence
  "Verify that multiple providers can be registered and saved."
  (let ((opencortex::*providers* nil))
    (opencortex:register-provider :ollama '(:url "http://localhost:11434" :model "llama3"))
    (opencortex:register-provider :groq '(:key "gsk_123" :model "mixtral-8x7b"))
    (is (equal "gsk_123" (getf (getf opencortex::*providers* :groq) :key)))))

Fallback Tests

(test test-sovereign-fallback-logic
  "Verify that the system identifies as healthy with only local providers."
  (let ((opencortex::*providers* (list :ollama '(:url "http://localhost:11434"))))
    (is (opencortex:system-ready-p))))

Phase C: Implementation (Build)

Package Context

(in-package :opencortex)

Global Provider Registry

(defvar *providers* nil "Global registry of configured LLM providers.")

Provider Templates

(defvar *provider-templates*
  '((:ollama . (:name "Ollama (Local)" :fields ((:url :label "URL") (:model :label "Model")) :default-url "http://localhost:11434" :default-model "llama3"))
    (:openrouter . (:name "OpenRouter" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "anthropic/claude-3-opus-20240229"))
    (:openai . (:name "OpenAI" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "gpt-4-turbo"))
    (:groq . (:name "Groq" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "mixtral-8x7b-32768"))
    (:gemini . (:name "Google Gemini" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "gemini-1.5-pro"))
    (:anthropic . (:name "Anthropic" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "claude-3-5-sonnet-20240620")))
  "Templates for supported LLM providers. Fields marked :secret go to .env.")

XDG Configuration Utilities

(defun get-oc-config-dir ()
  "Resolves the OpenCortex configuration directory following XDG standards."
  (let ((env (uiop:getenv "OC_CONFIG_DIR")))
    (if (and env (> (length env) 0))
        (uiop:ensure-directory-pathname env)
        (merge-pathnames ".config/opencortex/" (user-homedir-pathname)))))

Secret Persistence

(defun save-secret (id key value)
  "Appends a secret to the XDG config .env file and updates the current environment."
  (let* ((env-key (format nil "~:@(~a_~a~)" id key))
         (path (merge-pathnames ".env" (get-oc-config-dir))))
    (ensure-directories-exist path)
    (with-open-file (s path :direction :output :if-exists :append :if-does-not-exist :create)
      (format s "~%~a=\"~a\"" env-key value))
    (setf (uiop:getenv env-key) value)))

Provider Metadata Persistence

(defun save-providers ()
  "Persist provider configuration to XDG config directory."
  (let ((path (merge-pathnames "providers.lisp" (get-oc-config-dir))))
    (ensure-directories-exist path)
    (with-open-file (s path :direction :output :if-exists :supersede)
      (format s ";;; OpenCortex Provider Metadata~%~s~%" *providers*))))
(defun load-providers ()
  "Load provider configuration from XDG config directory."
  (let ((path (merge-pathnames "providers.lisp" (get-oc-config-dir))))
    (when (uiop:file-exists-p path)
      (with-open-file (s path)
        (setf *providers* (read s))))))

Registry API

(defun register-provider (id config)
  "Update the global provider registry."
  (setf (getf *providers* id) config))
(defun system-ready-p ()
  "Predicate verifying if at least one provider is configured."
  (and *providers* (> (length *providers*) 0)))

User Interface Primitives

(defun prompt-for (label &optional default)
  "Interactively prompt the user for input with an optional default."
  (format t "~a~@[ [~a]~]: " label default)
  (finish-output)
  (let ((input (read-line)))
    (if (and (string= input "") default)
        default
        input)))

Provider Configuration Loop

(defun configure-provider (id)
  "Guided configuration for a specific LLM provider template."
  (let* ((template (cdr (assoc id *provider-templates*)))
         (fields (getf template :fields))
         (config nil))
    (format t "~%--- Configuring ~a ---~%" (getf template :name))
    (dolist (field-spec fields)
      (let* ((field (first field-spec))
             (label (getf (rest field-spec) :label))
             (is-secret (getf (rest field-spec) :secret))
             (default-key (intern (format nil "DEFAULT-~a" field) :keyword))
             (default (getf template default-key))
             (val (prompt-for label default)))
        (if is-secret
            (save-secret id field val)
            (setf (getf config field) val))))
    (register-provider id config)
    (format t "✓ ~a metadata registered.~%" (getf template :name))))

Main Setup Orchestrator

(defun run-setup-wizard ()
  "Entry point for the interactive OpenCortex Lisp Setup Wizard."
  (format t "=== OpenCortex: Advanced Setup Wizard ===~%")
  
  ;; 1. Identity
  (let ((user (prompt-for "Your Name" "User"))
        (agent (prompt-for "Agent Name" "OpenCortex")))
    (format t "Welcome, ~a. I am ~a.~%" user agent))

  ;; 2. Providers
  (format t "~%Available Providers:~%")
  (loop for (id . data) in *provider-templates*
        do (format t "  ~a: ~a~%" id (getf data :name)))
  
  (format t "~%Enter provider IDs to configure (comma separated, or 'all'): ")
  (finish-output)
  (let* ((input (read-line))
         (ids (if (string= input "all")
                  (mapcar #'car *provider-templates*)
                  (mapcar (lambda (s) (intern (string-upcase (string-trim " " s)) :keyword))
                          (uiop:split-string input :separator ",")))))
    (dolist (id ids)
      (when (assoc id *provider-templates*)
        (configure-provider id))))

  (save-providers)
  (format t "~%Setup complete. Running doctor check...~%")
  (doctor-run-all))