Kucatoo-Code · Wiki

Architecture

Kucatoo-Code is a Flask web app layered over an async Python "model harness" library. Two OS processes and three TCP listeners make up a running system:

Listener Default bind Process Started by
Flask app (HTTP + SSE) 127.0.0.1:5000 run_webapp.py manual or supervisor
Voice WS proxy 127.0.0.1:5002 same process, daemon thread run_webapp.py
Supervisor control API 127.0.0.1:5001 server_manager.py manual (python server_manager.py)

The three listeners

Flask app — :5000

run_webapp.py puts the project root on sys.path, imports model_harness.web.app.app (built at import time, app.py:312), warns if the bind is exposed without a token, and calls app.run(host, port, threaded=True, debug=False) (run_webapp.py:75); threaded=True lets other requests proceed while an SSE stream is open. Bind from HOST / PORT env (run_webapp.py:57-58).

Supervisor — :5001

server_manager.py is a pure-stdlib HTTP service (no Flask, no model_harness imports) that can start / stop / restart the Flask app even when the app is down — which is exactly why it is a separate process and not a set of Flask routes (server_manager.py:1-11). It binds 127.0.0.1 only (server_manager.py:544); port from MANAGER_PORT (server_manager.py:60). Endpoints (JSON, CORS-enabled for the :5000 page, server_manager.py:512-538):

The managed PID lives in data/server.pid (server_manager.py:53); liveness = PID alive AND an HTTP probe of the port (server_manager.py:266-273). Start spawns run_webapp.py detached with the venv interpreter when present, stdout appended to logs/server.log (server_manager.py:316-335). An unmanaged process holding port 5000 is reported as managed: false and is killed on stop / taken over on start. Auth: optional MANAGER_TOKEN bearer (env only — this process does not read .env), unset = open (server_manager.py:66, server_manager.py:80-105).

Voice WS proxy — :5002 (and why it is outside Werkzeug)

Voice Chat (speech-to-speech) needs a WebSocket to the Qwen Omni realtime endpoint, but browsers cannot set the Authorization header on a WS handshake — so a local proxy adds the DASHSCOPE_API_KEY header and relays bidirectionally to wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime (voice_ws_server.py:27-28, voice_ws_server.py:49-70).

The proxy does not ride the Flask server: Werkzeug's WS layer (flask-sock + simple-websocket) intermittently emits frames Chrome rejects with "Invalid frame header" (voice_ws_server.py:1-8). The dedicated server uses the websockets library on its own asyncio loop in a daemon thread — the same stack already proven for the upstream DashScope connection. run_webapp.py starts it via voice_ws_server.ensure_started(host=host) (run_webapp.py:68-69); host/port configurable via VOICE_WS_HOST / VOICE_WS_PORT (voice_ws_server.py:118-127). The legacy in-Flask /ws/voice route still exists (app.py:172-302), but :5002 is what the launcher starts.

Component diagram

flowchart LR
    Browser[Browser UI]
    subgraph AppProc[run_webapp.py process]
        Flask[Flask app :5000 threaded]
        RT[HarnessRuntime bg asyncio thread]
        H[ModelHarness 8 subsystems]
        V[Voice WS proxy :5002 thread]
        Flask --> RT --> H
    end
    Browser -->|HTTP + SSE| Flask
    Browser -->|WS voice| V
    V -->|WSS + auth header| DS[DashScope Qwen Omni realtime]
    H -->|HTTPS| P[LLM providers Kimi GLM DeepSeek etc]
    Mgr[server_manager.py process :5001]
    Browser -->|server control CORS| Mgr
    Mgr -->|spawn stop restart| AppProc
    Flask --> D[DATA_DIR default data/]
    H --> D

DATA_DIR resolution

Generated-file locations are resolved once at startup by _resolve_storage_paths() (app.py:82-111) into app.config["STORAGE_PATHS"]: DATA_DIR (default <project>/data) roots everything; PROMPTS_DIR, RESPONSES_DIR, LOG_DIR, TRACES_DIR are per-type overrides defaulting to subdirectories of it.

Resolution rules (_resolve_path, app.py:62-79): unset/empty → default; ~ expands; relative paths resolve against the project root (BASE_DIR, app.py:50), not the cwd; absolute paths are honored. All directories are created with mkdir(parents=True, exist_ok=True). .env is loaded from the project root with override=True so its keys win over stale shell exports (app.py:52-59).

The app then sets the HARNESS_DATA_DIR env var (app.py:137) so the factory roots subsystem storage (memory DBs, schedules, skills, knowledge bases, user profiles) at the same place. Factory precedence: explicit data_dir arg > DATA_DIR env > HARNESS_DATA_DIR env > config default "data" (factory.py:207-213). The tool workspace root (where agent file tools write) is separate: explicit arg > persisted active_workspace.json > WORKSPACE_DIR env > sibling of the data dir (factory.py:39-76). Note: server_manager.py has its own fixed DATA_DIR = ROOT / "data" for server.pid (server_manager.py:52) — it ignores the app's DATA_DIR override.

Data stores

Persistent state lives under DATA_DIR (<DATA_DIR>, default <project>/data). SQLite databases:

File Module Contents
clients.db web/clients.py clients (Stripe subscriptions per Clerk user + hosting deployment fields tier/vm_size/memory_mb/volume_gb, added by _migrate() on older DBs), trial_usage (trial query/token counters), subscriber_keys (BYOK provider keys per Clerk user, plaintext — mask to last-4 on read via list_subscriber_keys), deployment_events (append-only audit trail: deploys, tier changes, terminations — see Hosting)
guests.db web/guests.py guest_tokens — shareable expiring guest tokens with quotas
memory/mtm.db memory/mtm.py MTM mid-term memory (WAL + busy timeout)
skills registry DB skills/registry.py (path from skills/system.py) versioned skill packages

