Architecture
KaaS uses a three-layer architecture: a React Web UI, a Go backend, and a Python AI engine. A single Docker image bundles all three — no sidecar containers needed.
Overview
| Layer | Tech | Purpose |
|---|---|---|
| Web UI | React 19 + Vite + shadcn/ui | Chat, Submit, Wiki, Status |
| Backend | Go (net/http + go-zero/conf) | REST API, Worker Pool, Task Queue, MCP endpoint |
| AI Engine | Python (kb-ai daemon) | LLM Compile Pipeline, Retrieval, Chat |
| Storage | SQLite (default) | Job queue, compile state |
Web UI Layer
The frontend is a single-page application built with React 19, Vite, and shadcn/ui. It provides four pages:
- Chat — SSE streaming conversation with source citations pointing back to wiki articles
- Submit — Upload content (paste text, drag files, or enter URLs)
- Wiki — Browse compiled wiki articles with Markdown rendering
- Status — Monitor compilation progress in real time
The UI includes built-in i18n (English / Chinese) and light/dark theme support. It connects directly to the Go backend REST API — no authentication layer (designed for local/intranet deployment).
Go Backend Layer
The Go backend is the orchestration hub. It uses the standard library net/http (not go-zero's rest.Server) to avoid write-timeout conflicts with SSE streaming.
Key components:
- REST API — Handles
/api/submit,/api/tasks,/api/wiki,/api/chat, and serves the embedded Web UI at/ - Worker Pool — Dispatcher + Extract workers + Pipeline workers for concurrent compilation
- Circuit Breaker — Automatically halts LLM calls after consecutive failures (configurable threshold + cooldown)
- Lease Recovery — Heartbeat-based lease renewal; orphaned tasks are automatically recovered on restart
- MCP Endpoint — Reverse proxies
/mcpto the Python MCP server for remote agent access - SQLite Store — Persists task queue, compile state, and job metadata
Python AI Engine
The Python AI engine (kb-ai) handles all LLM interactions:
- Compile Pipeline — Extract → Classify → Write → Index (4-phase Markdown generation)
- Retrieval — LLM-iterative approach: reads master-index → LLM selects pages → fetches full article text (no embeddings)
- Chat — Streaming conversation with prompt caching and citation sourcing
- MCP Server — Exposes an
asktool via stdio or streamable-http transport
The Go backend spawns the Python engine as a long-running daemon process, communicating via a multiplexed stdin/stdout JSON protocol (see below).
Go-Python Communication
KaaS uses a custom multiplexed protocol over the daemon's stdin/stdout pipes. This avoids HTTP overhead and cold-start latency while supporting concurrent requests.
Protocol Format
Request (Go → Python via stdin, one JSON per line):
{"id": "3", "cmd": "chat", "payload": {"query": "...", "kb_dir": "./data"}}Response (Python → Go via stdout, one JSON per line):
{"id": "3", "ok": true, "data": {"extraction": {...}, "cost": {...}}}Streaming response (multiple lines for the same id):
{"id": "3", "stream": true, "event": {"type": "delta", "text": "..."}}
{"id": "3", "stream": true, "event": {"type": "done"}, "final": true}Key Design Elements
| Element | Description |
|---|---|
id field | Every request carries a unique id. The Go readLoop dispatches responses by matching id back to the waiting goroutine. |
| Concurrency | Go uses a semaphore to limit in-flight requests. Python dispatches commands to a ThreadPoolExecutor. |
| Streaming | Streaming commands (chat, pipeline-stream) emit multiple {"stream": true} events until "final": true. |
| Cancellation | Go sends {"cmd": "cancel", "payload": {"target_id": "3"}} to abort a running stream. Python uses a per-request cancel_registry to signal threads. |
| Readiness | Python prints __READY__ to stderr after initialization. Go waits for this before sending any commands. |
| Thread safety | Python's _write_response acquires a lock before writing to stdout to prevent interleaved JSON lines. |
Lifecycle
- Go spawns
kb-ai daemonas a subprocess (exec.Cmd) - Python initializes, prints
__READY__to stderr - Go sends
initcommand with LLM credentials - Normal operation: multiplexed request/response over stdin/stdout
- On shutdown: Go closes stdin → Python's readline loop exits gracefully
- On crash: Go supervisor detects exit, restarts daemon automatically (up to
MaxRestarts)
Storage
KaaS uses SQLite by default for zero-dependency deployment:
- Task queue — Job metadata, status, lease information
- Compile state — Content hashes for incremental compilation
All wiki output is stored as plain Markdown files in the kb_dir directory (./data by default):
data/
├── wiki/ # Compiled wiki articles (Markdown)
├── raw/ # Original input content
├── index/ # master-index + topic-indexes
└── kaas.db # SQLite databaseDeployment
KaaS ships as a single Docker image that bundles:
- Pre-built React UI (served as static files by Go)
- Statically-linked Go binary
- Python runtime + kb-ai package (via uv)
docker run -d --name kaas \
-p 8080:8080 \
-v ./data:/app/data \
-e LLM_API_KEY=sk-xxx \
kaasOnly port 8080 is exposed. The Go backend handles all external traffic — REST API, embedded Web UI, and MCP endpoint. The Python daemon runs internally with no network surface.