Joy is configured by a file in your Joy home directory, not by a long list of environment variables. Environment variables exist to override individual values and to carry secrets.

Where Configuration Lives

Everything lives under ~/.joy/reborn. Override the location with JOY_REBORN_HOME.
Two files matter, and both are optional — delete them and Joy falls back to built-in defaults. config.toml selects by id from the catalog providers.json defines. The compiled-in provider defaults are appended with your entries, and later entries override earlier ones by id.

Precedence

Each field resolves through four layers, later winning over earlier:
So joy serve --port 3100 beats the port in config.toml, which beats the built-in default of 3000.

Editing Configuration

Generate a commented starter file:
It refuses to overwrite existing files unless you pass --force. Read and write individual keys with dot-separated paths:
list and get read every key. set is deliberately narrower — it accepts only the keys that have a routing destination:
Everything else — runner.*, webui.listen_port, budget.*, [llm.default] — is edited in config.toml directly. config set rejects an unsupported key rather than silently doing nothing. Secret-destination keys (<provider>.api_key, google.client_secret) reject a value on the command line and always prompt with input hidden, so the secret never reaches your shell history.

Applying a Change

config set never restarts anything. It writes the value and prints the step you still have to run:
Until you do, a running instance keeps serving the old value:
Or stop and restart joy serve if you’re running it in the foreground.

Secrets

Secrets are never stored in config.toml. Reference them by environment variable name:
Pasting a secret value where a variable name is expected is rejected at parse time, not silently accepted. api_key_env = "sk-..." will fail to load.
Your master encryption key lives in the OS keychain. On a headless host, supply it through JOY_REBORN_SECRET_MASTER_KEY. See Security.

Profiles

A profile selects a composition of runtime behavior — how capabilities are mediated, what storage is expected, and how processes are isolated.
Set it in config.toml under [boot], or per-invocation with JOY_REBORN_PROFILE.
Profile choice is a security boundary, not a preset. A deployment that serves more than one user must not run a profile with single-user host semantics. See Security.

Configuration Reference

Run joy config list to see every key with its current value. The main groups:
identity.tenant and identity.default_agent are optional overrides for the tenant and agent ids the runtime operates under. Leave them unset unless you know you need a different scope — a stale value here silently moves your work into another identity scope.identity.default_project is consumed by serve but not by run or repl; those two subcommands fail on startup rather than silently dropping it.
No slot is configured by default, so Joy falls back to the built-in relay’s environment variables — JOY_RELAY_API_KEY and JOY_RELAY_MODEL — until you set one. Resolution fails closed when the relay key is absent.Uncommenting only the [llm.default] header with no fields does not fall through to the environment — an empty slot is still present, and resolution fails with a missing-provider-id error. Always set provider_id together with the header.See Inference Providers.
The database URL is environment-only; this file may name the variable but must never contain the URL itself. See Storage.
Also worker_count, max_concurrent_runs_per_user, max_concurrent_conversation_runs, and max_concurrent_trigger_runs. Those four have JOY_REBORN_RUNNER_* environment overrides; heartbeat_interval_secs and poll_interval_ms are file-only.
Caps agent spend so a runaway loop can’t run up a bill:
  • budget.user_daily_usd, budget.project_daily_usd
  • budget.warn_at, budget.pause_at
  • budget.heartbeat_per_tick_usd, budget.mission_per_tick_usd
  • budget.routine_standard_usd, budget.routine_lightweight_usd
  • budget.background_job_default_usd, budget.overestimate_factor, budget.default_tz
Controls how scheduled and event-driven work is picked up: enabled, poll_interval_secs, fires_per_tick, max_concurrent_fires_per_trigger, startup_jitter_max_secs, tick_jitter_max_secs.See Routines.
Channels are not configured in this file. Slack and Telegram are turned on by installing their extension in the web interface and completing setup — there is no [slack] or [telegram] section, and no key that enables a channel. The retired [slack] setup keys are rejected outright: a config file that still carries them makes joy serve refuse to start, with a migration pointer. See Channels.google.* carries the OAuth client id, redirect URI, and hosted-domain hint. Set it either from the web interface or with joy config set google.client_id / google.client_secret / google.redirect_uri — both paths are supported.
When false, regex activation criteria no longer auto-load full skill context. Keyword and tag activation, and explicit $skill-name mentions, still work. See Skills.

Environment Variables

Overrides are namespaced JOY_REBORN_*. The ones you’re most likely to need: The built-in relay’s API key is read from JOY_RELAY_API_KEY; providers you add yourself carry the variable name their catalog entry declares.

Checking Your Configuration

Reports the resolved home and profile, whether each configuration file was found, and whether each driver initialized — without creating any state.