chore: jarvisChat → cAIc rename, single-node consolidation on jarvis, Qdrant UUID5 + chunk-size deploy fixes

This commit is contained in:
2026-08-08 09:56:39 -07:00
parent 44387919a8
commit e14ae2bd19
28 changed files with 72 additions and 48 deletions
+1 -1
View File
@@ -55,5 +55,5 @@ LLAMA_EXPOSE_PORT=8081
OLLAMA_EXPOSE_PORT=11434
# ── Image generation (ComfyUI on worker) ─────────────────────
CAIC_COMFYUI_BASE=http://192.168.50.115:8188
CAIC_COMFYUI_BASE=http://localhost:8188
CAIC_COMFYUI_TIMEOUT=120
+5 -5
View File
@@ -6,20 +6,20 @@ Previous task history archived at `docs/archive/TASKS-pre-1.0.md`.
## TASK 1 — Image Generation Service (corsair)
**Goal:** Add image generation as a cluster capability. corsair (RTX 5070 Ti, 16 GB) registers as an image gen worker in the cAIc cluster.
**Goal:** Add image generation as a cluster capability. The image-gen node (currently jarvis — single-node deployment) registers as an image gen worker in the cAIc cluster.
### Requirements:
1. **Add `"image_gen"` capability** to the cluster protocol in `cluster.py` — valid capability values should include `image_gen`
2. **Image gen API wrapper on corsair** — run ComfyUI, Automatic1111, or a lightweight API (e.g., `sd-api` or `comfyui-api`) that exposes a simple `POST /generate` endpoint accepting a prompt and returning a PNG
3. **Proxy endpoint in cAIc**`POST /api/image/generate` on the coordinator, routes the request to corsair's image gen service via AMQP or direct HTTP
2. **Image gen API wrapper** — run ComfyUI, Automatic1111, or a lightweight API (e.g., `sd-api` or `comfyui-api`) that exposes a simple `POST /generate` endpoint accepting a prompt and returning a PNG
3. **Proxy endpoint in cAIc**`POST /api/image/generate` on the coordinator, routes the request to the image gen service via AMQP or direct HTTP
4. **Update `hardware.py`** to probe the image gen service for reachability and status
5. **Update node_agent** to report image gen capability and service status on registration
### Architecture:
```
User prompt → cAIc coordinator → AMQP/HTTP → corsair (ComfyUI/API) → PNG → coordinator → user
User prompt → cAIc coordinator → AMQP/HTTP → image-gen node (ComfyUI/API) → PNG → coordinator → user
```
### Considerations:
@@ -37,7 +37,7 @@ User prompt → cAIc coordinator → AMQP/HTTP → corsair (ComfyUI/API) → PNG
- Verify node_agent registers with `image_gen` capability
- Verify 429/503 handling when service is busy or down
### Status: ✅ Backend Complete (ComfyUI install pending on corsair)
### Status: ✅ Backend Complete (ComfyUI install pending on jarvis — single-node deployment)
---
+20 -3
View File
@@ -88,7 +88,7 @@ Refactored from single-file (`app.py`) into modules under project root:
3. **`/v1/chat/completions`** → OpenAI-compatible for Continue.dev/IDE integration; FIM requests proxied without persistence
4. **`/api/upload`** → multipart file upload, PDF/text extraction, `mode=(context|ingest|both)`, stores SQLite context (1hr expiry) + Qdrant upsert
5. **`/api/ingest`** → Bearer token auth, programmatic RAG ingest (terminal hook, external tools)
6. **`POST /api/image/generate`** → admin required, routes to corsair node agent via AMQP → ComfyUI workflow → returns PNG; `GET /api/image/status` lists available image gen nodes
6. **`POST /api/image/generate`** → admin required, routes to an image-gen node via AMQP → ComfyUI workflow → returns PNG; `GET /api/image/status` lists available image gen nodes
### Perplexity / auto-search
@@ -131,7 +131,7 @@ All services are available bare-metal or as containers in `docker compose up`.
- `SUPPORTED_UPLOAD_TYPES` includes images (png/jpeg/gif/svg/webp) + text + PDF + JSON
- `UPLOAD_CONTEXT_EXPIRY_HOURS` = 1 hour
- Rate limits and payload caps in `config.py` — patch `security.RL_*` not `config.RL_*` for tests
- `COMFYUI_BASE` defaults to `http://192.168.50.115:8188` (overridable via `CAIC_COMFYUI_BASE`)
- `COMFYUI_BASE` defaults to `http://localhost:8188` (overridable via `CAIC_COMFYUI_BASE`)
- `COMFYUI_TIMEOUT` defaults to `120` seconds (overridable via `CAIC_COMFYUI_TIMEOUT`)
- RAG embedding requests go to `EMBED_URL` at `/api/embeddings` (Ollama on worker :11434)
@@ -148,6 +148,8 @@ All streaming endpoints yield `data: {json}\n\n`. Key shapes:
### Completed this session
- **Pre-Docker review**: Full findings report delivered -- 30+ issues across 7 categories (hardcoded hosts/paths, config/secrets, AMQP gaps, resource cleanup, SQLite container safety, completions concurrency, TASKS.md accuracy).
- **Project rename**: `jarvisChat`**cAIc** ("cake") — swept remaining branding (router docstrings, jc-ingest.sh env var), deleted stale `AGENTS.md.local`.
- **Single-node consolidation**: all services moved to jarvis (192.168.50.212) — `COMFYUI_BASE` default → `localhost:8188`, AMQP URL default → `localhost:5672`, `NODE_NAME` default → `jarvis`, `DEFAULT_PROFILE` topology rewritten, cluster/AMQP/node_agent left in place (degrades gracefully).
- **Deprecation fix**: Replaced `asyncio.ensure_future` with `asyncio.create_task` in `rag.py` and `routers/chat.py`.
- **Documentation**: Added inline comments and docstrings to all functions in `db.py`.
- **Uninstall scripts**: Created and committed `scripts/uninstall.sh`, `teardown-docker.sh`, `nuclear-clean.sh`.
@@ -155,7 +157,22 @@ All streaming endpoints yield `data: {json}\n\n`. Key shapes:
- **Docker containerization (B3)**: Created `Dockerfile`, `docker-compose.yml`, `.env.example`, `scripts/setup.sh`, `.dockerignore`, `searxng-settings.yml.dist`, `models/README.txt`. Fixed hardcoded defaults in `config.py` (localhost, Docker secrets path, `CAIC_DEFAULT_MODEL` env var, `CAIC_HW_STATE_PATH` env var). Added missing `psutil` + `jinja2` to `requirements.txt`. Fixed test discovery via `tests/conftest.py` sys.path insertion. 214 tests pass.
### Active
- Image generation service backend complete — cluster handlers, router proxy, node agent ComfyUI integration, hardware probe. 27 tests pass. ComfyUI install pending on corsair.
- Image generation service backend complete — cluster handlers, router proxy, node agent ComfyUI integration, hardware probe. 27 tests pass. ComfyUI install pending on jarvis (single-node).
### Deployed (2026-08-07) — v1.1.0 to production
- **Fixed crash-loop**: ultron `llama-server.service` had 911 restarts — its `--rpc 192.168.50.210:50052` pointed at a dead IP. Corrected to `192.168.50.212:50052` (jarvis GPU rpc-server). Model now loads, `/health` = ok.
- **Deployed v1.1.0**: workspace repo synced to `/opt/jarvischat` (jarvischat.service cwd). caic.db + venv preserved; `aio-pika` installed into prod venv (was missing → AMQP disabled).
- **Env fixes** (`/etc/systemd/system/jarvischat.service.d/override.conf`): added `LLAMA_SERVER_BASE=http://192.168.50.108:8081`, `CAIC_QDRANT_URL=http://192.168.50.108:6333`, `CAIC_COMPLETIONS_API_KEY` (was set as legacy `JARVISCHAT_` name), kept `CAIC_EMBED_URL=http://192.168.50.108:11434` + `CAIC_ADMIN_PIN=1319`. Wrote `/opt/jarvischat/.completions_key` (jc-ingest.sh).
- **Deploy-blocking bug fixes** (uncommitted, workspace + deploy):
- `rag.py` `chunk_text`: chunk_size 512→200 (chunks exceeded mxbai-embed-large's 512-token context → ollama 500).
- Qdrant 1.18.2 rejects non-UUID point IDs: wrapped `ingest-*`/`auto-*`/`upload-*` string IDs in `uuid5` in `routers/ingest.py`, `rag.py`, `routers/upload.py`.
- `docs/jc-ingest.sh`: `JC_URL` updated `.210``.212`.
- **Docs rebuilt**: 159 chunks (source `docs`) re-ingested via `/api/ingest` (README, ai.md, docker.md, CLAUDE.md, wiki/*). RAG now 378 vectors; chat verified injecting "Retrieved Context".
- **Tests**: all 244 pass (run per-file in a throwaway venv; the full-suite run deadlocks on TestClient/AMQP ordering, not a code failure).
### Follow-ups
- AMQP wiring: cluster subs degrade gracefully — **moot in the single-node (jarvis) deployment** until a multi-node cluster is stood back up. Needs `CAIC_AMQP_URL` + credentials if that happens.
- `CAIC_TRIAGE_BASE` set but triage not yet invoked by chat (config-only until TASK 2 wiring).
### Blocked
- Ball Gunner assets — waiting on Canva designs
+1 -1
View File
@@ -19,7 +19,7 @@ CLUSTER_EVENTS: deque = deque(maxlen=1000)
CLUSTER_COORDINATOR: str | None = None
_pending_pings: dict[str, tuple[str, asyncio.Event]] = {}
_pending_image: dict[str, tuple[str, asyncio.Event]] = {}
NODE_NAME: str = os.environ.get("CAIC_NODE_NAME", "ultron")
NODE_NAME: str = os.environ.get("CAIC_NODE_NAME", "jarvis")
PING_TIMEOUT: float = 5.0
+4 -6
View File
@@ -34,7 +34,7 @@ def get_amqp_url() -> str:
except (FileNotFoundError, OSError):
pw = "password"
log.warning("AMQP secret file not found at %s — using default password", AMQP_SECRET_PATH)
return f"amqp://caic:{pw}@rabbitmq:5672/caic"
return f"amqp://caic:{pw}@localhost:5672/caic"
# --- Auth ---
SESSION_TIMEOUT_SECONDS = 3600
@@ -53,7 +53,7 @@ TRUST_X_FORWARDED_FOR = (
)
# --- Image generation (ComfyUI) ---
COMFYUI_BASE = os.environ.get("CAIC_COMFYUI_BASE", "http://192.168.50.115:8188")
COMFYUI_BASE = os.environ.get("CAIC_COMFYUI_BASE", "http://localhost:8188")
COMFYUI_TIMEOUT = int(os.environ.get("CAIC_COMFYUI_TIMEOUT", "120"))
# --- Rate limits ---
@@ -179,12 +179,10 @@ ALLOWED_NETWORKS = parse_allowed_cidrs(ALLOWED_CIDRS_RAW)
DEFAULT_PROFILE = """You are a coding companion running locally on a machine called "jarvis".
## Environment
- jarvis: Debian 13 (trixie) x86_64, AMD Ryzen 5 5600X, 16GB RAM, AMD RX 6600 XT (8GB VRAM)
- ultron: Debian 13, Ryzen 7 7840HS, 16GB RAM, primary AI inference node, IP 192.168.50.108
- Corsair: Windows 11, gaming/streaming rig, RTX 5070 Ti
- jarvis: Debian 13 (trixie) x86_64, AMD Ryzen 5 5600X, 16GB RAM, AMD RX 6600 XT (8GB VRAM), IP 192.168.50.212
- Single-node deployment — all cAIc services run on jarvis: llama-server :8081 (OpenAI-compat API), Qdrant :6333, Ollama :11434, SearXNG :8888, RabbitMQ :5672, ComfyUI :8188
- pivault: RPi 5, 8GB RAM, Debian 13, 11TB RAID5 NAS at /mnt/pivault, IP 192.168.50.158
- Router: ASUS ROG Rapture GT-BE98 Pro "BigBlinkyRouter" at 192.168.50.1
- llama-server on ultron:8081 (OpenAI-compat API), Qdrant on ultron:6333
## About the User
- Experienced developer, BS in Computer Science (Oklahoma State), coding since 1981 (TRS-80)
+1 -1
View File
@@ -19,7 +19,7 @@ services:
- rabbitmq_password
environment:
- CAIC_AMQP_SECRET_PATH=/run/secrets/rabbitmq_password
- CAIC_COMFYUI_BASE=${CAIC_COMFYUI_BASE:-http://192.168.50.115:8188}
- CAIC_COMFYUI_BASE=${CAIC_COMFYUI_BASE:-http://localhost:8188}
- CAIC_COMFYUI_TIMEOUT=${CAIC_COMFYUI_TIMEOUT:-120}
env_file: .env
depends_on:
+5 -5
View File
@@ -1,11 +1,11 @@
#!/bin/bash
# jc-ingest.sh — pipe terminal commands into jarvisChat RAG
# Deploy to /home/gramps/bin/jc-ingest.sh on jarvis (192.168.50.210)
# jc-ingest.sh — pipe terminal commands into cAIc RAG
# Deploy to /home/gramps/bin/jc-ingest.sh on jarvis (192.168.50.212)
#
# Usage:
# 1. chmod +x /home/gramps/bin/jc-ingest.sh
# 2. Add to ~/.bashrc:
# export JARVISCHAT_COMPLETIONS_API_KEY="$(cat /opt/jarvischat/.completions_key)"
# export CAIC_COMPLETIONS_API_KEY="$(cat /opt/jarvischat/.completions_key)"
# export PROMPT_COMMAND="jc_capture"
# source /home/gramps/bin/jc-ingest.sh
#
@@ -15,8 +15,8 @@
# Filter: currently captures git, pip, systemctl, sudo, vi/vim, curl,
# wget, apt, python, pytest commands. Edit the grep pattern to adjust.
JC_URL="http://192.168.50.210:8080/api/ingest"
JC_TOKEN="${JARVISCHAT_COMPLETIONS_API_KEY}"
JC_URL="http://192.168.50.212:8080/api/ingest"
JC_TOKEN="${CAIC_COMPLETIONS_API_KEY}"
jc_capture() {
local cmd
+5 -3
View File
@@ -45,14 +45,16 @@ Key base URLs are configured via environment variables with sensible defaults:
| Variable | Default | Service |
|----------|---------|---------|
| `LLAMA_SERVER_BASE` | `http://192.168.50.108:8081` | llama-server on coordinator |
| `LLAMA_SERVER_BASE` | `http://localhost:8081` | llama-server on the same node |
| `OLLAMA_BASE` | `http://localhost:11434` | Legacy — all inference goes through LLAMA_SERVER_BASE |
| `SEARXNG_BASE` | `http://localhost:8888` | SearXNG |
| `QDRANT_URL` | `http://192.168.50.108:6333` | Qdrant on coordinator |
| `QDRANT_URL` | `http://localhost:6333` | Qdrant on the same node |
| `CAIC_AMQP_URL` | `amqp://caic:password@localhost:5672/caic` | RabbitMQ |
| `CAIC_COMFYUI_BASE` | `http://192.168.50.115:8188` | ComfyUI on worker |
| `CAIC_COMFYUI_BASE` | `http://localhost:8188` | ComfyUI (image gen) |
| `CAIC_COMFYUI_TIMEOUT` | `120` | ComfyUI generation timeout (seconds) |
> **Current deployment (single-node):** all services run on jarvis (192.168.50.212). The cluster/AMQP/node-agent layer is dormant — it degrades gracefully and can be re-enabled for a multi-node cluster later.
## 2. Request/Response Architecture
### 2.1 Chat Pipeline (`/api/chat`)
+2
View File
@@ -1,5 +1,7 @@
# WireGuard Tunnel — Encrypted Node Transit
> **Status: dormant (single-node deployment).** All cAIc services currently run on one node (jarvis, 192.168.50.212), so there is no inter-node traffic to encrypt. This document is kept as a reference for when a multi-node cluster is stood back up.
## Why
cAIc cluster traffic is plaintext today:
+3 -1
View File
@@ -6,10 +6,12 @@ Scope: Active roadmap items and backlog.
## In Progress
- **Image Generation Service** — Backend wired: cluster handlers, `POST /api/image/generate` proxy, node agent ComfyUI integration, hardware probe, 27 tests. ComfyUI install pending on corsair (RTX 5070 Ti).
- **Image Generation Service** — Backend wired: cluster handlers, `POST /api/image/generate` proxy, node agent ComfyUI integration, hardware probe, 27 tests. ComfyUI install pending on jarvis (single-node).
## Completed
- **Single-node consolidation (2026-08-08)** — all cAIc services moved onto jarvis (192.168.50.212): llama-server, Qdrant, SearXNG, RabbitMQ, Ollama, ComfyUI. Config defaults (`COMFYUI_BASE`, AMQP URL, `NODE_NAME`) updated; cluster/AMQP layer left dormant (degrades gracefully). Project renamed `jarvisChat`**cAIc**.
- **B8 (v0.19.3)** — Private Chat mode. Backend skip-DB/skip-RAG/skip-search flag, frontend PRIVATE badge, info popup.
- **WireGuard TLS (v0.19.4)** — Self-signed WireGuard mesh encrypts all inter-node traffic (AMQP, inference, RPC). No code changes to cAIc. Documented in wiki/WireGuard-Setup.md + docker.md §5.4.
- **At-Rest Encryption (v0.20.0)** — AES-256-GCM encrypts all query-derived text at rest. crypto.py with auto-keygen, key stored as `heartbeat_interval_ms` in settings. All 12 storage paths wired (SQLite: messages, conversations, memories, upload_context; Qdrant: RAG chunks, ingest, upload). 200 tests pass.
+2 -2
View File
@@ -11,13 +11,13 @@ responds to pings, and handles model swap commands.
# hostname — defaults to socket.gethostname()
node_name = jarvis
# LAN IP — defaults from socket
node_ip = 192.168.50.210
node_ip = 192.168.50.212
# "worker" (fixed)
node_type = worker
# comma-separated capability list
capabilities = llm
# RabbitMQ URL on coordinator
amqp_url = amqp://caic:password@192.168.50.108:5672/caic
amqp_url = amqp://caic:password@localhost:5672/caic
# port llama-server listens on
llama_port = 8081
# path to GGUF model files
+3 -2
View File
@@ -4,6 +4,7 @@ cAIc - RAG pipeline: Qdrant vector search + system prompt assembly.
import asyncio
import logging
import os
import uuid
from datetime import datetime, timezone
import httpx
@@ -45,7 +46,7 @@ async def _upsert_fact(fact: str, text: str, topic: str,
if er.status_code != 200:
continue
vector = er.json()["embedding"]
pid = f"auto-{ts}-{i}"
pid = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"auto-{ts}-{i}"))
payload = {
"text": encrypt_text(chunk), "source": "auto_fact", "fact": fact,
"ingest_date": datetime.now(timezone.utc).isoformat(),
@@ -124,7 +125,7 @@ async def confirm_fact_update(memory_id: int, old_fact: str, new_fact: str,
return True
def chunk_text(text: str, chunk_size: int = 512, overlap: int = 128) -> list:
def chunk_text(text: str, chunk_size: int = 200, overlap: int = 64) -> list:
words = text.split()
target_words = int(chunk_size / 1.3)
overlap_words = int(overlap / 1.3)
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - /api/chat streaming endpoint."""
"""cAIc routers - /api/chat streaming endpoint."""
import asyncio
import json
import logging
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - Cluster status API."""
"""cAIc routers - Cluster status API."""
from fastapi import APIRouter
import cluster
+1 -1
View File
@@ -1,5 +1,5 @@
"""
JarvisChat - /v1/chat/completions router.
cAIc - /v1/chat/completions router.
OpenAI-compatible endpoint for IDE integration (Continue.dev, etc.).
Runs all requests through the full jC pipeline: profile + RAG + memory injection.
FIM (fill-in-the-middle) requests are proxied directly — not persisted.
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - Conversation CRUD."""
"""cAIc routers - Conversation CRUD."""
import logging
import uuid
from datetime import datetime, timezone
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers — Hardware self-assessment endpoint."""
"""cAIc routers — Hardware self-assessment endpoint."""
import json
from fastapi import APIRouter
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers — Image generation proxy endpoint."""
"""cAIc routers — Image generation proxy endpoint."""
import base64
import logging
+3 -2
View File
@@ -1,7 +1,8 @@
"""JarvisChat routers - /api/ingest terminal command RAG hook."""
"""cAIc routers - /api/ingest terminal command RAG hook."""
import hashlib
import hmac
import logging
import uuid
from datetime import datetime, timezone
import httpx
@@ -53,7 +54,7 @@ async def ingest_content(request: Request):
continue
vector = embed_resp.json()["embedding"]
chunk_hash = hashlib.md5(chunk.encode("utf-8")).hexdigest()[:12]
point_id = f"ingest-{source}-{chunk_hash}-{i}"
point_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"ingest-{source}-{chunk_hash}-{i}"))
payload = {"text": encrypt_text(chunk), "source": source, "ingest_date": datetime.now(timezone.utc).isoformat(), "type": "ingest"}
payload.update(metadata)
upsert_resp = await client.put(
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - Memory CRUD API."""
"""cAIc routers - Memory CRUD API."""
from fastapi import APIRouter, HTTPException, Request
from typing import Optional
+1 -1
View File
@@ -1,5 +1,5 @@
"""
JarvisChat routers - Model listing, system stats.
cAIc routers - Model listing, system stats.
"""
import logging
from typing import Optional
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - System prompt presets."""
"""cAIc routers - System prompt presets."""
import uuid
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException, Request
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - Profile."""
"""cAIc routers - Profile."""
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException, Request
from db import get_db
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers — RAG corpus management admin endpoints."""
"""cAIc routers — RAG corpus management admin endpoints."""
import logging
from datetime import datetime, timezone
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - /api/search explicit search endpoint."""
"""cAIc routers - /api/search explicit search endpoint."""
import json
import logging
import uuid
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - Settings."""
"""cAIc routers - Settings."""
from fastapi import APIRouter, HTTPException, Request
from db import get_db
from security import read_json_body, BODY_LIMIT_DEFAULT_BYTES
+1 -1
View File
@@ -1,4 +1,4 @@
"""JarvisChat routers - Skills."""
"""cAIc routers - Skills."""
from fastapi import APIRouter, HTTPException, Request
from db import get_db, get_setting, list_skills_with_state, set_skill_enabled
from security import read_json_body, BODY_LIMIT_DEFAULT_BYTES
+3 -2
View File
@@ -1,7 +1,8 @@
"""JarvisChat routers - /api/upload file/document attachment endpoint."""
"""cAIc routers - /api/upload file/document attachment endpoint."""
import json
import logging
import os
import uuid
from datetime import datetime, timezone, timedelta
import httpx
@@ -19,7 +20,7 @@ router = APIRouter()
def _point_id(filename: str, chunk_idx: int) -> str:
return f"upload-{filename}-{chunk_idx}"
return str(uuid.uuid5(uuid.NAMESPACE_DNS, f"upload-{filename}-{chunk_idx}"))
@router.post("/api/upload")