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):
- Defaults - Sensible built-in defaults
- Global Config -
~/.zen/config.yaml - Repo Config -
.zen/config.yamlin repository - Environment Variables - Always highest priority
Global Configuration
Create ~/.zen/config.yaml for user-wide preferences:
# 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:
# 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: falseClaude settingSources
Controls which CLAUDE.md files the Claude Agent SDK loads during sessions:
| Value | Description |
|---|---|
project | Load the project's CLAUDE.md (default, always included) |
user | Also load ~/.claude/CLAUDE.md (user's global preferences) |
Default: ['project']; only project-level instructions are loaded.
Set in global or repo config:
assistants:
claude:
settingSources:
- project
- userThis 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:
- If
worktree.baseBranchis set: Uses the configured branch. Fails with an error if the branch doesn't exist on remote (no silent fallback). - If omitted: Auto-detects the default branch via
git remote show origin. Works without any config for standard repos. - 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
| Variable | Description | Default |
|---|---|---|
ZEN_HOME | Base directory for all Z.E.N.-managed files | ~/.zen |
PORT | HTTP server listen port | 3090 (auto-allocated in worktrees) |
LOG_LEVEL | Logging verbosity (fatal, error, warn, info, debug, trace) | info |
BOT_DISPLAY_NAME | Bot name shown in batch-mode "starting" messages | Z.E.N. |
DEFAULT_AI_ASSISTANT | Default provider (claude or codex) | claude |
MAX_CONCURRENT_CONVERSATIONS | Maximum concurrent AI conversations | 10 |
SESSION_RETENTION_DAYS | Delete inactive sessions older than N days | 30 |
AI Providers; Claude
| Variable | Description | Default |
|---|---|---|
CLAUDE_USE_GLOBAL_AUTH | Use global auth from claude /login (true/false) | Auto-detect |
CLAUDE_CODE_OAUTH_TOKEN | Explicit OAuth token (alternative to global auth) | ; |
CLAUDE_API_KEY | Explicit API key (alternative to global auth) | ; |
TITLE_GENERATION_MODEL | Lightweight model for generating conversation titles | SDK default |
ZEN_CLAUDE_FIRST_EVENT_TIMEOUT_MS | Timeout (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
| Variable | Description | Default |
|---|---|---|
CODEX_ID_TOKEN | Codex ID token (from ~/.codex/auth.json) | ; |
CODEX_ACCESS_TOKEN | Codex access token | ; |
CODEX_REFRESH_TOKEN | Codex refresh token | ; |
CODEX_ACCOUNT_ID | Codex account ID | ; |
Platform Adapters; Slack
| Variable | Description | Default |
|---|---|---|
SLACK_BOT_TOKEN | Slack bot token (xoxb-...) | ; |
SLACK_APP_TOKEN | Slack app-level token for Socket Mode (xapp-...) | ; |
SLACK_ALLOWED_USER_IDS | Comma-separated Slack user IDs for whitelist | Open access |
SLACK_STREAMING_MODE | Streaming mode (stream or batch) | batch |
Platform Adapters; Telegram
| Variable | Description | Default |
|---|---|---|
TELEGRAM_BOT_TOKEN | Telegram bot token from @BotFather | ; |
TELEGRAM_ALLOWED_USER_IDS | Comma-separated Telegram user IDs for whitelist | Open access |
TELEGRAM_STREAMING_MODE | Streaming mode (stream or batch) | stream |
Platform Adapters; GitHub
| Variable | Description | Default |
|---|---|---|
GITHUB_TOKEN | GitHub personal access token (also used by gh CLI) | ; |
GH_TOKEN | Alias for GITHUB_TOKEN (used by GitHub CLI) | ; |
WEBHOOK_SECRET | HMAC SHA-256 secret for GitHub webhook signature verification | ; |
GITHUB_ALLOWED_USERS | Comma-separated GitHub usernames for whitelist (case-insensitive) | Open access |
GITHUB_BOT_MENTION | @mention name the bot responds to in issues/PRs | Falls back to BOT_DISPLAY_NAME |
Database
| Variable | Description | Default |
|---|---|---|
DATABASE_URL | PostgreSQL connection string (omit to use SQLite) | SQLite at ~/.zen/zen.db |
Web UI
| Variable | Description | Default |
|---|---|---|
WEB_UI_ORIGIN | CORS origin for API routes (restrict when exposing publicly) | * (allow all) |
WEB_UI_DEV | When set, skip serving static frontend (Vite dev server used instead) | ; |
Worktree Management
| Variable | Description | Default |
|---|---|---|
STALE_THRESHOLD_DAYS | Days before an inactive worktree is considered stale | 14 |
MAX_WORKTREES_PER_CODEBASE | Max worktrees per codebase before auto-cleanup | 25 |
CLEANUP_INTERVAL_HOURS | How often the background cleanup service runs | 6 |
Docker / Deployment
| Variable | Description | Default |
|---|---|---|
ZEN_DATA | Host path for Z.E.N. data (workspaces, worktrees, artifacts) | Docker-managed volume |
DOMAIN | Public domain for Caddy reverse proxy (TLS auto-provisioned) | ; |
CADDY_BASIC_AUTH | Caddy basicauth directive to protect Web UI and API | Disabled |
AUTH_USERNAME | Username for form-based auth (Caddy forward_auth) | ; |
AUTH_PASSWORD_HASH | Bcrypt hash for form-based auth password (escape $ as $$ in Compose) | ; |
COOKIE_SECRET | 64-hex-char secret for auth session cookies | ; |
AUTH_SERVICE_PORT | Port for the auth service container | 9000 |
COOKIE_MAX_AGE | Auth cookie lifetime in seconds | 86400 |
.env File Locations
Infrastructure configuration (database URL, platform tokens) is stored in .env files:
| Component | Location | Purpose |
|---|---|---|
| CLI | ~/.zen/.env | Global infrastructure config; CWD .env keys stripped before loading (no override needed) |
| Server (dev) | <zen-repo>/.env + ~/.zen/.env | Repo .env for platform tokens; ~/.zen/.env loaded with override: true |
| Server (binary) | ~/.zen/.env | Single 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:
# Create global config
mkdir -p ~/.zen
cp .env.example ~/.zen/.env
# Edit with your valuesDocker Configuration
In Docker containers, paths are automatically set:
/.zen/
├── workspaces/owner/repo/
│ ├── source/
│ ├── worktrees/
│ ├── artifacts/
│ └── logs/
└── zen.dbEnvironment variables still work and override defaults.
Command Folder Detection
When cloning or switching repositories, Z.E.N. looks for commands in this priority order:
.zen/commands/- Always searched first- Configured folder from
commands.folderin.zen/config.yaml(if specified)
Example .zen/config.yaml:
commands:
folder: .claude/commands/zen # Additional folder to search
autoLoad: trueExamples
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
# ~/.zen/config.yaml
defaultAssistant: codexProject-Specific Settings
# .zen/config.yaml in your repo
assistant: claude # Workflows inherit this provider unless they specify their own
commands:
autoLoad: trueDocker with Custom Volume
docker run -v /my/data:/.zen ghcr.io/Cadence-Intelligence/zenStreaming 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.
TELEGRAM_STREAMING_MODE=stream
SLACK_STREAMING_MODE=streamPros:
- 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.
TELEGRAM_STREAMING_MODE=batch
SLACK_STREAMING_MODE=batchPros:
- 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
| Platform | Default Mode |
|---|---|
| Telegram | stream |
| Slack | batch |
| GitHub | batch |
| Web UI | SSE streaming (always real-time, not configurable) |
Concurrency Settings
Control how many conversations the system processes simultaneously:
MAX_CONCURRENT_CONVERSATIONS=10 # Default: 10How 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:
| Resources | Recommended Setting |
|---|---|
| Low resources | 3-5 |
| Standard | 10 (default) |
| High resources | 20-30 (monitor API limits) |
Health Check Endpoints
The application exposes health check endpoints for monitoring:
Basic Health Check:
curl http://localhost:3090/healthReturns: {"status":"ok"}
Database Connectivity:
curl http://localhost:3090/health/dbReturns: {"status":"ok","database":"connected"}
Concurrency Status:
curl http://localhost:3090/health/concurrencyReturns: {"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.