Skip to content

Z.E.N. supports a layered configuration system with sensible defaults, optional YAML config files, and environment variable overrides. For a quick introduction, see Getting Started: Configuration.

Directory Structure

User-Level (~/.zen/)

~/.zen/
├── workspaces/owner/repo/  # Project-centric layout
│   ├── source/             # Clone or symlink -> local path
│   ├── worktrees/          # Git worktrees for this project
│   ├── artifacts/          # Workflow artifacts
│   └── logs/               # Workflow execution logs
├── zen.db               # SQLite database (when DATABASE_URL not set)
└── config.yaml             # Global configuration (optional)

Repository-Level (.zen/)

.zen/
├── commands/       # Custom commands
│   └── plan.md
├── workflows/      # Workflow definitions (YAML files)
└── config.yaml     # Repo-specific configuration (optional)

Configuration Priority

Settings are loaded in this order (later overrides earlier):

  1. Defaults - Sensible built-in defaults
  2. Global Config - ~/.zen/config.yaml
  3. Repo Config - .zen/config.yaml in repository
  4. Environment Variables - Always highest priority

Global Configuration

Create ~/.zen/config.yaml for user-wide preferences:

yaml
# Default provider
defaultAssistant: claude # or 'codex'

# Assistant defaults
assistants:
  claude:
    model: sonnet
    settingSources:   # Which CLAUDE.md files the SDK loads (default: ['project'])
      - project       # Project-level CLAUDE.md (always recommended)
      - user          # Also load ~/.claude/CLAUDE.md (global preferences)
  codex:
    model: gpt-5.3-codex
    modelReasoningEffort: medium
    webSearchMode: disabled
    additionalDirectories:
      - /absolute/path/to/other/repo

# Streaming preferences per platform
streaming:
  telegram: stream # 'stream' or 'batch'
  slack: batch
  github: batch

# Custom paths (usually not needed)
paths:
  workspaces: ~/.zen/workspaces
  worktrees: ~/.zen/worktrees

# Concurrency limits
concurrency:
  maxConversations: 10

# Env-leak gate bypass (last resort; weakens a security control)
# allow_target_repo_keys: false  # Set true to skip the env-leak-gate
                                 # globally for all codebases on this machine.
                                 # `env_leak_gate_disabled` is logged once per
                                 # process per source. See security.md.

Repository Configuration

Create .zen/config.yaml in any repository for project-specific settings:

yaml
# provider for this project (used as default provider for workflows)
assistant: claude

# Assistant defaults (override global)
assistants:
  claude:
    model: sonnet
    settingSources:  # Override global settingSources for this repo
      - project
  codex:
    model: gpt-5.3-codex
    webSearchMode: live

# Commands configuration
commands:
  folder: .zen/commands
  autoLoad: true

# Worktree settings
worktree:
  baseBranch: main  # Optional: auto-detected from git when not set
  copyFiles:  # Optional: Additional files to copy to worktrees
    - .env.example -> .env  # Rename during copy
    - .vscode               # Copy entire directory

# Documentation directory
docs:
  path: docs  # Optional: default is docs/

# Defaults configuration
defaults:
  loadDefaultCommands: true   # Load app's bundled default commands at runtime
  loadDefaultWorkflows: true  # Load app's bundled default workflows at runtime

# Concurrency policy (opt-in; default off)
concurrency:
  allowParallelWorkflows: false   # When true, different workflow names can run
                                  # in parallel on the same cwd. The dispatch
                                  # lock scopes by (working_path, workflow_name)
                                  # instead of working_path alone. Same-name
                                  # back-to-back dispatches still block
                                  # themselves. Useful for fleets that share a
                                  # cwd with `worktree.enabled: false`.

# Default subprocess timeouts for bash/script nodes (ms).
# Per-node `timeout:` in the workflow yaml always overrides these.
# Values must be positive integers ≤ 2147483647 (≈24.8 days); invalid values
# are ignored and the built-in 2-minute default applies.
timeouts:
  bash: 600000                    # 10 minutes for bash nodes
  script: 900000                  # 15 minutes for script nodes

