#!/usr/bin/env bb
;; duck-rhizome-bridge.bb - Bridge between duck (Babashka/DuckDB) and rhizome (Haskell/Polysemy)
;; Seed 1069: [+1, -1, -1, +1, +1, +1, +1]
;;
;; Architecture:
;; duck (Babashka) <--JSON-RPC--> rhizome (Haskell)
;; | |
;; xm.duckdb Polysemy effects
;; | |
;; Conversation VersAPI effect
;; History LLM effect
;;
;; Usage:
;; ./duck-rhizome-bridge.bb serve # Start bridge server
;; ./duck-rhizome-bridge.bb sync # Sync xm.duckdb with rhizome state
;; ./duck-rhizome-bridge.bb query <sql> # Query xm.duckdb
;; ./duck-rhizome-bridge.bb vms # List VMs via rhizome
;; ./duck-rhizome-bridge.bb fork <vm-id> # Trigger withForkedSelf on VM
(require '[babashka.process :refer [shell process]]
'[clojure.string :as str]
'[cheshire.core :as json]
'[babashka.fs :as fs]
'[org.httpkit.server :as http])
;;; ============================================================
;;; Configuration
;;; ============================================================
(def PORT (or (some-> (System/getenv "BRIDGE_PORT") parse-long) 8069))
(def XM_DB_PATH
(or (System/getenv "XM_DB_PATH")
(str (System/getProperty "user.home") "/xm.duckdb")))
(def RHIZOME_PATH
(or (System/getenv "RHIZOME_PATH")
(str (System/getProperty "user.home") "/ies-rhizome")))
(def VERS_API_URL "https://api.vers.sh")
;; GF(3) trit values for bridge operations
(def TRIT_MINUS -1) ;; Contraction (query/read)
(def TRIT_ERGODIC 0) ;; Transformation
(def TRIT_PLUS +1) ;; Expansion (create/fork)
;;; ============================================================
;;; DuckDB Integration (xm.duckdb)
;;; ============================================================
(defn duck-query [sql]
"Execute SQL against xm.duckdb"
(let [result (shell {:out :string :err :string :continue true}
"duckdb" XM_DB_PATH "-json" "-c" sql)]
(if (= 0 (:exit result))
{:ok true
:trit TRIT_MINUS ;; Query is contraction
:data (try (json/parse-string (:out result) true)
(catch Exception _ (:out result)))}
{:ok false :error (:err result)})))
(defn get-neighborhood-stats []
"Get neighborhood statistics from xm.duckdb"
(duck-query "SELECT
COUNT(DISTINCT neighborhood_id) as neighborhoods,
COUNT(*) as total_interactions,
SUM(CASE WHEN trit = 1 THEN 1 ELSE 0 END) as plus_count,
SUM(CASE WHEN trit = 0 THEN 1 ELSE 0 END) as ergodic_count,
SUM(CASE WHEN trit = -1 THEN 1 ELSE 0 END) as minus_count
FROM xm_interactions"))
(defn get-cross-source-duplicates []
"Find content that appears in multiple sources"
(duck-query "SELECT content_hash, COUNT(DISTINCT source) as source_count,
GROUP_CONCAT(DISTINCT source) as sources
FROM xm_interactions
GROUP BY content_hash
HAVING COUNT(DISTINCT source) > 1
ORDER BY source_count DESC
LIMIT 20"))
(defn search-unified [term limit]
"Search across unified history"
(duck-query (str "SELECT source, content, timestamp, trit
FROM xm_interactions
WHERE content LIKE '%" term "%'
ORDER BY timestamp DESC
LIMIT " (or limit 10))))
;;; ============================================================
;;; VERS API Integration (via rhizome or direct)
;;; ============================================================
(defn vers-api-call [endpoint method body]
"Make direct VERS API call"
(let [api-key (System/getenv "VERS_API_KEY")
url (str VERS_API_URL "/api/v1" endpoint)]
(if api-key
(let [result (shell {:out :string :err :string :continue true}
"curl" "-sS" "-X" method url
"-H" (str "Authorization: Bearer " api-key)
"-H" "Content-Type: application/json"
(when body ["-d" (json/generate-string body)]))]
(if (= 0 (:exit result))
{:ok true
:trit (case method
"GET" TRIT_MINUS
"POST" TRIT_PLUS
"DELETE" TRIT_MINUS
TRIT_ERGODIC)
:data (try (json/parse-string (:out result) true)
(catch Exception _ (:out result)))}
{:ok false :error (:err result)}))
{:ok false :error "VERS_API_KEY not set"})))
(defn list-vms []
"List VMs via VERS API"
(vers-api-call "/vms" "GET" nil))
(defn get-vm [vm-id]
"Get VM details"
(vers-api-call (str "/vms/" vm-id) "GET" nil))
(defn branch-vm [vm-id]
"Branch a VM (withForkedSelf trigger)"
(vers-api-call (str "/vms/" vm-id "/branch") "POST" {}))
(defn delete-vm [vm-id]
"Delete a VM"
(vers-api-call (str "/vms/" vm-id) "DELETE" nil))
;;; ============================================================
;;; Rhizome Integration (Haskell/Polysemy)
;;; ============================================================
(defn build-rhizome []
"Build rhizome with nix"
(println "Building rhizome...")
(let [result (shell {:dir RHIZOME_PATH :out :string :err :string :continue true}
"nix" "build" ".#vers-client")]
(if (= 0 (:exit result))
{:ok true :message "Rhizome built successfully"}
{:ok false :error (:err result)})))
(defn check-rhizome-health []
"Check if rhizome is available"
{:available (fs/exists? RHIZOME_PATH)
:vers-client (fs/exists? (str RHIZOME_PATH "/vers-client"))
:llm-effect (fs/exists? (str RHIZOME_PATH "/llm-effect"))
:polysemy-extra (fs/exists? (str RHIZOME_PATH "/polysemy-extra"))})
;;; ============================================================
;;; Bridge State Synchronization
;;; ============================================================
(defn sync-vm-inventory []
"Sync VM inventory from VERS to xm.duckdb"
(println "Syncing VM inventory...")
(let [vms-result (list-vms)]
(if (:ok vms-result)
(let [vms (get-in vms-result [:data :result :vms] [])
insert-sql (str "INSERT OR REPLACE INTO vm_sync (vm_id, state, ip_address, alias, synced_at)
VALUES "
(->> vms
(map (fn [vm]
(str "('" (:id vm) "', "
"'" (:state vm) "', "
"'" (:ip_address vm) "', "
"'" (:alias vm) "', "
"CURRENT_TIMESTAMP)")))
(str/join ", ")))]
;; Create sync table if needed
(duck-query "CREATE TABLE IF NOT EXISTS vm_sync (
vm_id TEXT PRIMARY KEY,
state TEXT,
ip_address TEXT,
alias TEXT,
synced_at TIMESTAMP)")
(if (seq vms)
(duck-query insert-sql)
{:ok true :message "No VMs to sync"}))
vms-result)))
;;; ============================================================
;;; JSON-RPC Server for Rhizome Integration
;;; ============================================================
(defn handle-rpc-request [req]
"Handle JSON-RPC request from rhizome"
(let [method (:method req)
params (:params req)
id (:id req)]
(case method
;; xm.duckdb queries
"xm.query"
{:jsonrpc "2.0" :id id :result (duck-query (:sql params))}
"xm.stats"
{:jsonrpc "2.0" :id id :result (get-neighborhood-stats)}
"xm.duplicates"
{:jsonrpc "2.0" :id id :result (get-cross-source-duplicates)}
"xm.search"
{:jsonrpc "2.0" :id id :result (search-unified (:term params) (:limit params))}
;; VERS operations
"vers.listVMs"
{:jsonrpc "2.0" :id id :result (list-vms)}
"vers.getVM"
{:jsonrpc "2.0" :id id :result (get-vm (:vmId params))}
"vers.branchVM"
{:jsonrpc "2.0" :id id :result (branch-vm (:vmId params))}
"vers.deleteVM"
{:jsonrpc "2.0" :id id :result (delete-vm (:vmId params))}
;; Rhizome health
"rhizome.health"
{:jsonrpc "2.0" :id id :result (check-rhizome-health)}
;; Sync operations
"bridge.sync"
{:jsonrpc "2.0" :id id :result (sync-vm-inventory)}
;; Unknown method
{:jsonrpc "2.0" :id id :error {:code -32601 :message "Method not found"}})))
(defn json-rpc-handler [{:keys [uri body request-method]}]
"HTTP handler for JSON-RPC"
(cond
(and (= uri "/rpc") (= request-method :post))
(let [req (json/parse-string (slurp body) true)
result (handle-rpc-request req)]
{:status 200
:headers {"Content-Type" "application/json"}
:body (json/generate-string result)})
(= uri "/health")
{:status 200
:headers {"Content-Type" "application/json"}
:body (json/generate-string
{:status "ok"
:xm-db (fs/exists? XM_DB_PATH)
:rhizome (check-rhizome-health)
:seed 1069
:trit-sequence [+1 -1 -1 +1 +1 +1 +1]})}
(= uri "/")
{:status 200
:headers {"Content-Type" "text/html"}
:body "<html>
<head><title>Duck-Rhizome Bridge</title></head>
<body style='font-family: monospace; background: #0a0a0f; color: #e0e0e0; padding: 2rem;'>
<pre>
╔═══════════════════════════════════════════╗
║ 🦆⟷🌿 DUCK-RHIZOME BRIDGE ║
║ Seed 1069: [+1,-1,-1,+1,+1,+1,+1] ║
╠═══════════════════════════════════════════╣
║ Babashka/DuckDB ←─JSON-RPC─→ Haskell ║
║ duck ↕ rhizome ║
║ xm.duckdb ↕ Polysemy ║
╚═══════════════════════════════════════════╝
</pre>
<h2>Endpoints</h2>
<ul>
<li><a href='/health'>/health</a> - Bridge health status</li>
<li>POST /rpc - JSON-RPC endpoint</li>
</ul>
<h2>JSON-RPC Methods</h2>
<pre>
xm.query {sql: \"SELECT...\"} Query xm.duckdb
xm.stats {} Neighborhood stats
xm.duplicates {} Cross-source duplicates
xm.search {term: \"...\", limit: 10} Search unified history
vers.listVMs {} List VERS VMs
vers.getVM {vmId: \"...\"} Get VM details
vers.branchVM {vmId: \"...\"} Fork VM (withForkedSelf)
vers.deleteVM {vmId: \"...\"} Delete VM
rhizome.health {} Check rhizome availability
bridge.sync {} Sync VM inventory to xm.duckdb
</pre>
</body></html>"}
:else
{:status 404 :body "Not Found"}))
(defn start-server []
"Start the bridge server"
(println (str "🦆⟷🌿 Duck-Rhizome Bridge starting on port " PORT))
(println (str " xm.duckdb: " XM_DB_PATH))
(println (str " rhizome: " RHIZOME_PATH))
(println (str " Seed 1069: [+1,-1,-1,+1,+1,+1,+1]"))
(http/run-server json-rpc-handler {:port PORT})
(println "✅ Bridge running. Press Ctrl+C to stop.")
@(promise))
;;; ============================================================
;;; CLI
;;; ============================================================
(defn print-usage []
(println "
🦆⟷🌿 duck-rhizome-bridge - Bridge between duck and rhizome
Usage: duck-rhizome-bridge.bb [command] [args...]
Commands:
serve Start JSON-RPC bridge server (port 8069)
sync Sync VM inventory to xm.duckdb
query <sql> Query xm.duckdb directly
stats Show neighborhood statistics
vms List VMs via VERS API
get-vm <vm-id> Get VM details
fork <vm-id> Branch VM (trigger withForkedSelf)
health Show bridge health status
build Build rhizome with nix
Environment:
BRIDGE_PORT Server port (default: 8069)
XM_DB_PATH Path to xm.duckdb
RHIZOME_PATH Path to rhizome repo
VERS_API_KEY VERS API key for VM operations
"))
(defn -main [& args]
(let [cmd (first args)]
(case cmd
"serve" (start-server)
"sync" (let [result (sync-vm-inventory)]
(println (json/generate-string result {:pretty true})))
"query" (let [sql (str/join " " (rest args))
result (duck-query sql)]
(println (json/generate-string result {:pretty true})))
"stats" (let [result (get-neighborhood-stats)]
(println (json/generate-string result {:pretty true})))
"vms" (let [result (list-vms)]
(println (json/generate-string result {:pretty true})))
"get-vm" (if-let [vm-id (second args)]
(let [result (get-vm vm-id)]
(println (json/generate-string result {:pretty true})))
(println "Usage: get-vm <vm-id>"))
"fork" (if-let [vm-id (second args)]
(do
(println (str "🌿 Branching VM " vm-id " (withForkedSelf trigger)..."))
(let [result (branch-vm vm-id)]
(println (json/generate-string result {:pretty true}))))
(println "Usage: fork <vm-id>"))
"health" (let [result {:xm-db (fs/exists? XM_DB_PATH)
:rhizome (check-rhizome-health)
:vers-api-key (boolean (System/getenv "VERS_API_KEY"))
:seed 1069}]
(println (json/generate-string result {:pretty true})))
"build" (let [result (build-rhizome)]
(println (json/generate-string result {:pretty true})))
(nil "-h" "--help" "help") (print-usage)
(do
(println (str "Unknown command: " cmd))
(print-usage)
(System/exit 1)))))
(apply -main *command-line-args*)