Skip to content

Environment variables

All env vars are prefixed Z4J_ (brain-side) or read from the Z4J dict in framework settings (agent-side). Brain settings map onto fields in z4j_brain.settings.Settings; the prefix is dropped and the name lowercased (e.g. Z4J_EVENT_RETENTION_DAYS -> settings.event_retention_days).

This page lists every Z4J_* variable the brain reads. Most operators set only the required five plus a handful from the Database, Retention, and Metrics sections; the rest are exposed for fine-tuning under sustained load or unusual deployments.

Variable Description
Z4J_DATABASE_URL Database connection string: postgresql+asyncpg://user:pw@host/db for PostgreSQL, or sqlite+aiosqlite:///path/to/z4j.db for the packaged SQLite mode. Any other driver prefix is rejected. When unset, the CLI (z4j serve and the management commands) supplies sqlite+aiosqlite:///$Z4J_HOME/z4j.db and sets Z4J_REGISTRY_BACKEND=local.
Z4J_SECRET Master application secret. Derives per-project frame HMAC keys, hashes agent and reset tokens, and encrypts stored TOTP secrets. It does not sign sessions; Z4J_SESSION_SECRET is independent. Any random string of at least 32 bytes; the packaged SQLite bootstrap mints a 48-byte urlsafe token.
Z4J_SESSION_SECRET Independent secret for user-session cookies. Rotating it invalidates active sessions unless the previous value is listed in Z4J_PREVIOUS_SESSION_SECRETS; new cookies are always signed with the current value.
Z4J_AUDIT_CHAIN_SECRET Dedicated audit-chain signing key, at least 32 bytes. Use a key independent of Z4J_SECRET (the brain does not check that they differ). Required outside development, with no fallback: the brain refuses to start without it rather than signing the audit log with a key that protects other things. Keep it where the database operator cannot read it.
Z4J_PUBLIC_URL Full externally reachable base URL with scheme (https://z4j.example.com). Defaults to http://localhost:7700, which only passes validation when Z4J_ENVIRONMENT is exactly dev; outside dev the value must start with https:// unless Z4J_ALLOW_HTTP_PUBLIC_URL is set. Validated: no whitespace, no userinfo, http(s) only.
Variable Default Description
Z4J_PREVIOUS_SECRETS - Comma-separated previous Z4J_SECRET values still accepted when verifying agent bearer tokens, decrypting stored TOTP secrets and notification channel configs, and verifying or activating legacy audit rows. It does NOT cover API keys, invitation tokens or password-reset tokens (those hash with the current Z4J_SECRET only), and it does NOT cover frame signing: post-handshake frames use a per-project key derived from the current Z4J_SECRET alone, so rotation still requires re-credentialing every agent. Keep the old value until z4j secrets rewrap reports every encrypted row under the current master (it re-encrypts every channel config and stored TOTP secret in one run; without it a row is re-wrapped only when it is next written or, for TOTP, verified), and until any legacy audit activation or reseal work is verified. Each entry must be at least 32 bytes. Empty = no rotation in progress. See incident response.
Z4J_PREVIOUS_SESSION_SECRETS - Comma-separated previous session secrets still accepted while verifying older cookies during rotation.
Z4J_AUDIT_CHAIN_PREVIOUS_SECRETS - Comma-separated previous audit-chain keys still accepted by the verifier during rotation. Writes use the current Z4J_AUDIT_CHAIN_SECRET. Rotation never re-signs or re-anchors old rows. Keep an old key until retention has removed every live row signed by it, then use z4j audit retire-chain-key --key-id <id>; the command refuses while its authenticated live count is non-zero.
Variable Default Description
Z4J_DATABASE_POOL_SIZE 20 Connections held open per engine. Each brain worker builds its own engine, so worst-case demand is workers x (pool_size + max_overflow). Check that against your server's max_connections before raising it.
Z4J_DATABASE_MAX_OVERFLOW 10 Additional connections allowed above the pool under burst, per engine. Counts toward the same worst-case total.
Z4J_DATABASE_STATEMENT_CACHE_SIZE 50 Per-connection asyncpg prepared-statement cache cap. 0 disables. See Brain memory tuning.
Z4J_DATABASE_MAX_INACTIVE_CONNECTION_LIFETIME_SECONDS 60 Seconds before an idle asyncpg connection is closed and reopened. Maps to SQLAlchemy's pool_recycle.
Z4J_AUTO_MIGRATE true Run Alembic head migrations on brain boot. Set to false for orchestrators that run migrations as a separate step.
Z4J_REQUIRE_DB_SSL true Refuse Postgres URLs whose sslmode is not require, verify-ca or verify-full. Relaxed only when Z4J_ENVIRONMENT is exactly dev; there is no loopback exception.
Z4J_DATABASE_HOST, Z4J_DATABASE_PORT, Z4J_DATABASE_USER, Z4J_DATABASE_PASSWORD, Z4J_DATABASE_NAME - Structured alternative to Z4J_DATABASE_URL, read by the CLI's configuration capture rather than by Settings. When Z4J_DATABASE_URL is absent, all five are composed into one postgresql+asyncpg:// URL with the user and password quoted as URL data. All five are required together; a partial set, an empty value, or a port outside 1 to 65535 is rejected. When Z4J_DATABASE_URL is set they are ignored.
Z4J_DB_STATEMENT_TIMEOUT_MS 10000 Postgres statement_timeout (milliseconds). Caps any single query.
Z4J_DB_LOCK_TIMEOUT_MS 3000 Postgres lock_timeout (milliseconds). Caps how long a transaction waits for a row lock.
Z4J_DB_IDLE_IN_TX_TIMEOUT_MS 30000 Postgres idle_in_transaction_session_timeout (milliseconds).
Z4J_STARTUP_VERIFY_LOCK_TIMEOUT_MS 600000 How long one starting worker may wait for a sibling worker's audit-chain startup walk (milliseconds). Every worker verifies the chain before it serves, one after another under the chain lock, so on a large trail the later workers queue; inside that one transaction this bound replaces Z4J_DB_LOCK_TIMEOUT_MS (and Z4J_DB_STATEMENT_TIMEOUT_MS, which Postgres also counts a lock wait against), never below either. Range 1000 to 3600000. Ignored on SQLite. See scaling.
Z4J_ASYNCPG_CONNECT_TIMEOUT 10.0 Seconds before the registry's asyncpg LISTEN/NOTIFY connection attempt times out. Only that connection reads it; the SQLAlchemy pool does not. Range 1 to 60.
Z4J_ASYNCPG_CLOSE_TIMEOUT 5.0 Seconds allowed for closing the registry's LISTEN/NOTIFY connection. Only that connection reads it. Range 1 to 30.
Z4J_WAL_CHECKPOINT_INTERVAL_SECONDS 300 SQLite-only PRAGMA wal_checkpoint(TRUNCATE) cadence. Ignored on Postgres.
Variable Default Description
Z4J_PASSWORD_MIN_LENGTH 12 Minimum password length. Operators may configure it down to the supported floor of 8.
Z4J_ARGON2_TIME_COST 3 argon2id time cost.
Z4J_ARGON2_MEMORY_COST 65536 argon2id memory (KiB, 64 MiB default).
Z4J_ARGON2_PARALLELISM 4 argon2id threads.
Variable Default Description
Z4J_SESSION_ABSOLUTE_LIFETIME_SECONDS 604800 Hard cap on a session's age (default 7 days). Sessions are rejected past this regardless of activity.
Z4J_SESSION_IDLE_TIMEOUT_SECONDS 1800 Sliding idle timeout (default 30 minutes). Sessions whose last_seen_at is older than this are rejected.
Z4J_SESSION_REMEMBER_ME_LIFETIME_SECONDS 2592000 Hard lifetime of a session where the user chose "Keep me signed in" at login (default 30 days). It replaces the absolute lifetime for that session, and the idle timeout is bypassed. Remembered status is inferred from the stored session duration rather than a flag, so keep this comfortably greater than Z4J_SESSION_ABSOLUTE_LIFETIME_SECONDS and do not change either lifetime while sessions minted under the old values remain live; otherwise a normal session can be mistaken for a remembered one and bypass the idle timeout.
Z4J_SESSION_COOKIE_SAMESITE lax SameSite attribute on the session cookie. lax or strict.
Z4J_SESSION_PIN_USER_AGENT false If true, the resolved client User-Agent at session issue time is enforced on every subsequent request. Off by default; too many false positives on mobile networks. A mismatch revokes the session (reason user_agent_changed) on cookie-authenticated REST requests and on dashboard WebSocket connects.
Z4J_LOGIN_LOCKOUT_THRESHOLD 10 Failed login attempts on a single account before lockout.
Z4J_LOGIN_LOCKOUT_DURATION_SECONDS 900 Lockout duration after threshold exceeded.
Z4J_LOGIN_BACKOFF_BASE_SECONDS 0.5 Deprecated compatibility value; the login path does not read it.
Z4J_LOGIN_BACKOFF_MAX_SECONDS 5.0 Deprecated compatibility value; the login path does not read it.
Z4J_LOGIN_MIN_DURATION_MS 300 Minimum response floor applied before account-dependent bookkeeping. It reduces short-path timing differences but is not a whole-request constant-time guarantee.
Z4J_LOG_LOGIN_EMAIL false Log the attempted email on failed logins. Off by default (PII consideration).
Variable Default Description
Z4J_RECONCILIATION_SWEEP_SECONDS 300 Seconds between reconciliation passes that compare in-flight tasks against engine result backends.
Z4J_RECONCILIATION_STALE_THRESHOLD_SECONDS 900 Minimum age before a task still in pending, started or retry state becomes eligible for reconciliation.
Variable Default Description
Z4J_EVENT_RETENTION_DAYS 30 Days raw events rows live before the retention worker drops their partition. Also the cutoff for agent_status_history rows, which the audit-retention sweep prunes on this value rather than on Z4J_AUDIT_RETENTION_DAYS.
Z4J_AUDIT_RETENTION_DAYS 90 Days audit_log rows live before the retention worker or z4j audit prune removes them, as a verified oldest-first prefix under the authenticated prune boundary. Minimum 1, no ceiling; a window above 3650 days is honoured and named in one startup WARNING. Read at startup only: there is no write-back from the dashboard or the API. See audit retention.
Z4J_AUDIT_RETENTION_BY_CLASS {} JSON object mapping an action class (the first dotted segment of an action name: auth, command, schedule, dead_letters) to its own window in days, for example {"auth": 365, "command": 30}. A class not listed uses Z4J_AUDIT_RETENTION_DAYS. Keys must look like a class (no dots), values must be whole positive days; anything else is rejected when settings load. Because the chain only loses a contiguous prefix, a longer window holds every row written after the oldest row it keeps, and a shorter one only reaches rows older than every retained neighbour. See audit retention.
Z4J_AUDIT_RETENTION_SWEEP_INTERVAL_SECONDS 3600 Seconds between audit retention sweeps (default 1 hour).
Z4J_AUDIT_RETENTION_SWEEP_BATCH_SIZE 5000 Rows pruned per database round-trip inside one sweep pass.
Z4J_AUDIT_RETENTION_SWEEP_MAX_PER_PASS 200000 Hard cap on rows deleted in a single sweep pass. Sweeps stop and resume on the next interval if the cap is hit.
Z4J_AUDIT_CHAIN_VERIFY_ENABLED false Run the scheduled audit-chain verification worker. Off by default, because verification walks every retained row. Leader-gated, so replicas do not each walk the same table. A failed verification is logged at error level and counted; it does not stop the brain.
Z4J_AUDIT_CHAIN_VERIFY_INTERVAL_SECONDS 86400 Cadence for the scheduled verification. Floor 900 (15 minutes), ceiling 604800 (one week).
Variable Default Description
Z4J_METRICS_AUTH_TOKEN - (auto-minted on packaged SQLite) Bearer token required by /metrics. Operators may provide one explicitly via env or ~/.z4j/secret.env. It is auto-minted into ~/.z4j/secret.env only on the packaged SQLite bootstrap, whenever it is missing there, regardless of Z4J_METRICS_PUBLIC. PostgreSQL deployments must set it explicitly, or /metrics answers 401 until they do (unless Z4J_METRICS_PUBLIC is set). Run z4j metrics-token to print or rotate.
Z4J_METRICS_PUBLIC false Set to 1 to leave an enabled /metrics endpoint open. It has no effect when Z4J_METRICS_ENABLED=false. Use only when the endpoint is firewalled or behind an authenticated proxy.
Z4J_METRICS_ENABLED true Set to false to leave the /metrics route unmounted; requests return 404 regardless of the public or token settings.

Optional error capture, off by default. Install the SDK with pip install "z4j[sentry]" and set a DSN; without a DSN the brain runs unchanged even when the SDK is installed. A before_send scrubber strips Authorization headers, cookies and OAuth-style query tokens before any event leaves the brain.

Variable Default Description
Z4J_SENTRY_DSN - Sentry DSN. Empty or unset disables Sentry entirely. Treated as a secret: it never lands in startup logs or validation errors.
Z4J_SENTRY_ENVIRONMENT - Override the Sentry environment tag. Defaults to Z4J_ENVIRONMENT, so a production brain shows up under that name without a second knob. Set it when the deployment label and the Sentry project layout disagree (several staging brains routing into one Sentry project, for example). Max 64 characters.
Z4J_SENTRY_TRACES_SAMPLE_RATE 0.0 Tracing sample rate, 0.0 to 1.0. At 0.0 only unhandled exceptions ship and no performance spans are created. A small positive value (0.05 is a good starting point) captures transaction performance on a fraction of requests.
Z4J_SENTRY_PROFILES_SAMPLE_RATE 0.0 Profiling sample rate, 0.0 to 1.0. Profiling only fires inside transactions, so it is bounded above by the traces sample rate; leave it at 0 unless traces are already enabled.
Z4J_SENTRY_SEND_DEFAULT_PII false Forward identifying data (IPs, usernames) to Sentry. The scrubber strips Authorization, cookie and OAuth-token surfaces regardless of this flag, so enabling it still leaves credentials redacted.

Optional distributed tracing, off by default. Install the SDK and the OTLP HTTP exporter with pip install "z4j[otel]" and set an endpoint; without an endpoint the brain runs unchanged even when the SDK is installed. The extra instruments FastAPI, SQLAlchemy and httpx.

Variable Default Description
Z4J_OTEL_EXPORTER_OTLP_ENDPOINT - OTLP exporter endpoint. Empty or unset disables tracing completely. Treated as a secret, because some collectors embed an API key in the endpoint path. Honeycomb, Lightstep, Tempo and the Jaeger collector all expose an OTLP HTTP endpoint at /v1/traces.
Z4J_OTEL_PROTOCOL http/protobuf OTLP transport: http/protobuf, http or grpc. The default works through plain HTTPS. Set grpc only when the collector exposes gRPC alone and opentelemetry-exporter-otlp-proto-grpc is installed.
Z4J_OTEL_EXPORTER_OTLP_HEADERS - Comma-separated key=value pairs forwarded to the exporter as OTEL_EXPORTER_OTLP_HEADERS. The place for an x-honeycomb-team or authorization header. Treated as a secret.
Z4J_OTEL_SERVICE_NAME z4j-brain The service.name resource attribute. Multi-brain deployments set distinct names (z4j-brain-eu, z4j-brain-us) to tell them apart on the collector side. Max 128 characters.
Z4J_OTEL_SERVICE_NAMESPACE z4j The service.namespace resource attribute, so every z4j service groups together in the collector UI. Max 128 characters.
Z4J_OTEL_ENVIRONMENT - The deployment.environment resource attribute. Defaults to Z4J_ENVIRONMENT. Max 64 characters.
Z4J_OTEL_TRACES_SAMPLER_ARG 0.0 Sampler argument, 0.0 to 1.0. At 0.0 every locally started trace is dropped. The sampler is ParentBased(TraceIdRatioBased(arg)), so an inbound trace context from an upstream service is always honoured; set a small positive value to capture a fraction of requests.
Z4J_OTEL_INCLUDE_HEALTH false When false, /health* and /metrics are excluded from tracing: they carry too much background traffic for any sample budget to be meaningful. Set true only to trace health-check latency.
Z4J_OTEL_EXCLUDED_URL_PATTERNS - Comma-separated additional URL substrings to exclude from tracing, layered on top of the health exclusion. Max 512 characters.

Optional out-of-band mirror of audit rows to a SIEM receiver, off by default. The brain's own audit log stays the source of truth; a leader-gated background worker delivers rows past a durable cursor at least once and in order, so a slow or absent receiver never blocks the audit write path and never loses a row. See audit webhook forwarding.

Variable Default Description
Z4J_AUDIT_WEBHOOK_URL - Receiver URL. Empty or unset disables the forwarder entirely. Treated as a secret, because Splunk HEC and Datadog intake URLs embed a token in the path.
Z4J_AUDIT_WEBHOOK_HMAC_SECRET - HMAC-SHA256 key that signs every forwarded row. Required, and at least 32 bytes, whenever Z4J_AUDIT_WEBHOOK_URL is set to a non-blank value; the brain refuses to start otherwise. Receivers verify by recomputing hmac.new(secret, timestamp + "." + body, sha256).hexdigest() and comparing it in constant time to the X-Z4J-Audit-Signature header.
Z4J_AUDIT_WEBHOOK_TIMEOUT_SECONDS 10.0 Per-row POST timeout in seconds. Range 1 to 120.
Z4J_AUDIT_WEBHOOK_BATCH_SIZE 100 Rows read past the cursor per pass, each POSTed in chain order. A pass that delivers a full batch runs again at once, so this bounds the length of one leader-locked pass, not throughput. Range 1 to 1000.
Z4J_AUDIT_WEBHOOK_POLL_INTERVAL_SECONDS 5.0 How often the worker looks for rows past the cursor once it is caught up; delivery latency on a quiet brain is at most this. Range 1 to 300.
Z4J_AUDIT_WEBHOOK_MAX_BACKOFF_SECONDS 300.0 Ceiling on the wait between attempts while the receiver keeps failing. The wait starts at one second and doubles per consecutive failure, the count is persisted with the cursor so a restart keeps it, and a success resets it. Range 1 to 3600.
Z4J_AUDIT_WEBHOOK_BUFFER_SIZE 1000 Inert. The forwarder no longer buffers rows in memory; it delivers from the durable cursor and drops nothing. Still accepted so existing configurations keep loading. Range 10 to 100000.

Where background audit exports and the scheduled chain-head export are written. See audit exports.

Variable Default Description
Z4J_EXPORT_SINK none none, local or s3. none disables export jobs (the API answers 409) and the head export, and the export-jobs worker is not started.
Z4J_EXPORT_SINK_PATH - Directory for the local sink. Required when the sink is local; must exist and must not be a symlink. Files are created 0640 and written atomically; the sink refuses to write through a symlink below the base.
Z4J_EXPORT_SINK_S3_BUCKET - Bucket for the s3 sink. Required when the sink is s3.
Z4J_EXPORT_SINK_S3_PREFIX z4j-exports Key prefix inside the bucket; jobs land under <prefix>/audit/<slug>/ and heads under <prefix>/audit-head/.
Z4J_EXPORT_SINK_S3_ENDPOINT_URL - Endpoint URL for S3-compatible stores (MinIO, Ceph RGW, B2). Unset means AWS.
Z4J_EXPORT_SINK_S3_REGION - Region name passed to the client. Unset defers to the AWS environment.
Z4J_EXPORT_SINK_S3_ACCESS_KEY_ID - Explicit access key. Set together with the secret key, or neither, in which case the standard AWS credential chain is used. Secret; never logged.
Z4J_EXPORT_SINK_S3_SECRET_ACCESS_KEY - Explicit secret key. Secret; never logged.
Z4J_EXPORT_JOBS_POLL_INTERVAL_SECONDS 5 How often the export-jobs worker looks for queued jobs, 1 to 300. A tick with more work waiting re-runs sooner.
Z4J_EXPORT_JOBS_PAGE_SIZE 2000 Rows fetched per page while streaming a job, 100 to 20000. The whole result is never held in memory.
Z4J_AUDIT_HEAD_EXPORT_INTERVAL_SECONDS 0 Write the authenticated audit-chain head to the sink every this many seconds under audit-head/current.json plus a dated copy. 0 disables; otherwise 60 to 604800. Requires a sink and Z4J_AUDIT_CHAIN_SECRET.
Variable Default Description
Z4J_BOOTSTRAP_ADMIN_EMAIL - Skip the first-boot setup URL and auto-provision an admin with this email. Read once at boot.
Z4J_BOOTSTRAP_ADMIN_PASSWORD - Required when Z4J_BOOTSTRAP_ADMIN_EMAIL is set. Eagerly popped from os.environ after use.
Z4J_BOOTSTRAP_ADMIN_DISPLAY_NAME - Optional display name for the auto-provisioned admin. Read once at boot alongside the email; z4j bootstrap-admin --display-name (or its createsuperuser alias) sets it for you.
Z4J_FIRST_BOOT_TOKEN_TTL_SECONDS 900 Validity window for the one-time setup token (default 15 minutes).
Z4J_FIRST_BOOT_ATTEMPTS_PER_IP 30 Sliding-window cap on setup-token verification attempts per IP per 15 minutes. A global cap of 8x this value applies across all IPs in the same window, so rotating source addresses cannot multiply the budget; setup.completed rows are not counted against it.
Variable Default Description
Z4J_MFA_ENFORCE_FOR_ADMINS false Require MFA enrollment for every user with the global is_admin bit. Past the grace window their sessions are restricted to the enrollment flow.
Z4J_MFA_ENFORCE_FOR_ALL false Same for every user. Stricter superset of the admins-only flag.
Z4J_MFA_ENROLLMENT_GRACE_DAYS 7 Days an enforcement-targeted user has to enroll once their grace clock starts. Range 0..90; 0 restricts from the first post-policy login.
Z4J_MFA_RECOVERY_CODE_COUNT 10 Single-use recovery codes minted at enrollment. Range 5..50.
Z4J_MFA_VERIFICATION_TTL_SECONDS 3600 How long a successful MFA verify counts as fresh for the sensitive-action gate (default 60 min).
Z4J_MFA_REMEMBER_DEVICE_DAYS 30 Lifetime of the z4j_mfa_trust "remember this device" cookie. Hard upper bound 90.
Z4J_MFA_VERIFICATION_RATE_PER_MIN 10 Per-IP cap on /auth/mfa/verify attempts per minute. Tighter than login because the endpoint is a TOTP brute-force target.
Z4J_MFA_TRUSTED_DEVICES_MAX_PER_USER 20 Cap on active trusted-device rows per user. At the cap, the least recently seen active row (smallest last_seen_at) is revoked to make room.
Z4J_MFA_LOCKOUT_THRESHOLD 5 Consecutive wrong TOTP codes, counted across /auth/mfa/verify, enrollment completion and disable, before the account is locked. A successful verification resets the counter. Complements the per-IP verify throttle, which IP rotation can bypass. Range 3 to 100.
Z4J_MFA_LOCKOUT_DURATION_SECONDS 900 How long the per-account MFA lock lasts once the threshold trips (default 15 minutes). Range 60 to 86400.

See Multi-factor authentication for the full design + threat model, and MFA enforcement for the enforcement policy semantics.

Variable Default Description
Z4J_NOTIFICATIONS_WEBHOOK_ALLOW_HTTP false Allow http:// webhook URLs. Default refuses plaintext at both config-validation and dispatch time.
Variable Default Description
Z4J_AGENT_OFFLINE_TIMEOUT_SECONDS 30 Heartbeats older than this mark the agent offline in the dashboard.
Z4J_AGENT_OFFLINE_ALERT_GRACE_SECONDS 60 Extra silence past the offline timeout before the outage is confirmed and alerted (audit row + worker.offline rules + agent.offline subscriptions). See agent offline alerts.
Z4J_AGENT_HEALTH_SWEEP_SECONDS 10 Cadence for the agent health-check sweep.
Z4J_AGENT_STALE_PRUNE_DAYS 30 Live agents are soft-revoked and hidden after this many days without activity. This includes offline agents whose last heartbeat is old and UNKNOWN agents that never connected whose creation time is old. The agent row and historical IDs remain; audit entries retain target_id as a value, not through a foreign key. Set 0 to disable.
Z4J_AGENT_HYGIENE_SWEEP_SECONDS 86400 Cadence of the hygiene worker that applies Z4J_AGENT_STALE_PRUNE_DAYS. Once a day is enough; the prune target is weeks stale, not minutes. Range 60 to 604800.
Variable Default Description
Z4J_WS_IDLE_TIMEOUT_SECONDS 90 Per-connection idle timeout for /ws/agent (agent) and /ws/dashboard.
Z4J_WS_INGEST_QUEUE_MAXSIZE 2000 Bounded per-connection ingest queue. Decouples app-level frame dispatch from the WS recv loop so PING/PONG keeps flowing.
Z4J_WS_MAX_FRAME_BYTES 1048576 Maximum inbound WebSocket frame size (1 MiB).
Z4J_MAX_WS_FRAME_BYTES 1048576 Compatibility cap for inbound WebSocket frames. The gateway enforces the smaller of this and Z4J_WS_MAX_FRAME_BYTES. Minimum 1024.
Z4J_WS_PER_AGENT_CONCURRENCY_CAP 64 Maximum concurrent worker connections per agent_id (worker-first protocol). 0 disables the cap. A new worker connection over the cap is closed with code 4429 during the handshake; a reconnect of an existing worker_id replaces its slot and does not count against the cap.

The brain's BrainRegistry routes commands to agent connections across replicas. The default postgres_notify backend uses Postgres LISTEN/NOTIFY; SQLite forces local automatically.

Variable Default Description
Z4J_REGISTRY_BACKEND postgres_notify postgres_notify or local. SQLite forces local.
Z4J_REGISTRY_LISTENER_HEARTBEAT_SECONDS 10 Self-NOTIFY heartbeat for the watchdog on the LISTEN connection.
Z4J_REGISTRY_LISTENER_HEARTBEAT_TIMEOUT_SECONDS 25 Timeout before the watchdog reconnects the LISTEN connection.
Z4J_REGISTRY_LISTENER_MAX_AGE_SECONDS 900 Hard-recycle interval for the LISTEN connection.
Z4J_REGISTRY_RECONCILE_INTERVAL_SECONDS 30 Poll cadence for pending commands targeting an agent this replica owns.

These are app-level caps; the per-endpoint per-IP buckets in authentication and rate-limits layer on top.

Variable Default Description
Z4J_MAX_PAYLOAD_SIZE_BYTES 8192 Maximum REST request body size. Larger requests return 413.
Z4J_REDACTION_EXTRA_KEY_PATTERNS [] JSON array of extra key-name regexes (["^launch_code$", "customer_ssn"]) the brain's redaction pass applies to every inbound event on top of the built-in patterns, matched case-insensitively against each key of task args, kwargs, results and exception payloads. An entry that does not compile refuses to start, and so does one that backtracks catastrophically: each pattern is timed at startup against adversarial keys and refused when one key takes over 20 ms or the probe set over 100 ms. Extra patterns run only on keys of up to 64 characters (no pattern runs on a key over 256); a longer key is treated as sensitive and its value scrubbed. The agent-side variable of the same name is comma-separated and governs the agent's first pass; this one governs the brain's second pass. See redaction.
Z4J_TASKS_EXPORT_MAX_ROWS 50000 Upper bound on rows served by the task export endpoint (CSV, XLSX, JSON); 100 to 100000. A filter that matches more rows than this is refused with a 422 validation error pointing operators at narrower filters (details.cap, details.format, details.setting); the export is never truncated. Audit export has its own fixed ceiling of 50000 rows (25000 for XLSX) that this setting does not change.
Z4J_RATELIMIT_COMMANDS_PER_MINUTE 100 Reserved compatibility setting. No command path enforces this value; do not rely on it as a security boundary.
Z4J_RATELIMIT_EVENTS_PER_SECOND 10000 Reserved compatibility setting. No ingestion path enforces this value; do not rely on it as a security boundary.
Z4J_ADMIN_PROJECT_LIST_CAP 500 Upper bound on admin project-listing endpoints.
Z4J_REQUEST_TIMEOUT_SECONDS 30 Reserved compatibility setting. There is no global handler timeout or automatic 504 path, so do not rely on this value as a request wall-clock budget.
Z4J_REST_DEFAULT_PAGE_SIZE 50 Default page size on REST list endpoints.
Z4J_REST_MAX_PAGE_SIZE 500 Maximum page size on REST list endpoints.
Variable Default Description
Z4J_ALLOWED_HOSTS [] Host-header allow-list. Production deployments must populate this. When it is set, the ~/.z4j/allowed-hosts file and auto-detect are skipped. That file (z4j allowed-hosts add) is read only by a non-PostgreSQL brain, and only when z4j serve starts; a PostgreSQL brain never consults it, and no change takes effect on a running process. See allowed hosts.
Z4J_CORS_ORIGINS [] Allowed CORS origins for the dashboard. JSON array of full origins.
Z4J_CORS_ALLOW_CREDENTIALS true Allow credentials on CORS requests.
Z4J_TRUSTED_PROXIES [] JSON array of IPv4/IPv6 CIDRs (a bare address means one host) of the reverse proxies whose X-Forwarded-For the brain should trust. Empty list means trust no proxy. Validated at startup like the allowlists: a malformed entry or an IPv6 zone id refuses to start, entries are stored canonical. A catch-all entry (0.0.0.0/0 or ::/0) is honoured but logged as a startup WARNING, because it lets any peer choose its own client address, which is what the agent connect rate limit and the three IP allowlists see. See threat model.
Z4J_DASHBOARD_IP_ALLOWLIST [] JSON array of IPv4/IPv6 CIDRs (a bare address means one host) that the login route and every session-cookie request must come from. Matched against the client IP after Z4J_TRUSTED_PROXIES resolution. Empty means no restriction. Loopback is not implicitly allowed. A malformed entry refuses to start. See threat model.
Z4J_API_IP_ALLOWLIST [] Same shape, for every Bearer API-key request. A key's own allowed_cidrs narrows this further; both must admit the request. See API keys.
Z4J_AGENT_IP_ALLOWLIST [] Same shape, for the agent transports (WebSocket and long-poll).
Z4J_HSTS_MAX_AGE_SECONDS 31536000 HSTS max-age (default 1 year). Emitted only in production over HTTPS.
Z4J_HSTS_INCLUDE_SUBDOMAINS true Append includeSubDomains to the HSTS header.
Z4J_ALLOW_HTTP_PUBLIC_URL false Test/dev escape hatch: permit a plaintext http:// Z4J_PUBLIC_URL. Never set in production.
Variable Default Description
Z4J_OPENAPI_VISIBILITY private Visibility of the OpenAPI schema (/api/v1/openapi.json) and Swagger UI (/api/v1/docs). public: reachable by any anonymous caller, for brains that intentionally expose their API surface (demo sites, public API products). private: requires a session cookie or an API key; anonymous callers get 401 with a generic WWW-Authenticate: Bearer realm="z4j". disabled: not mounted; everyone gets 404, for compliance-bound deployments where even authenticated discovery is unwanted. A per-IP rate limit, Cache-Control headers, ETag round-tripping, a best-effort openapi.schema_accessed audit row before each successful 200 (an audit-store outage is logged and does not deny the schema) and a build watermark in the schema apply in every mode.
Z4J_OPENAPI_DOCS_ENABLED - Deprecated alias of Z4J_OPENAPI_VISIBILITY, kept so old configurations keep working and logged with a warning at startup. true maps to private, false to disabled. When both are set, the explicit Z4J_OPENAPI_VISIBILITY wins. Leave unset on new deployments.
Variable Default Description
Z4J_BIND_HOST 0.0.0.0 ASGI bind host.
Z4J_BIND_PORT 7700 ASGI bind port.
Z4J_ENVIRONMENT production Load-bearing, not a label. The exact string dev relaxes the startup invariants (audit-chain key, allowed hosts, HTTPS public URL), names the session, CSRF and MFA-trust cookies without their hardened prefixes, and loosens host validation. Any other value, including development, is treated as production. See dev vs production.
Z4J_LOG_JSON true Emit logs as JSON (true) or human-readable console output (false).
Z4J_LOG_LEVEL INFO Stdlib logging level.
Z4J_VERSION_CHECK_URL (canonical GitHub raw URL) Source URL for the dashboard "Check for updates" button. Override to point at a private mirror in restricted environments. Empty hides the button and makes the check endpoint answer 404; the URL is fetched only when an admin clicks the button, never in the background.
Variable Default Description
Z4J_COMMAND_TIMEOUT_SECONDS 60 Age threshold past which a dispatched command is marked timed-out. Surfaces as 504 command_timeout to API callers.
Z4J_COMMAND_TIMEOUT_SWEEP_SECONDS 5 Cadence for the command-timeout sweeper worker.
Z4J_AGENT_LONGPOLL_REDISPATCH_SECONDS 60.0 How long a dispatched command is still treated as a possibly lost delivery on the long-poll transport. Within this window a reconnecting agent whose HTTP response was dropped is re-offered the command, and the dispatcher's idempotent re-issue guard treats a dispatched row younger than this as in flight; older rows count as orphaned and are re-driven once, for re-drivable actions. Range 1 to 3600.
Z4J_AGENT_LONGPOLL_REDISPATCH_MIN_INTERVAL_SECONDS 10.0 Minimum interval between successive re-sends of the same still-dispatched command on the long-poll recovery path. A re-send fires only if the last send was at least this long ago, capping re-sends to one per interval instead of one per poll. Range 1 to 3600. Startup refuses a value that is not strictly less than Z4J_AGENT_LONGPOLL_REDISPATCH_SECONDS, and refuses a Z4J_COMMAND_TIMEOUT_SECONDS below this value.
Variable Default Description
Z4J_BULK_RETRY_SCAN_SECONDS 1.0 Cadence of the durable child-outbox coordinator. The supervisor invokes it immediately on startup, then at this interval. Range 0.1 to 60.
Z4J_BULK_RETRY_MAX_IN_FLIGHT 8 Children in flight per bulk-retry parent, sealed into the parent row at creation and enforced across replicas under the locked parent row. Range 1 to 256.
Z4J_BULK_RETRY_PARENT_TIMEOUT_SECONDS 900 Absolute attempt budget for one parent, stamped as its deadline at creation. Resuming a parent explicitly grants a fresh window; individual children never mint their own. Range 30 to 86400.
Z4J_BULK_RETRY_SCAN_BATCH 64 Global work cap per coordinator tick. Range 1 to 1000.

The brain accepts mTLS gRPC connections from z4j-scheduler instances. Disabled by default.

Variable Default Description
Z4J_SCHEDULER_GRPC_ENABLED false Accept mTLS gRPC connections from z4j-scheduler.
Z4J_SCHEDULER_GRPC_BIND_HOST 0.0.0.0 Bind interface for the scheduler gRPC server.
Z4J_SCHEDULER_GRPC_BIND_PORT 7701 Bind port for the scheduler gRPC server.
Z4J_SCHEDULER_GRPC_ALLOWED_CNS [] JSON array of allow-listed scheduler client CNs.
Z4J_SCHEDULER_GRPC_REQUIRE_ALLOWLIST false Fail closed if _ALLOWED_CNS is missing instead of falling back to trust-the-CA.
Z4J_SCHEDULER_GRPC_CN_PROJECT_BINDINGS {} JSON object mapping scheduler CN to allowed project list.
Z4J_SCHEDULER_GRPC_TLS_CERT - Brain's gRPC server certificate (PEM path).
Z4J_SCHEDULER_GRPC_TLS_KEY - Brain's gRPC server private key (PEM path).
Z4J_SCHEDULER_GRPC_TLS_CA - CA bundle for validating incoming scheduler client certs.
Z4J_SCHEDULER_GRPC_INSECURE false Dev/test only: bind the scheduler gRPC port without TLS. Refuses to start in production.
Z4J_SCHEDULER_GRPC_GRACE_SECONDS 5.0 Graceful drain window on shutdown for in-flight RPCs.
Z4J_SCHEDULER_GRPC_FIRE_RATE_LIMIT_ENABLED true Enable the per-cert rate limit on FireSchedule.
Z4J_SCHEDULER_GRPC_FIRE_RATE_PER_SECOND 10.0 Sustained refill rate (tokens / sec) for the per-cert fire-rate limit.
Z4J_SCHEDULER_GRPC_FIRE_RATE_CAPACITY 600.0 Maximum burst size for the per-cert fire-rate limit.
Z4J_SCHEDULER_GRPC_WATCH_MAX_CONCURRENT 64 Hard cap on concurrent WatchSchedules streams per brain process.
Z4J_SCHEDULER_GRPC_WATCH_MAX_PER_CERT 4 Per-CN cap on concurrent WatchSchedules streams.
Z4J_SCHEDULER_GRPC_WATCH_POLL_SECONDS 2.0 Poll cadence for the watch-stream backend.
Z4J_SCHEDULER_INFO_URLS [] HTTP URLs of scheduler instances for the dashboard's fleet-status page. When empty and Z4J_EMBEDDED_SCHEDULER is on, the page polls http://127.0.0.1:7800.

Inert. Pushing an operator trigger out to z4j-scheduler and back was an earlier design; a scheduler cannot carry one, because it cannot see a hold and is not the authority on whether a schedule may run. The brain dispatches "fire now" itself in every configuration and nothing reads these four settings, so setting them changes no behaviour. They remain only so an existing deployment that still supplies them starts rather than failing validation. Unset them.

Variable Default Description
Z4J_SCHEDULER_TRIGGER_URL - Inert. Accepted so an old configuration that names a scheduler TriggerSchedule listener still validates; never read.
Z4J_SCHEDULER_TRIGGER_TLS_CERT - Inert. Accepted for validation only; never read.
Z4J_SCHEDULER_TRIGGER_TLS_KEY - Inert. Accepted for validation only; never read.
Z4J_SCHEDULER_TRIGGER_TLS_CA - Inert. Accepted for validation only; never read.

For homelab / single-instance deployments the brain can spawn z4j-scheduler as a supervised subprocess. See z4j-scheduler.

Variable Default Description
Z4J_EMBEDDED_SCHEDULER false Spawn z4j-scheduler as a supervised subprocess inside the brain lifespan. Implies Z4J_SCHEDULER_GRPC_ENABLED=true and overrides the gRPC TLS certificate, key, CA and allowed CNs with the auto-minted loopback PKI.
Z4J_EMBEDDED_SCHEDULER_ARGV ["serve"] Subprocess argv after the implicit [sys.executable, "-m", "z4j_scheduler"] prefix.
Z4J_EMBEDDED_SCHEDULER_PKI_DIR $Z4J_HOME/embedded-pki/ Directory for auto-minted loopback mTLS PKI.
Z4J_EMBEDDED_SCHEDULER_RESTART_MAX_ATTEMPTS 10 Maximum auto-restart attempts before the supervisor gives up. 0 disables auto-restart: a single crash is permanent.
Z4J_EMBEDDED_SCHEDULER_RESTART_BACKOFF_SECONDS 2.0 Initial backoff between restart attempts (doubles up to a 60s cap).
Z4J_EMBEDDED_SCHEDULER_SHUTDOWN_GRACE_SECONDS 10.0 Grace window for SIGTERM before the supervisor sends SIGKILL.

Schedule fires, misfires, and circuit breaker

Section titled “Schedule fires, misfires, and circuit breaker”
Variable Default Description
Z4J_SCHEDULE_FIRES_RETENTION_DAYS 30 Days schedule_fires rows live. Postgres reclaims expired days by partition drop; SQLite by DELETE. See schedule fire history.
Z4J_PENDING_FIRES_RETENTION_DAYS 7 Days buffered fires can stay pending before being dropped.
Z4J_PENDING_FIRES_REPLAY_INTERVAL_SECONDS 10 Cadence for the buffered-fire replay worker.
Z4J_SCHEDULE_CIRCUIT_BREAKER_THRESHOLD 5 Consecutive failed fires before the schedule is auto-disabled. 0 disables the breaker.
Z4J_SCHEDULE_CIRCUIT_BREAKER_INTERVAL_SECONDS 60 Sweep cadence for the schedule circuit-breaker worker.
Z4J_SCHEDULER_MISFIRE_GRACE_SECONDS 60 Lateness past a schedule's expected fire before it counts as misfired. See misfire detection.
Z4J_SCHEDULER_MISFIRE_SWEEP_SECONDS 60 Cadence for the brain-side misfire detector. 0 disables misfire detection. The detector runs only when Z4J_SCHEDULER_GRPC_ENABLED or Z4J_EMBEDDED_SCHEDULER is on.
Variable Default Description
Z4J_AUTOMATION_NOTIFY_COALESCE_SECONDS 0 When > 0, a rule that already emitted a notify within the window suppresses further notifies, so an event flood cannot fan out one notification per event per member. The first alert in each window always goes out. 0 notifies on every matching event.
Z4J_AUTOMATION_OUTBOX_DRAIN_INTERVAL_SECONDS 30 Cadence for the firing-outbox drain worker that replays automation firings deferred under backpressure.
Z4J_AUTOMATION_OUTBOX_MAX_ROWS_PER_PROJECT 10000 Per-project ceiling on deferred firings; above it, further firings are dropped (counted on a metric) rather than growing the outbox without bound.

These variables are consumed by the CLI, the migration tooling or middleware rather than by z4j_brain.settings.Settings, so they are not Settings fields. The structured Z4J_DATABASE_* parts in the Database section and the Z4J_BOOTSTRAP_ADMIN_* trio in First boot are read the same way.

Variable Default Description
Z4J_HOME ~/.z4j State directory for the brain (config.env, secret.env, the packaged SQLite database, embedded-scheduler PKI) and for the agent's per-process buffer file. Expanded and resolved to an absolute path.
Z4J_LOG_FORMAT - json or text. The brain's logging is keyed off Z4J_LOG_JSON; z4j serve translates this value into it (json to true, text to false) unless Z4J_LOG_JSON is already set, in which case Z4J_LOG_JSON wins. The agent runtime reads the same variable for its own log output.
Z4J_ALEMBIC_INI - Path to the Alembic configuration used by z4j migrate. Checked before the current directory and the bundled copies.
Z4J_DEBUG_HOST_ERRORS 0 Dev only. When truthy (1, true, yes, on) and Z4J_ENVIRONMENT is exactly dev, a rejected Host header gets a verbose 400 body naming the rejected host, the allow-list and a fix command. Outside dev the variable does nothing, and the z4j serve --debug-host-errors flag that sets it is refused. The configuration validator accepts 0, 1, true, false, yes, no, on, off.
Z4J_RELEASE_SOURCE_REVISION - Read by z4j migrate prepare-runtime-rollback only. The 40-hex Git revision of the release candidate the rollback ceremony binds to; the command exits 2 when it is missing or malformed. See upgrade and rollback.
Z4J_RELEASE_IMAGE - Read by z4j migrate prepare-runtime-rollback only. The candidate image by OCI sha256 digest; the command exits 2 when it is empty.
Z4J_SCHEDULER_INFO_URL http://localhost:7800 Read by the z4j-scheduler info command only, not by the scheduler's Settings: the running scheduler's HTTP base URL to fetch /info from (--url overrides it). Distinct from the brain's Z4J_SCHEDULER_INFO_URLS.
Z4J_SCHEDULER_BRAIN_API_TOKEN - Read by the z4j-scheduler schedules ..., import and export commands only, not by the scheduler's Settings: the bearer token they present to the brain's REST API (--api-token overrides it). See z4j-scheduler.

The following fields exist on Settings but are infrastructure or test-only and should not be set by operators:

  • Z4J_DASHBOARD_DIST -- filesystem path to built dashboard assets (set by the container image).
  • Z4J_DISABLE_SPA_FALLBACK -- unit-test fixture flag.

Set these in the framework's config dict (for example Django settings.Z4J) or, where the table names one, through the exact environment variable shown. Fields marked "dict/kwargs only" are deliberately not environment-backed.

Key Environment variable Required Default Description
brain_url Z4J_BRAIN_URL yes - HTTP(S) base URL of the brain. The transport derives its WebSocket URL.
token Z4J_TOKEN yes - Agent bearer token.
project_id Z4J_PROJECT_ID yes - Project slug. There is no "default" fallback.
hmac_secret Z4J_HMAC_SECRET yes at runtime None Per-project frame-signing secret returned at agent-mint time. The model permits None, but the runtime refuses to start without it.
agent_name Z4J_AGENT_NAME no None Optional display label. It does not default to $HOSTNAME.
agent_id Z4J_AGENT_ID long-poll only "" Required when transport=longpoll; WebSocket learns it during the handshake.
environment Z4J_ENVIRONMENT no "production" Reserved deployment label available to adapters.
tags Z4J_TAGS no {} Comma-separated key=value pairs. Reserved metadata available to adapters.
transport Z4J_TRANSPORT no "auto" auto, ws, or longpoll; auto selects WebSocket.
engines Z4J_ENGINES no [] Comma-separated engine adapter names.
schedulers Z4J_SCHEDULERS no [] Comma-separated scheduler adapter names.
heartbeat_seconds Z4J_HEARTBEAT_SECONDS no 10 Seconds between heartbeats.
buffer_path dict/kwargs only no $Z4J_HOME/buffer-<pid>.sqlite Per-process SQLite buffer path. Set Z4J_HOME to move its parent; the removed Z4J_BUFFER_PATH, Z4J_BUFFER_DIR and Z4J_RUNTIME_DIR variables are rejected at startup (the brain CLI and the agent runtime raise rather than ignore them).
buffer_max_events Z4J_BUFFER_MAX_EVENTS no 100000 Buffered event cap. Minimum 1000.
buffer_max_bytes Z4J_BUFFER_MAX_BYTES no 268435456 Buffer file-size cap in bytes.
max_payload_bytes Z4J_MAX_PAYLOAD_BYTES no 8192 Per-field truncation limit.
log_level Z4J_LOG_LEVEL no "INFO" Local agent log level.
autostart Z4J_AUTOSTART no true Start the runtime during installation.
strict_mode Z4J_STRICT_MODE no false Accepted for configuration compatibility; neither the runtime nor the framework adapters branch on it, and configuration validation stays fail-fast.
worker_role Z4J_WORKER_ROLE no None Dashboard hint: web, task, scheduler, beat, or other.
redaction_extra_key_patterns Z4J_REDACTION_EXTRA_KEY_PATTERNS no [] Comma-separated additional key-name regex patterns.
redaction_extra_value_patterns Z4J_REDACTION_EXTRA_VALUE_PATTERNS no [] Comma-separated additional value regex patterns.
redaction_defaults_enabled dict/kwargs only no true Whether built-in redaction patterns remain enabled.
dev_mode dict/kwargs only no false Explicit local plaintext opt-in. Z4J_DEV_MODE from the process environment is ignored and cannot disable frame signing.

Agent configuration precedence, highest first, is explicit installer keyword arguments, Z4J_* environment variables, framework settings, then Config defaults. An empty environment value is treated as unset. Agent configuration does not read the brain's ~/.z4j/config.env file.

These are read from the process environment only, by the agent runtime or by one engine adapter; they are not Config keys and the framework settings dict does not carry them. Truthy means 1, true, yes or on.

Variable Default Description
Z4J_DISABLED - Truthy skips agent startup entirely: the Django app ready(), Flask init_app() (a Z4J_DISABLED key in the Flask config does the same), the FastAPI integration, and the Celery and RQ worker bootstraps.
Z4J_HEARTBEAT on 0, false, no, off or an empty value disables the agent's liveness heartbeat loop. Meant for short-lived processes that auto-start the agent (a boot-then-exit manage.py command); buffered business events still ship on the next connect.
Z4J_AGENT_STATUS_DISABLED - Truthy suppresses the agent-status frame that rides alongside each heartbeat and feeds the dashboard's per-agent status timeline (agent_status_history). Heartbeats themselves are unaffected.
Z4J_ORCHESTRATED - Supervisor detection override for the restart_worker action, which refuses to self-exit when no process supervisor is detected. 0, false or no forces "no supervisor" even where container markers match. 1, true or yes counts as the explicit opt-in, but only together with the /etc/z4j-orchestrated filesystem marker; on its own it is ignored, so a process that can only set environment variables cannot trigger a non-respawning exit. PID 1, /.dockerenv and container cgroups are detected without it.
Z4J_ACCEPT_LEGACY_PURGE_TOKEN off Truthy makes the agent accept the unkeyed purge confirmation token during a rolling upgrade from a brain that does not issue keyed tokens. Off by default because the unkeyed token is forgeable by anyone who can observe queue depth. Shared by every engine adapter. See upgrades.
Z4J_PURGE_THRESHOLD 1000 (Celery), 10000 (Dramatiq, RQ) Queue depth above which the purge_queue action refuses to mass-delete unless the command carries force=true. Read on each call, so it can be raised without a restart; a non-integer value falls back to the default. Celery clamps it to at least 1, Dramatiq and RQ to at least 0.
Z4J_CELERY_LOCAL_HOSTNAME - Celery only. Restricts the broker events monitor to events emitted by this Celery worker hostname, for the one-agent-per-worker topology where every agent would otherwise receive every event on the fanout exchange. Falls back to CELERY_HOSTNAME when unset; with neither set the monitor accepts every event and relies on brain-side dedupe.