JSONL file stores (not databases — the Usage tab reports are aggregated from these, no SQL involved):

In-memory only: per-run cost tracking (mesh/cost_tracker.py) resets on restart.

Directory map

Project root highlights: run_webapp.py, server_manager.py, static/ and templates/ (Flask assets, app.py:116-117), data/, logs/, workspace/, tests/, docs/.

model_harness/ subpackages (H-numbers are the build phases):

Lazy harness initialization

Importing the web package is side-effect-free: the module-level HarnessRuntime singleton (runtime.py:215) builds nothing until the first get_harness() / run() / stream() call. On that first touch, _ensure_started() (runtime.py:57-88):

  1. Creates a dedicated asyncio loop in a daemon thread named harness-async-loop. One long-lived loop is required because the harness runs persistent background tasks (mesh health checks, scheduler loop) that must outlive any single request (runtime.py:1-23).
  2. Builds the harness on that loop via create_full_harness(start_scheduler=True) (runtime.py:101), wiring all 8 subsystems in dependency order (factory.py:165-310) with the scheduler as a fire-and-forget background task (factory.py:264-273). Construction has a 120 s timeout; failure tears the loop down so a retry is not wedged (runtime.py:82-86).

Flask↔async bridge (runtime.py:157-209): run(coro) schedules a coroutine and blocks for its result (JSON endpoints); stream(agen) bridges an async generator to a sync generator via a queue.Queue (SSE endpoints), cancelling the producer if the client disconnects. Shutdown runs on atexit (app.py:304-306).

Direct-mode query lifecycle

POST /api/query (routes_query.py:212) parses the body, enforces guest gates, validates model + image/reasoning/context-file inputs, then returns Response(stream_with_context(_query_stream(...)), mimetype="text/event-stream") (routes_query.py:309-325). With use_agent false, _direct_stream_async (routes_query.py:569) runs:

  1. Explicit remember … prompts short-circuit into a memory_proposal event (no model call).
  2. SkillDAG enrichment and memory recall (top-5) build an optional system block; session history is prepended when session_id is set.
  3. harness.query_stream(...) (core/harness.py:347) is pumped through an asyncio.Queue so reasoning deltas reach the client in real time.
  4. Terminal done carries model_used, cost_usd, token counts (with cache hits), elapsed, estimated; history, workspace stats, and SkillDAG mining are recorded fail-open.

SSE wire protocol (streaming.py:1-30): single data: {"event": <type>, "data": <payload>} lines. Event types: content, reasoning, thought, tool_call, step, observation, permission_request, question, memory_proposal, cap_warning, done, error.

sequenceDiagram
    participant B as Browser
    participant F as Flask :5000
    participant R as HarnessRuntime
    participant H as Harness
    participant M as Provider mesh
    B->>F: POST /api/query prompt model
    F->>F: role check + guest gates + input validation
    F->>R: stream(_direct_stream_async)
    R->>R: lazy _ensure_started first call only
    R->>H: schedule async generator on bg loop
    H->>H: SkillDAG + memory recall + session history
    H->>M: query_stream prompt
    M-->>H: content chunks + reasoning deltas
    H-->>R: asyncio.Queue items
    R-->>F: queue.Queue bridge
    F-->>B: SSE reasoning and content events
    H-->>F: done event tokens cost elapsed
    F-->>B: SSE done

Agent-mode run lifecycle

With use_agent: true, _agent_run_async (routes_query.py:879) replaces the direct stream:

  1. agent_framework.create_agent(...) with agent_type (react / planner / reflective; default reflective), mode (explore / plan / write_test / write_no_test; invalid → EXPLORE), max_steps (default 60), max_time_sec (0 = unlimited), auto_confirm. When the active workspace is the app's own source tree, the mode is clamped to read-only EXPLORE (routes_query.py:226-231).
  2. The agent is registered in live_agent_runs keyed by session_id, enabling the mid-run controls /api/agent/auto-approve and /api/agent/instruct.
  3. Confirmation/question handlers are installed unconditionally: a gated tool call (write_file, execute_python) with auto_confirm off emits a permission_request event and parks the run until the client POSTs to /api/permissions/respond (timeout ~300 s = denial); PLAN-mode ask_user emits a question event the same way.
  4. af.run_agent_streaming is pumped through a merged asyncio.Queue; deduplicated events: step (status transitions), thought (new or grown suffix), tool_call (ACTING transition only), observation (OBSERVING), streamed content chunks, cap_warning near budgets (routes_query.py:1016-1092).
  5. Terminal done carries per-run token sums (from steps, not the agent's lifetime counter), context_prompt, estimated cost, plan_file (PLAN mode writes Plan-N.md to the workspace root), stop_reason, resumable (routes_query.py:1247-1261). Cap-stopped runs (max_steps / max_tokens / max_time) persist message history so POST /api/agent/continue resumes with full context (routes_query.py:426-482).

Auth

Role-based access (auth.py:1-24): every request resolves a role in resolve_role (auth.py:142-164), enforced by a before_request guard (auth.py:193-233).

Binding beyond localhost with API_TOKEN unset triggers a loud startup warning listing the exposed routes — never a hard failure (warn_if_exposed, auth.py:256-272; surfaced in run_webapp.py:60-64). The supervisor's MANAGER_TOKEN is independent of all this.

verified against code: 2026-08-11