# Per-project environment variables for workflow execution (Claude SDK only)
# Injected into the Claude subprocess env. Use the Web UI Settings panel for secrets.
# env:
#   MY_API_KEY: value
#   CUSTOM_ENDPOINT: https://...

# Per-repo override for the env-leak-gate bypass.
# Set to `false` to re-enable the gate for THIS repo even when the global
# config has `allow_target_repo_keys: true`. Set to `true` to grant the
# bypass for THIS repo only. Wins over the global flag in either direction.
# allow_target_repo_keys: false

Claude settingSources

Controls which CLAUDE.md files the Claude Agent SDK loads during sessions:

ValueDescription
projectLoad the project's CLAUDE.md (default, always included)
userAlso load ~/.claude/CLAUDE.md (user's global preferences)

Default: ['project']; only project-level instructions are loaded.

Set in global or repo config:

yaml
assistants:
  claude:
    settingSources:
      - project
      - user

This is useful when you maintain coding style or identity preferences in ~/.claude/CLAUDE.md and want Z.E.N. sessions to respect them.

Default behavior: When a workflow opts into a worktree via worktree.enabled: true, the .zen/ directory is copied automatically (artifacts, plans, workflows). Use copyFiles only for additional files like .env or .vscode.

Defaults behavior: The app's bundled default commands and workflows are loaded at runtime and merged with repo-specific ones. Repo commands/workflows override app defaults by name. Set defaults.loadDefaultCommands: false or defaults.loadDefaultWorkflows: false to disable runtime loading.

Base branch behavior: Before creating a worktree, the canonical workspace is synced to the latest code. Resolution order:

  1. If worktree.baseBranch is set: Uses the configured branch. Fails with an error if the branch doesn't exist on remote (no silent fallback).
  2. If omitted: Auto-detects the default branch via git remote show origin. Works without any config for standard repos.
  3. If auto-detection fails and a workflow references $BASE_BRANCH: Fails with an error explaining the resolution chain.

Docs path behavior: The docs.path setting controls where the $DOCS_DIR variable points. When not configured, $DOCS_DIR defaults to docs/. Unlike $BASE_BRANCH, this variable always has a safe default and never throws an error. Configure it when your documentation lives outside the standard docs/ directory (e.g., packages/docs).

Environment Variables

Environment variables override all other configuration. They are organized by category below.

Core

VariableDescriptionDefault
ZEN_HOMEBase directory for all Z.E.N.-managed files~/.zen
PORTHTTP server listen port3090 (auto-allocated in worktrees)
LOG_LEVELLogging verbosity (fatal, error, warn, info, debug, trace)info
BOT_DISPLAY_NAMEBot name shown in batch-mode "starting" messagesZ.E.N.
DEFAULT_AI_ASSISTANTDefault provider (claude or codex)claude
MAX_CONCURRENT_CONVERSATIONSMaximum concurrent AI conversations10
SESSION_RETENTION_DAYSDelete inactive sessions older than N days30

AI Providers; Claude

VariableDescriptionDefault
CLAUDE_USE_GLOBAL_AUTHUse global auth from claude /login (true/false)Auto-detect
CLAUDE_CODE_OAUTH_TOKENExplicit OAuth token (alternative to global auth);
CLAUDE_API_KEYExplicit API key (alternative to global auth);
TITLE_GENERATION_MODELLightweight model for generating conversation titlesSDK default
ZEN_CLAUDE_FIRST_EVENT_TIMEOUT_MSTimeout (ms) before Claude subprocess is considered hung (throws with diagnostic log)60000

When CLAUDE_USE_GLOBAL_AUTH is unset, Z.E.N. auto-detects: it uses explicit tokens if present, otherwise falls back to global auth.

AI Providers; Codex

VariableDescriptionDefault
CODEX_ID_TOKENCodex ID token (from ~/.codex/auth.json);
CODEX_ACCESS_TOKENCodex access token;
CODEX_REFRESH_TOKENCodex refresh token;
CODEX_ACCOUNT_IDCodex account ID;

Platform Adapters; Slack

VariableDescriptionDefault
SLACK_BOT_TOKENSlack bot token (xoxb-...);
SLACK_APP_TOKENSlack app-level token for Socket Mode (xapp-...);
SLACK_ALLOWED_USER_IDSComma-separated Slack user IDs for whitelistOpen access
SLACK_STREAMING_MODEStreaming mode (stream or batch)batch

Platform Adapters; Telegram

VariableDescriptionDefault
TELEGRAM_BOT_TOKENTelegram bot token from @BotFather;
TELEGRAM_ALLOWED_USER_IDSComma-separated Telegram user IDs for whitelistOpen access
TELEGRAM_STREAMING_MODEStreaming mode (stream or batch)stream

Platform Adapters; GitHub

VariableDescriptionDefault
GITHUB_TOKENGitHub personal access token (also used by gh CLI);
GH_TOKENAlias for GITHUB_TOKEN (used by GitHub CLI);
WEBHOOK_SECRETHMAC SHA-256 secret for GitHub webhook signature verification;
GITHUB_ALLOWED_USERSComma-separated GitHub usernames for whitelist (case-insensitive)Open access
GITHUB_BOT_MENTION@mention name the bot responds to in issues/PRsFalls back to BOT_DISPLAY_NAME

Database

VariableDescriptionDefault
DATABASE_URLPostgreSQL connection string (omit to use SQLite)SQLite at ~/.zen/zen.db

Web UI

VariableDescriptionDefault
WEB_UI_ORIGINCORS origin for API routes (restrict when exposing publicly)* (allow all)
WEB_UI_DEVWhen set, skip serving static frontend (Vite dev server used instead);

Worktree Management

VariableDescriptionDefault
STALE_THRESHOLD_DAYSDays before an inactive worktree is considered stale14
MAX_WORKTREES_PER_CODEBASEMax worktrees per codebase before auto-cleanup25
CLEANUP_INTERVAL_HOURSHow often the background cleanup service runs6

Docker / Deployment

VariableDescriptionDefault
ZEN_DATAHost path for Z.E.N. data (workspaces, worktrees, artifacts)Docker-managed volume
DOMAINPublic domain for Caddy reverse proxy (TLS auto-provisioned);
CADDY_BASIC_AUTHCaddy basicauth directive to protect Web UI and APIDisabled
AUTH_USERNAMEUsername for form-based auth (Caddy forward_auth);
AUTH_PASSWORD_HASHBcrypt hash for form-based auth password (escape $ as $$ in Compose);
COOKIE_SECRET64-hex-char secret for auth session cookies;
AUTH_SERVICE_PORTPort for the auth service container9000
COOKIE_MAX_AGEAuth cookie lifetime in seconds86400

.env File Locations

Infrastructure configuration (database URL, platform tokens) is stored in .env files:

ComponentLocationPurpose
CLI~/.zen/.envGlobal infrastructure config; CWD .env keys stripped before loading (no override needed)
Server (dev)<zen-repo>/.env + ~/.zen/.envRepo .env for platform tokens; ~/.zen/.env loaded with override: true
Server (binary)~/.zen/.envSingle source of truth (repo .env path is not available in compiled binaries)

How it works: At startup, the CLI strips all keys that Bun auto-loaded from the current working directory (from .env, .env.local, .env.development, .env.production) before loading ~/.zen/.env. This ensures CWD repo keys are fully removed rather than merely overridden. Target repo env vars cannot reach AI subprocesses; SUBPROCESS_ENV_ALLOWLIST blocks all non-whitelisted keys.

Best practice: Use ~/.zen/.env as the single source of truth:

bash
# Create global config
mkdir -p ~/.zen
cp .env.example ~/.zen/.env
# Edit with your values

Docker Configuration

In Docker containers, paths are automatically set:

/.zen/
├── workspaces/owner/repo/
│   ├── source/
│   ├── worktrees/
│   ├── artifacts/
│   └── logs/
└── zen.db

Environment variables still work and override defaults.

Command Folder Detection

When cloning or switching repositories, Z.E.N. looks for commands in this priority order:

  1. .zen/commands/ - Always searched first
  2. Configured folder from commands.folder in .zen/config.yaml (if specified)

Example .zen/config.yaml:

yaml
commands:
  folder: .claude/commands/zen  # Additional folder to search
  autoLoad: true

Examples

Minimal Setup (Using Defaults)

No configuration needed. Z.E.N. works out of the box with:

  • ~/.zen/ for all managed files
  • Claude as default provider
  • Platform-appropriate streaming modes

Custom AI Preference

yaml
# ~/.zen/config.yaml
defaultAssistant: codex

Project-Specific Settings

yaml
# .zen/config.yaml in your repo
assistant: claude  # Workflows inherit this provider unless they specify their own
commands:
  autoLoad: true

Docker with Custom Volume

bash
docker run -v /my/data:/.zen ghcr.io/Cadence-Intelligence/zen

Streaming Modes

Each platform adapter supports two streaming modes, configured via environment variable or ~/.zen/config.yaml.

Stream Mode

Messages are sent in real-time as the AI generates responses.

ini
TELEGRAM_STREAMING_MODE=stream
SLACK_STREAMING_MODE=stream

Pros:

  • Real-time feedback and progress indication
  • More interactive and engaging
  • See AI reasoning as it works

Cons:

  • More API calls to platform
  • May hit rate limits with very long responses
  • Creates many messages/comments

Best for: Interactive chat platforms (Telegram)

Batch Mode

Only the final summary message is sent after AI completes processing.

ini
TELEGRAM_STREAMING_MODE=batch
SLACK_STREAMING_MODE=batch

Pros:

  • Single coherent message/comment
  • Fewer API calls
  • No spam or clutter

Cons:

  • No progress indication during processing
  • Longer wait for first response
  • Can't see intermediate steps

Best for: Issue trackers and async platforms (GitHub)

Platform Defaults

PlatformDefault Mode
Telegramstream
Slackbatch
GitHubbatch
Web UISSE streaming (always real-time, not configurable)

Concurrency Settings

Control how many conversations the system processes simultaneously:

ini
MAX_CONCURRENT_CONVERSATIONS=10  # Default: 10

How it works:

  • Conversations are processed with a lock manager
  • If the max concurrent limit is reached, new messages are queued
  • Prevents resource exhaustion and API rate limits
  • Each conversation maintains its own independent context

Tuning guidance:

ResourcesRecommended Setting
Low resources3-5
Standard10 (default)
High resources20-30 (monitor API limits)

Health Check Endpoints

The application exposes health check endpoints for monitoring:

Basic Health Check:

bash
curl http://localhost:3090/health

Returns: {"status":"ok"}

Database Connectivity:

bash
curl http://localhost:3090/health/db

Returns: {"status":"ok","database":"connected"}

Concurrency Status:

bash
curl http://localhost:3090/health/concurrency

Returns: {"status":"ok","active":0,"queued":0,"maxConcurrent":10}

Use cases:

  • Docker healthcheck configuration
  • Load balancer health checks
  • Monitoring and alerting systems (Prometheus, Datadog, etc.)
  • CI/CD deployment verification

Troubleshooting

Config Parse Errors

If your config file has invalid YAML syntax, you'll see error messages like:

[Config] Failed to parse global config at ~/.zen/config.yaml: <error details>
[Config] Using default configuration. Please fix the YAML syntax in your config file.

Common YAML syntax issues:

  • Incorrect indentation (use spaces, not tabs)
  • Missing colons after keys
  • Unquoted values with special characters

The application will continue running with default settings until the config file is fixed.

AI that follows a recipe, not a conversation.