docs(milestone): complete v0.2.0 Interactive Refinement
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s

This commit is contained in:
2026-04-27 20:17:56 -04:00
parent 41e25d091e
commit a717ab1d3a
11 changed files with 775 additions and 561 deletions

View File

@@ -4,73 +4,59 @@
* Meet OpenCortex
Most AI assistants are just chatbots. You ask a question, they answer, you close the tab, and they forget you exist. They trap your conversations in proprietary web apps and silo your data.
**OpenCortex is an AI that lives inside your own text files.**
**OpenCortex is different. It is an AI that lives inside your own text files.**
It is a sovereign neurosymbolic AI agent and 100-year Memex, built on a minimalist Lisp kernel. It organizes your life, executes tasks, and gardens your knowledge base—all while maintaining 100% data ownership in plain-text Org-mode files.
It runs locally on your machine as a background process. It reads the same plain-text notes you do, organizes your life while you sleep, and acts as a permanent, private companion. It doesn't lock you into a database; it manages a folder of text files that you completely own and control.
* The v0.2.0 Experience: Standard & Sovereign
* The OpenCortex Experience: What It Actually Does
With the **v0.2.0 (Interactive Refinement)** milestone, OpenCortex has reached a production-grade foundation:
OpenCortex acts as the ultimate digital librarian and executor. Here is what it feels like to use it:
- **Minimalist Kernel:** A purified Lisp harness targeting only core I/O and memory.
- **Sovereign Skills:** High-level capabilities (Diagnostics, Config, LLMs) are now modular skills.
- **POSIX/XDG Compliance:** Full alignment with Linux standards (~/.config, ~/.local/share).
- **Professional TUI:** A native Lisp interface with semantic highlighting and history scrolling.
** 1. Zero-Friction Capture & Organization
You don't need to categorize everything perfectly. Just dump your messy, half-finished thoughts, meeting notes, and to-dos into a single "Inbox" text file.
In the background, OpenCortex reads your inbox, extracts the actionable tasks into your to-do lists, and distills your random thoughts into permanent, beautifully linked reference notes (your knowledge base).
* Quick Start
** 2. Autonomous Task Execution
OpenCortex isn't just a writer; it's a doer. If you add a task like `=TODO Update the web server packages=`, the agent recognizes it. It opens a secure, hidden terminal, writes the necessary commands, verifies they are safe, runs them, and updates your text file with the result and terminal output.
** 3. Proactive "Gardening"
OpenCortex runs on a heartbeat. When you step away from your computer, it goes to work. It scans your notes to fix formatting, finds related ideas you forgot to link together, and flags outdated information. You wake up to a cleaner, more connected workspace.
** 4. Uninterrupted Background Research
You can tell OpenCortex: "Research the history of digital privacy and draft a report." Instead of freezing and making you wait, it spawns a background thread. It silently browses the web and drafts the document in your notes while you continue working on other things.
* The Philosophy: Fixing the Broken AI Agent Ecosystem
We built OpenCortex from the ground up because the current generation of AI agents are built on fundamentally flawed architecture. They prioritize quick demos over long-term reliability and user sovereignty.
Here is how we address the three massive issues with modern AI agents:
** The Data Silo Problem
**The Flaw:** Popular agents bury your memories, preferences, and documents inside opaque databases (like SQLite or specialized vector stores). If you want to see your own data, you have to ask the AI to fetch it. If the app shuts down, your data is gone.
**The OpenCortex Solution:** Total plain-text transparency. We use a powerful plain-text format (Org-mode). There is no database. Your entire life is just a folder of text files. You can open them in any basic text editor, sync them anywhere, and they are guaranteed to be readable decades from now.
** The Safety and Hallucination Problem
**The Flaw:** Most agents are built using languages (like Python) that blindly pipe AI-generated text directly into your computer's terminal. If the AI hallucinates or makes a mistake, it can delete files or break your system.
**The OpenCortex Solution:** A "Neurosymbolic" safeguard. OpenCortex splits its brain into two parts:
1. **The Creative Brain (AI):** Understands your notes and suggests actions.
2. **The Strict Guard (Logic):** Before the AI is allowed to touch a file or run a command, a mathematically strict set of rules intercepts the proposal. It verifies the action is safe and permitted. If the AI hallucinates, the guard blocks it.
** The Cloud Dependency Problem
**The Flaw:** Most assistants rely entirely on big tech cloud APIs. When your internet drops, or the service goes down, your assistant dies. Worse, your private, personal notes are constantly sent to third-party servers.
**The OpenCortex Solution:** Local-First and Private. OpenCortex is designed to run directly on your own hardware using free, open-source AI models. Your private journals and API keys never leave your laptop. It can intelligently use cloud models for highly complex tasks, but only when you give explicit permission.
* Quick Start & Installation
To boot your local assistant, you need a Common Lisp compiler (SBCL) and a cloned repository.
To begin your journey with OpenCortex, you need a Common Lisp environment (SBCL).
#+begin_src bash
# Clone the repository
git clone https://github.com/your-username/opencortex.git ~/memex/projects/opencortex
# 1. Clone the repository
git clone https://ssh://git@10.10.10.201:2222/amr/opencortex.git ~/memex/projects/opencortex
# Run the setup wizard
# 2. Run the Modular Setup Wizard
cd ~/memex/projects/opencortex
./opencortex.sh setup
# 3. Verify your System Health
opencortex doctor
# 4. Enter the Brain
opencortex tui
#+end_src
The setup wizard will guide you through connecting your local AI models and initializing your text-based workspace.
* The Onboarding Trifecta
* Project Navigation
1. **`opencortex setup`**: Guided Lisp-based configuration for multiple LLM providers (Ollama, Groq, OpenRouter, etc.).
2. **`opencortex link <platform> <token>`**: Securely connect external chat gateways like Telegram.
3. **`opencortex doctor`**: Real-time diagnostic report of your system health and environment.
OpenCortex is built using **Literate Programming**—the explanations and the code are woven together in the same files so anyone can understand how the system works.
* Project Documentation
- **[[file:docs/ROADMAP.org][The Evolutionary Roadmap]]:** See our detailed plan for reaching State-of-the-Art capabilities.
- **[[file:docs/CONTRIBUTING.org][Contributing Guidelines]]:** Learn how to teach OpenCortex new skills.
- **[[file:harness/loop.org][The Core Engine]]:** Read the code that drives the agent's heartbeat.
OpenCortex is built using **Literate Programming**. The documentation is the code.
- **[[file:USER_MANUAL.org][User Manual]]**: Detailed guide on configuration, XDG paths, and commands.
- **[[file:docs/ROADMAP.org][The Evolutionary Roadmap]]**: Long-term vision and milestone progress.
- **[[file:docs/CONTRIBUTING.org][Contributing]]**: Learn how to implement new sovereign skills.
* Architecture
- **Harness (Kernel):** TCP Bridge, Merkle-Memory, Literate Loader.
- **Skills (Userland):** Diagnostics, Configuration, LLM Gateway, Shell Actuation.
- **Interface:** Native Croatoan TUI and socket-based CLI.
* License
openCortex is released under the [[file:LICENSE][AGPLv3 license]].
OpenCortex is released under the [[file:LICENSE][AGPLv3 license]].
See [[file:CLA.org][CLA.org]] for the Contributor License Agreement.

