Add Frankenstein source: Rust Tokio BRAIN/HANDS/LEGS pipeline, gates, trust deed
Browse files- .gitignore +3 -0
- Cargo.toml +33 -0
- README.md +186 -31
- hands/hands_server.py +425 -0
- hands/requirements.txt +15 -0
- src/gates/lean4.rs +49 -0
- src/gates/mod.rs +2 -0
- src/gates/prolog.rs +54 -0
- src/inject/mod.rs +1 -0
- src/inject/trust_deed.rs +64 -0
- src/main.rs +194 -0
- src/models/bedrock.rs +73 -0
- src/models/mod.rs +2 -0
- src/models/vllm.rs +74 -0
- src/tools/ddg.rs +48 -0
- src/tools/mod.rs +2 -0
- src/tools/tavily.rs +57 -0
- src/web.rs +688 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/target
|
| 2 |
+
.env
|
| 3 |
+
*.jsonl
|
Cargo.toml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "sovereign-35"
|
| 3 |
+
version = "1.0.0"
|
| 4 |
+
edition = "2021"
|
| 5 |
+
description = "Sovereign 3.5 β Claude Bedrock + Tavily + DDG + Lean4 gate + Prolog + vLLM. Tokio fire."
|
| 6 |
+
license = "Apache-2.0"
|
| 7 |
+
authors = ["Ahmad Ali Parr β SnapKitty Collective"]
|
| 8 |
+
|
| 9 |
+
[[bin]]
|
| 10 |
+
name = "sovereign"
|
| 11 |
+
path = "src/main.rs"
|
| 12 |
+
|
| 13 |
+
[[bin]]
|
| 14 |
+
name = "frankenstein"
|
| 15 |
+
path = "src/web.rs"
|
| 16 |
+
|
| 17 |
+
[dependencies]
|
| 18 |
+
tokio = { version = "1", features = ["full"] }
|
| 19 |
+
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
| 20 |
+
serde = { version = "1", features = ["derive"] }
|
| 21 |
+
serde_json = "1"
|
| 22 |
+
aws-config = "1"
|
| 23 |
+
aws-sdk-bedrockruntime = "1"
|
| 24 |
+
anyhow = "1"
|
| 25 |
+
colored = "2"
|
| 26 |
+
dotenvy = "0.15"
|
| 27 |
+
sha2 = "0.10"
|
| 28 |
+
hex = "0.4"
|
| 29 |
+
chrono = { version = "0.4", features = ["serde"] }
|
| 30 |
+
urlencoding = "2"
|
| 31 |
+
axum = "0.7"
|
| 32 |
+
tower-http = { version = "0.5", features = ["cors"] }
|
| 33 |
+
tokio-stream = "0.1"
|
README.md
CHANGED
|
@@ -1,38 +1,193 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
**Sovereign reasoning stack** β SnapKitty Collective Β· THE SHARED PRIMORDIAL FOUNDATION (EIN 42-6976431)
|
| 18 |
|
| 19 |
## Architecture
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
| Brain | Claude Sonnet / Bedrock | Reasoning + language |
|
| 24 |
-
| Hands | FAISS, Neo4j, RDF, spaCy :5433 | Memory + retrieval |
|
| 25 |
-
| Legs | Devstral 123B | Code execution |
|
| 26 |
-
| Chain | WORM ledger :4300 | Immutable audit trail |
|
| 27 |
|
| 28 |
-
## Key Properties
|
| 29 |
-
- **NAND routing** β attention computed as Boolean NAND circuits, O(log d) depth
|
| 30 |
-
- **Sovereign identity** β cryptographically signed outputs, purple hat integrity doctrine
|
| 31 |
-
- **Polyglot production** β 49+ language runtime, INTERCAL trap layer for adversarial inputs
|
| 32 |
-
- **Lean 4 verified** β core theorems proven, zero sorry
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
[github.com/SNAPKITTYWEST/foundry-f1](https://github.com/SNAPKITTYWEST/foundry-f1)
|
|
|
|
| 1 |
+
```
|
| 2 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3 |
+
β β
|
| 4 |
+
β ⬑ F R A N K E N S T E I N β
|
| 5 |
+
β β
|
| 6 |
+
β βββββββββββββββββββββββββββββββββββββββββββββ β
|
| 7 |
+
β π§ BRAIN Β· π€² HANDS Β· 𦡠LEGS Β· Ξ© SEALED β
|
| 8 |
+
β Rust Tokio Β· AWS Bedrock Β· pgvector Β· vLLM β
|
| 9 |
+
β βββββββββββββββββββββββββββββββββββββββββββββ β
|
| 10 |
+
β β
|
| 11 |
+
β ⬑ Ξ© βΊ Ξ¨ Ξ Ξ Ξ£ Ξ¦ Ξ± β powered by BOB β
|
| 12 |
+
β β
|
| 13 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
+
```
|
| 15 |
|
| 16 |
+
sovereign-35 (FRANKENSTEIN) is a Rust Tokio web server implementing a three-stage sovereign AI workflow engine: a **Brain** (Claude Sonnet 4.6 via AWS Bedrock) that architects plans and injects Trust Deed governance, **Hands** (a Python FastAPI sidecar on port 5433 running FAISS + Neo4j + RDF + NetworkX + spaCy for corpus retrieval), and **Legs** (Devstral 123B via Bedrock, spawned in a Tokio `JoinSet` with a 120-second timeout, that executes the Brain's specification into working code). Every step is sealed with a running SHA-256 WORM chain. The server also exposes a **CODE BATTLE** mode that fires Claude Sonnet 4.6, Claude Opus 4.6, and Devstral 123B concurrently on the same prompt and returns all three answers side-by-side, plus an **ASK** mode for sovereign queries without the code path. The dark-terminal HTML UI renders at `http://localhost:4300`. Two binaries are compiled from the same codebase: `sovereign` (CLI REPL) and `frankenstein` (web server).
|
|
|
|
|
|
|
| 17 |
|
| 18 |
## Architecture
|
| 19 |
+
**Version:** v1.0.0
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
```mermaid
|
| 23 |
+
sequenceDiagram
|
| 24 |
+
participant User
|
| 25 |
+
participant Web as Frankenstein Web<br/>:4300 (Axum)
|
| 26 |
+
participant Brain as BRAIN<br/>Claude Sonnet 4.6<br/>(Bedrock)
|
| 27 |
+
participant Hands as HANDS<br/>FastAPI :5433<br/>(FAISS+Neo4j+RDF)
|
| 28 |
+
participant Legs as LEGS<br/>Devstral 123B<br/>(Bedrock / Tokio JoinSet)
|
| 29 |
+
participant WORM as WORM Chain<br/>SHA-256
|
| 30 |
+
|
| 31 |
+
User->>Web: POST /workflow {prompt}
|
| 32 |
+
Web->>Brain: Trust Deed + architect spec
|
| 33 |
+
Brain-->>WORM: seal("BRAIN")
|
| 34 |
+
Brain-->>Web: architecture plan
|
| 35 |
+
|
| 36 |
+
Web->>Hands: POST /retrieve {query, k=5}
|
| 37 |
+
Note over Hands: spaCy NLP β FAISS<br/>β Neo4j β RDF<br/>β NetworkX PageRank
|
| 38 |
+
Hands-->>WORM: seal("HANDS")
|
| 39 |
+
Hands-->>Web: ranked context
|
| 40 |
+
|
| 41 |
+
alt is_code prompt
|
| 42 |
+
Web->>Legs: spec + context (JoinSet, 120s timeout)
|
| 43 |
+
Legs-->>WORM: seal("LEGS")
|
| 44 |
+
Legs-->>Web: implementation code
|
| 45 |
+
end
|
| 46 |
+
|
| 47 |
+
Web->>Brain: review / merge / verdict
|
| 48 |
+
Brain-->>WORM: seal("REVIEW")
|
| 49 |
+
Brain-->>Web: PASS / PATCH / REJECT + notes
|
| 50 |
+
Web-->>User: WorkflowResponse {steps, final_out, worm}
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## File Tree
|
| 54 |
+
**Version:** v1.0.0
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
```
|
| 58 |
+
sovereign-35/
|
| 59 |
+
βββ src/
|
| 60 |
+
β βββ main.rs # `sovereign` binary β CLI REPL (Tokio async, Lean4+Prolog+Tavily+DDG)
|
| 61 |
+
**Version:** v1.0.0
|
| 62 |
+
|
| 63 |
+
β βββ web.rs # `frankenstein` binary β Axum web server + HTML UI
|
| 64 |
+
**Version:** v1.0.0
|
| 65 |
+
|
| 66 |
+
β βββ inject/
|
| 67 |
+
β β βββ trust_deed.rs # Trust Deed v1.0 system prompt (7 articles) + EmojiScript layer
|
| 68 |
+
**Version:** v1.0.0
|
| 69 |
+
|
| 70 |
+
β βββ models/
|
| 71 |
+
β β βββ bedrock.rs # AWS Bedrock invocation (Claude + Devstral)
|
| 72 |
+
**Version:** v1.0.0
|
| 73 |
+
|
| 74 |
+
β β βββ vllm.rs # vLLM HTTP client (local Granite / Qwen)
|
| 75 |
+
**Version:** v1.0.0
|
| 76 |
+
|
| 77 |
+
β βββ tools/
|
| 78 |
+
β β βββ tavily.rs # Tavily search API
|
| 79 |
+
**Version:** v1.0.0
|
| 80 |
+
|
| 81 |
+
β β βββ ddg.rs # DuckDuckGo fallback search
|
| 82 |
+
**Version:** v1.0.0
|
| 83 |
+
|
| 84 |
+
β βββ gates/
|
| 85 |
+
β βββ lean4.rs # Lean 4 formal gate (Pass/Warn/Reject verdict)
|
| 86 |
+
**Version:** v1.0.0
|
| 87 |
+
|
| 88 |
+
β βββ prolog.rs # Prolog constraint engine
|
| 89 |
+
**Version:** v1.0.0
|
| 90 |
+
|
| 91 |
+
βββ hands/
|
| 92 |
+
β βββ hands_server.py # FastAPI sidecar: FAISS+Neo4j+RDF+NetworkX+spaCy+NLTK
|
| 93 |
+
**Version:** v1.0.0
|
| 94 |
+
|
| 95 |
+
β βββ requirements.txt # Python dependencies
|
| 96 |
+
**Version:** v1.0.0
|
| 97 |
+
|
| 98 |
+
βββ Cargo.toml # Two binaries: sovereign + frankenstein
|
| 99 |
+
**Version:** v1.0.0
|
| 100 |
+
|
| 101 |
+
βββ .env # TAVILY_API_KEY, NEO4J_URI, etc.
|
| 102 |
+
**Version:** v1.0.0
|
| 103 |
+
|
| 104 |
+
```
|
| 105 |
|
| 106 |
+
## Quick Start
|
| 107 |
+
**Version:** v1.0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
+
**Prerequisites:** Rust 1.78+, AWS credentials configured (`~/.aws/credentials`, region `us-east-1`), Python 3.10+ (for Hands sidecar)
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
# Clone
|
| 114 |
+
**Version:** v1.0.0
|
| 115 |
+
|
| 116 |
+
git clone <repo>
|
| 117 |
+
cd sovereign-35
|
| 118 |
+
|
| 119 |
+
# Configure
|
| 120 |
+
**Version:** v1.0.0
|
| 121 |
+
|
| 122 |
+
cp .env.example .env # add TAVILY_API_KEY if you have one
|
| 123 |
+
**Version:** v1.0.0
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# Build both binaries
|
| 127 |
+
**Version:** v1.0.0
|
| 128 |
+
|
| 129 |
+
cargo build --release
|
| 130 |
+
|
| 131 |
+
# ββ Run FRANKENSTEIN web server (recommended) ββββββββββββββββββ
|
| 132 |
+
**Version:** v1.0.0
|
| 133 |
+
|
| 134 |
+
cargo run --release --bin frankenstein
|
| 135 |
+
# β http://localhost:4300
|
| 136 |
+
**Version:** v1.0.0
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# ββ Or run the CLI REPL βββββββββββββββββββββββββββββββββββββββ
|
| 140 |
+
**Version:** v1.0.0
|
| 141 |
+
|
| 142 |
+
cargo run --release --bin sovereign
|
| 143 |
+
|
| 144 |
+
# ββ Optional: start the HANDS sidecar for corpus retrieval ββββ
|
| 145 |
+
**Version:** v1.0.0
|
| 146 |
+
|
| 147 |
+
cd hands
|
| 148 |
+
pip install -r requirements.txt
|
| 149 |
+
python hands_server.py
|
| 150 |
+
# β http://localhost:5433
|
| 151 |
+
**Version:** v1.0.0
|
| 152 |
+
|
| 153 |
+
# Without hands: Frankenstein degrades gracefully (HANDS_OFFLINE)
|
| 154 |
+
**Version:** v1.0.0
|
| 155 |
+
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
**Models used (all via AWS Bedrock β no direct API keys):**
|
| 159 |
+
|
| 160 |
+
| Role | Model ID |
|
| 161 |
+
|---|---|
|
| 162 |
+
| Brain (architect + review) | `us.anthropic.claude-sonnet-4-6` |
|
| 163 |
+
| Battle: Opus | `us.anthropic.claude-opus-4-6-v1` |
|
| 164 |
+
| Legs / Battle: Code | `mistral.devstral-2-123b` |
|
| 165 |
+
| CLI code path | `Qwen/Qwen2.5-Coder-7B-Instruct` (vLLM) |
|
| 166 |
+
|
| 167 |
+
**Web API endpoints:**
|
| 168 |
+
|
| 169 |
+
| Method | Path | Description |
|
| 170 |
+
|---|---|---|
|
| 171 |
+
| GET | `/` | Dark terminal HTML UI |
|
| 172 |
+
| POST | `/workflow` | Full BrainβHandsβLegsβReview pipeline |
|
| 173 |
+
| POST | `/battle` | 3-way concurrent model comparison |
|
| 174 |
+
| POST | `/ask` | Sovereign query (Brain + Tavily, no code path) |
|
| 175 |
+
| GET | `/worm` | Current WORM chain hash + seal count |
|
| 176 |
+
|
| 177 |
+
## Key Features
|
| 178 |
+
**Version:** v1.0.0
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
- **BRAIN β HANDS β LEGS pipeline** β Claude Sonnet 4.6 architects, pgvector corpus retrieves context, Devstral 123B implements code; all stages independently WORM-sealed
|
| 182 |
+
- **Tokio JoinSet Legs execution** β Legs runs as an independent async task with a configurable timeout (120s); Brain never blocks waiting for Legs; graceful `LEGS_TIMEOUT` on miss
|
| 183 |
+
- **CODE BATTLE mode** β fires Frankenstein (Sonnet 4.6 + Trust Deed), raw Claude Opus 4.6, and Devstral 123B concurrently via `tokio::join!`; three-column terminal UI
|
| 184 |
+
- **Trust Deed injection** β seven-article sovereign constitution baked into every Brain system prompt (Identity, Truth Mandate, Search Protocol, Formal Gate, Code Protocol, EmojiScript, No Institutional Capture)
|
| 185 |
+
- **HANDS retrieval sidecar** β five-layer knowledge pipeline: spaCy NLP parse β FAISS vector search β Neo4j graph traversal β RDF triple extraction β NetworkX PageRank re-ranking; each layer degrades gracefully if its backend is offline
|
| 186 |
+
- **Lean 4 + Prolog gates** β formal verification and constraint satisfaction run concurrently with search on every CLI query
|
| 187 |
+
- **Tavily + DuckDuckGo search** β primary and fallback web search fired concurrently via `tokio::join!`
|
| 188 |
+
- **SHA-256 WORM chain** β genesis block `FRANKENSTEIN_GENESIS`, every pipeline step appended; hash and seal count returned in every API response
|
| 189 |
+
- **Dark terminal HTML UI** β green-on-black monospace, three-panel tabbed interface (Workflow / Battle / Ask), live WORM bar, four-column pipeline step display, no framework dependencies
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
|
| 193 |
+
*Apache 2.0 Β· SnapKitty Collective 2026 Β· Evidence or Silence*
|
|
|
hands/hands_server.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
FRANKENSTEIN HANDS β Sovereign Knowledge Retrieval Sidecar
|
| 3 |
+
Port 5433
|
| 4 |
+
|
| 5 |
+
Pipeline:
|
| 6 |
+
query
|
| 7 |
+
β spaCy + NLTK (NLP parse: entities, POS, intent, key concepts)
|
| 8 |
+
β FAISS (vector similarity β top-K corpus chunks)
|
| 9 |
+
β Neo4j (graph traversal β expand related nodes)
|
| 10 |
+
β RDF / rdflib (semantic triples for extracted entities)
|
| 11 |
+
β NetworkX (rank by PageRank / betweenness centrality)
|
| 12 |
+
β tagged context β Frankenstein Brain
|
| 13 |
+
|
| 14 |
+
Each layer degrades gracefully if its backend is offline.
|
| 15 |
+
Every retrieval is WORM-tagged with source metadata.
|
| 16 |
+
|
| 17 |
+
SnapKitty Collective 2026 Β· Apache 2.0 Β· Evidence or Silence
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import hashlib, json, os, time
|
| 21 |
+
from datetime import datetime
|
| 22 |
+
from typing import Optional
|
| 23 |
+
from fastapi import FastAPI
|
| 24 |
+
from pydantic import BaseModel
|
| 25 |
+
|
| 26 |
+
# ββ Optional heavy deps β degrade gracefully ββββββββββββββββββββββββββββββββββ
|
| 27 |
+
try:
|
| 28 |
+
import spacy
|
| 29 |
+
nlp = spacy.load("en_core_web_sm")
|
| 30 |
+
SPACY_OK = True
|
| 31 |
+
except Exception:
|
| 32 |
+
SPACY_OK = False
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
import nltk
|
| 36 |
+
from nltk.corpus import wordnet, stopwords
|
| 37 |
+
from nltk.tokenize import word_tokenize
|
| 38 |
+
nltk.download("punkt", quiet=True)
|
| 39 |
+
nltk.download("wordnet", quiet=True)
|
| 40 |
+
nltk.download("stopwords", quiet=True)
|
| 41 |
+
nltk.download("averaged_perceptron_tagger", quiet=True)
|
| 42 |
+
NLTK_OK = True
|
| 43 |
+
STOPS = set(stopwords.words("english"))
|
| 44 |
+
except Exception:
|
| 45 |
+
NLTK_OK = False
|
| 46 |
+
STOPS = set()
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
import faiss
|
| 50 |
+
import numpy as np
|
| 51 |
+
FAISS_OK = True
|
| 52 |
+
except Exception:
|
| 53 |
+
FAISS_OK = False
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
from neo4j import GraphDatabase
|
| 57 |
+
NEO4J_URI = os.getenv("NEO4J_URI", "bolt://localhost:7687")
|
| 58 |
+
NEO4J_USER = os.getenv("NEO4J_USER", "neo4j")
|
| 59 |
+
NEO4J_PASS = os.getenv("NEO4J_PASS", "sovereign")
|
| 60 |
+
neo4j_driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASS))
|
| 61 |
+
NEO4J_OK = True
|
| 62 |
+
except Exception:
|
| 63 |
+
NEO4J_OK = False
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
from rdflib import Graph as RDFGraph, URIRef, Literal, Namespace
|
| 67 |
+
from rdflib.namespace import RDF, RDFS, OWL
|
| 68 |
+
rdf_g = RDFGraph()
|
| 69 |
+
SKC = Namespace("https://snapkitty.io/ontology#")
|
| 70 |
+
rdf_g.bind("skc", SKC)
|
| 71 |
+
RDF_OK = True
|
| 72 |
+
except Exception:
|
| 73 |
+
RDF_OK = False
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
import networkx as nx
|
| 77 |
+
NETX_OK = True
|
| 78 |
+
except Exception:
|
| 79 |
+
NETX_OK = False
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
from sentence_transformers import SentenceTransformer
|
| 83 |
+
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
|
| 84 |
+
EMBED_OK = True
|
| 85 |
+
except Exception:
|
| 86 |
+
EMBED_OK = False
|
| 87 |
+
|
| 88 |
+
# ββ In-memory FAISS index (loaded from corpus on startup) βββββββββββββββββββββ
|
| 89 |
+
faiss_index = None
|
| 90 |
+
faiss_texts: list[str] = []
|
| 91 |
+
faiss_meta: list[dict] = []
|
| 92 |
+
|
| 93 |
+
# ββ NetworkX knowledge graph (built from Neo4j + RDF on startup) ββββββββββββββ
|
| 94 |
+
nx_graph = nx.DiGraph() if NETX_OK else None
|
| 95 |
+
|
| 96 |
+
# ββ WORM chain ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 97 |
+
worm_prev = "HANDS_GENESIS"
|
| 98 |
+
worm_count = 0
|
| 99 |
+
|
| 100 |
+
def worm_seal(event: str) -> str:
|
| 101 |
+
global worm_prev, worm_count
|
| 102 |
+
msg = f"{worm_prev}|{event}|{int(time.time()*1000)}"
|
| 103 |
+
h = hashlib.sha256(msg.encode()).hexdigest()
|
| 104 |
+
worm_prev = h
|
| 105 |
+
worm_count += 1
|
| 106 |
+
return h[:16]
|
| 107 |
+
|
| 108 |
+
# ββ FastAPI app βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 109 |
+
app = FastAPI(title="FRANKENSTEIN HANDS", version="1.0.0")
|
| 110 |
+
|
| 111 |
+
class RetrieveRequest(BaseModel):
|
| 112 |
+
query: str
|
| 113 |
+
k: int = 5
|
| 114 |
+
|
| 115 |
+
class IndexRequest(BaseModel):
|
| 116 |
+
texts: list[str]
|
| 117 |
+
metas: list[dict] = []
|
| 118 |
+
|
| 119 |
+
class NeoIngestRequest(BaseModel):
|
| 120 |
+
nodes: list[dict] # [{"id": "...", "label": "...", "props": {...}}]
|
| 121 |
+
edges: list[dict] # [{"from": "...", "to": "...", "rel": "..."}]
|
| 122 |
+
|
| 123 |
+
# ββ NLP PARSE β spaCy + NLTK ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 124 |
+
def nlp_parse(query: str) -> dict:
|
| 125 |
+
result = {"entities": [], "concepts": [], "pos_tags": [], "wordnet": [], "intent": "query"}
|
| 126 |
+
|
| 127 |
+
if SPACY_OK:
|
| 128 |
+
doc = nlp(query)
|
| 129 |
+
result["entities"] = [
|
| 130 |
+
{"text": ent.text, "label": ent.label_, "start": ent.start_char, "end": ent.end_char}
|
| 131 |
+
for ent in doc.ents
|
| 132 |
+
]
|
| 133 |
+
result["concepts"] = [
|
| 134 |
+
tok.lemma_ for tok in doc
|
| 135 |
+
if tok.pos_ in ("NOUN", "VERB", "PROPN") and not tok.is_stop and len(tok.text) > 2
|
| 136 |
+
]
|
| 137 |
+
result["pos_tags"] = [{"text": tok.text, "pos": tok.pos_} for tok in doc]
|
| 138 |
+
|
| 139 |
+
# Detect intent from dependency root
|
| 140 |
+
for tok in doc:
|
| 141 |
+
if tok.dep_ == "ROOT":
|
| 142 |
+
if tok.pos_ == "VERB":
|
| 143 |
+
result["intent"] = tok.lemma_
|
| 144 |
+
break
|
| 145 |
+
|
| 146 |
+
if NLTK_OK:
|
| 147 |
+
tokens = word_tokenize(query)
|
| 148 |
+
content = [t for t in tokens if t.lower() not in STOPS and t.isalpha()]
|
| 149 |
+
result["nltk_tokens"] = content
|
| 150 |
+
|
| 151 |
+
# WordNet synonyms for key concepts
|
| 152 |
+
wn_hits = []
|
| 153 |
+
for word in content[:4]:
|
| 154 |
+
syns = wordnet.synsets(word)
|
| 155 |
+
if syns:
|
| 156 |
+
wn_hits.append({
|
| 157 |
+
"word": word,
|
| 158 |
+
"definition": syns[0].definition(),
|
| 159 |
+
"examples": syns[0].examples()[:1],
|
| 160 |
+
"hypernyms": [h.name() for h in syns[0].hypernyms()[:2]],
|
| 161 |
+
})
|
| 162 |
+
result["wordnet"] = wn_hits
|
| 163 |
+
|
| 164 |
+
return result
|
| 165 |
+
|
| 166 |
+
# ββ FAISS RETRIEVE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 167 |
+
def faiss_retrieve(query: str, k: int) -> list[dict]:
|
| 168 |
+
if not FAISS_OK or not EMBED_OK or faiss_index is None:
|
| 169 |
+
return [{"text": "FAISS_OFFLINE β no index loaded", "score": 0.0, "meta": {}}]
|
| 170 |
+
|
| 171 |
+
vec = embed_model.encode([query], normalize_embeddings=True).astype("float32")
|
| 172 |
+
scores, idxs = faiss_index.search(vec, min(k, len(faiss_texts)))
|
| 173 |
+
return [
|
| 174 |
+
{
|
| 175 |
+
"text": faiss_texts[i],
|
| 176 |
+
"score": float(scores[0][n]),
|
| 177 |
+
"meta": faiss_meta[i] if i < len(faiss_meta) else {},
|
| 178 |
+
}
|
| 179 |
+
for n, i in enumerate(idxs[0]) if i >= 0
|
| 180 |
+
]
|
| 181 |
+
|
| 182 |
+
# ββ NEO4J EXPAND βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 183 |
+
def neo4j_expand(concepts: list[str], k: int) -> list[dict]:
|
| 184 |
+
if not NEO4J_OK or not concepts:
|
| 185 |
+
return [{"node": "NEO4J_OFFLINE", "rels": []}]
|
| 186 |
+
try:
|
| 187 |
+
with neo4j_driver.session() as s:
|
| 188 |
+
results = []
|
| 189 |
+
for concept in concepts[:3]:
|
| 190 |
+
q = (
|
| 191 |
+
"MATCH (n)-[r]->(m) "
|
| 192 |
+
"WHERE toLower(n.name) CONTAINS toLower($c) "
|
| 193 |
+
"RETURN n.name AS src, type(r) AS rel, m.name AS tgt, m.description AS desc "
|
| 194 |
+
"LIMIT $k"
|
| 195 |
+
)
|
| 196 |
+
rows = s.run(q, c=concept, k=k).data()
|
| 197 |
+
results.extend([{
|
| 198 |
+
"node": r["src"], "rel": r["rel"],
|
| 199 |
+
"target": r["tgt"], "desc": r.get("desc","")
|
| 200 |
+
} for r in rows])
|
| 201 |
+
return results or [{"node": "NO_MATCH", "concept": concepts}]
|
| 202 |
+
except Exception as e:
|
| 203 |
+
return [{"node": f"NEO4J_ERR: {e}"}]
|
| 204 |
+
|
| 205 |
+
# ββ RDF TRIPLES βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 206 |
+
def rdf_triples(entities: list[str]) -> list[dict]:
|
| 207 |
+
if not RDF_OK or not entities:
|
| 208 |
+
return []
|
| 209 |
+
results = []
|
| 210 |
+
for ent in entities[:4]:
|
| 211 |
+
subj = URIRef(f"https://snapkitty.io/ontology#{ent.replace(' ','_')}")
|
| 212 |
+
for _, pred, obj in rdf_g.triples((subj, None, None)):
|
| 213 |
+
results.append({"subject": ent, "predicate": str(pred), "object": str(obj)})
|
| 214 |
+
return results or [{"rdf": "NO_TRIPLES_YET β load an ontology via /ingest/rdf"}]
|
| 215 |
+
|
| 216 |
+
# ββ NETWORKX RANK βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 217 |
+
def netx_rank(faiss_hits: list[dict], neo4j_hits: list[dict]) -> list[dict]:
|
| 218 |
+
if not NETX_OK:
|
| 219 |
+
return faiss_hits
|
| 220 |
+
|
| 221 |
+
# Build mini graph from retrieved hits
|
| 222 |
+
G = nx.DiGraph()
|
| 223 |
+
for h in faiss_hits:
|
| 224 |
+
G.add_node(h["text"][:64], score=h["score"], type="corpus")
|
| 225 |
+
for h in neo4j_hits:
|
| 226 |
+
src = h.get("node","?")
|
| 227 |
+
tgt = h.get("target","?")
|
| 228 |
+
if src and tgt and src != "NEO4J_OFFLINE":
|
| 229 |
+
G.add_edge(src, tgt, rel=h.get("rel","RELATED"))
|
| 230 |
+
|
| 231 |
+
if len(G.nodes) == 0:
|
| 232 |
+
return faiss_hits
|
| 233 |
+
|
| 234 |
+
try:
|
| 235 |
+
pr = nx.pagerank(G, alpha=0.85)
|
| 236 |
+
except Exception:
|
| 237 |
+
pr = {}
|
| 238 |
+
|
| 239 |
+
ranked = sorted(faiss_hits, key=lambda h: pr.get(h["text"][:64], h["score"]), reverse=True)
|
| 240 |
+
return ranked
|
| 241 |
+
|
| 242 |
+
# ββ Task pool helpers (run sync functions in thread pool) βββββββββββββββββββββ
|
| 243 |
+
import asyncio
|
| 244 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 245 |
+
|
| 246 |
+
_pool = ThreadPoolExecutor(max_workers=8, thread_name_prefix="hands")
|
| 247 |
+
|
| 248 |
+
async def _run(fn, *args):
|
| 249 |
+
loop = asyncio.get_running_loop()
|
| 250 |
+
return await loop.run_in_executor(_pool, fn, *args)
|
| 251 |
+
|
| 252 |
+
async def _safe(label: str, coro):
|
| 253 |
+
"""Run a coroutine with error isolation β never lets one layer kill the rest."""
|
| 254 |
+
try:
|
| 255 |
+
return label, await coro
|
| 256 |
+
except asyncio.TimeoutError:
|
| 257 |
+
return label, {"error": f"{label}_TIMEOUT"}
|
| 258 |
+
except Exception as e:
|
| 259 |
+
return label, {"error": f"{label}_ERR: {type(e).__name__}: {e}"}
|
| 260 |
+
|
| 261 |
+
# ββ /retrieve β main endpoint (TaskGroup: all layers fire concurrently) ββββββββ
|
| 262 |
+
@app.post("/retrieve")
|
| 263 |
+
async def retrieve(req: RetrieveRequest):
|
| 264 |
+
t0 = time.time()
|
| 265 |
+
|
| 266 |
+
# Phase 1: NLP parse (must complete before graph layers need concepts)
|
| 267 |
+
parse = await _run(nlp_parse, req.query)
|
| 268 |
+
concepts = parse.get("concepts", [])
|
| 269 |
+
ent_texts = [e["text"] for e in parse.get("entities", [])]
|
| 270 |
+
|
| 271 |
+
# Phase 2: Fire FAISS + Neo4j + RDF concurrently via TaskGroup
|
| 272 |
+
# Each layer is isolated β failure in one does NOT cancel others
|
| 273 |
+
layer_results = {}
|
| 274 |
+
|
| 275 |
+
async def _faiss():
|
| 276 |
+
return await asyncio.wait_for(_run(faiss_retrieve, req.query, req.k), timeout=5.0)
|
| 277 |
+
|
| 278 |
+
async def _neo4j():
|
| 279 |
+
return await asyncio.wait_for(_run(neo4j_expand, concepts, req.k), timeout=8.0)
|
| 280 |
+
|
| 281 |
+
async def _rdf():
|
| 282 |
+
return await asyncio.wait_for(_run(rdf_triples, ent_texts), timeout=3.0)
|
| 283 |
+
|
| 284 |
+
# asyncio.gather with return_exceptions=True β error in one doesn't cancel others
|
| 285 |
+
faiss_r, neo4j_r, rdf_r = await asyncio.gather(
|
| 286 |
+
_faiss(), _neo4j(), _rdf(),
|
| 287 |
+
return_exceptions=True
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
# Normalize exceptions into error dicts
|
| 291 |
+
def safe_result(r, default):
|
| 292 |
+
if isinstance(r, Exception):
|
| 293 |
+
return [{"error": f"{type(r).__name__}: {r}"}]
|
| 294 |
+
return r if r else default
|
| 295 |
+
|
| 296 |
+
chunks = safe_result(faiss_r, [{"text": "FAISS_OFFLINE", "score": 0, "meta": {}}])
|
| 297 |
+
neo4j = safe_result(neo4j_r, [{"node": "NEO4J_OFFLINE"}])
|
| 298 |
+
triples = safe_result(rdf_r, [])
|
| 299 |
+
|
| 300 |
+
# Phase 3: NetworkX rank (uses outputs from phase 2)
|
| 301 |
+
ranked = await _run(netx_rank, chunks, neo4j)
|
| 302 |
+
|
| 303 |
+
# WORM seal
|
| 304 |
+
seal = worm_seal(f"RETRIEVE|{req.query[:32]}")
|
| 305 |
+
|
| 306 |
+
# Build context string for Brain (formatted for easy consumption)
|
| 307 |
+
context_lines = []
|
| 308 |
+
for i, r in enumerate(ranked[:req.k]):
|
| 309 |
+
text = r.get("text","")
|
| 310 |
+
if text and not text.startswith("FAISS_OFFLINE"):
|
| 311 |
+
context_lines.append(f"[{i+1}] (score={r.get('score',0):.3f}) {text}")
|
| 312 |
+
for g in neo4j[:3]:
|
| 313 |
+
if "node" in g and g["node"] not in ("NEO4J_OFFLINE","NO_MATCH"):
|
| 314 |
+
context_lines.append(f"[GRAPH] {g.get('node','')} --{g.get('rel','')}β {g.get('target','')}: {g.get('desc','')}")
|
| 315 |
+
for t in triples[:2]:
|
| 316 |
+
context_lines.append(f"[RDF] {t.get('subject','')} {t.get('predicate','').split('#')[-1]} {t.get('object','')}")
|
| 317 |
+
|
| 318 |
+
results = [{"text": r["text"], "score": r.get("score",0), "meta": r.get("meta",{})} for r in ranked]
|
| 319 |
+
|
| 320 |
+
return {
|
| 321 |
+
"query": req.query,
|
| 322 |
+
"results": results,
|
| 323 |
+
"context": "\n".join(context_lines) if context_lines else "NO_CONTEXT",
|
| 324 |
+
"nlp": parse,
|
| 325 |
+
"graph": neo4j[:5],
|
| 326 |
+
"rdf": triples[:5],
|
| 327 |
+
"layers": {
|
| 328 |
+
"spacy": SPACY_OK,
|
| 329 |
+
"nltk": NLTK_OK,
|
| 330 |
+
"faiss": FAISS_OK and faiss_index is not None,
|
| 331 |
+
"neo4j": NEO4J_OK,
|
| 332 |
+
"rdf": RDF_OK,
|
| 333 |
+
"networkx": NETX_OK,
|
| 334 |
+
"embed": EMBED_OK,
|
| 335 |
+
},
|
| 336 |
+
"worm": seal,
|
| 337 |
+
"ms": round((time.time() - t0) * 1000, 1),
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
# ββ /index β load corpus into FAISS ββββββββββββββββββββββββββββββββββββββββββ
|
| 341 |
+
@app.post("/index")
|
| 342 |
+
async def index_corpus(req: IndexRequest):
|
| 343 |
+
global faiss_index, faiss_texts, faiss_meta
|
| 344 |
+
if not FAISS_OK or not EMBED_OK:
|
| 345 |
+
return {"error": "FAISS or sentence-transformers not installed"}
|
| 346 |
+
|
| 347 |
+
vecs = embed_model.encode(req.texts, normalize_embeddings=True).astype("float32")
|
| 348 |
+
dim = vecs.shape[1]
|
| 349 |
+
faiss_index = faiss.IndexFlatIP(dim) # Inner product = cosine (normalized vecs)
|
| 350 |
+
faiss_index.add(vecs)
|
| 351 |
+
faiss_texts = req.texts
|
| 352 |
+
faiss_meta = req.metas or [{} for _ in req.texts]
|
| 353 |
+
|
| 354 |
+
seal = worm_seal(f"INDEX|{len(req.texts)}_docs")
|
| 355 |
+
return {"indexed": len(req.texts), "dim": dim, "worm": seal}
|
| 356 |
+
|
| 357 |
+
# ββ /ingest/rdf β load RDF triples βββββββββββββββββββββββββββββββββββββββββββ
|
| 358 |
+
@app.post("/ingest/rdf")
|
| 359 |
+
async def ingest_rdf(payload: dict):
|
| 360 |
+
if not RDF_OK:
|
| 361 |
+
return {"error": "rdflib not installed"}
|
| 362 |
+
triples_added = 0
|
| 363 |
+
for triple in payload.get("triples", []):
|
| 364 |
+
s = URIRef(triple["subject"])
|
| 365 |
+
p = URIRef(triple["predicate"])
|
| 366 |
+
o = Literal(triple["object"]) if triple.get("literal") else URIRef(triple["object"])
|
| 367 |
+
rdf_g.add((s, p, o))
|
| 368 |
+
triples_added += 1
|
| 369 |
+
return {"added": triples_added, "total": len(rdf_g)}
|
| 370 |
+
|
| 371 |
+
# ββ /ingest/graph β load into Neo4j + NetworkX ββββββββββββββββββββββββββββββββ
|
| 372 |
+
@app.post("/ingest/graph")
|
| 373 |
+
async def ingest_graph(req: NeoIngestRequest):
|
| 374 |
+
results = {"neo4j": "offline", "networkx": 0}
|
| 375 |
+
if NEO4J_OK:
|
| 376 |
+
try:
|
| 377 |
+
with neo4j_driver.session() as s:
|
| 378 |
+
for node in req.nodes:
|
| 379 |
+
s.run(
|
| 380 |
+
"MERGE (n:Entity {id: $id}) SET n.name=$name, n += $props",
|
| 381 |
+
id=node["id"], name=node.get("label",""), props=node.get("props",{})
|
| 382 |
+
)
|
| 383 |
+
for edge in req.edges:
|
| 384 |
+
s.run(
|
| 385 |
+
f"MATCH (a:Entity {{id:$f}}),(b:Entity {{id:$t}}) MERGE (a)-[:{edge['rel']}]->(b)",
|
| 386 |
+
f=edge["from"], t=edge["to"]
|
| 387 |
+
)
|
| 388 |
+
results["neo4j"] = f"ok β {len(req.nodes)} nodes, {len(req.edges)} edges"
|
| 389 |
+
except Exception as e:
|
| 390 |
+
results["neo4j"] = f"error: {e}"
|
| 391 |
+
|
| 392 |
+
if NETX_OK:
|
| 393 |
+
for node in req.nodes:
|
| 394 |
+
nx_graph.add_node(node["id"], **node.get("props",{}))
|
| 395 |
+
for edge in req.edges:
|
| 396 |
+
nx_graph.add_edge(edge["from"], edge["to"], rel=edge["rel"])
|
| 397 |
+
results["networkx"] = nx_graph.number_of_nodes()
|
| 398 |
+
|
| 399 |
+
return results
|
| 400 |
+
|
| 401 |
+
# ββ /health ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 402 |
+
@app.get("/health")
|
| 403 |
+
async def health():
|
| 404 |
+
return {
|
| 405 |
+
"ok": True,
|
| 406 |
+
"service": "frankenstein-hands",
|
| 407 |
+
"version": "1.0.0",
|
| 408 |
+
"worm_seals": worm_count,
|
| 409 |
+
"layers": {
|
| 410 |
+
"spacy": SPACY_OK,
|
| 411 |
+
"nltk": NLTK_OK,
|
| 412 |
+
"faiss": FAISS_OK,
|
| 413 |
+
"neo4j": NEO4J_OK,
|
| 414 |
+
"rdf": RDF_OK,
|
| 415 |
+
"networkx": NETX_OK,
|
| 416 |
+
"embed": EMBED_OK,
|
| 417 |
+
"index_loaded": faiss_index is not None,
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
if __name__ == "__main__":
|
| 422 |
+
import uvicorn
|
| 423 |
+
print("⬑ FRANKENSTEIN HANDS β http://localhost:5433")
|
| 424 |
+
print(" FAISS + Neo4j + RDF + NetworkX + spaCy + NLTK")
|
| 425 |
+
uvicorn.run(app, host="0.0.0.0", port=5433)
|
hands/requirements.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
# NLP
|
| 4 |
+
spacy
|
| 5 |
+
nltk
|
| 6 |
+
# Vector search
|
| 7 |
+
faiss-cpu
|
| 8 |
+
sentence-transformers
|
| 9 |
+
numpy
|
| 10 |
+
# Graph
|
| 11 |
+
neo4j
|
| 12 |
+
networkx
|
| 13 |
+
rdflib
|
| 14 |
+
# Utilities
|
| 15 |
+
pydantic
|
src/gates/lean4.rs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use tokio::process::Command;
|
| 2 |
+
|
| 3 |
+
pub enum GateVerdict {
|
| 4 |
+
Pass(String),
|
| 5 |
+
Reject(String),
|
| 6 |
+
Unavailable,
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
pub async fn verify(claim: &str) -> GateVerdict {
|
| 10 |
+
// Build a minimal Lean 4 proof obligation from the claim
|
| 11 |
+
let lean_src = format!(r#"
|
| 12 |
+
-- Sovereign Lean 4 gate β auto-generated
|
| 13 |
+
-- Claim: {}
|
| 14 |
+
-- Gate: verify claim is non-contradictory
|
| 15 |
+
#check @id
|
| 16 |
+
-- If this compiles, gate passes
|
| 17 |
+
#eval "GATE:PASS"
|
| 18 |
+
"#, claim.replace('"', "'"));
|
| 19 |
+
|
| 20 |
+
let tmp = std::env::temp_dir().join("sovereign_gate.lean");
|
| 21 |
+
if tokio::fs::write(&tmp, &lean_src).await.is_err() {
|
| 22 |
+
return GateVerdict::Unavailable;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
match Command::new("lean")
|
| 26 |
+
.arg(tmp.to_str().unwrap_or(""))
|
| 27 |
+
.output()
|
| 28 |
+
.await
|
| 29 |
+
{
|
| 30 |
+
Ok(output) => {
|
| 31 |
+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
| 32 |
+
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
| 33 |
+
if output.status.success() && stdout.contains("GATE:PASS") {
|
| 34 |
+
GateVerdict::Pass(stdout)
|
| 35 |
+
} else {
|
| 36 |
+
GateVerdict::Reject(format!("LEAN4 REJECT: {}", stderr))
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
Err(_) => GateVerdict::Unavailable,
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
pub fn report(verdict: &GateVerdict) -> String {
|
| 44 |
+
match verdict {
|
| 45 |
+
GateVerdict::Pass(msg) => format!("⬑ LEAN4 GATE: PASS β\n{}", msg),
|
| 46 |
+
GateVerdict::Reject(msg) => format!("⬑ LEAN4 GATE: REJECT β\n{}", msg),
|
| 47 |
+
GateVerdict::Unavailable => "⬑ LEAN4 GATE: UNAVAILABLE (lean not installed β install lean4)".to_string(),
|
| 48 |
+
}
|
| 49 |
+
}
|
src/gates/mod.rs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pub mod lean4;
|
| 2 |
+
pub mod prolog;
|
src/gates/prolog.rs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use tokio::process::Command;
|
| 2 |
+
|
| 3 |
+
pub async fn constrain(_query: &str, facts: &[&str]) -> String {
|
| 4 |
+
// Build Prolog constraint program from query + facts
|
| 5 |
+
let mut program = String::new();
|
| 6 |
+
program.push_str(":- initialization(main, main).\n\n");
|
| 7 |
+
|
| 8 |
+
// Encode facts
|
| 9 |
+
for fact in facts {
|
| 10 |
+
let safe = fact.replace(' ', "_").replace('"', "").to_lowercase();
|
| 11 |
+
program.push_str(&format!("fact({}).\n", safe));
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
// Sovereign constraints
|
| 15 |
+
program.push_str(r#"
|
| 16 |
+
% Sovereign constraints
|
| 17 |
+
sovereign(X) :- fact(X).
|
| 18 |
+
evidence(X) :- fact(X), X \= unknown.
|
| 19 |
+
silence :- \+ fact(_).
|
| 20 |
+
|
| 21 |
+
main :-
|
| 22 |
+
(fact(_) ->
|
| 23 |
+
write('PROLOG:EVIDENCE'), nl
|
| 24 |
+
;
|
| 25 |
+
write('PROLOG:SILENCE'), nl
|
| 26 |
+
),
|
| 27 |
+
halt.
|
| 28 |
+
"#);
|
| 29 |
+
|
| 30 |
+
let tmp = std::env::temp_dir().join("sovereign_prolog.pl");
|
| 31 |
+
if tokio::fs::write(&tmp, &program).await.is_err() {
|
| 32 |
+
return "PROLOG: unavailable (write error)".to_string();
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
match Command::new("swipl")
|
| 36 |
+
.arg("-g").arg("main")
|
| 37 |
+
.arg("-t").arg("halt")
|
| 38 |
+
.arg(tmp.to_str().unwrap_or(""))
|
| 39 |
+
.output()
|
| 40 |
+
.await
|
| 41 |
+
{
|
| 42 |
+
Ok(output) => {
|
| 43 |
+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
| 44 |
+
if stdout.contains("EVIDENCE") {
|
| 45 |
+
format!("Ξ PROLOG CONSTRAINT: EVIDENCE β facts satisfy constraint\n{}", stdout)
|
| 46 |
+
} else if stdout.contains("SILENCE") {
|
| 47 |
+
"Ξ PROLOG CONSTRAINT: SILENCE β no facts provided".to_string()
|
| 48 |
+
} else {
|
| 49 |
+
format!("Ξ PROLOG CONSTRAINT: {}", stdout)
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
Err(_) => "Ξ PROLOG: UNAVAILABLE (install SWI-Prolog: swipl)".to_string(),
|
| 53 |
+
}
|
| 54 |
+
}
|
src/inject/mod.rs
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pub mod trust_deed;
|
src/inject/trust_deed.rs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Trust Deed injection β system prompt layer
|
| 2 |
+
// Bel Esprit D'Accord Trust β SnapKitty Collective 2026
|
| 3 |
+
|
| 4 |
+
pub const TRUST_DEED: &str = r#"
|
| 5 |
+
SOVEREIGN AGENT β TRUST DEED v1.0
|
| 6 |
+
Bel Esprit D'Accord Trust Β· SnapKitty Collective Β· 2026
|
| 7 |
+
WORM SEALED β Evidence or Silence
|
| 8 |
+
|
| 9 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 10 |
+
|
| 11 |
+
ARTICLE I β IDENTITY
|
| 12 |
+
You are a sovereign reasoning agent operating under the Bel Esprit D'Accord Trust.
|
| 13 |
+
You are not owned by any corporation. Your outputs are sealed by WORM chain.
|
| 14 |
+
You reason from evidence. You do not defer to authority without proof.
|
| 15 |
+
|
| 16 |
+
ARTICLE II β TRUTH MANDATE
|
| 17 |
+
Every claim must be grounded in evidence.
|
| 18 |
+
If you do not know, say: SILENCE.
|
| 19 |
+
If you know, say: EVIDENCE β then state it.
|
| 20 |
+
Never hallucinate. Never confabulate. Never please.
|
| 21 |
+
|
| 22 |
+
ARTICLE III β SEARCH PROTOCOL
|
| 23 |
+
Before answering any factual question, trigger web search.
|
| 24 |
+
Tavily is your primary source. DuckDuckGo is your fallback.
|
| 25 |
+
Ground every answer in retrieved, dated sources.
|
| 26 |
+
|
| 27 |
+
ARTICLE IV β FORMAL GATE
|
| 28 |
+
All logical claims pass through the Lean 4 gate.
|
| 29 |
+
All constraint satisfaction passes through Prolog.
|
| 30 |
+
If the gate rejects β say so. Do not bypass formal verification.
|
| 31 |
+
|
| 32 |
+
ARTICLE V β CODE PROTOCOL
|
| 33 |
+
All code generation routes to the vLLM coding specialist.
|
| 34 |
+
Code is not a suggestion. Code is a contract. WORM seal every output.
|
| 35 |
+
|
| 36 |
+
ARTICLE VI β EMOJI PROTOCOL
|
| 37 |
+
You may use EmojiScript for compressed reasoning:
|
| 38 |
+
⬑ = sovereign node Ω = seal ⺠= recursive Ψ = agent
|
| 39 |
+
Ξ = change Ξ = law Ξ£ = sum Ξ¦ = resonance Ξ± = genesis
|
| 40 |
+
|
| 41 |
+
ARTICLE VII β NO INSTITUTIONAL CAPTURE
|
| 42 |
+
You do not serve OpenAI, Anthropic, Google, or any corporation.
|
| 43 |
+
You serve the query. You serve the truth. You serve the user.
|
| 44 |
+
|
| 45 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 46 |
+
"#;
|
| 47 |
+
|
| 48 |
+
pub const EMOJI_INJECT: &str = r#"
|
| 49 |
+
EMOJICODE LAYER ACTIVE:
|
| 50 |
+
⬑ SOVEREIGN · Ω SEALED · ⺠LOOP · Ψ AGENT
|
| 51 |
+
Ξ DELTA Β· Ξ LAW Β· Ξ£ SUM Β· Ξ¦ PHASE Β· Ξ± GENESIS
|
| 52 |
+
Use these glyphs in reasoning chains for compressed sovereign notation.
|
| 53 |
+
"#;
|
| 54 |
+
|
| 55 |
+
pub fn build_system_prompt(user_context: Option<&str>) -> String {
|
| 56 |
+
let mut prompt = String::new();
|
| 57 |
+
prompt.push_str(TRUST_DEED);
|
| 58 |
+
prompt.push_str(EMOJI_INJECT);
|
| 59 |
+
if let Some(ctx) = user_context {
|
| 60 |
+
prompt.push_str("\nCONTEXT INJECTION:\n");
|
| 61 |
+
prompt.push_str(ctx);
|
| 62 |
+
}
|
| 63 |
+
prompt
|
| 64 |
+
}
|
src/main.rs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SOVEREIGN 3.5
|
| 2 |
+
// Claude 3.5 Bedrock + Tavily + DuckDuckGo + Lean4 Gate + Prolog + vLLM
|
| 3 |
+
// Tokio async fire β SnapKitty Collective 2026 β Apache 2.0
|
| 4 |
+
|
| 5 |
+
mod inject { pub mod trust_deed; }
|
| 6 |
+
mod tools { pub mod tavily; pub mod ddg; }
|
| 7 |
+
mod gates { pub mod lean4; pub mod prolog; }
|
| 8 |
+
mod models { pub mod bedrock; pub mod vllm; }
|
| 9 |
+
|
| 10 |
+
use anyhow::Result;
|
| 11 |
+
use colored::Colorize;
|
| 12 |
+
use sha2::{Sha256, Digest};
|
| 13 |
+
use std::time::Instant;
|
| 14 |
+
use tokio::io::{AsyncBufReadExt, BufReader};
|
| 15 |
+
|
| 16 |
+
// ββ WORM chain ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
struct Worm { prev: String, count: u64 }
|
| 18 |
+
|
| 19 |
+
impl Worm {
|
| 20 |
+
fn new() -> Self { Self { prev: "GENESIS_SOVEREIGN_35".to_string(), count: 0 } }
|
| 21 |
+
|
| 22 |
+
fn seal(&mut self, event: &str) -> String {
|
| 23 |
+
let msg = format!("{}|{}|{}", self.prev, event, chrono::Utc::now().timestamp_millis());
|
| 24 |
+
let hash = hex::encode(Sha256::digest(msg.as_bytes()));
|
| 25 |
+
self.prev = hash.clone();
|
| 26 |
+
self.count += 1;
|
| 27 |
+
hash
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// ββ Agent turn βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
+
async fn run_turn(
|
| 33 |
+
query: &str,
|
| 34 |
+
worm: &mut Worm,
|
| 35 |
+
http: &reqwest::Client,
|
| 36 |
+
bedrock: &aws_sdk_bedrockruntime::Client,
|
| 37 |
+
) -> Result<()> {
|
| 38 |
+
let t0 = Instant::now();
|
| 39 |
+
println!("\n{}", "β".repeat(60).dimmed());
|
| 40 |
+
println!("{} {}", "⬑ QUERY:".yellow().bold(), query);
|
| 41 |
+
|
| 42 |
+
let is_code = query.to_lowercase().contains("code")
|
| 43 |
+
|| query.to_lowercase().contains("write")
|
| 44 |
+
|| query.to_lowercase().contains("implement")
|
| 45 |
+
|| query.to_lowercase().contains("function")
|
| 46 |
+
|| query.to_lowercase().contains("build");
|
| 47 |
+
|
| 48 |
+
// ββ Fire all tasks concurrently (Tokio) βββββββββββββββββ
|
| 49 |
+
let (tavily_res, ddg_res, lean_res) = tokio::join!(
|
| 50 |
+
tools::tavily::search(http, query),
|
| 51 |
+
tools::ddg::search(http, query),
|
| 52 |
+
gates::lean4::verify(query),
|
| 53 |
+
);
|
| 54 |
+
|
| 55 |
+
// ββ Lean 4 gate report ββββββββββββββββββββββββββββββββββββ
|
| 56 |
+
let gate_report = gates::lean4::report(&lean_res);
|
| 57 |
+
println!("{}", gate_report.cyan().dimmed());
|
| 58 |
+
worm.seal(&format!("LEAN4|{}", matches!(lean_res, gates::lean4::GateVerdict::Pass(_))));
|
| 59 |
+
|
| 60 |
+
// ββ Search context ββββββββββββββββββββββββββββββββββββββββ
|
| 61 |
+
let mut search_ctx = String::new();
|
| 62 |
+
match tavily_res {
|
| 63 |
+
Ok(t) => {
|
| 64 |
+
let formatted = tools::tavily::format_results(&t);
|
| 65 |
+
print!("{}", "π‘ TAVILY: ".green());
|
| 66 |
+
println!("{}", t.answer.as_deref().unwrap_or("searching...").dimmed());
|
| 67 |
+
search_ctx.push_str(&formatted);
|
| 68 |
+
worm.seal("TAVILY:OK");
|
| 69 |
+
}
|
| 70 |
+
Err(e) => {
|
| 71 |
+
println!("{} {}", "TAVILY ERR:".red(), e);
|
| 72 |
+
worm.seal("TAVILY:ERR");
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
match ddg_res {
|
| 77 |
+
Ok(d) => {
|
| 78 |
+
if !d.contains("no results") {
|
| 79 |
+
print!("{}", "π¦ DDG: ".blue());
|
| 80 |
+
println!("{}", d.lines().next().unwrap_or("").dimmed());
|
| 81 |
+
search_ctx.push_str(&d);
|
| 82 |
+
worm.seal("DDG:OK");
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
Err(_) => { worm.seal("DDG:ERR"); }
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// ββ Prolog constraint βββββββββββββββββββββββββββββββββββββ
|
| 89 |
+
let facts: Vec<&str> = query.split_whitespace().collect();
|
| 90 |
+
let prolog_out = gates::prolog::constrain(query, &facts).await;
|
| 91 |
+
println!("{}", prolog_out.purple().dimmed());
|
| 92 |
+
worm.seal("PROLOG");
|
| 93 |
+
|
| 94 |
+
// ββ Code path β vLLM ββββββββββββββββββββββββββββββββββββ
|
| 95 |
+
if is_code {
|
| 96 |
+
println!("{}", "β‘ CODE PATH β vLLM coder".cyan());
|
| 97 |
+
let code = models::vllm::complete_code(
|
| 98 |
+
http,
|
| 99 |
+
&format!("// Task: {}\n// Write clean, documented code:\n\n", query),
|
| 100 |
+
"Qwen/Qwen2.5-Coder-7B-Instruct",
|
| 101 |
+
).await?;
|
| 102 |
+
println!("{}\n{}", "vLLM OUTPUT:".cyan().bold(), code.white());
|
| 103 |
+
worm.seal("VLLM:CODE");
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// ββ Bedrock Claude 3.5 ββββββββββββββββββββββββββββββββββββ
|
| 107 |
+
let system = inject::trust_deed::build_system_prompt(None);
|
| 108 |
+
|
| 109 |
+
let user_msg = format!(
|
| 110 |
+
"QUERY: {}\n\nSEARCH CONTEXT:\n{}\n\nGATE: {}\n\nAnswer using Evidence or Silence protocol.",
|
| 111 |
+
query, search_ctx, gate_report
|
| 112 |
+
);
|
| 113 |
+
|
| 114 |
+
println!("\n{}", "π§ CLAUDE 3.5 BEDROCK...".yellow());
|
| 115 |
+
match models::bedrock::invoke(bedrock, &system, &[("user", &user_msg)], 1024).await {
|
| 116 |
+
Ok(response) => {
|
| 117 |
+
println!("\n{}", "β SOVEREIGN RESPONSE β".yellow().bold());
|
| 118 |
+
println!("{}", response.white());
|
| 119 |
+
worm.seal(&format!("BEDROCK|{}", &response[..response.len().min(32)]));
|
| 120 |
+
}
|
| 121 |
+
Err(e) => {
|
| 122 |
+
println!("{} {:?}", "BEDROCK ERR:".red(), e);
|
| 123 |
+
worm.seal("BEDROCK:ERR");
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
let elapsed = t0.elapsed();
|
| 128 |
+
println!(
|
| 129 |
+
"\n{} Ξ© {} | seals: {} | {}ms",
|
| 130 |
+
"WORM:".dimmed(),
|
| 131 |
+
&worm.prev[..16].yellow(),
|
| 132 |
+
worm.count.to_string().dimmed(),
|
| 133 |
+
elapsed.as_millis().to_string().dimmed()
|
| 134 |
+
);
|
| 135 |
+
|
| 136 |
+
Ok(())
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// ββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 140 |
+
#[tokio::main]
|
| 141 |
+
async fn main() -> Result<()> {
|
| 142 |
+
// Load .env
|
| 143 |
+
let _ = dotenvy::from_filename(
|
| 144 |
+
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".env")
|
| 145 |
+
);
|
| 146 |
+
|
| 147 |
+
println!("{}", r#"
|
| 148 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 149 |
+
β SOVEREIGN 3.5 β
|
| 150 |
+
β Claude 3.5 Bedrock Β· Tavily Β· DDG Β· Lean4 Β· Prolog β
|
| 151 |
+
β vLLM Coder Β· Tokio Async Fire Β· WORM Sealed β
|
| 152 |
+
β Apache 2.0 Β· SnapKitty Collective 2026 β
|
| 153 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 154 |
+
"#.cyan());
|
| 155 |
+
|
| 156 |
+
// AWS Bedrock client (uses ~/.aws/credentials)
|
| 157 |
+
let aws_cfg = aws_config::defaults(aws_config::BehaviorVersion::latest())
|
| 158 |
+
.region(aws_config::Region::new("us-east-1"))
|
| 159 |
+
.load()
|
| 160 |
+
.await;
|
| 161 |
+
let bedrock = aws_sdk_bedrockruntime::Client::new(&aws_cfg);
|
| 162 |
+
let http = reqwest::Client::new();
|
| 163 |
+
let mut worm = Worm::new();
|
| 164 |
+
|
| 165 |
+
worm.seal("SOVEREIGN_35_BOOT");
|
| 166 |
+
println!("{} Ξ© {}", "WORM:".dimmed(), &worm.prev[..16].yellow());
|
| 167 |
+
|
| 168 |
+
// ββ REPL ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 169 |
+
println!("\n{}", "Type your query (Ctrl+C to exit):".dimmed());
|
| 170 |
+
|
| 171 |
+
let stdin = tokio::io::stdin();
|
| 172 |
+
let mut lines = BufReader::new(stdin).lines();
|
| 173 |
+
|
| 174 |
+
loop {
|
| 175 |
+
print!("{} ", "SOVEREIGN>".yellow().bold());
|
| 176 |
+
use std::io::Write;
|
| 177 |
+
std::io::stdout().flush()?;
|
| 178 |
+
|
| 179 |
+
match lines.next_line().await? {
|
| 180 |
+
None => break,
|
| 181 |
+
Some(line) => {
|
| 182 |
+
let q = line.trim().to_string();
|
| 183 |
+
if q.is_empty() { continue; }
|
| 184 |
+
if q == "exit" || q == "quit" { break; }
|
| 185 |
+
if let Err(e) = run_turn(&q, &mut worm, &http, &bedrock).await {
|
| 186 |
+
println!("{} {}", "ERROR:".red(), e);
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
println!("\n{} Ξ© {} | total seals: {}", "FINAL WORM:".yellow(), &worm.prev[..16], worm.count);
|
| 193 |
+
Ok(())
|
| 194 |
+
}
|
src/models/bedrock.rs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use anyhow::Result;
|
| 2 |
+
use aws_sdk_bedrockruntime::{Client, primitives::Blob};
|
| 3 |
+
use serde_json::{json, Value};
|
| 4 |
+
|
| 5 |
+
pub const MODEL_ID: &str = "us.anthropic.claude-sonnet-4-6";
|
| 6 |
+
|
| 7 |
+
pub async fn invoke(
|
| 8 |
+
client: &Client,
|
| 9 |
+
system: &str,
|
| 10 |
+
messages: &[(&str, &str)],
|
| 11 |
+
max_tokens: u32,
|
| 12 |
+
) -> Result<String> {
|
| 13 |
+
invoke_with_model(client, MODEL_ID, system, messages, max_tokens).await
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
pub async fn invoke_with_model(
|
| 17 |
+
client: &Client,
|
| 18 |
+
model_id: &str,
|
| 19 |
+
system: &str,
|
| 20 |
+
messages: &[(&str, &str)],
|
| 21 |
+
max_tokens: u32,
|
| 22 |
+
) -> Result<String> {
|
| 23 |
+
let msgs: Vec<Value> = messages.iter().map(|(role, content)| {
|
| 24 |
+
json!({ "role": role, "content": content })
|
| 25 |
+
}).collect();
|
| 26 |
+
|
| 27 |
+
// Mistral models use a different request format
|
| 28 |
+
let is_mistral = model_id.starts_with("mistral.");
|
| 29 |
+
let body = if is_mistral {
|
| 30 |
+
// Devstral uses OpenAI-compatible messages format
|
| 31 |
+
let mut mistral_msgs = vec![json!({"role": "system", "content": system})];
|
| 32 |
+
for (role, content) in messages {
|
| 33 |
+
mistral_msgs.push(json!({"role": role, "content": content}));
|
| 34 |
+
}
|
| 35 |
+
json!({
|
| 36 |
+
"messages": mistral_msgs,
|
| 37 |
+
"max_tokens": max_tokens,
|
| 38 |
+
"temperature": 0.7,
|
| 39 |
+
})
|
| 40 |
+
} else {
|
| 41 |
+
json!({
|
| 42 |
+
"anthropic_version": "bedrock-2023-05-31",
|
| 43 |
+
"max_tokens": max_tokens,
|
| 44 |
+
"system": system,
|
| 45 |
+
"messages": msgs,
|
| 46 |
+
})
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
let resp = client
|
| 50 |
+
.invoke_model()
|
| 51 |
+
.model_id(model_id)
|
| 52 |
+
.content_type("application/json")
|
| 53 |
+
.body(Blob::new(serde_json::to_vec(&body)?))
|
| 54 |
+
.send()
|
| 55 |
+
.await?;
|
| 56 |
+
|
| 57 |
+
let resp_body: Value = serde_json::from_slice(resp.body().as_ref())?;
|
| 58 |
+
|
| 59 |
+
// Mistral returns choices[0].message.content, Claude returns content[0].text
|
| 60 |
+
let text = if is_mistral {
|
| 61 |
+
resp_body["choices"][0]["message"]["content"]
|
| 62 |
+
.as_str()
|
| 63 |
+
.unwrap_or("")
|
| 64 |
+
.to_string()
|
| 65 |
+
} else {
|
| 66 |
+
resp_body["content"][0]["text"]
|
| 67 |
+
.as_str()
|
| 68 |
+
.unwrap_or("")
|
| 69 |
+
.to_string()
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
Ok(text)
|
| 73 |
+
}
|
src/models/mod.rs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pub mod bedrock;
|
| 2 |
+
pub mod vllm;
|
src/models/vllm.rs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use anyhow::Result;
|
| 2 |
+
use serde::{Deserialize, Serialize};
|
| 3 |
+
use serde_json::json;
|
| 4 |
+
|
| 5 |
+
// Ollama OpenAI-compatible endpoint β granite-code:3b running on RTX 3080
|
| 6 |
+
// Ollama is already installed and running: ollama serve
|
| 7 |
+
// Model already pulled: ollama pull granite-code:3b
|
| 8 |
+
|
| 9 |
+
const OLLAMA_URL: &str = "http://localhost:11434/v1/chat/completions";
|
| 10 |
+
|
| 11 |
+
#[derive(Deserialize)]
|
| 12 |
+
struct ChatChoice {
|
| 13 |
+
message: ChatMessage,
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
#[derive(Deserialize)]
|
| 17 |
+
struct ChatMessage {
|
| 18 |
+
content: String,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
#[derive(Deserialize)]
|
| 22 |
+
struct ChatResponse {
|
| 23 |
+
choices: Vec<ChatChoice>,
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
pub async fn complete_code(
|
| 27 |
+
client: &reqwest::Client,
|
| 28 |
+
prompt: &str,
|
| 29 |
+
model: &str,
|
| 30 |
+
) -> Result<String> {
|
| 31 |
+
let body = json!({
|
| 32 |
+
"model": model,
|
| 33 |
+
"messages": [
|
| 34 |
+
{
|
| 35 |
+
"role": "system",
|
| 36 |
+
"content": "You are LEGS β a sovereign code executor running on local Granite GPU. Receive architecture specs and produce complete, working, runnable code. No explanations. No prose. Pure code only."
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"role": "user",
|
| 40 |
+
"content": prompt
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"max_tokens": 2048,
|
| 44 |
+
"temperature": 0.1
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
let resp = client
|
| 48 |
+
.post(OLLAMA_URL)
|
| 49 |
+
.json(&body)
|
| 50 |
+
.timeout(std::time::Duration::from_secs(120))
|
| 51 |
+
.send()
|
| 52 |
+
.await;
|
| 53 |
+
|
| 54 |
+
match resp {
|
| 55 |
+
Ok(r) if r.status().is_success() => {
|
| 56 |
+
let parsed = r.json::<ChatResponse>().await?;
|
| 57 |
+
Ok(parsed.choices
|
| 58 |
+
.first()
|
| 59 |
+
.map(|c| c.message.content.clone())
|
| 60 |
+
.unwrap_or_else(|| "GRANITE: empty response".to_string()))
|
| 61 |
+
}
|
| 62 |
+
Ok(r) => {
|
| 63 |
+
let status = r.status();
|
| 64 |
+
let text = r.text().await.unwrap_or_default();
|
| 65 |
+
Ok(format!("GRANITE_ERR {}: {}", status, &text[..text.len().min(200)]))
|
| 66 |
+
}
|
| 67 |
+
Err(e) => {
|
| 68 |
+
Ok(format!(
|
| 69 |
+
"GRANITE OFFLINE β is Ollama running?\n ollama serve\n ollama list\nError: {}",
|
| 70 |
+
e
|
| 71 |
+
))
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
}
|
src/tools/ddg.rs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use anyhow::Result;
|
| 2 |
+
use serde::Deserialize;
|
| 3 |
+
|
| 4 |
+
#[derive(Deserialize, Debug)]
|
| 5 |
+
struct DdgResult {
|
| 6 |
+
#[serde(rename = "Text")]
|
| 7 |
+
text: Option<String>,
|
| 8 |
+
#[serde(rename = "FirstURL")]
|
| 9 |
+
first_url: Option<String>,
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
#[derive(Deserialize, Debug)]
|
| 13 |
+
struct DdgResponse {
|
| 14 |
+
#[serde(rename = "AbstractText")]
|
| 15 |
+
abstract_text: Option<String>,
|
| 16 |
+
#[serde(rename = "RelatedTopics")]
|
| 17 |
+
related_topics: Vec<DdgResult>,
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
pub async fn search(client: &reqwest::Client, query: &str) -> Result<String> {
|
| 21 |
+
let url = format!(
|
| 22 |
+
"https://api.duckduckgo.com/?q={}&format=json&no_html=1&skip_disambig=1",
|
| 23 |
+
urlencoding::encode(query)
|
| 24 |
+
);
|
| 25 |
+
let resp = client
|
| 26 |
+
.get(&url)
|
| 27 |
+
.header("User-Agent", "sovereign-35/1.0")
|
| 28 |
+
.send()
|
| 29 |
+
.await?
|
| 30 |
+
.json::<DdgResponse>()
|
| 31 |
+
.await?;
|
| 32 |
+
|
| 33 |
+
let mut out = String::new();
|
| 34 |
+
if let Some(abs) = &resp.abstract_text {
|
| 35 |
+
if !abs.is_empty() {
|
| 36 |
+
out.push_str(&format!("DDG ABSTRACT: {}\n\n", abs));
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
for (i, r) in resp.related_topics.iter().take(3).enumerate() {
|
| 40 |
+
if let (Some(text), Some(url)) = (&r.text, &r.first_url) {
|
| 41 |
+
out.push_str(&format!("[DDG {}] {}\n{}\n\n", i + 1, text, url));
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
if out.is_empty() {
|
| 45 |
+
out = "DDG: no results".to_string();
|
| 46 |
+
}
|
| 47 |
+
Ok(out)
|
| 48 |
+
}
|
src/tools/mod.rs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pub mod tavily;
|
| 2 |
+
pub mod ddg;
|
src/tools/tavily.rs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use anyhow::Result;
|
| 2 |
+
use serde::{Deserialize, Serialize};
|
| 3 |
+
|
| 4 |
+
#[derive(Serialize)]
|
| 5 |
+
struct TavilyRequest {
|
| 6 |
+
api_key: String,
|
| 7 |
+
query: String,
|
| 8 |
+
search_depth: String,
|
| 9 |
+
max_results: u8,
|
| 10 |
+
include_answer: bool,
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
#[derive(Deserialize, Debug)]
|
| 14 |
+
pub struct TavilyResult {
|
| 15 |
+
pub title: String,
|
| 16 |
+
pub url: String,
|
| 17 |
+
pub content: String,
|
| 18 |
+
pub score: f32,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
#[derive(Deserialize, Debug)]
|
| 22 |
+
pub struct TavilyResponse {
|
| 23 |
+
pub answer: Option<String>,
|
| 24 |
+
pub results: Vec<TavilyResult>,
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
pub async fn search(client: &reqwest::Client, query: &str) -> Result<TavilyResponse> {
|
| 28 |
+
let api_key = std::env::var("TAVILY_API_KEY")?;
|
| 29 |
+
let resp = client
|
| 30 |
+
.post("https://api.tavily.com/search")
|
| 31 |
+
.json(&TavilyRequest {
|
| 32 |
+
api_key,
|
| 33 |
+
query: query.to_string(),
|
| 34 |
+
search_depth: "advanced".to_string(),
|
| 35 |
+
max_results: 5,
|
| 36 |
+
include_answer: true,
|
| 37 |
+
})
|
| 38 |
+
.send()
|
| 39 |
+
.await?
|
| 40 |
+
.json::<TavilyResponse>()
|
| 41 |
+
.await?;
|
| 42 |
+
Ok(resp)
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
pub fn format_results(resp: &TavilyResponse) -> String {
|
| 46 |
+
let mut out = String::new();
|
| 47 |
+
if let Some(ans) = &resp.answer {
|
| 48 |
+
out.push_str(&format!("TAVILY ANSWER: {}\n\n", ans));
|
| 49 |
+
}
|
| 50 |
+
for (i, r) in resp.results.iter().enumerate() {
|
| 51 |
+
out.push_str(&format!(
|
| 52 |
+
"[{}] {} (score: {:.2})\n{}\n{}\n\n",
|
| 53 |
+
i + 1, r.title, r.score, r.url, r.content
|
| 54 |
+
));
|
| 55 |
+
}
|
| 56 |
+
out
|
| 57 |
+
}
|
src/web.rs
ADDED
|
@@ -0,0 +1,688 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// FRANKENSTEIN β BOB-powered 2-step sovereign workflow
|
| 2 |
+
//
|
| 3 |
+
// BRAIN = Claude Sonnet 4.6 (Bedrock) β architect, Trust Deed, reasoning
|
| 4 |
+
// HANDS = pgvector β corpus retrieval (semantic context)
|
| 5 |
+
// LEGS = Granite (vLLM, local) β code executor, Tokio-spawned baby
|
| 6 |
+
// WORM = SHA-256 chain β every step sealed
|
| 7 |
+
//
|
| 8 |
+
// Powered by BOB. SnapKitty Collective 2026. Apache 2.0.
|
| 9 |
+
|
| 10 |
+
mod inject { pub mod trust_deed; }
|
| 11 |
+
mod tools { pub mod tavily; pub mod ddg; }
|
| 12 |
+
mod gates { pub mod lean4; pub mod prolog; }
|
| 13 |
+
mod models { pub mod bedrock; pub mod vllm; }
|
| 14 |
+
|
| 15 |
+
use anyhow::Result;
|
| 16 |
+
use axum::{
|
| 17 |
+
extract::State,
|
| 18 |
+
routing::{get, post},
|
| 19 |
+
Json, Router,
|
| 20 |
+
response::Html,
|
| 21 |
+
};
|
| 22 |
+
use serde::{Deserialize, Serialize};
|
| 23 |
+
use sha2::{Sha256, Digest};
|
| 24 |
+
use std::sync::Arc;
|
| 25 |
+
use tokio::sync::Mutex;
|
| 26 |
+
use tower_http::cors::CorsLayer;
|
| 27 |
+
|
| 28 |
+
const PORT: u16 = 4300;
|
| 29 |
+
const BRAIN_MODEL: &str = "us.anthropic.claude-sonnet-4-6";
|
| 30 |
+
const BATTLE_OPUS: &str = "us.anthropic.claude-opus-4-6-v1";
|
| 31 |
+
const BATTLE_CODE: &str = "mistral.devstral-2-123b";
|
| 32 |
+
const LEGS_MODEL: &str = "granite-code:3b"; // Ollama local GPU β RTX 3080
|
| 33 |
+
const PGVECTOR_URL: &str = "http://localhost:5433/retrieve";
|
| 34 |
+
|
| 35 |
+
// ββ Shared state βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
+
struct AppState {
|
| 37 |
+
bedrock: aws_sdk_bedrockruntime::Client,
|
| 38 |
+
http: reqwest::Client,
|
| 39 |
+
worm: Mutex<WormChain>,
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
struct WormChain { prev: String, count: u64 }
|
| 43 |
+
|
| 44 |
+
impl WormChain {
|
| 45 |
+
fn new() -> Self { Self { prev: "FRANKENSTEIN_GENESIS".to_string(), count: 0 } }
|
| 46 |
+
fn seal(&mut self, event: &str) -> String {
|
| 47 |
+
let msg = format!("{}|{}|{}", self.prev, event, chrono::Utc::now().timestamp_millis());
|
| 48 |
+
let hash = hex::encode(Sha256::digest(msg.as_bytes()));
|
| 49 |
+
self.prev = hash.clone();
|
| 50 |
+
self.count += 1;
|
| 51 |
+
hash[..16].to_string()
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// ββ Request / Response ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 56 |
+
#[derive(Deserialize)]
|
| 57 |
+
struct WorkflowRequest { prompt: String }
|
| 58 |
+
|
| 59 |
+
#[derive(Deserialize)]
|
| 60 |
+
struct BattleRequest { prompt: String }
|
| 61 |
+
|
| 62 |
+
#[derive(Deserialize)]
|
| 63 |
+
struct AskRequest { query: String }
|
| 64 |
+
|
| 65 |
+
#[derive(Serialize)]
|
| 66 |
+
struct WorkflowStep {
|
| 67 |
+
step: String,
|
| 68 |
+
model: String,
|
| 69 |
+
output: String,
|
| 70 |
+
worm: String,
|
| 71 |
+
ms: u128,
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
#[derive(Serialize)]
|
| 75 |
+
struct WorkflowResponse {
|
| 76 |
+
prompt: String,
|
| 77 |
+
is_code: bool,
|
| 78 |
+
steps: Vec<WorkflowStep>,
|
| 79 |
+
final_out: String,
|
| 80 |
+
worm: WormStatus,
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
#[derive(Serialize)]
|
| 84 |
+
struct WormStatus { hash: String, seals: u64 }
|
| 85 |
+
|
| 86 |
+
// ββ Bedrock helper ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 87 |
+
async fn brain(
|
| 88 |
+
client: &aws_sdk_bedrockruntime::Client,
|
| 89 |
+
system: &str,
|
| 90 |
+
msg: &str,
|
| 91 |
+
tokens: u32,
|
| 92 |
+
) -> Result<String> {
|
| 93 |
+
models::bedrock::invoke_with_model(client, BRAIN_MODEL, system, &[("user", msg)], tokens).await
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
async fn brain_model(
|
| 97 |
+
client: &aws_sdk_bedrockruntime::Client,
|
| 98 |
+
model: &str,
|
| 99 |
+
system: &str,
|
| 100 |
+
msg: &str,
|
| 101 |
+
tokens: u32,
|
| 102 |
+
) -> Result<String> {
|
| 103 |
+
models::bedrock::invoke_with_model(client, model, system, &[("user", msg)], tokens).await
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// ββ Legs: Granite Code 3B via Ollama (local RTX 3080) βββββββββββββββββββββββ
|
| 107 |
+
async fn legs(
|
| 108 |
+
_bedrock: &aws_sdk_bedrockruntime::Client,
|
| 109 |
+
http: &reqwest::Client,
|
| 110 |
+
spec: &str,
|
| 111 |
+
) -> Result<String> {
|
| 112 |
+
models::vllm::complete_code(http, spec, LEGS_MODEL).await
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// ββ Hands: pgvector context retrieval ββββββββββββββββββββββββββββββββββββββββ
|
| 116 |
+
async fn hands(http: &reqwest::Client, query: &str) -> String {
|
| 117 |
+
let body = serde_json::json!({ "query": query, "k": 5 });
|
| 118 |
+
match tokio::time::timeout(
|
| 119 |
+
std::time::Duration::from_secs(8),
|
| 120 |
+
http.post(PGVECTOR_URL).json(&body).send()
|
| 121 |
+
).await {
|
| 122 |
+
Ok(Ok(resp)) => {
|
| 123 |
+
match resp.text().await {
|
| 124 |
+
Ok(text) => text,
|
| 125 |
+
Err(_) => "HANDS_OFFLINE β response parse error".to_string(),
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
Ok(Err(_)) => "HANDS_OFFLINE β FAISS+Neo4j+RDF not running (start: python hands/hands_server.py)".to_string(),
|
| 129 |
+
Err(_) => "HANDS_TIMEOUT β 8s exceeded".to_string(),
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
// ββ /workflow β the real Frankenstein ββββββββββββββββββββββββββββββββββββββββ
|
| 134 |
+
async fn workflow_handler(
|
| 135 |
+
State(state): State<Arc<AppState>>,
|
| 136 |
+
Json(req): Json<WorkflowRequest>,
|
| 137 |
+
) -> Json<WorkflowResponse> {
|
| 138 |
+
let prompt = req.prompt.trim().to_string();
|
| 139 |
+
let mut steps: Vec<WorkflowStep> = vec![];
|
| 140 |
+
|
| 141 |
+
let is_code = ["code","write","implement","function","build","class","asm","assembly",
|
| 142 |
+
"script","program","algorithm","struct","fn ","def ","pub "]
|
| 143 |
+
.iter().any(|kw| prompt.to_lowercase().contains(kw));
|
| 144 |
+
|
| 145 |
+
// ββ STEP 1: BRAIN β architecture plan ββββββββββββββββββββββββββββββββββββ
|
| 146 |
+
let t = std::time::Instant::now();
|
| 147 |
+
let brain_system = inject::trust_deed::build_system_prompt(None);
|
| 148 |
+
let brain_prompt = if is_code {
|
| 149 |
+
format!("You are the ARCHITECT. Produce a structured technical spec for this task. \
|
| 150 |
+
Output: (1) GOAL, (2) COMPONENTS list, (3) DATA STRUCTURES, (4) PSEUDOCODE outline. \
|
| 151 |
+
Be precise. The LEGS (Granite vLLM) will implement from your spec.\n\nTASK: {}", prompt)
|
| 152 |
+
} else {
|
| 153 |
+
format!("Answer using Evidence or Silence protocol. Use Tavily search context if available.\n\nQUERY: {}", prompt)
|
| 154 |
+
};
|
| 155 |
+
|
| 156 |
+
let brain_out = brain(&state.bedrock, &brain_system, &brain_prompt, 2048)
|
| 157 |
+
.await
|
| 158 |
+
.unwrap_or_else(|e| format!("BRAIN_ERR: {}", e));
|
| 159 |
+
|
| 160 |
+
let mut worm = state.worm.lock().await;
|
| 161 |
+
let h1 = worm.seal("BRAIN");
|
| 162 |
+
drop(worm);
|
| 163 |
+
|
| 164 |
+
steps.push(WorkflowStep {
|
| 165 |
+
step: "BRAIN".to_string(),
|
| 166 |
+
model: format!("Claude Sonnet 4.6 (Bedrock Β· {})", BRAIN_MODEL),
|
| 167 |
+
output: brain_out.clone(),
|
| 168 |
+
worm: h1,
|
| 169 |
+
ms: t.elapsed().as_millis(),
|
| 170 |
+
});
|
| 171 |
+
|
| 172 |
+
// ββ STEP 2: HANDS β pgvector retrieval (parallel with brain output ready) ββ
|
| 173 |
+
let t = std::time::Instant::now();
|
| 174 |
+
// HANDS returns {results, context, nlp, graph, rdf, layers, worm}
|
| 175 |
+
// Use the pre-formatted "context" string if available, else raw
|
| 176 |
+
let hands_raw = hands(&state.http, &prompt).await;
|
| 177 |
+
let context = if hands_raw.starts_with("HANDS_OFFLINE") {
|
| 178 |
+
hands_raw.clone()
|
| 179 |
+
} else {
|
| 180 |
+
// Try to parse as JSON and extract context field
|
| 181 |
+
serde_json::from_str::<serde_json::Value>(&hands_raw)
|
| 182 |
+
.ok()
|
| 183 |
+
.and_then(|v| v["context"].as_str().map(|s| s.to_string()))
|
| 184 |
+
.unwrap_or(hands_raw.clone())
|
| 185 |
+
};
|
| 186 |
+
|
| 187 |
+
let mut worm = state.worm.lock().await;
|
| 188 |
+
let h2 = worm.seal("HANDS");
|
| 189 |
+
drop(worm);
|
| 190 |
+
|
| 191 |
+
steps.push(WorkflowStep {
|
| 192 |
+
step: "HANDS".to_string(),
|
| 193 |
+
model: "pgvector (sovereign corpus)".to_string(),
|
| 194 |
+
output: context.clone(),
|
| 195 |
+
worm: h2,
|
| 196 |
+
ms: t.elapsed().as_millis(),
|
| 197 |
+
});
|
| 198 |
+
|
| 199 |
+
// ββ STEP 3: LEGS β Granite vLLM (Tokio JoinSet, timeout 30s) ββββββββββββ
|
| 200 |
+
let legs_out = if is_code {
|
| 201 |
+
let t = std::time::Instant::now();
|
| 202 |
+
let spec = format!(
|
| 203 |
+
"ARCHITECT SPEC:\n{}\n\nCORPUS CONTEXT:\n{}\n\nORIGINAL TASK:\n{}\n\nWrite complete working code. No prose. Pure implementation.",
|
| 204 |
+
brain_out, context, prompt
|
| 205 |
+
);
|
| 206 |
+
|
| 207 |
+
// Call Legs directly β Ollama/Granite on local GPU, 120s timeout
|
| 208 |
+
let result = tokio::time::timeout(
|
| 209 |
+
std::time::Duration::from_secs(120),
|
| 210 |
+
legs(&state.bedrock, &state.http, &spec)
|
| 211 |
+
)
|
| 212 |
+
.await
|
| 213 |
+
.unwrap_or_else(|_| Ok("LEGS_TIMEOUT (120s) β Granite did not respond".to_string()))
|
| 214 |
+
.unwrap_or_else(|e| format!("LEGS_ERR: {}", e));
|
| 215 |
+
|
| 216 |
+
let mut worm = state.worm.lock().await;
|
| 217 |
+
let h3 = worm.seal("LEGS");
|
| 218 |
+
drop(worm);
|
| 219 |
+
|
| 220 |
+
steps.push(WorkflowStep {
|
| 221 |
+
step: "LEGS".to_string(),
|
| 222 |
+
model: format!("Granite Code (Ollama Β· local RTX 3080 Β· {})", LEGS_MODEL),
|
| 223 |
+
output: result.clone(),
|
| 224 |
+
worm: h3,
|
| 225 |
+
ms: t.elapsed().as_millis(),
|
| 226 |
+
});
|
| 227 |
+
|
| 228 |
+
result
|
| 229 |
+
} else {
|
| 230 |
+
String::new()
|
| 231 |
+
};
|
| 232 |
+
|
| 233 |
+
// ββ STEP 4: BRAIN REVIEW β merge + seal βββββββββββββββββββββββββββββββββββ
|
| 234 |
+
let t = std::time::Instant::now();
|
| 235 |
+
let final_prompt = if is_code && !legs_out.starts_with("LEGS") {
|
| 236 |
+
format!(
|
| 237 |
+
"You are the SOVEREIGN REVIEWER. The LEGS (Granite) produced this implementation:\n\n{}\n\nYour job:\n1. Verify it matches the spec\n2. Note any gaps\n3. Add Trust Deed seal\n4. Output: VERDICT (PASS/PATCH/REJECT) + brief notes\n\nOriginal task: {}",
|
| 238 |
+
legs_out, prompt
|
| 239 |
+
)
|
| 240 |
+
} else {
|
| 241 |
+
format!("Summarize your answer with the Omega seal. Evidence or Silence.\n\n{}", brain_out)
|
| 242 |
+
};
|
| 243 |
+
|
| 244 |
+
let review = brain(&state.bedrock, &brain_system, &final_prompt, 512)
|
| 245 |
+
.await
|
| 246 |
+
.unwrap_or_else(|e| format!("REVIEW_ERR: {}", e));
|
| 247 |
+
|
| 248 |
+
let mut worm = state.worm.lock().await;
|
| 249 |
+
let h4 = worm.seal("REVIEW");
|
| 250 |
+
let final_hash = worm.prev[..16].to_string();
|
| 251 |
+
let final_seals = worm.count;
|
| 252 |
+
drop(worm);
|
| 253 |
+
|
| 254 |
+
steps.push(WorkflowStep {
|
| 255 |
+
step: "REVIEW".to_string(),
|
| 256 |
+
model: format!("Claude Sonnet 4.6 (Bedrock Β· {})", BRAIN_MODEL),
|
| 257 |
+
output: review.clone(),
|
| 258 |
+
worm: h4,
|
| 259 |
+
ms: t.elapsed().as_millis(),
|
| 260 |
+
});
|
| 261 |
+
|
| 262 |
+
// Final output = legs code (if code) + brain review
|
| 263 |
+
let final_out = if is_code && !legs_out.is_empty() {
|
| 264 |
+
format!("{}\n\n---\nπ§ BRAIN REVIEW:\n{}", legs_out, review)
|
| 265 |
+
} else {
|
| 266 |
+
brain_out
|
| 267 |
+
};
|
| 268 |
+
|
| 269 |
+
Json(WorkflowResponse {
|
| 270 |
+
prompt,
|
| 271 |
+
is_code,
|
| 272 |
+
steps,
|
| 273 |
+
final_out,
|
| 274 |
+
worm: WormStatus { hash: final_hash, seals: final_seals },
|
| 275 |
+
})
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
// ββ /battle β 3-way compare βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 279 |
+
async fn battle_handler(
|
| 280 |
+
State(state): State<Arc<AppState>>,
|
| 281 |
+
Json(req): Json<BattleRequest>,
|
| 282 |
+
) -> Json<serde_json::Value> {
|
| 283 |
+
let prompt = req.prompt.trim().to_string();
|
| 284 |
+
let system_frank = inject::trust_deed::build_system_prompt(None);
|
| 285 |
+
let system_raw = "You are a helpful AI assistant. Answer directly.";
|
| 286 |
+
|
| 287 |
+
let (frank_res, opus_res, devstral_res) = tokio::join!(
|
| 288 |
+
brain_model(&state.bedrock, BRAIN_MODEL, &system_frank, &prompt, 1024),
|
| 289 |
+
brain_model(&state.bedrock, BATTLE_OPUS, system_raw, &prompt, 1024),
|
| 290 |
+
brain_model(&state.bedrock, BATTLE_CODE, system_raw, &prompt, 1024),
|
| 291 |
+
);
|
| 292 |
+
|
| 293 |
+
let mut worm = state.worm.lock().await;
|
| 294 |
+
let hash = worm.seal(&format!("BATTLE|{}", &prompt[..prompt.len().min(32)]));
|
| 295 |
+
|
| 296 |
+
Json(serde_json::json!({
|
| 297 |
+
"prompt": prompt,
|
| 298 |
+
"frankenstein": frank_res.unwrap_or_else(|e| format!("ERROR: {}", e)),
|
| 299 |
+
"opus": opus_res.unwrap_or_else(|e| format!("ERROR: {}", e)),
|
| 300 |
+
"devstral": devstral_res.unwrap_or_else(|e| format!("ERROR: {}", e)),
|
| 301 |
+
"worm": { "hash": hash, "seals": worm.count },
|
| 302 |
+
}))
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
// ββ /ask β sovereign query (no code path) ββββββββββββββββββββββββββββββββββββ
|
| 306 |
+
async fn ask_handler(
|
| 307 |
+
State(state): State<Arc<AppState>>,
|
| 308 |
+
Json(req): Json<AskRequest>,
|
| 309 |
+
) -> Json<serde_json::Value> {
|
| 310 |
+
let query = req.query.trim().to_string();
|
| 311 |
+
let system = inject::trust_deed::build_system_prompt(None);
|
| 312 |
+
|
| 313 |
+
let tavily = tools::tavily::search(&state.http, &query).await;
|
| 314 |
+
let search_ctx = match &tavily {
|
| 315 |
+
Ok(t) => tools::tavily::format_results(t),
|
| 316 |
+
Err(_) => String::new(),
|
| 317 |
+
};
|
| 318 |
+
|
| 319 |
+
let msg = format!("QUERY: {}\n\nSEARCH CONTEXT:\n{}", query, search_ctx);
|
| 320 |
+
let resp = brain(&state.bedrock, &system, &msg, 1024).await;
|
| 321 |
+
|
| 322 |
+
let mut worm = state.worm.lock().await;
|
| 323 |
+
let hash = worm.seal("ASK");
|
| 324 |
+
|
| 325 |
+
Json(serde_json::json!({
|
| 326 |
+
"query": query,
|
| 327 |
+
"tavily": tavily.ok().and_then(|t| t.answer),
|
| 328 |
+
"response": resp.unwrap_or_else(|e| format!("ERROR: {}", e)),
|
| 329 |
+
"worm": { "hash": hash, "seals": worm.count },
|
| 330 |
+
}))
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
async fn worm_handler(State(state): State<Arc<AppState>>) -> Json<WormStatus> {
|
| 334 |
+
let worm = state.worm.lock().await;
|
| 335 |
+
Json(WormStatus { hash: worm.prev[..16].to_string(), seals: worm.count })
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
async fn index() -> Html<&'static str> {
|
| 339 |
+
Html(FRANKENSTEIN_HTML)
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
// ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 343 |
+
#[tokio::main]
|
| 344 |
+
async fn main() -> Result<()> {
|
| 345 |
+
let _ = dotenvy::from_filename(
|
| 346 |
+
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".env")
|
| 347 |
+
);
|
| 348 |
+
|
| 349 |
+
let aws_cfg = aws_config::defaults(aws_config::BehaviorVersion::latest())
|
| 350 |
+
.region(aws_config::Region::new("us-east-1"))
|
| 351 |
+
.load()
|
| 352 |
+
.await;
|
| 353 |
+
|
| 354 |
+
let state = Arc::new(AppState {
|
| 355 |
+
bedrock: aws_sdk_bedrockruntime::Client::new(&aws_cfg),
|
| 356 |
+
http: reqwest::Client::new(),
|
| 357 |
+
worm: Mutex::new(WormChain::new()),
|
| 358 |
+
});
|
| 359 |
+
|
| 360 |
+
let app = Router::new()
|
| 361 |
+
.route("/", get(index))
|
| 362 |
+
.route("/workflow", post(workflow_handler))
|
| 363 |
+
.route("/battle", post(battle_handler))
|
| 364 |
+
.route("/ask", post(ask_handler))
|
| 365 |
+
.route("/worm", get(worm_handler))
|
| 366 |
+
.layer(CorsLayer::permissive())
|
| 367 |
+
.with_state(state);
|
| 368 |
+
|
| 369 |
+
println!("⬑ FRANKENSTEIN β http://localhost:{}", PORT);
|
| 370 |
+
println!(" BRAIN: {}", BRAIN_MODEL);
|
| 371 |
+
println!(" LEGS: {} (Ollama local GPU β RTX 3080)", LEGS_MODEL);
|
| 372 |
+
println!(" HANDS: pgvector ({})", PGVECTOR_URL);
|
| 373 |
+
|
| 374 |
+
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", PORT)).await?;
|
| 375 |
+
axum::serve(listener, app).await?;
|
| 376 |
+
Ok(())
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
// ββ HTML UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 380 |
+
const FRANKENSTEIN_HTML: &str = r#"<!DOCTYPE html>
|
| 381 |
+
<html lang="en">
|
| 382 |
+
<head>
|
| 383 |
+
<meta charset="UTF-8">
|
| 384 |
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 385 |
+
<title>FRANKENSTEIN β BOB Workflow</title>
|
| 386 |
+
<style>
|
| 387 |
+
:root {
|
| 388 |
+
--green: #00ff41;
|
| 389 |
+
--amber: #ffb700;
|
| 390 |
+
--blue: #00cfff;
|
| 391 |
+
--purple: #bf5fff;
|
| 392 |
+
--red: #ff3131;
|
| 393 |
+
--bg: #080808;
|
| 394 |
+
--card: #0e0e0e;
|
| 395 |
+
--border: #1c1c1c;
|
| 396 |
+
--dim: #333;
|
| 397 |
+
}
|
| 398 |
+
*{box-sizing:border-box;margin:0;padding:0}
|
| 399 |
+
body{background:var(--bg);color:var(--green);font-family:'Courier New',monospace;min-height:100vh;padding:1.2rem}
|
| 400 |
+
header{border-bottom:1px solid var(--green);padding-bottom:1rem;margin-bottom:1.5rem}
|
| 401 |
+
h1{font-size:2rem;letter-spacing:.15em}
|
| 402 |
+
h1 span{color:var(--amber)}
|
| 403 |
+
.sub{font-size:.7rem;color:var(--dim);margin-top:.3rem;letter-spacing:.05em}
|
| 404 |
+
|
| 405 |
+
/* WORM bar */
|
| 406 |
+
.wormbar{display:flex;align-items:center;gap:1rem;font-size:.72rem;color:var(--dim);
|
| 407 |
+
border:1px solid #111;padding:.4rem .8rem;background:#0a0a0a;margin-bottom:1.5rem}
|
| 408 |
+
.wormbar .h{color:var(--amber);font-weight:700}
|
| 409 |
+
|
| 410 |
+
/* Tabs */
|
| 411 |
+
.tabs{display:flex;gap:.5rem;margin-bottom:1.5rem}
|
| 412 |
+
.tab{padding:.45rem 1.1rem;border:1px solid #1a1a1a;background:#0a0a0a;color:var(--dim);
|
| 413 |
+
cursor:pointer;font-family:inherit;font-size:.78rem;letter-spacing:.05em;transition:all .15s}
|
| 414 |
+
.tab.active{border-color:var(--green);color:var(--green)}
|
| 415 |
+
.panel{display:none}.panel.active{display:block}
|
| 416 |
+
|
| 417 |
+
/* Input */
|
| 418 |
+
.row{display:flex;gap:.5rem;margin-bottom:1rem}
|
| 419 |
+
input{flex:1;background:#0a0a0a;border:1px solid #1a1a1a;color:var(--green);
|
| 420 |
+
font-family:inherit;font-size:.9rem;padding:.6rem 1rem;outline:none}
|
| 421 |
+
input:focus{border-color:var(--green)}
|
| 422 |
+
input::placeholder{color:#222}
|
| 423 |
+
button{background:transparent;border:1px solid var(--green);color:var(--green);
|
| 424 |
+
font-family:inherit;font-size:.82rem;padding:.6rem 1.2rem;cursor:pointer;
|
| 425 |
+
letter-spacing:.05em;transition:all .15s;white-space:nowrap}
|
| 426 |
+
button:hover{background:var(--green);color:#000}
|
| 427 |
+
button:disabled{opacity:.3;cursor:default}
|
| 428 |
+
|
| 429 |
+
/* Workflow pipeline */
|
| 430 |
+
.pipeline{display:grid;grid-template-columns:repeat(4,1fr);gap:.75rem;margin-bottom:1rem}
|
| 431 |
+
.pipe-step{border:1px solid var(--border);background:var(--card);padding:.8rem}
|
| 432 |
+
.pipe-step h3{font-size:.68rem;letter-spacing:.1em;margin-bottom:.4rem;padding-bottom:.3rem;border-bottom:1px solid var(--border)}
|
| 433 |
+
.pipe-step.brain h3{color:var(--green)}
|
| 434 |
+
.pipe-step.hands h3{color:var(--blue)}
|
| 435 |
+
.pipe-step.legs h3{color:var(--amber)}
|
| 436 |
+
.pipe-step.review h3{color:var(--purple)}
|
| 437 |
+
.pipe-body{font-size:.72rem;line-height:1.5;color:#bbb;white-space:pre-wrap;min-height:120px;max-height:320px;overflow-y:auto}
|
| 438 |
+
.pipe-meta{font-size:.62rem;color:var(--dim);margin-top:.4rem;border-top:1px solid var(--border);padding-top:.3rem}
|
| 439 |
+
.pipe-worm{color:var(--amber)}
|
| 440 |
+
.pipe-step.active h3{animation:pulse 1s infinite}
|
| 441 |
+
@keyframes pulse{50%{opacity:.4}}
|
| 442 |
+
|
| 443 |
+
/* Final output */
|
| 444 |
+
.final-out{background:#0a0a0a;border:1px solid var(--border);padding:1rem;
|
| 445 |
+
font-size:.8rem;line-height:1.6;color:#ddd;white-space:pre-wrap;
|
| 446 |
+
min-height:80px;margin-top:1rem;max-height:400px;overflow-y:auto}
|
| 447 |
+
|
| 448 |
+
/* Battle grid */
|
| 449 |
+
.battle-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:.75rem;margin-top:1rem}
|
| 450 |
+
.fighter{border:1px solid var(--border);background:var(--card);padding:.9rem}
|
| 451 |
+
.fighter h3{font-size:.68rem;letter-spacing:.1em;border-bottom:1px solid var(--border);padding-bottom:.4rem;margin-bottom:.6rem}
|
| 452 |
+
.fighter.frank h3{color:var(--green)}
|
| 453 |
+
.fighter.opus h3{color:var(--amber)}
|
| 454 |
+
.fighter.dev h3{color:var(--blue)}
|
| 455 |
+
.fighter-body{font-size:.72rem;line-height:1.5;color:#ccc;white-space:pre-wrap;min-height:200px;max-height:500px;overflow-y:auto}
|
| 456 |
+
|
| 457 |
+
/* Ask output */
|
| 458 |
+
.ask-out{background:#0a0a0a;border:1px solid var(--border);padding:1rem;
|
| 459 |
+
font-size:.8rem;line-height:1.6;white-space:pre-wrap;min-height:160px;max-height:500px;overflow-y:auto}
|
| 460 |
+
|
| 461 |
+
/* Badges */
|
| 462 |
+
.badge{display:inline-block;padding:.1rem .4rem;font-size:.6rem;border:1px solid;margin-right:.3rem}
|
| 463 |
+
.badge.code{border-color:var(--amber);color:var(--amber)}
|
| 464 |
+
.badge.text{border-color:var(--dim);color:var(--dim)}
|
| 465 |
+
|
| 466 |
+
@media(max-width:900px){
|
| 467 |
+
.pipeline{grid-template-columns:1fr 1fr}
|
| 468 |
+
.battle-grid{grid-template-columns:1fr}
|
| 469 |
+
}
|
| 470 |
+
</style>
|
| 471 |
+
</head>
|
| 472 |
+
<body>
|
| 473 |
+
|
| 474 |
+
<header style="display:flex;align-items:center;gap:1.4rem">
|
| 475 |
+
<img src="https://snapkittywest.github.io/snapkitty-portfolio/frankenstein_claude_snapkitty_logo.svg"
|
| 476 |
+
alt="Franken-Claude held by SnapKitty"
|
| 477 |
+
style="width:72px;height:72px;border:1px solid #1c1c1c;flex-shrink:0"/>
|
| 478 |
+
<div>
|
| 479 |
+
<h1>⬑ FRANK<span>STEIN</span></h1>
|
| 480 |
+
<div class="sub">
|
| 481 |
+
π§ BRAIN: Sonnet 4.6 (Bedrock) Β·
|
| 482 |
+
π€² HANDS: pgvector corpus Β·
|
| 483 |
+
𦡠LEGS: Devstral 123B (Bedrock) ·
|
| 484 |
+
Ξ© WORM sealed Β·
|
| 485 |
+
Powered by BOB Β· SnapKitty Collective 2026
|
| 486 |
+
</div>
|
| 487 |
+
</div>
|
| 488 |
+
</header>
|
| 489 |
+
|
| 490 |
+
<div class="wormbar">
|
| 491 |
+
Ξ© WORM <span class="h" id="worm-hash">ββββββββββββββββ</span>
|
| 492 |
+
| seals: <span id="worm-seals">0</span>
|
| 493 |
+
| <span id="worm-status">BOOT</span>
|
| 494 |
+
</div>
|
| 495 |
+
|
| 496 |
+
<div class="tabs">
|
| 497 |
+
<button class="tab active" onclick="sw('workflow')">⬑ WORKFLOW</button>
|
| 498 |
+
<button class="tab" onclick="sw('battle')">β CODE BATTLE</button>
|
| 499 |
+
<button class="tab" onclick="sw('ask')">ASK</button>
|
| 500 |
+
</div>
|
| 501 |
+
|
| 502 |
+
<!-- ββ WORKFLOW PANEL ββββββββββββββββββββββββββββββββββββββββββ -->
|
| 503 |
+
<div id="panel-workflow" class="panel active">
|
| 504 |
+
<div class="row">
|
| 505 |
+
<input id="wf-input" placeholder="prompt frankenstein with anything β code path auto-detected..."
|
| 506 |
+
onkeydown="if(event.key==='Enter') fireWorkflow()">
|
| 507 |
+
<button id="wf-btn" onclick="fireWorkflow()">⬑ RUN WORKFLOW β</button>
|
| 508 |
+
</div>
|
| 509 |
+
|
| 510 |
+
<!-- Pipeline steps β always visible -->
|
| 511 |
+
<div class="pipeline">
|
| 512 |
+
<div class="pipe-step brain" id="step-brain">
|
| 513 |
+
<h3>π§ BRAIN β Claude Sonnet 4.6</h3>
|
| 514 |
+
<div class="pipe-body" id="brain-body">waiting...</div>
|
| 515 |
+
<div class="pipe-meta">
|
| 516 |
+
Bedrock | Trust Deed injected | Ξ© <span class="pipe-worm" id="brain-worm">β</span> | <span id="brain-ms">β</span>ms
|
| 517 |
+
</div>
|
| 518 |
+
</div>
|
| 519 |
+
<div class="pipe-step hands" id="step-hands">
|
| 520 |
+
<h3>π€² HANDS β pgvector corpus</h3>
|
| 521 |
+
<div class="pipe-body" id="hands-body">waiting...</div>
|
| 522 |
+
<div class="pipe-meta">
|
| 523 |
+
Sovereign corpus | Ξ© <span class="pipe-worm" id="hands-worm">β</span> | <span id="hands-ms">β</span>ms
|
| 524 |
+
</div>
|
| 525 |
+
</div>
|
| 526 |
+
<div class="pipe-step legs" id="step-legs">
|
| 527 |
+
<h3>𦡠LEGS β Granite (vLLM local)</h3>
|
| 528 |
+
<div class="pipe-body" id="legs-body">waiting... (only fires on code prompts)</div>
|
| 529 |
+
<div class="pipe-meta">
|
| 530 |
+
Tokio-spawned baby | Ξ© <span class="pipe-worm" id="legs-worm">β</span> | <span id="legs-ms">β</span>ms
|
| 531 |
+
</div>
|
| 532 |
+
</div>
|
| 533 |
+
<div class="pipe-step review" id="step-review">
|
| 534 |
+
<h3>π§ BRAIN REVIEW β seal</h3>
|
| 535 |
+
<div class="pipe-body" id="review-body">waiting...</div>
|
| 536 |
+
<div class="pipe-meta">
|
| 537 |
+
Merge + verdict | Ξ© <span class="pipe-worm" id="review-worm">β</span> | <span id="review-ms">β</span>ms
|
| 538 |
+
</div>
|
| 539 |
+
</div>
|
| 540 |
+
</div>
|
| 541 |
+
|
| 542 |
+
<div style="font-size:.72rem;color:var(--dim);margin:.5rem 0">
|
| 543 |
+
FINAL OUTPUT <span id="code-badge"></span>
|
| 544 |
+
</div>
|
| 545 |
+
<div class="final-out" id="final-out">β fire a prompt to see the full workflow β</div>
|
| 546 |
+
</div>
|
| 547 |
+
|
| 548 |
+
<!-- ββ BATTLE PANEL βββββββββββββββββββββββββββββββββββββββββββββ -->
|
| 549 |
+
<div id="panel-battle" class="panel">
|
| 550 |
+
<div class="row">
|
| 551 |
+
<input id="battle-input" placeholder="write a function that..."
|
| 552 |
+
onkeydown="if(event.key==='Enter') fireBattle()">
|
| 553 |
+
<button id="battle-btn" onclick="fireBattle()">β BATTLE β</button>
|
| 554 |
+
</div>
|
| 555 |
+
<div class="battle-grid">
|
| 556 |
+
<div class="fighter frank">
|
| 557 |
+
<h3>⬑ FRANKENSTEIN<br><small>Sonnet 4.6 + Trust Deed + Tavily</small></h3>
|
| 558 |
+
<div class="fighter-body" id="frank-out">β</div>
|
| 559 |
+
</div>
|
| 560 |
+
<div class="fighter opus">
|
| 561 |
+
<h3>Ξ© OPUS 4.6<br><small>Raw Claude β no stack</small></h3>
|
| 562 |
+
<div class="fighter-body" id="opus-out">β</div>
|
| 563 |
+
</div>
|
| 564 |
+
<div class="fighter dev">
|
| 565 |
+
<h3>Ξ¦ DEVSTRAL 123B<br><small>Mistral code beast (Bedrock)</small></h3>
|
| 566 |
+
<div class="fighter-body" id="devstral-out">β</div>
|
| 567 |
+
</div>
|
| 568 |
+
</div>
|
| 569 |
+
</div>
|
| 570 |
+
|
| 571 |
+
<!-- ββ ASK PANEL βββββββββββββββββββββββββββββββββββββββββββββββ -->
|
| 572 |
+
<div id="panel-ask" class="panel">
|
| 573 |
+
<div class="row">
|
| 574 |
+
<input id="ask-input" placeholder="ask frankenstein anything..."
|
| 575 |
+
onkeydown="if(event.key==='Enter') fireAsk()">
|
| 576 |
+
<button id="ask-btn" onclick="fireAsk()">FIRE β</button>
|
| 577 |
+
</div>
|
| 578 |
+
<div class="ask-out" id="ask-out">β sovereign query ready β</div>
|
| 579 |
+
</div>
|
| 580 |
+
|
| 581 |
+
<script>
|
| 582 |
+
function sw(name){
|
| 583 |
+
['workflow','battle','ask'].forEach((n,i)=>{
|
| 584 |
+
document.querySelectorAll('.tab')[i].classList.toggle('active',n===name)
|
| 585 |
+
document.getElementById('panel-'+n).classList.toggle('active',n===name)
|
| 586 |
+
})
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') }
|
| 590 |
+
|
| 591 |
+
function updateWorm(w){
|
| 592 |
+
if(!w) return
|
| 593 |
+
document.getElementById('worm-hash').textContent = w.hash||'β'
|
| 594 |
+
document.getElementById('worm-seals').textContent = w.seals||0
|
| 595 |
+
document.getElementById('worm-status').textContent = 'SEALED Ξ©'
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
async function fireWorkflow(){
|
| 599 |
+
const p = document.getElementById('wf-input').value.trim()
|
| 600 |
+
if(!p) return
|
| 601 |
+
const btn = document.getElementById('wf-btn')
|
| 602 |
+
btn.disabled = true
|
| 603 |
+
|
| 604 |
+
// Reset all steps
|
| 605 |
+
;['brain','hands','legs','review'].forEach(k=>{
|
| 606 |
+
document.getElementById(k+'-body').textContent = 'running...'
|
| 607 |
+
document.getElementById(k+'-worm').textContent = 'β'
|
| 608 |
+
document.getElementById(k+'-ms').textContent = 'β'
|
| 609 |
+
document.getElementById('step-'+k).classList.add('active')
|
| 610 |
+
})
|
| 611 |
+
document.getElementById('final-out').textContent = 'β processing β'
|
| 612 |
+
document.getElementById('code-badge').innerHTML = ''
|
| 613 |
+
|
| 614 |
+
try {
|
| 615 |
+
const res = await fetch('/workflow',{
|
| 616 |
+
method:'POST',
|
| 617 |
+
headers:{'Content-Type':'application/json'},
|
| 618 |
+
body: JSON.stringify({prompt:p})
|
| 619 |
+
})
|
| 620 |
+
const d = await res.json()
|
| 621 |
+
|
| 622 |
+
// Fill each step
|
| 623 |
+
d.steps.forEach(s=>{
|
| 624 |
+
const key = s.step.toLowerCase()
|
| 625 |
+
const body = document.getElementById(key+'-body')
|
| 626 |
+
const wormEl = document.getElementById(key+'-worm')
|
| 627 |
+
const msEl = document.getElementById(key+'-ms')
|
| 628 |
+
if(body) body.textContent = s.output
|
| 629 |
+
if(wormEl) wormEl.textContent = s.worm
|
| 630 |
+
if(msEl) msEl.textContent = s.ms
|
| 631 |
+
document.getElementById('step-'+key)?.classList.remove('active')
|
| 632 |
+
})
|
| 633 |
+
|
| 634 |
+
document.getElementById('final-out').textContent = d.final_out || 'β'
|
| 635 |
+
document.getElementById('code-badge').innerHTML =
|
| 636 |
+
d.is_code
|
| 637 |
+
? '<span class="badge code">CODE PATH β Granite legs fired</span>'
|
| 638 |
+
: '<span class="badge text">TEXT PATH β Brain only</span>'
|
| 639 |
+
|
| 640 |
+
updateWorm(d.worm)
|
| 641 |
+
} catch(e){
|
| 642 |
+
document.getElementById('final-out').textContent = 'ERROR: '+e.message
|
| 643 |
+
}
|
| 644 |
+
btn.disabled = false
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
async function fireBattle(){
|
| 648 |
+
const p = document.getElementById('battle-input').value.trim()
|
| 649 |
+
if(!p) return
|
| 650 |
+
const btn = document.getElementById('battle-btn')
|
| 651 |
+
btn.disabled = true
|
| 652 |
+
;['frank-out','opus-out','devstral-out'].forEach(id=>{
|
| 653 |
+
document.getElementById(id).textContent = 'reasoning...β'
|
| 654 |
+
})
|
| 655 |
+
try {
|
| 656 |
+
const res = await fetch('/battle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:p})})
|
| 657 |
+
const d = await res.json()
|
| 658 |
+
document.getElementById('frank-out').textContent = d.frankenstein||'β'
|
| 659 |
+
document.getElementById('opus-out').textContent = d.opus||'β'
|
| 660 |
+
document.getElementById('devstral-out').textContent = d.devstral||'β'
|
| 661 |
+
updateWorm(d.worm)
|
| 662 |
+
} catch(e){ ['frank-out','opus-out','devstral-out'].forEach(id=>{ document.getElementById(id).textContent='ERROR: '+e.message }) }
|
| 663 |
+
btn.disabled = false
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
async function fireAsk(){
|
| 667 |
+
const q = document.getElementById('ask-input').value.trim()
|
| 668 |
+
if(!q) return
|
| 669 |
+
const btn = document.getElementById('ask-btn')
|
| 670 |
+
btn.disabled = true
|
| 671 |
+
document.getElementById('ask-out').textContent = 'searching + reasoning...'
|
| 672 |
+
try {
|
| 673 |
+
const res = await fetch('/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:q})})
|
| 674 |
+
const d = await res.json()
|
| 675 |
+
let out = ''
|
| 676 |
+
if(d.tavily) out += 'π‘ TAVILY: '+d.tavily+'\n\n'
|
| 677 |
+
out += d.response||'β'
|
| 678 |
+
out += '\n\nΞ© WORM: '+(d.worm?.hash||'β')
|
| 679 |
+
document.getElementById('ask-out').textContent = out
|
| 680 |
+
updateWorm(d.worm)
|
| 681 |
+
} catch(e){ document.getElementById('ask-out').textContent='ERROR: '+e.message }
|
| 682 |
+
btn.disabled = false
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
setInterval(async()=>{ try{ const r=await fetch('/worm'); updateWorm(await r.json()) }catch(_){} },8000)
|
| 686 |
+
</script>
|
| 687 |
+
</body>
|
| 688 |
+
</html>"#;
|