Backend configuration¶
Every setting is an environment variable. The backend also reads a .env file in the directory it starts in; a variable set in the environment wins over the file.
General¶
| Variable | Type | Default | What it does |
|---|---|---|---|
PARLEML_DATA_ROOT |
path | data_root (in the directory the backend starts in) |
On-disk layout root — every Workspace folder lives under here (DATA_ROOT/workspaces/<workspace_id>/). |
PARLEML_CATALOG_PATH |
path | unset | SQLite catalog file path (derived index over the filesystem). Default sits next to the data root so a clean checkout boots cleanly. |
PARLEML_CORS_ORIGINS |
JSON list of text | ["http://localhost:5173"] |
CORS — wide-open in local dev, locked down in production. |
PARLEML_MODE |
one of dev, test, prod |
dev |
Operating mode — drives a small number of defaults (e.g. catalog auto-rebuild on stale schema). Kept narrow so it doesn't sprawl. |
PARLEML_AGENT_MAX_POINTS |
integer | 2000 (≥ 1) |
Max points per series returned by the agent-facing chart "data mode" (?format=data). Caps trace arrays so a large scatter / time series doesn't blow up an agent's context window; the figure (UI) path is unaffected. |
PARLEML_AGENT_MAX_ROWS |
integer | 1000 (≥ 1) |
Row ceiling for the analyst SQL endpoint (POST /api/workspaces/{id}/query). Caps result sets so an LLM-authored SELECT can't pull an unbounded table into context; the body limit is clamped to this ceiling. |
PARLEML_MAX_UPLOAD_MB |
integer | 512 (≥ 1) |
Hard ceiling (MiB) for any single multipart upload (datasets, KB source files, BYO models). Uploads are buffered in memory before parsing, so an unbounded body is a trivial memory-exhaustion vector; a crafted zip-based workbook can also decompress far past its wire size, so this caps the compressed bytes we ever read. |
PARLEML_SAMPLES_DIR |
path | <repo>/sample_datasets (image: /opt/parleml-data/samples) |
The sample datasets Home's first-run gallery starts workspaces from: the repository's sample_datasets/ in a checkout; the delivery image ships them and points here. A sample whose file is missing is left out of the gallery, which hides itself when none is left. |
PARLEML_WARM_IMPORTS |
true or false |
true |
Import the ML stack the read paths load lazily (UMAP and its numba kernels, the AutoGluon pipeline, the model families, SHAP) on a background thread at startup, so the first reads of a freshly started server — an agent's first get_capabilities / get_model_evaluation — do not outlive a client's timeout. Readiness never waits for it: /api/health answers at once. The test suite turns it off, so each app a test builds skips those imports. |
Background jobs (subprocess execution)¶
Each long workload runs as python -m app.worker <job_id>; the parent enforces per-kind concurrency caps with a pending queue and a scheduler+reaper tick. See backend/README.md (Subprocess execution model).
| Variable | Type | Default | What it does |
|---|---|---|---|
PARLEML_JOBS_HEAVY_CONCURRENCY |
integer | 0 (≥ 0) |
Server-wide cap on heavy workers — experiment fits, explanation computes and batch scoring — running at once; further ones wait as queued (the experiment and explanation status say how many are ahead). Each fit uses every core it is given, so more at once only slows all of them and the API with them. 0 resolves to max(1, cpu//4) (see heavy_concurrency). |
PARLEML_JOBS_MLI_CONCURRENCY |
integer | 1 (≥ 1) |
MLI explanation worker slots: compute + on-demand narration share them. One by default — an explain of a large ensemble can take many GB. |
PARLEML_JOBS_BATCH_CONCURRENCY |
integer | 2 (≥ 1) |
Batch-predict slots (unkeyed; many jobs per experiment). |
PARLEML_JOBS_PROBE_CONCURRENCY |
integer | 2 (≥ 1) |
Signal Check probe slots (tabular + forecast). Each probe loads a sample and a torch foundation model; more than two at once competes for memory, not for answers. |
PARLEML_JOBS_BUILD_CONCURRENCY |
integer | 2 (≥ 1) |
Build slots: a dataset's EDA profile or leakage scan, and an experiment's model card (AutoDoc). Short jobs with their own slots, so they never wait behind a long fit. |
PARLEML_JOBS_CANCEL_GRACE_SPARLEML_JOBS_KILL_GRACE_S |
number number |
10.0 (≥ 0)30.0 (≥ 0) |
Cancel escalation: CANCEL sentinel -> after cancel_grace SIGTERM -> after kill_grace SIGKILL, driven by the reaper off the persisted pid so it survives a parent restart. |
PARLEML_JOBS_REAPER_INTERVAL_S |
number | 2.0 (> 0) |
How often the parent scheduler+reaper tick runs. |
PARLEML_JOBS_RETENTION_HOURS |
integer | 24 (≥ 1) |
Terminal job rows older than this are pruned by the reaper (the console keeps recent history; the table stays bounded). |
PARLEML_JOBS_WORKER_LOG_MAX_BYTES |
integer | 64 * 1024 * 1024 (≥ 1024) |
Hard ceiling on a run's worker.log. That file captures the entire worker stdout + stderr (AutoGluon / joblib / loky chatter), has no rotation of its own, lives under data_root forever, and is streamed whole by the download endpoint — a long fit can otherwise write hundreds of MB per run. Checked at spawn and on every reaper tick; past the ceiling the file is truncated and a marker line is written (see app.services.job_launcher.cap_worker_log), so the Console tab shows recent output rather than nothing. Floored at 1 KiB — the marker line alone is ~120 bytes, so a smaller cap would re-truncate every tick. |
PARLEML_JOBS_HEARTBEAT_INTERVAL_S |
number | 10.0 (> 0) |
Stall detection (see app.services.worker_watchdog). A worker's watchdog thread checks this often whether its job made progress — its processes used CPU, or its main thread's stack moved — and touches the job's worker.heartbeat when it did. |
PARLEML_JOBS_STACK_DUMP_AFTER_S |
number | 300.0 (> 0) |
A job that made no progress this long has every thread's stack written to its worker.log, and again each time this passes while it stays stuck; a healthy job's log carries none. Shorter than the stall limit, so a job the reaper fails as stalled always leaves its stacks behind. |
PARLEML_JOBS_STALL_AFTER_S |
number | 900.0 (> 0) |
A running job whose last heartbeat is older than this is failed as stalled and its process tree stopped; its waiters get the failure. Must exceed the longest silent wait a job legitimately makes (an LLM call's own timeout, a model download on a slow link). |
Authentication / SSO (OIDC relying-party)¶
All off by default so single-tenant local dev keeps working without credentials.
| Variable | Type | Default | What it does |
|---|---|---|---|
PARLEML_AUTH_REQUIRED |
true or false |
false |
The one authentication gate (and break-glass toggle). When true, every /api and /mcp request (except GET /api/health, the login flow and the OpenAPI docs) needs a session cookie or a bearer API token. Without oidc_issuer nobody can sign in, so only tokens minted before the gate was turned on get through (a headless, token-only server). When false the API is open: local dev, or emergency recovery while SSO is fixed. |
PARLEML_SESSION_SECRET |
text | unset | HMAC secret for the stateless session JWT. When unset we derive a stable key from the data-root .secret_key (see services.sessions), so sessions survive a restart without any extra configuration. |
PARLEML_SESSION_TTL_HOURS |
integer | 4 (≥ 1) |
Session lifetime. Short by default to bound the window after an IdP deprovision (revocation = IdP deprovision + TTL); the cookie is sliding-renewed while the user stays active. |
PARLEML_OIDC_ISSUERPARLEML_OIDC_CLIENT_IDPARLEML_OIDC_CLIENT_SECRETPARLEML_OIDC_REDIRECT_URIPARLEML_OIDC_SCOPES |
text text text text text |
unset unset unset unset openid email profile |
OIDC relying-party config. Presence of oidc_issuer enables SSO; discovery (<issuer>/.well-known/openid-configuration) yields the JWKS + endpoints, so one code path serves Entra / Okta / Google / Ping. |
PARLEML_POST_LOGIN_REDIRECT |
text | / |
Where to send the browser after a successful login. In dev the SPA is served separately (:5173) from the API (:8000), so set this to the SPA origin; in prod the SPA is same-origin and / is correct. |
PARLEML_OIDC_RP_LOGOUT |
true or false |
false |
RP-initiated logout (OIDC): when true, signing out of parleml also ends the IdP SSO session (the browser is bounced through the issuer's end_session_endpoint) so the next login re-prompts. Default off — most SSO deployments expect the IdP session to persist across apps. |
PARLEML_OIDC_POST_LOGOUT_REDIRECT |
text | unset | Where the IdP returns the browser after RP-initiated logout. Must be a post-logout redirect URI registered with the IdP. Falls back to post_login_redirect (the SPA origin) when unset. |
PARLEML_OPEN_MODE_AGENT_ADMIN |
true or false |
false |
Open mode (sign-in off) only: the anonymous user administers agents — profiles, skill packs, connections — through the published port too, not only from inside the container. An agent administrator can add a local tool server, which this server starts as a program, so with this on anyone who can reach the port can run programs here. It is for a single person on their own machine, where the published port reaches the app from the container bridge and the default left Settings → Agents closed to everyone. |
PARLEML_ADMIN_EMAILS |
comma-separated list | empty | Emails granted the admin role at SSO provisioning time. Accepts a comma-separated env value (a@x.com,b@y.com); NoDecode skips pydantic-settings' JSON decoding so the validator below can split it. |
PARLEML_OAUTH_ISSUER |
text | unset | The issuer of ParleML's OAuth authorization server for agent hosts: the backend's public origin, where /mcp, /oauth/* and /.well-known/* are served (https://parleml.example.com). Unset, the origin each request arrived at. Set it behind a proxy that rewrites the host. It is not where the web app's links point: that is PARLEML_PUBLIC_URL, which differs in local dev (the UI on Vite). |
Agent runtime¶
The agent runtime ParleML embeds (parleml_agents) has settings prefixed PARLEML_AGENTS_. The backend reads them from the same environment and .env as its own settings and builds the runtime's settings itself, so there is one reader. Where the runtime's files live follows PARLEML_DATA_ROOT and cannot be set: configuration (profiles, connections, skill packs, preferences and the vault) in <data_root>/config, runtime state in <data_root>/agents. Settings → Profiles, Skill packs and Connections edit the configuration.
| Variable | Type | Default | What it does |
|---|---|---|---|
PARLEML_AGENTS_PLATFORM_DIR |
path | <data_root>/agents (set by ParleML; cannot be set) |
Runtime state: runs/ and work/. ParleML passes <data_root>/agents. |
PARLEML_AGENTS_CONFIG_DIR |
path | <data_root>/config (set by ParleML; cannot be set) |
Installation configuration: connections.yaml, profiles/, packs/, settings.yaml and vault.json. Unset, it is <platform_dir>/config; ParleML passes <data_root>/config. |
PARLEML_AGENTS_VAULT_KEY_FILE |
path | <data_root>/.secret_key (set by ParleML; cannot be set) |
The Fernet key that encrypts vault.json. Unset, it is <config_dir>/.vault_key, created on the first write. ParleML passes its data-root key, so one key protects every secret it stores. |
PARLEML_AGENTS_MCP_HOME |
path | . |
Installation-local root for stdio MCP servers referenced by relative cwd values in connections.yaml. |
PARLEML_AGENTS_HOST_ADAPTERPARLEML_AGENTS_CLAUDE_BINARY |
one of claude-cli, cursor-cli, codex-cli, gemini-cli, faketext |
claude-cliclaude |
The adapter used when an agent profile does not name one. agents.py describes what each supports; hosts builds them. |
PARLEML_AGENTS_FAKE_SCENARIO |
text | unset | With host_adapter: fake, a named script every skill follows (hosts/fake_scenarios.py): end-to-end tests and demos without a CLI. |
PARLEML_AGENTS_CLAUDE_PINNED_VERSION |
text | 2.1.270 |
The argv in hosts/claude_cli.py is verified against this exact version. |
PARLEML_AGENTS_CODEX_BINARYPARLEML_AGENTS_CODEX_PINNED_VERSION |
text text |
codex0.162.1 |
codex exec --json (hosts/codex_cli.py). Signs in with codex login (ChatGPT plan) or OPENAI_API_KEY; verified against 0.162.1. |
PARLEML_AGENTS_GEMINI_BINARYPARLEML_AGENTS_GEMINI_PINNED_VERSION |
text text |
gemini0.62.0 |
gemini -p --output-format stream-json (hosts/gemini_cli.py). Signs in with Google or GEMINI_API_KEY; verified against 0.62.0. |
CURSOR_API_KEYPARLEML_AGENTS_CURSOR_API_KEY |
text | unset | Cursor Dashboard → Integrations. Also accepted unprefixed as CURSOR_API_KEY. |
PARLEML_AGENTS_CURSOR_HOST_ASSETS_ENABLED |
true or false |
false |
Release gate for pack-owned Cursor rules/hooks. Plain Cursor runs remain available while the credentialed native-assets spike is unresolved. |
PARLEML_AGENTS_USER_SETTINGS_ALLOWED |
true or false |
false |
Whether an agent profile may launch its CLIs with the running account's own settings (host_assets.include_user_settings). Those settings can carry instructions, hooks and permissions nobody reviewed, so only a local, one-person install should turn this on. |
PARLEML_AGENTS_SESSION_MCP_URL |
text | http://127.0.0.1:8000/session-mcp/ |
Address the spawned host uses to reach the session MCP mount. The embedding application mounts it at /session-mcp; the trailing slash is required, without it the endpoint 307s. |
PARLEML_AGENTS_MCP_TOOL_TIMEOUT_MS |
integer | 86_400_000 |
The chosen maximum human wait. A gate holds an MCP call this long, and so does await_operation on a blocking adapter. |
PARLEML_AGENTS_CURSOR_DEFERRED_MCP_WAITS_ENABLED |
true or false |
true |
Cursor's MCP client currently drops the configured timeout and cancels calls after about 60 seconds. Defer only session calls that wait. |
PARLEML_AGENTS_CURSOR_DEFERRED_WAIT_SECONDS |
number | 50.0 |
One bounded await, kept under Cursor's observed timeout with enough margin for the round trip. Every second below that cutoff is a second the operator's answer is observed by a wait already in flight instead of by a poll the model has to choose to make, so stay close to it. |
PARLEML_AGENTS_AWAITABLE_POLL_SECONDSPARLEML_AGENTS_AWAITABLE_PROGRESS_SECONDS |
number number |
2.030.0 |
How often a held await_operation on a registered kind (for example job:<id>) polls the kind's status function, and how often it sends MCP progress so a host's idle timer never fires during a long wait. |
PARLEML_AGENTS_AWAITABLE_MAX_HOLD_SECONDS |
number | 900.0 |
The longest one await_operation on a registered kind holds on a blocking adapter. Past it the call hands back pending with how long the run has waited on the handle and the kind's remedy (how to stop the work, or what to read), so stuck server work never holds a run silently for the whole mcp_tool_timeout_ms; awaiting again keeps waiting. Cursor's slices are shorter still; past this total wait they carry the same report. |
PARLEML_AGENTS_WALL_CLOCK_SECONDSPARLEML_AGENTS_MAX_TURNSPARLEML_AGENTS_MAX_CONCURRENT_CHILDREN |
integer integer integer |
86_400805 |
Default run caps for one attempt. A profile or skill may raise them. |
PARLEML_AGENTS_BUILTIN_TOOLS |
JSON list of text | Read, Write, Edit, Glob, Grep |
Built-in host tools granted besides MCP. No shell and no web fetch by default: ML work goes through the business MCP, and the agent's own files go to the per-root work directory. Claude Code gets the file tools per directory: reads in the work directory, uploads, reference paths and packs, writes in the work directory only. |
PARLEML_AGENTS_PREFLIGHT_TIMEOUT_SECONDS |
number | 30.0 |
Per-connection bound on how long a preflight probe may take. A CLI's status call can take over 10 s while training jobs hold the CPU, and a shorter bound reads a working sign-in as none. |
PARLEML_AGENTS_SSE_QUEUE_SIZE |
integer | 1000 |
Outbound SSE queue depth per browser connection. Overflow drops transcript frames (recoverable) and closes on durable events (not). |
PARLEML_AGENTS_ACCESS_LOG_READS |
true or false |
false |
When true, uvicorn's access log also prints successful reads. Off by default: the UI polls, so those lines are noise that hides the rest. Writes and every 4xx/5xx print either way. |
PARLEML_AGENTS_MODEL_ROUTING_ENABLED |
true or false |
true |
When false, orchestrator routing uses only its local scorer. The scorer matches words, so work described in business terms rarely matches an orchestrator described in implementation terms; turning this off trades that accuracy for no model call on the start screen. |
PARLEML_AGENTS_ADMIN_ENABLED |
true or false |
true |
When false, /admin returns 403 so the UI renders access-denied rather than partially editable controls. |
PARLEML_AGENTS_AUTHORING_ADAPTER |
one of model, fake |
model |
model calls the configured host for metadata proposals; fake returns deterministic proposals for CI and unit tests. |
PARLEML_AGENTS_ASK_CONCURRENCY |
integer | 2 (≥ 1) |
How many one-shot parleml_agents.ask.ask calls (annotations, routing, suggestions) run at once in one process. ParleML also caps its annotation jobs at this number, so a burst of finished objects queues instead of starting a CLI per object. |
PARLEML_AGENTS_RUN_DELETE_ENABLED |
true or false |
true |
When false, deleting a run tree returns 403. Deleting lives on /runs, which admin_enabled does not guard, so a shared host needs its own switch for the one irreversible action there. |
PARLEML_AGENTS_MAX_UPLOAD_BYTES |
integer | 25 * 1024 * 1024 |
Bound on one operator upload. Applied before anything reaches disk. |
PARLEML_AGENTS_MAX_UPLOAD_FILES |
integer | 20 (≥ 1) |
Bound on how many files one start may attach, each read into memory. |
PARLEML_AGENTS_TOMBSTONE_RETENTION_DAYS |
integer | 365 (≥ 1) |
How long a deleted session's tombstone stays readable. Older ones are removed at start and once a day after. |
Read directly by the backend¶
| Variable | Default | What it does |
|---|---|---|
PARLEML_BUILD_COMMIT |
set by the image build | The commit the image was built from, shown in Help → About. Set by the build, not by hand. |
PARLEML_BUILD_DATE |
set by the image build | When the image was built (UTC), shown in Help → About. Set by the build, not by hand. |
PARLEML_DISABLE_TRUSTSTORE |
unset | 1 keeps Python's bundled certificate list. By default TLS goes through the operating system's trust store, so a corporate proxy's root CA is trusted for model downloads. |
PARLEML_EDA_MAX_ROWS |
1000000 |
Rows an EDA profile reads at most, as a seeded random sample; 0 profiles every row. |
PARLEML_HELP_DIR |
unset (image: /opt/parleml-help) |
The built documentation (delivery/build_help.py output) the backend serves at /help. Unset, or without an index.html, /help answers with a page that says how to build it. |
PARLEML_MLI_CANDIDATE_MAX_ROWS |
6000 |
Rows the candidate pass of Explain computes on. Raise it for a deeper, slower pass. |
PARLEML_MLI_EDA |
on | 0, false, no or off skips the EDA profile inside Explain. |
PARLEML_MLI_EXPLANATIONS |
on | 0, false, no or off skips the explanation step of Explain. |
PARLEML_MLI_HOLDOUT_MAX_ROWS |
50000 |
Held-out rows a supervised Explain profile tests its rules on, capped separately so the mining cap never thins the test. |
PARLEML_MLI_MAX_ROWS |
20000 |
Rows an Explain profile mines at most (a seeded sample). |
PARLEML_MLI_RULES_ALL |
on | 0, false, no or off skips mining applyable rules for every class. |
PARLEML_MLI_UMAP_MAX_POINTS |
20000 |
Points the UMAP scatter sends to the browser at most. |
PARLEML_MLI_VERBOSE |
on | 0, false, no or off quiets the Explain computation's progress log. |
PARLEML_SAMPLE_VALUES_PER_COLUMN |
5 |
Sample values the Columns tab shows per column. |
PARLEML_WEB_DIST |
unset | A built web app (apps/web/dist) for the backend to serve at /. Unset, the web app is served separately. |
Read by the MCP server¶
The hosted /mcp endpoint runs inside the backend; the standalone server (python -m parleml_mcp.server) reads the same variables.
| Variable | Default | What it does |
|---|---|---|
PARLEML_API_BASE |
http://localhost:8000 |
The backend the standalone MCP server forwards to. |
PARLEML_API_TOKEN |
unset | The API token the standalone MCP server uses when its host passes none. The hosted /mcp endpoint never falls back to it. |
PARLEML_APPS_DIR |
the package's own copy | A directory of built app panels to serve instead of the packaged ones. |
PARLEML_MCP_HOST |
127.0.0.1 |
The address the standalone HTTP MCP server listens on. |
PARLEML_MCP_PORT |
8765 |
The port the standalone HTTP MCP server listens on. |
PARLEML_MCP_TRANSPORT |
stdio |
http serves the standalone MCP server over HTTP. |
PARLEML_PUBLIC_URL |
unset | Where people open the web app, for the links agents hand out (ui_url). It must be an absolute http(s) URL with no query or fragment; a bad value refuses to boot. Unset, links use the request's own origin. Not the OAuth issuer: that is PARLEML_OAUTH_ISSUER. |
PARLEML_REFERENCE_DIR |
the package's own copy | A directory of playbook pages to serve instead of the packaged ones. |
Read by your agent host¶
Set where the agent host (Claude Code, Cursor, Codex, Gemini) runs, not on the server.
| Variable | Default | What it does |
|---|---|---|
PARLEML_MCP_URL |
unset | ParleML's /mcp address, which the skill bundles' MCP server entry reads from the agent host's environment (with PARLEML_API_TOKEN). The server itself never reads it. |