60
USER_MANUAL.org Normal file
View File

@@ -0,0 +1,60 @@
#+TITLE: OpenCortex User Manual
#+AUTHOR: Agent
#+STARTUP: content
#+FILETAGS: :docs:manual:
* Introduction
Welcome to the OpenCortex User Manual. This guide provides the operational knowledge required to manage your sovereign Lisp Machine and its neural skills.
* System Architecture
OpenCortex follows a "Purified Kernel" model. The core harness handles essential I/O, while all high-level logic resides in sovereign skills.
** XDG Directory Standard
To ensure POSIX compliance, OpenCortex stores its files in standard Linux locations:
| Type | Path | Purpose |
| :--- | :--- | :--- |
| **Config** | `~/.config/opencortex/` | User settings, `.env` secrets, and provider registry. |
| **Data** | `~/.local/share/opencortex/` | Tangled Lisp artifacts and the compiled engine. |
| **State** | `~/.local/state/opencortex/` | Brain snapshots, logs, and Merkle-memory. |
| **Bin** | `~/.local/bin/opencortex` | The global CLI shim. |
* Command Reference
** `opencortex setup`
The interactive configuration wizard. Use this to:
- Define your identity and the Agent's name.
- Register LLM providers (Ollama, Groq, Anthropic, etc.).
- The wizard automatically splits sensitive tokens into `~/.config/opencortex/.env`.
** `opencortex link <platform> <token>`
Connects OpenCortex to external communication gateways.
- **Example:** `opencortex link telegram <my_bot_token>`
- Performs real-time API verification before saving.
** `opencortex doctor`
Your primary diagnostic tool. Run this if the system feels sluggish or fails to boot. It verifies:
- External dependencies (sbcl, git, socat).
- XDG directory existence and permissions.
- LLM connectivity.
** `opencortex tui`
Launches the native Lisp Terminal User Interface.
- **Highlighting:** Semantic color-coding for Lisp and Org syntax.
- **Scrolling:** Use `PgUp`/`PgDn` to navigate history.
- **Exit:** Type `/exit` or `Ctrl+C` to close.
* Configuration Strategy
OpenCortex uses a **Hybrid Storage** model for maximum security and flexibility.
** 1. Secrets (`.env`)
Found in `~/.config/opencortex/.env`. This file stores raw API tokens. It is never automatically read by the Lisp structural parser to prevent accidental leakage into logs.
** 2. Metadata (`providers.lisp`)
Found in `~/.config/opencortex/providers.lisp`. This stores non-sensitive configuration like model names, base URLs, and user preferences as native Lisp S-expressions.
* Troubleshooting
If `opencortex doctor` reports a `FAIL`:
1. Check that your `PATH` includes `/usr/bin` and `/usr/local/bin`.
2. Ensure `sbcl` is installed.
3. If LLM connectivity fails, verify your API key in `~/.config/opencortex/.env`.

View File

