Configuration
All runtime configuration is via environment variables, conventionally stored
in a .env file at the project root. .env.example documents the main vars;
copy it to .env and fill in values. .env is gitignored — it holds secrets.
.env loading
model_harness/web/app.py:59 loads .env at import time:
load_dotenv(BASE_DIR / ".env", override=True)
BASE_DIRis the project root (two levels abovemodel_harness/web/app.py,app.py:50).override=Truemeans values in.envwin over same-named variables already exported in the shell — this prevents stale shell keys (e.g. an oldDEEPSEEK_API_KEY) from shadowing the.envvalue (rationale in the comment atapp.py:52-58).run_evals.py:47loads.envthe same way.server_manager.pyis pure stdlib and does not read.env— export its vars in the shell (server_manager.py:65-66).
Ports
Three HTTP/WS services, three ports:
| Port | Service | Configured by | Notes |
|---|---|---|---|
| 5000 | Flask web app (UI + /api/*) |
HOST, PORT |
Started by run_webapp.py:57-75 (threaded=True for SSE) |
| 5001 | Server manager (Start/Stop/Restart) | MANAGER_PORT |
server_manager.py:60; binds 127.0.0.1 only; stdlib-only, no .env |
| 5002 | Voice Chat (STS) WebSocket proxy | VOICE_WS_HOST, VOICE_WS_PORT |
voice_ws_server.py:118-127; daemon thread started from run_webapp.py:66-69 |
The voice WS proxy exists because Werkzeug's WS layer intermittently emits
frames Chrome rejects; the standalone server uses the websockets library
(voice_ws_server.py:1-14). It relays to
wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime, adding the
DASHSCOPE_API_KEY bearer header browsers cannot set. A legacy in-Flask
proxy also exists at /ws/voice (app.py:172-302).
Environment variables
Provider API keys
At least one provider key is required. Key resolution per model: direct key →
primary env var → fallback env var (model_harness/core/config.py:173-183).
See Provider Mesh for which models use which key.
| Variable | Used by | Default / fallback |
|---|---|---|
KIMI_API_KEY |
All four Kimi chat models | Fallback: MOONSHOT_API_KEY |
MOONSHOT_API_KEY |
Fallback for Kimi models | Optional |
BAILIAN_CODING_PLAN_API_KEY |
deepseek-v4-pro, glm-5.2, qwen3.7-max, qwen3.8-max (Bailian token-plan endpoint) |
No fallback; not listed in .env.example |
DEEPSEEK_API_KEY |
deepseek-v4-flash (direct api.deepseek.com) |
— |
MINIMAX_API_KEY |
MiniMax-M3 chat; MiniMax-H3 video |
— |
MINIMAX_GROUP_ID |
MiniMax-M3 GroupId header, resolved at call time |
Header dropped if unset (config.py:220-240) |
DASHSCOPE_API_KEY |
Voice WS proxies (app.py:188, voice_ws_server.py:49), Qwen TTS / voiceclone / imagegen |
Not used by chat presets |
GLM_API_KEY, ZAI_API_KEY |
Listed in .env.example, but no live chat preset reads them — glm-5.2 is served via the Bailian endpoint with no Zhipu fallback (config.py:415-419) |
— |
Tool API keys (optional)
| Variable | Used by | Default |
|---|---|---|
TAVILY_API_KEY |
Builtin web_search tool |
Tool reports a clear error when unset |
OPENAI_API_KEY |
Remote embedding engine for the memory subsystem (embedding_api_key_env, config.py:622) |
Local embedding engine needs no key |
SkillDAG sidecar (optional, off by default)
| Variable | Default | Purpose |
|---|---|---|
SKILLDAG_ENABLED |
false |
Connect the query pipeline to an external SkillDAG server |
SKILLS_AGENT_ENABLED |
true |
Register the use_skill tool and list committed skills in agent system prompts |
DOMAIN_CONTEXT_ENABLED |
true |
Inject domain RAG context into agent system prompts on confident detection |
USER_PROFILE_CONTEXT_ENABLED |
true |
Inject known-user preference context into agent system prompts |
SESSION_LOG_ENABLED |
true |
Persist session stores (direct-chat turns, agent resume history) to <DATA_DIR>/sessions/*.jsonl so they survive restarts |
SKILLDAG_URL |
http://localhost:8000 |
SkillDAG server address |
SKILLDAG_READ_GRAPH |
default |
Graph for skill enrichment (read path) |
SKILLDAG_WRITE_GRAPH |
default |
Graph for skill mining (write path, fire-and-forget) |
SKILLDAG_TIMEOUT |
5 |
Sidecar timeout (seconds) |
SKILLDAG_ENRICH_ENABLED |
true |
Prompt enrichment with relevant skills |
SKILLDAG_MINE_ENABLED |
true |
Mine finished query/response pairs for skills |
MCP satellite servers (optional)
| Variable | Default | Purpose |
|---|---|---|
HARNESS_MCP_SERVERS |
unset | JSON array of MCP stdio server configs; alternative to the YAML mcp_servers: list (config.py:608-610). Missing/crashed servers only log warnings |
Language servers (LSP tools, optional)
| Variable | Default | Purpose |
|---|---|---|
HARNESS_LSP_PYTHON_CMD / _ARGS |
pylsp / empty |
Python language server binary |
HARNESS_LSP_TS_CMD / _ARGS |
typescript-language-server / --stdio |
TS/JS language server |
HARNESS_LSP_GO_CMD / _ARGS |
gopls / serve |
Go language server |
HARNESS_LSP_IDLE_TIMEOUT |
1800 |
Seconds an idle language server stays alive |
Missing binaries produce an Error: ... not installed tool result; nothing is
auto-installed.
File locations
Resolution rules (app.py:62-79): ~ expands to the user home; relative
paths resolve against the project root (not the cwd); absolute paths are
honored as-is; / and \ both work on Windows. All directories are created
at startup (app.py:101-103).
| Variable | Default | Holds |
|---|---|---|
DATA_DIR |
<project>/data |
Root for all generated files: harness subsystem storage (memory DBs, schedules, skills, knowledge bases, user profiles, guests DB) plus the subfolders below |
HARNESS_DATA_DIR |
set from DATA_DIR |
Legacy alias; the app sets it via os.environ.setdefault so the harness factory roots subsystem storage at DATA_DIR (app.py:133-137) |
PROMPTS_DIR |
<DATA_DIR>/prompts |
Saved user prompts |
RESPONSES_DIR |
<DATA_DIR>/responses |
Saved model responses |
LOG_DIR |
<DATA_DIR>/logs |
App log files |
TRACES_DIR |
<DATA_DIR>/traces |
Trace files (app.py:97-99; not listed in .env.example) |
WORKSPACE_DIR |
sibling of DATA_DIR named workspace |
Agent tool workspace (files written by write_file/create_file); also the Workspaces-panel root. Active workspace persisted to <DATA_DIR>/active_workspace.json, stats to <DATA_DIR>/workspace_stats/ |
Web server and auth
| Variable | Default | Purpose |
|---|---|---|
HOST |
127.0.0.1 |
Bind interface. 0.0.0.0 exposes the app to the network — set API_TOKEN if you do; startup logs a loud warning when binding beyond localhost without a token (run_webapp.py:60-64, app.py:154-160) |
PORT |
5000 |
Flask listen port (run_webapp.py:58) |
API_TOKEN |
unset = no auth | When set, ALL /api/* routes require Authorization: Bearer <token> or ?token=<token> (auth.py:108-118; constant-time compare). The UI shows a token field persisted to localStorage. Recommended when HOST is not 127.0.0.1 |
MANAGER_TOKEN |
unset = open | Bearer token for server_manager.py (:5001). Use the same value as API_TOKEN so the UI's server-control buttons work. Not read from .env — export in shell |
MANAGER_PORT |
5001 |
Server manager port (server_manager.py:60) |
FLASK_SECRET |
random per restart | Flask session-cookie signing key (app.py:124). Unset means sessions reset on restart |
KUCATOO_REMOTE |
unset | =1 marks a platform-managed deployment: POST /api/server/stop returns 400 disabled (routes_server.py:24-30) and pages get a remote_mode flag (routes_pages.py:91) |
VOICE_WS_HOST |
falls back to HOST |
Bind host for the :5002 voice WS proxy (voice_ws_server.py:124-125) |
VOICE_WS_PORT |
5002 |
Voice WS proxy port (voice_ws_server.py:126) |
Logging
| Variable | Default | Purpose |
|---|---|---|
LOG_LEVEL |
INFO |
Python logging level: DEBUG, INFO, WARNING, ERROR (run_webapp.py:45-47) |
Data directory layout
With defaults (DATA_DIR = <project>/data):
<project>/
├── data/ # DATA_DIR — all internal state
│ ├── prompts/ # PROMPTS_DIR
│ ├── responses/ # RESPONSES_DIR
│ ├── logs/ # LOG_DIR
│ ├── traces/ # TRACES_DIR
│ ├── active_workspace.json # selected workspace
│ ├── workspace_stats/ # per-workspace usage stats
│ └── ... # memory DBs, schedules, skills, KBs,
│ # user profiles, guests DB
└── workspace/ # WORKSPACE_DIR — agent-written files,
# one subdir per selectable workspace
The workspace is deliberately a sibling of DATA_DIR, not a child, so
user-generated files stay separate from internal app state
(.env.example lines 152-162).
Auth model summary
model_harness/web/auth.py implements a role hierarchy: owner (Google
OAuth, when configured) > token (the API_TOKEN convenience path) >
guest (sandboxed trial tokens). With no auth configured, everything is
open — the intended local single-user default on 127.0.0.1
(auth.py:11-16). Webhook-triggered schedules without a webhook_secret
accept unauthenticated triggers (backward-compatible default, independent of
API_TOKEN).