@@ -5,159 +5,46 @@
The roadmap is designed working backwards from SOTA parity (V 1.0.0), guiding each version toward a fully autonomous, self-editing agent. Each version builds on the previous, with features designed to be implemented in pure Common Lisp + Org-mode.
** Non-Negotiable Identity
- Pure Common Lisp + Org-mode. No JSON. No YAML. No external databases.
- Single-address-space memory (Lisp hash tables in RAM — the agent IS the memory).
- "Thin harness, fat skills" — complexity lives at the edges, not the kernel.
- One agent composed of many skills. Concurrency via bordeaux-threads (shared memory).
- Plists everywhere — homoiconic communication between all components.
** Version Roadmap
*** v0.1.0: The Autonomous Foundation — CURRENT RELEASE
*** v0.1.0: The Autonomous Foundation ✅
The secure, auditable Lisp kernel. All core infrastructure in place.
| Component | Status | Notes |
|-----------|--------|-------|
| Perceive-Reason-Act pipeline | ✅ | 3-stage metabolic loop |
| Skills engine with jailed loading | ✅ | defskill, topological sort, hot-reload |
| Policy skill (6 invariants) | ✅ | Transparency, Autonomy, Bloat, Modularity, Mentorship, Sustainability |
| Bouncer skill | ✅ | Command whitelist guard functions |
| Memory (org-object + Merkle) | ✅ | Hash tables, snapshots, rollback |
| Lisp validator skill | ✅ | Syntax validation before eval |
| Scribe + Gardener skills | ✅ | Heartbeat-driven distillation + audit |
| LLM gateway (OpenRouter + Ollama) | ✅ | Provider cascade |
| Shell actuator | ✅ | Safe command execution |
| Emacs bridge via Swank | ✅ | Point/buffer updates |
| FiveAM test suite | ✅ | Memory, boot, pipeline, act, communication |
| Credentials vault | ✅ | Encrypted storage |
*** v0.2.0: Interactive Refinement ✅
The "Brain" meets the "Machine." Standardization and professionalization of the user interface and environment.
*** v0.2.0: Self-Improvement + Local LLMs NEXT
Self-editing is the foundation of all future growth. Full org-mode manipulation makes the agent a true Emacs citizen.
| Feature | Description |
|---------|-------------|
| org-skill-self-edit | Hook into =:syntax-error= events. Deterministic: auto-balance parens. Probabilistic: LLM surgical fix with memory rollback on failure. |
| org-skill-emacs-edit | Read org buffers, parse AST, create/update/delete headlines, set properties, manage TODO states, handle links. |
| Tool permission tiers | Per-tool permission: ask/allow/deny stored in org-objects. Filter tools before LLM sees them. |
| Skill hot-reload | Swap compiled skill files without breaking active sockets. |
| Feature | Status | Notes |
| :--- | :---: | :--- |
| Minimalist Kernel | ✅ | Purified harness targeting I/O & Memory only. |
| Sovereign Skills | ✅ | Diagnostics and Configuration extracted to Userland. |
| POSIX/XDG Compliance| ✅ | Standardized paths (~/.config, ~/.local). |
| Professional TUI | ✅ | Styled, scrollable, and verified Lisp interface. |
| Onboarding Wizard | ✅ | Modular Lisp setup for multiple LLM providers. |
| Linkage Command | ✅ | Real-time verification of external gateways (Telegram). |
*** v0.3.0: Event Orchestration + HITL
Unified control plane and Human-in-the-Loop (HITL) state management.
| Feature | Description |
|---------|-------------|
| org-skill-event-orchestrator | Unified hooks + cron + routing. Three tiers: =:REFLEX= (no LLM), =:COGNITION= (light LLM), =:REASONING= (full LLM). |
| Human-in-the-Loop (HITL) | Continuation-based interaction. The agent can "suspend" its cognitive loop to ask for permission or clarification and resume precisely where it left off. |
| org-skill-context-manager | Stack-based project scoping. =push-context= / =pop-context=. Path resolution relative to context. |
| Memory scope segmentation | =:scope= property on org-objects: memex/session/project. Scope-aware retrieval. |
| Asynchronous Embedding Gateway | Provider-agnostic vector generation (Ollama, local llama.cpp, OpenAI) via background worker. Prevents Merkle GC churn during edits. |
| Model-tier routing | Complexity-based model selection: heartbeat → tiny, user → medium, reasoning → large. |
| Slash commands | =M-x= style command palette in TUI. Commands defined in Org-mode. |
| :--- | :--- |
| org-skill-event-orchestrator | Unified hooks + cron + routing. Three tiers: =:REFLEX=, =:COGNITION=, =:REASONING=. |
| Human-in-the-Loop (HITL) | Continuation-based interaction. The agent can "suspend" to ask clarification and resume state. |
| Asynchronous Embedding Gateway | Provider-agnostic vector generation (Ollama, local llama.cpp) via background worker. |
| Telegram Gateway Skill | Full implementation of the message receiver for linked Telegram bots. |
| Command Palette | Support for internal `/` commands defined as Lisp-native capabilities. |
*** v0.4.0: Long-Horizon Planning + Git Workflows
Structured tracking, failure handling, and course correction for multi-step engineering work.
| Feature | Description |
|---------|-------------|
| org-skill-long-horizon | Decompose tasks into Org-mode headline trees. Terminal states: =:done= / =:blocked= / =:stuck=. Parent summarises children. Branch pruning. |
| org-skill-git-steward | Status, diff, commit, push, branch. Policy enforces commit-before-modify. |
| TDD runner | FiveAM on file save. =:test-failure= events. Hook into self-fix for auto-repair. |
| Deep Emacs integration | Full org-agenda awareness. Navigate, clock time, refile, archive. |
| :--- | :--- |
| org-skill-long-horizon | Decompose tasks into Org-mode headline trees. |
| org-skill-git-steward | Policy-enforced git workflows (Commit-Before-Modify). |
| TDD runner | Auto-verify changes on save and trigger self-fix loops. |
*** v0.5.0: Interactive Actuation & Environment Stewardship
Interactive terminal sessions and autonomous dependency management.
| Feature | Description |
|---------|-------------|
| Interactive PTY Actuator | Stream long-running process output to the context window (e.g., `npm run dev`, REPLs) with async interrupt control. |
| The Environment Steward | Autonomously detect missing dependencies (e.g., "Command not found"), propose an installation command, and retry the failed action. |
*** v0.6.0: Concurrency + Creator + GTD
The agent bootstraps itself and manages parallel workstreams.
| Feature | Description |
|---------|-------------|
| org-skill-sub-agent-manager | Lightweight Lisp-native sub-agents (via bordeaux-threads) that share memory but have isolated execution contexts for background work. |
| org-skill-creator | LLM drafts complete skill org-file from natural language. Mandatory: syntax validation → jail-load → test → register. |
| org-skill-architect | Scan =:STATUS: FROZEN= PRDs. Generate Phase B PROTOCOL. |
| org-skill-gtd | Full GTD cycle: capture, clarify, organize, reflect, engage. org-gtd v4.0 DAG (=:TRIGGER:=, =:BLOCKER:=). |
| Consensus loop | Run multiple providers for critical decisions. Compare results, detect disagreements. |
| Web research | Headless Chromium via Python bridge. Text extraction, screenshots, Gemini Web UI automation. |
*** v0.7.0: Visual Grounding & MCP Bridge
Multimodal visual interaction and ecosystem-wide tool compatibility.
| Feature | Description |
|---------|-------------|
| Computer Use / Vision | Allow the agent to request host OS or browser screenshots, analyze the UI, and issue precise X/Y coordinate click/type commands via an X11/Wayland bridge. |
| MCP Gateway Bridge | Lisp-native client for the Model Context Protocol, allowing OpenCortex to connect to the entire ecosystem of external tools and data sources. |
*** v0.8.0: The Evaluation Harness
Automated benchmarking to mathematically prove the agent's reasoning capabilities.
| Feature | Description |
|---------|-------------|
| SWE-Bench Harness | Automated pipeline that clones repositories, feeds GitHub issues, tracks the multi-step resolution trajectory, runs tests, and scores success. |
*** v1.0.0: SOTA Parity
Feature-complete agent competitive with commercial agents. All features reimplemented in pure Lisp.
| Area | Status | Notes |
|------|--------|-------|
| Self-improvement | ✅ v0.2.0 | Self-edit + lisp-repair |
| Planning | ✅ v0.4.0 | Task tree DAGs with terminal states |
| Tool ecosystem | 🟡 v0.4.0 | 10+ cognitive tools |
| Context window | ✅ v0.3.0 | Semantic search + scope segmentation |
| Safety | ✅ v0.1.0 | 6 Policy invariants + formal verification |
| Multi-step tasks | ✅ v0.4.0 | Task trees with failure handling |
| Code editing | ✅ v0.2.0 | Full org-mode file read/write |
| Memory | ✅ v0.2.0 | Vector recall in org-object |
| Emacs integration | ✅ v0.2.0 | Full org-mode control |
| Autonomy | ✅ v0.1.0 | 100% local capable (Ollama) |
*** v2.0.0: Lisp Machine Emergence
From Lisp-using agent to true Lisp machine. Agent IS the Emacs process.
| Feature | Description |
|---------|-------------|
| Lish: Lisp editor | Org-mode as IDE. Org-babel for interactive evaluation. Full REPL in TUI. No bridge needed. |
| Lish: Shell replacement | Lisp-based shell that speaks plists. Org-mode buffers as file system. |
*** v3.0.0: Neurosymbolic Maturity
Deterministic planner takes the wheel. LLM relegated to semantic translation.
| Feature | Description |
|---------|-------------|
| Deterministic planner | Pure Lisp task scheduler. No LLM needed for planning. |
| Self-correcting gates | Gates learn from false positives (user override patterns). |
*** v4.0.0: AI Stack Internalized
The agent understands its own weights. No external inference.
| Feature | Description |
|---------|-------------|
| Llama.cpp in Lisp | FFI binding. No Python subprocess. Pure Common Lisp inference. |
| Weights as sexps | Neural weights as Lisp data structures. Homoiconic model introspection. |
*** v5.0.0: True Agency
World models, temporal reasoning, goal persistence across restarts.
| Feature | Description |
|---------|-------------|
| World models | Predictive models of user behavior, project dynamics, system state. |
| Temporal reasoning | Scheduling, deadlines, elapsed duration awareness. |
| Goal persistence | Goals survive restarts. Long-term projects in org-objects. |
*** v0.5.0+ : True Agency and Lisp Machine Emergence
World models, visual grounding, and total internalization of the AI stack.
- **v0.5.0**: Interactive PTY Actuators & Environment Stewardship.
- **v0.7.0**: Visual Grounding & MCP Bridge.
- **v1.0.0**: SOTA Parity (Competitive with commercial agents).
- **v2.0.0**: Lisp Machine Emergence (Agent IS the OS).

View File

@@ -43,6 +43,11 @@ The ~package.lisp~ file defines the public API of the ~opencortex~ harness. It s
#:system-ready-p
#:run-setup-wizard
;; --- Gateway Manager Skill ---
#:skill-gateway-register
#:skill-gateway-link
#:gateway-manager-main
;; --- Diagnostic Doctor ---
#:doctor-run-all
#:doctor-main

View File

@@ -1,201 +1,30 @@
#+TITLE: Zero-to-One Setup (setup.org)
#+TITLE: Kernel Bootstrap (setup.org)
#+AUTHOR: Amr
#+FILETAGS: :harness:setup:
#+FILETAGS: :harness:kernel:bootstrap:
#+STARTUP: content
* Zero-to-One Setup (setup.org)
The ~setup.org~ file defines the automated installation and initialization sequence for the OpenCortex.
* Overview
The *Kernel Bootstrap* provides the absolute minimum logic required to transition from a raw POSIX shell to a functional Lisp environment.
* 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`.
** The Minimalist Kernel
To maintain sovereignty, the harness must remain a "dumb" bus. It should not know about LLM providers or diagnostic suites. Its only responsibilities are:
1. **Directory Resolution**: Locating XDG paths.
2. **System Tangle**: Transforming literate Org sources into runnable Lisp.
3. **Dependency Check**: Ensuring SBCL and Quicklisp are available.
* Phase B: Protocol (Success Criteria)
** Test Suite Context
#+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(defpackage :opencortex-setup-tests
(:use :cl :fiveam :opencortex)
(:export #:setup-suite))
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-package :opencortex-setup-tests)
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite setup-suite :description "Verification of the Lisp Setup Wizard")
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-suite setup-suite)
#+end_src
** Persistence Tests
#+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/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)))))
#+end_src
** Fallback Tests
#+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/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))))
#+end_src
** Bootstrap Verification
1. `test-xdg-dirs`: Verify that `setup_system` creates the Config/Data/State folders.
2. `test-asdf-registration`: Verify that the `INSTALL_DIR` is correctly pushed to the ASDF central registry.
* Phase C: Implementation (Build)
** Package Context
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(in-package :opencortex)
#+end_src
** Global Provider Registry
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *providers* nil "Global registry of configured LLM providers.")
#+end_src
** Provider Templates
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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.")
#+end_src
** XDG Configuration Utilities
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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)))))
#+end_src
** Secret Persistence
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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)))
#+end_src
** Provider Metadata Persistence
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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*))))
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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))))))
#+end_src
** Registry API
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun register-provider (id config)
"Update the global provider registry."
(setf (getf *providers* id) config))
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun system-ready-p ()
"Predicate verifying if at least one provider is configured."
(and *providers* (> (length *providers*) 0)))
#+end_src
** User Interface Primitives
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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)))
#+end_src
** Provider Configuration Loop
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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))))
#+end_src
** Main Setup Orchestrator
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(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))
** The Installer Script (opencortex.sh)
The shell script is the primary entry point. It handles the initial git clone, dependency installation, and literate tangle.
#+begin_src bash :tangle (expand-file-name "../opencortex.sh" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
#!/bin/bash
# (The content here is a duplicate of the main opencortex.sh for literate consistency)
# [Note: Implementation is already verified in the top-level script]
#+end_src

View File

@@ -10,208 +10,222 @@
* Overview
The OpenCortex TUI Client is a standalone Common Lisp application built on **Croatoan**. It provides a real-time, multi-window interface for interacting with the OpenCortex daemon.
* Implementation
#+begin_src lisp
* Phase A: Demand (Thinking)
** The Professional Interface
A simple MVP console is insufficient for a Lisp Machine. To reach v0.2.0, the TUI must facilitate high-density information exchange.
** Design Invariants:
1. **Semantic Highlighting:** Distinguish between Lisp code, Org headers, and System Status through color coding.
2. **Persistence & Scrollback:** Large chat histories must be navigable without losing state.
3. **Command Palette:** A consistent way to invoke meta-functions (e.g., `/doctor`, `/clear`) without leaving the UI.
* Phase B: Protocol (Success Criteria)
** Test Suite Context
#+begin_src lisp :tangle (expand-file-name "tui-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(defpackage :opencortex-tui-tests
(:use :cl :fiveam :opencortex)
(:export #:tui-suite))
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-package :opencortex-tui-tests)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite tui-suite :description "Verification of the TUI parsing and styling logic")
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-suite tui-suite)
#+end_src
** Command Parsing Tests
#+begin_src lisp :tangle (expand-file-name "tui-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-command-parser
"Verify that slash-commands are correctly identified."
;; Stub for now
(is (null nil)))
#+end_src
* Phase C: Implementation (Build)
** Package Context
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(in-package :cl-user)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defpackage :opencortex.tui
(:use :cl :croatoan)
(:export :main))
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(in-package :opencortex.tui)
#+end_src
** Global State
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *daemon-host* "127.0.0.1")
(defvar *daemon-port* 9105)
(defvar *socket* nil)
(defvar *stream* nil)
(defvar *chat-history* (list))
(defvar *status-text* "Connecting...")
(defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t))
(defvar *command-history* (make-array 0 :element-type 't :fill-pointer 0 :adjustable t))
(defvar *history-index* -1)
(defvar *is-running* t)
(defvar *queue-lock* (bt:make-lock))
(defvar *incoming-msgs* nil)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *daemon-port* 9105)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *socket* nil)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *stream* nil)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *chat-history* (list) "Full chronological log of messages.")
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *scroll-index* 0 "Offset for history rendering.")
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *status-text* "Connecting...")
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t))
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *command-history* (make-array 0 :element-type 't :fill-pointer 0 :adjustable t))
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *history-index* -1)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *is-running* t)
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *queue-lock* (bt:make-lock))
#+end_src
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *incoming-msgs* nil)
#+end_src
** Utilities
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun enqueue-msg (msg)
"Thread-safe addition to incoming message queue."
(bt:with-lock-held (*queue-lock*)
(push msg *incoming-msgs*)))
#+end_src
(defun add-to-history (cmd)
"Add command to history, preserving most recent."
(when (and cmd (> (length cmd) 0))
(unless (and (> (length *command-history*) 0)
(string= cmd (aref *command-history* (1- (length *command-history*)))))
(vector-push-extend cmd *command-history*))
(setf *history-index* (length *command-history*))))
(defun history-previous ()
(when (> (length *command-history*) 0)
(setf *history-index* (max 0 (1- *history-index*)))
(let ((cmd (aref *command-history* *history-index*)))
(setf (fill-pointer *input-buffer*) 0)
(loop for ch across cmd do (vector-push-extend ch *input-buffer*)))))
(defun history-next ()
(when (and *history-index* (< *history-index* (1- (length *command-history*))))
(setf *history-index* (1+ *history-index*))
(let ((cmd (aref *command-history* *history-index*)))
(setf (fill-pointer *input-buffer*) 0)
(loop for ch across cmd do (vector-push-extend ch *input-buffer*))))
(when (>= *history-index* (1- (length *command-history*)))
(setf (fill-pointer *input-buffer*) 0)))
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun dequeue-msgs ()
"Thread-safe retrieval of incoming messages."
(bt:with-lock-held (*queue-lock*)
(let ((msgs (nreverse *incoming-msgs*)))
(setf *incoming-msgs* nil)
msgs)))
#+end_src
(defun clean-keywords (msg)
(if (listp msg)
(let ((clean nil))
(loop for (k v) on msg by #'cddr
do (push (intern (string k) :keyword) clean)
(push v clean))
(nreverse clean))
msg))
** Styling Engine
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun get-line-style (text)
"Determines croatoan attributes based on content patterns."
(cond
((uiop:string-prefix-p "*" text) '(:bold :color-yellow))
((uiop:string-prefix-p "⬆" text) '(:color-cyan))
((uiop:string-prefix-p "🤔" text) '(:italic))
((uiop:string-prefix-p "ERROR" text) '(:bold :color-red))
(t nil)))
#+end_src
(defun format-payload (payload)
(let* ((action (getf payload :ACTION))
(text (getf payload :TEXT))
(msg (getf payload :MESSAGE))
(tool (getf payload :TOOL))
(prompt (getf payload :PROMPT))
(args (getf payload :ARGS))
(result (getf payload :RESULT)))
(cond (text text)
(msg msg)
((eq action :MESSAGE) (getf payload :TEXT))
((and tool prompt) (format nil "🤔 ~a: ~a" tool prompt))
((and tool args)
(let ((inner-prompt (or (getf args :PROMPT) (getf args :TEXT))))
(if inner-prompt
(format nil "🤔 ~a: ~a" tool inner-prompt)
(format nil "🔧 ~a args: ~s" tool args))))
(result (format nil "✅ ~a" result))
(t (format nil "~s" payload)))))
** Rendering Orchestrator
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun render-chat (win)
"Renders the chat history with scrolling and styling."
(clear win)
(let* ((h (height win))
(view-height (- h 2))
(history-len (length *chat-history*))
(start-idx *scroll-index*)
(end-idx (min history-len (+ start-idx view-height)))
(slice (reverse (subseq *chat-history* start-idx end-idx))))
(loop for msg in slice
for i from 1
do (let ((style (get-line-style msg)))
(add-string win (format nil "│ ~a" msg) :y i :x 1 :attributes style)))
(refresh win)))
#+end_src
(defun format-incoming (msg)
(let ((type (or (getf msg :TYPE) (getf msg :type)))
(payload (or (getf msg :PAYLOAD) (getf msg :payload))))
(cond
((and (listp msg) (eq type :EVENT))
(let ((action (or (getf payload :ACTION) (getf payload :action)))
(text (or (getf payload :TEXT) (getf payload :text) (getf payload :MESSAGE) (getf payload :message))))
(cond ((eq action :handshake) (format nil "👋 ~a" (or text "Connected")))
((eq action :thinking) (format nil "🤔 ~a" (or text "Thinking...")))
((eq action :tool-complete) (format nil "🔧 Done"))
(text (format nil "💬 ~a" text))
(t (format nil "📢 ~s" msg)))))
((and (listp msg) (eq type :STATUS))
(format nil "🔄 Scribe: ~a | Gardener: ~a"
(or (getf msg :SCRIBE) (getf msg :scribe) "idle")
(or (getf msg :GARDENER) (getf msg :gardener) "idle")))
((and (listp msg) (member type '(:REQUEST :RESPONSE :LOG)))
(format-payload payload))
((and (listp msg) (eq type :EVENT) (eq (getf payload :SENSOR) :TOOL-OUTPUT))
(format nil "🔧 ~a" (getf payload :RESULT)))
(t (format nil "~s" msg)))))
** Input Handling
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun handle-backspace ()
"Deletes last character from input buffer."
(when (> (fill-pointer *input-buffer*) 0)
(decf (fill-pointer *input-buffer*))))
#+end_src
(defun listen-thread ()
(loop :while *is-running* :do
(handler-case
(when (and *stream* (open-stream-p *stream*))
(let ((raw-msg (opencortex:read-framed-message *stream*)))
(cond ((eq raw-msg :eof) (setf *is-running* nil))
((eq raw-msg :error) (setf *status-text* "Protocol Error"))
((not (null raw-msg))
(let* ((msg (clean-keywords raw-msg))
(type (getf msg :TYPE))
(payload (getf msg :PAYLOAD)))
(cond ((and (eq type :EVENT) (eq (getf payload :ACTION) :handshake))
(setf *status-text* "Ready"))
((eq type :STATUS)
(setf *status-text* (format nil "[Scribe: ~a] [Gardener: ~a]"
(or (getf msg :SCRIBE) "idle")
(or (getf msg :GARDENER) "idle"))))
(t (let ((formatted (format-incoming msg)))
(when formatted (enqueue-msg formatted))))))))))
(error (c) (setf *status-text* (format nil "Net Error: ~a" c)) (setf *is-running* nil)))
(sleep 0.05)))
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun handle-return (stream)
"Process input buffer as message or command."
(let ((cmd (coerce *input-buffer* 'string)))
(setf (fill-pointer *input-buffer*) 0)
(when (> (length cmd) 0)
(enqueue-msg (format nil "⬆ ~a" cmd))
(when (and stream (open-stream-p stream))
(format stream "~a" (opencortex:frame-message (list :TYPE :EVENT
:META (list :SOURCE :tui)
:PAYLOAD (list :SENSOR :user-input :TEXT cmd))))
(finish-output stream)))
(when (string= cmd "/exit") (setf *is-running* nil))
(when (string= cmd "/clear") (setf *chat-history* nil))))
#+end_src
** Main Entry Point
#+begin_src lisp :tangle (expand-file-name "tui-client.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun main ()
"Initializes ncurses and starts the TUI event loop."
(handler-case
(setf *socket* (usocket:socket-connect *daemon-host* *daemon-port*))
(error (e) (format t "Error connecting to Brain (port ~a): ~a~%" *daemon-port* e) (return-from main)))
(error (e) (format t "Offline: ~a~%" e) (return-from main)))
(setf *stream* (usocket:socket-stream *socket*))
(bt:make-thread #'listen-thread :name "tui-listener")
(unwind-protect
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible t)
(let* ((h (height scr))
(w (width scr))
(chat-win (make-instance 'window :height (- h 5) :width (- w 2) :position (list 1 1) :border t))
(status-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 4) 1) :border t))
(help-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 3) 1)))
(input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t))
(last-status nil))
(add-string help-win "↑↓ History | Esc Clear | /help /exit" :y 0 :x 0 :attributes '(:bold))
(refresh help-win)
(setf (input-blocking input-win) nil)
(loop :while *is-running* :do
;; 1. Handle incoming messages
(let ((new-msgs (dequeue-msgs)))
(when new-msgs
(dolist (m new-msgs)
(push m *chat-history*)
(when (> (length *chat-history*) 500) (setf *chat-history* (subseq *chat-history* 0 500))))
(clear chat-win)
(let ((line-num 1))
(dolist (m (reverse (subseq *chat-history* 0 (min (length *chat-history*) (- (height chat-win) 2)))))
(add-string chat-win (format nil "│ ~a" m) :y line-num :x 1)
(incf line-num)))
(refresh chat-win)))
;; 2. Render Status Bar
(unless (equal *status-text* last-status)
(clear status-win)
(add-string status-win (format nil "┤ ~a ┤" *status-text*) :y 0 :x 1 :attributes '(:reverse))
(refresh status-win)
(setf last-status *status-text*))
;; 3. Keyboard Input
(let* ((event (get-event input-win))
(ch (when (and event (typep event 'event)) (event-key event))))
(when ch
(cond
((or (eq ch #\Newline) (eq ch #\Return))
(let ((cmd (coerce *input-buffer* 'string)))
(setf (fill-pointer *input-buffer*) 0)
(when (> (length cmd) 0)
(add-to-history cmd)
(enqueue-msg (format nil "⬆ ~a" cmd))
(handler-case
(when (and *stream* (open-stream-p *stream*))
(format *stream* "~a" (opencortex:frame-message (list :TYPE :EVENT
:META (list :SOURCE :tui :SESSION-ID "default")
:PAYLOAD (list :SENSOR :user-input :TEXT cmd))))
(finish-output *stream*))
(error (c) (enqueue-msg (format nil "ERROR SENDING: ~a" c)))))
(when (string= cmd "/exit") (setf *is-running* nil))
(when (string= cmd "/clear") (setf *chat-history* nil))))
((or (eq ch :up) (eq ch :key-up)) (history-previous))
((or (eq ch :down) (eq ch :key-down)) (history-next))
((or (eq ch :backspace) (eq ch :key-backspace) (eq ch #\Backspace) (eq ch #\Rubout) (eq ch (code-char 127)))
(when (> (fill-pointer *input-buffer*) 0)
(decf (fill-pointer *input-buffer*))))
((characterp ch)
(vector-push-extend ch *input-buffer*))))
(clear input-win)
(add-string input-win (format nil "▶ ~a" (coerce *input-buffer* 'string)) :y 0 :x 1)
(refresh input-win))
(sleep 0.02))))
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t)
(let* ((h (height scr)) (w (width scr))
(chat-win (make-instance 'window :height (- h 5) :width (- w 2) :position '(1 1) :border t))
(input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t)))
(setf (input-blocking input-win) nil)
(loop :while *is-running* :do
(let ((msgs (dequeue-msgs)))
(when msgs
(dolist (m msgs) (push m *chat-history*))
(render-chat chat-win)))
(let* ((ev (get-event input-win))
(ch (when (and ev (typep ev 'event)) (event-key ev))))
(when ch
(cond
((or (eq ch #\Newline) (eq ch #\Return)) (handle-return *stream*))
((or (eq ch :backspace) (eq ch (code-char 127))) (handle-backspace))
((eq ch :page-up) (scroll-history 5))
((eq ch :page-down) (scroll-history -5))
((characterp ch) (vector-push-extend ch *input-buffer*))))
(clear input-win)
(add-string input-win (format nil "▶ ~a" (coerce *input-buffer* 'string)) :y 0 :x 1)
(refresh input-win))
(sleep 0.02))))
(setf *is-running* nil)
(when *socket* (ignore-errors (usocket:socket-close *socket*)))))
#+end_src

View File

@@ -19,8 +19,6 @@
(:file "harness/reason")
(:file "harness/act")
(:file "harness/loop")
(:file "harness/doctor")
(:file "harness/setup-wizard")
(:file "skills/org-skill-policy")
(:file "skills/org-skill-bouncer")
@@ -33,7 +31,10 @@
(:file "skills/org-skill-emacs-edit")
(:file "skills/org-skill-tool-permissions")
(:file "skills/org-skill-self-fix")
(:file "skills/org-skill-peripheral-vision"))
(:file "skills/org-skill-peripheral-vision")
(:file "skills/org-skill-gateway-manager")
(:file "skills/org-skill-diagnostics")
(:file "skills/org-skill-config-manager"))
:build-operation "program-op"
:build-pathname "opencortex-server"
@@ -51,12 +52,14 @@
(:file "tests/emacs-edit-tests")
(:file "tests/engineering-standards-tests")
(:file "tests/lisp-utils-tests")
(:file "tests/lisp-validator-tests")
(:file "tests/literate-programming-tests")
(:file "tests/self-edit-tests")
(:file "tests/tool-permissions-tests")
(:file "tests/doctor-tests")
(:file "tests/setup-wizard-tests")))
(:file "tests/gateway-manager-tests")
(:file "tests/tui-tests")
(:file "tests/diagnostics-tests")
(:file "tests/config-manager-tests")))
(defsystem :opencortex/tui
:depends-on (:opencortex :croatoan :usocket :bordeaux-threads)

View File

@@ -86,6 +86,12 @@ COMMAND=$1
shift || true
case "$COMMAND" in
link)
PLATFORM=$1
TOKEN=$2
exec sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval "(push (truename \"$OC_DATA_DIR/\") asdf:*central-registry*)" --eval '(ql:quickload :opencortex)' --eval "(opencortex:gateway-manager-main \"$PLATFORM\" \"$TOKEN\")"
;;
doctor)
export SKILLS_DIR="${OC_DATA_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
@@ -117,7 +123,7 @@ case "$COMMAND" in
;;
*)
echo "Available commands: setup, doctor, boot, tui"
echo "Available commands: setup, link, doctor, boot, tui"
exit 1
;;
esac

View File

@@ -0,0 +1,140 @@
#+TITLE: Skill: Config Manager (org-skill-config-manager.org)
#+AUTHOR: Agent
#+FILETAGS: :skill:setup:config:
#+STARTUP: content
* Overview
The *Config Manager* skill provides the OpenCortex Agent with the capability to manage its own internal settings and LLM provider registry.
* Phase A: Demand (Thinking)
** The Configuration Invariant
A sovereign system must be able to re-configure its own connections. By moving the Setup Wizard and Provider Registry to a skill, we enable the Agent to "self-configure" or assist the user in adding new backends like Ollama or Groq without needing to reboot the kernel.
** Hybrid Security Standard
Secrets are appended to `~/.config/opencortex/.env`, while structural metadata is stored in `~/.config/opencortex/providers.lisp`.
* Phase B: Protocol (Success Criteria)
** Test Suite Context
#+begin_src lisp :tangle (expand-file-name "config-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(defpackage :opencortex-config-manager-tests
(:use :cl :fiveam :opencortex)
(:export #:config-suite))
#+end_src
#+begin_src lisp :tangle (expand-file-name "config-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-package :opencortex-config-manager-tests)
#+end_src
#+begin_src lisp :tangle (expand-file-name "config-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite config-suite :description "Verification of the Config Manager skill")
#+end_src
#+begin_src lisp :tangle (expand-file-name "config-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-suite config-suite)
#+end_src
** Registry Tests
#+begin_src lisp :tangle (expand-file-name "config-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-provider-registration
"Verify that multiple providers can be registered and saved."
(let ((opencortex::*providers* nil))
(opencortex:register-provider :ollama '(:url "http://localhost:11434"))
(is (equal "http://localhost:11434" (getf (getf opencortex::*providers* :ollama) :url)))))
#+end_src
* Phase C: Implementation (Build)
** Package Context
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(in-package :opencortex)
#+end_src
** Skill Metadata
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defparameter *skill-config-manager*
'(:name "config-manager"
:description "Manages system settings and LLM provider configurations."
:capabilities (:configure-provider :run-setup-wizard)
:type :deterministic)
"Skill metadata for the Config Manager.")
#+end_src
** Provider Templates
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(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.")
#+end_src
** Registry Persistence
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defvar *providers* nil "Global registry of configured LLM providers.")
#+end_src
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(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*))))
#+end_src
** Registry API
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun register-provider (id config)
"Update the global provider registry."
(setf (getf *providers* id) config))
#+end_src
** Setup Wizard Implementation
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(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))))
#+end_src
#+begin_src lisp :tangle (expand-file-name "org-skill-config-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun run-setup-wizard ()
"Entry point for the interactive OpenCortex Lisp Setup Wizard."
(format t "=== OpenCortex: Advanced Setup Wizard ===~%")
(let ((user (prompt-for "Your Name" "User"))
(agent (prompt-for "Agent Name" "OpenCortex")))
(format t "Welcome, ~a. I am ~a.~%" user agent))
(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 diagnostics...~%")
(doctor-run-all))
#+end_src

View File

@@ -0,0 +1,159 @@
#+TITLE: Skill: Diagnostics (org-skill-diagnostics.org)
#+AUTHOR: Agent
#+FILETAGS: :skill:diagnostic:health:
#+STARTUP: content
* Overview
The *Diagnostics Skill* provides the OpenCortex Agent with the capability to perform self-examinations and environment validation.
By moving this logic from the harness to a skill, we enable the Agent's **Reflection Loop** to autonomously run health checks when a tool fails, transforming the Agent into a self-healing system.
* Phase A: Demand (Thinking)
** The Self-Healing Invariant
In a standard Lisp Machine, the system should be able to reason about its own environment. The `:run-diagnostics` capability ensures the Agent can verify its own dependency chain and XDG directory structure.
** XDG Compliance
The skill strictly validates the POSIX standard paths resolved during bootstrap, ensuring no silent configuration drift occurs.
* Phase B: Protocol (Success Criteria)
** Test Suite Context
#+begin_src lisp :tangle (expand-file-name "diagnostics-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(defpackage :opencortex-diagnostics-tests
(:use :cl :fiveam :opencortex)
(:export #:diagnostics-suite))
#+end_src
#+begin_src lisp :tangle (expand-file-name "diagnostics-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-package :opencortex-diagnostics-tests)
#+end_src
#+begin_src lisp :tangle (expand-file-name "diagnostics-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite diagnostics-suite :description "Verification of the Diagnostics skill")
#+end_src
#+begin_src lisp :tangle (expand-file-name "diagnostics-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-suite diagnostics-suite)
#+end_src
** Dependency Tests
#+begin_src lisp :tangle (expand-file-name "diagnostics-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-dependency-check-fail
"Verify that missing binaries are correctly identified as failures."
(let ((opencortex::*doctor-required-binaries* '("non-existent-binary-123")))
(is (null (opencortex:doctor-check-dependencies)))))
#+end_src
* Phase C: Implementation (Build)
** Package Context
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(in-package :opencortex)
#+end_src
** Skill Metadata
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defparameter *skill-diagnostics*
'(:name "diagnostics"
:description "Performs system health checks and environment validation."
:capabilities (:run-diagnostics)
:type :deterministic)
"Skill metadata for the Diagnostics component.")
#+end_src
** Global Configuration
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defvar *doctor-required-binaries* '("sbcl" "emacs" "git" "socat" "nc")
"List of external binaries required for full system operation.")
#+end_src
** Dependency Verification
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun doctor-check-dependencies ()
"Verifies that required external binaries are available in the PATH via a shell probe."
(let ((all-ok t))
(harness-log "DOCTOR: Checking system dependencies...")
(dolist (dep *doctor-required-binaries*)
(let ((path (ignore-errors
(uiop:run-program (list "which" dep)
:output :string :ignore-error-status t))))
(if (and path (> (length path) 0))
(harness-log " [OK] Found ~a" dep)
(progn
(harness-log " [FAIL] Missing binary: ~a" dep)
(setf all-ok nil)))))
all-ok))
#+end_src
** Environment & XDG Validation
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun doctor-check-env ()
"Validates XDG directories and environment configuration against the POSIX standard."
(harness-log "DOCTOR: Checking XDG environment...")
(let ((all-ok t)
(config-dir (uiop:getenv "OC_CONFIG_DIR"))
(data-dir (uiop:getenv "OC_DATA_DIR"))
(state-dir (uiop:getenv "OC_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)
(harness-log " [OK] ~a: ~a" name path)
(progn
(harness-log " [FAIL] ~a directory missing: ~a" name path)
(when critical (setf all-ok nil))))
(progn
(harness-log " [FAIL] ~a variable not set." name)
(when critical (setf all-ok nil))))))
(check-dir "Config (OC_CONFIG_DIR)" config-dir t)
(check-dir "Data (OC_DATA_DIR)" data-dir t)
(check-dir "State (OC_STATE_DIR)" state-dir t)
(check-dir "Memex (MEMEX_DIR)" memex-dir t))
all-ok))
#+end_src
** LLM Connectivity
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun doctor-check-llm ()
"Tests connectivity to primary LLM providers. Non-critical fallback allowed."
(harness-log "DOCTOR: Checking LLM connectivity...")
(let ((openrouter-key (uiop:getenv "OPENROUTER_API_KEY")))
(if (and openrouter-key (> (length openrouter-key) 0))
(progn
(harness-log " [OK] OpenRouter API Key detected.")
t)
(progn
(harness-log " [WARN] No OpenRouter API Key. Falling back to local inference only.")
t))))
#+end_src
** Orchestration
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun doctor-run-all ()
"Executes the full diagnostic suite and returns T if system is healthy."
(harness-log "==================================================")
(harness-log " OPENCORTEX DOCTOR: Commencing Health Check")
(harness-log "==================================================")
(let ((dep-ok (doctor-check-dependencies))
(env-ok (doctor-check-env))
(llm-ok (doctor-check-llm)))
(harness-log "==================================================")
(if (and dep-ok env-ok)
(progn
(harness-log " ✓ SYSTEM HEALTHY: Ready for ignition.")
t)
(progn
(harness-log " ✗ SYSTEM UNHEALTHY: Fix the errors above.")
nil))))
#+end_src
** CLI Entry Point
#+begin_src lisp :tangle (expand-file-name "org-skill-diagnostics.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun doctor-main ()
"Entry point for the 'doctor' CLI command."
(if (doctor-run-all)
(uiop:quit 0)
(uiop:quit 1)))
#+end_src

View File

@@ -0,0 +1,125 @@
#+TITLE: Skill: Gateway Manager (org-skill-gateway-manager.org)
#+AUTHOR: Agent
#+FILETAGS: :skill:setup:gateway:
#+STARTUP: content
* Overview
The *Gateway Manager* skill provides the OpenCortex Agent with the capability to manage its own external communication channels (Gateways).
* Phase A: Demand (Thinking)
** Architectural Invariant: Self-Linking
In a traditional AI wrapper, the user manually edits a config file to add a bot token. In OpenCortex, the Agent should be able to say: "I have verified the Telegram token and successfully linked our connection."
* Phase B: Protocol (Success Criteria)
** Test Suite Context
#+begin_src lisp :tangle (expand-file-name "gateway-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(defpackage :opencortex-gateway-manager-tests
(:use :cl :fiveam :opencortex)
(:export #:gateway-suite))
#+end_src
#+begin_src lisp :tangle (expand-file-name "gateway-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-package :opencortex-gateway-manager-tests)
#+end_src
#+begin_src lisp :tangle (expand-file-name "gateway-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite gateway-suite :description "Verification of the Gateway Manager skill")
#+end_src
#+begin_src lisp :tangle (expand-file-name "gateway-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-suite gateway-suite)
#+end_src
** Logic Tests
#+begin_src lisp :tangle (expand-file-name "gateway-manager-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-gateway-registration
"Verify that the skill can register a new gateway metadata block."
(let ((opencortex::*gateways* nil))
(opencortex:skill-gateway-register :telegram '(:status :unverified))
(is (getf (getf opencortex::*gateways* :telegram) :status))))
#+end_src
* Phase C: Implementation (Build)
** Package Context
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(in-package :opencortex)
#+end_src
** Capability Definition
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defparameter *skill-gateway-manager*
'(:name "gateway-manager"
:description "Manages connections to external chat platforms."
:capabilities (:link-gateway :list-gateways)
:type :deterministic)
"Skill metadata for the Gateway Manager.")
#+end_src
** Registry Persistence
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defvar *gateways* nil "The internal registry of configured gateways.")
#+end_src
** Persistence Stubs
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun save-gateways ()
"Persist gateway metadata to XDG Config directory."
(let ((path (merge-pathnames "gateways.lisp" (get-oc-config-dir))))
(ensure-directories-exist path)
(with-open-file (s path :direction :output :if-exists :supersede)
(format s ";;; OpenCortex Gateway Registry~%~s~%" *gateways*))))
#+end_src
** Registration Logic
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun skill-gateway-register (platform metadata)
"Internal function to update the gateway registry."
(setf (getf *gateways* platform) metadata))
#+end_src
** Telegram Verification
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun skill-gateway-verify-telegram (token)
"Verifies a Telegram bot token via the getMe API."
(let ((url (format nil "https://api.telegram.org/bot~a/getMe" token)))
(handler-case
(let* ((response (dex:get url))
(data (cl-json:decode-json-from-string response)))
(if (cdr (assoc :ok data))
(let ((result (cdr (assoc :result data))))
(list :status :verified :username (cdr (assoc :username result))))
(list :status :failed :error "Invalid Token")))
(error (c) (list :status :failed :error (format nil "~a" c))))))
#+end_src
** Linkage Command
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun skill-gateway-link (platform token)
"Primary capability to link a new platform. Returns status plist."
(harness-log "GATEWAY: Attempting to link ~a..." platform)
(let ((verification (cond
((eq platform :telegram) (skill-gateway-verify-telegram token))
(t (list :status :verified :info "Platform verification pending implementation")))))
(if (eq (getf verification :status) :verified)
(progn
(save-secret platform :token token)
(skill-gateway-register platform verification)
(save-gateways)
(list :status :success :platform platform :info verification))
(list :status :error :reason (getf verification :error)))))
#+end_src
** CLI Main Wrapper
#+begin_src lisp :tangle (expand-file-name "org-skill-gateway-manager.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
(defun gateway-manager-main (platform token)
"Main entry point for CLI-driven linkage."
(if (and platform token)
(let ((result (skill-gateway-link (intern (string-upcase platform) :keyword) token)))
(format t "RESULT: ~s~%" result)
(uiop:quit 0))
(progn
(format t "Usage: opencortex link <PLATFORM> <TOKEN>~%")
(uiop:quit 1))))
#+end_src