▞ jazzdocsblogpersonas
github

Configuration Reference

Jazz is configured via configuration files and environment variables.

Configuration File Locations

Jazz loads configuration from two layers:

  1. ~/.jazz/config.json — Global config (agents, API keys, defaults). All writes go here.
  2. ./.jazz/config.json — Optional project-local overrides (like .claude/). Read-only merge on top of global.

Merge order: defaults → ~/.jazz/config.json./.jazz/config.json.

Agents and storage always live in the Jazz home directory (~/.jazz or JAZZ_HOME), even when project overrides exist.

You can replace the global config path with $JAZZ_CONFIG_PATH or --config. Project-local ./.jazz/config.json still merges on top.

Legacy ./jazz.config.json in the project root is no longer discovered automatically. Move settings to ~/.jazz/config.json.

MCP Servers: .agents/mcp.json

Jazz also loads MCP servers from the .agents convention paths. These are merged with the main config (project overrides user):

  • ~/.agents/mcp.json — User-level MCP config
  • ./.agents/mcp.json — Project-level MCP config

See MCP Servers for format details.

Main Config: ~/.jazz/config.json

{
  "defaultModel": "anthropic:claude-3-5-sonnet",
  "theme": "dark",
  "notifications": true,
  "autoUpdate": true,
  "logLevel": "info"
}

maxIterations and maxSubagentIterations

Iteration budgets — how many reasoning loops a run gets before it stops and asks whether to continue.

{
  "maxIterations": 100,
  "maxSubagentIterations": 30
}
KeyDefaultApplies to
maxIterations100A top-level run
maxSubagentIterations30Each sub-agent run

They are separate knobs on purpose. A sub-agent gets its own budget rather than the parent’s remainder — a child spawned on the parent’s last iteration would be useless with one round — so without a lower default, every level of delegation would be as expensive as the run that spawned it. A sub-agent answers one scoped task; a child still working after 30 iterations has usually misunderstood the brief rather than found more to do.

An explicit --max-iterations on the command line, or a workflow’s own maxIterations, still wins over maxIterations here. Both values are floored at 1.

maxCostUSD, maxTokens, and maxDurationMs

Three more per-run budgets, alongside maxIterations: a dollar ceiling, a token ceiling, and a wall-clock ceiling. Unlike maxIterations none of them has a default — leave a key unset and that dimension is uncapped.

{
  "maxCostUSD": 0.2,
  "maxTokens": 200000,
  "maxDurationMs": 1800000
}
KeyUnitChecks
maxCostUSDUS dollarsOwn tokens priced via models.dev, plus any sub-agent spend rolled up through childCostUSD.
maxTokenstoken countOwn prompt + completion tokens. Sub-agent tokens are not rolled up (cost is; tokens are not).
maxDurationMsmillisecondsWall-clock time since the run started.

All three share the same enforcement model, distinct from maxIterations/maxSubagentIterations in one important way:

  • Checked between iterations, not preemptively. Each is evaluated once after an iteration’s LLM call and tool phase both finish, the same timing as the iteration-budget check. A single expensive iteration — one that calls an expensive tool, or delegates to a sub-agent that itself runs for a while — can push the total past the cap before the next check trips. None of them interrupt an in-flight LLM call or tool execution.
  • maxCostUSD never guess-aborts an unpriced run. If the model’s pricing is unknown (a local model with no catalog entry, say), costUSD stays undefined and the cap is skipped entirely rather than assumed to be zero or exceeded. maxTokens has no such gap — it needs no pricing metadata, so it still enforces where maxCostUSD cannot.
  • All three nudge the agent before they stop it. Ephemeral pressure messages — never persisted to the conversation, same reasoning as the iteration-budget nudge — are injected at 50%, 80%, and 90% of whichever budget is closest to being spent, mirroring the context-window and iteration-budget warnings. The 90% message asks the agent to wrap up immediately; at 100% the run is stopped between iterations regardless of what it answers back. maxCostUSD’s nudge is skipped under the same unknown-pricing condition as its hard stop.

A run stopped by one of these reports it on the AgentResponse: costCapped, tokenCapped, or durationCapped, each true only when that specific cap tripped. jazz run --json and jazz workflow run --json surface the same fields in their envelope.

An explicit --max-cost-usd, --max-tokens, or --max-duration-ms on the command line, or a workflow’s own frontmatter key, still wins over the config value here.

Distinct from --timeout (jazz run --timeout <ms> / jazz workflow run --timeout <ms>), which is an external hard kill — a race against the whole run with no pressure warning — rather than a soft, in-loop checkpoint. Use --timeout as the outer safety net and maxDurationMs for the warned, graceful budget.

maxSubagentDepth

How many levels of sub-agent may nest below a top-level run. Defaults to 3.

{
  "maxSubagentDepth": 3
}

Each level of delegation gets a fresh iteration budget rather than its parent’s remainder — a child spawned on the parent’s last iteration would be useless otherwise — so this depth, not the parent’s remaining budget, is what bounds how much a nest of sub-agents can spend. Past the limit spawn_subagent returns an error telling the agent to do the work itself; it never silently runs the child anyway. Set 0 to stop agents delegating at all. See Sub-agents.

context

When to warn the model that its context is filling, and when to compact history automatically. Both are fractions of the run’s context budget — the effective model window, after any numCtx pin or agent maxContextTokens ceiling.

{
  "context": {
    "warnThresholdRatio": 0.7,
    "compactThresholdRatio": 0.8
  }
}
KeyDefaultEffect
warnThresholdRatio0.7The model is told to consolidate what it has while detail still exists
compactThresholdRatio0.8Older history is summarized automatically

The ordering warn < compact < 0.95 is enforced. The 0.95 ceiling is the trim ratio: trimming discards messages rather than summarizing them, so a compaction threshold at or above it would let trimming pre-empt compaction and turn the whole scheme into a sliding window. A value that breaks the ordering — or that isn’t a number strictly between 0 and 1 — is ignored with a logged warning and the default is used; the run never fails on a bad ratio.

Raising compactThresholdRatio keeps more verbatim history but leaves less headroom, which bites hardest on local servers whose real window is smaller than advertised. Lowering it compacts earlier and more often, costing a summarizer call each time. The reserved-space figure in /context is derived from this setting, so the grid always reflects where compaction actually fires.

output

Terminal display of reasoning, tools, and formatting. The interactive TUI reads these when a session starts.

{
  "output": {
    "showReasoning": true,
    "collapseReasoning": true,
    "showToolExecution": true,
    "mode": "hybrid"
  }
}
KeyDefaultEffect
showReasoningtrueStream the model’s reasoning while it thinks
collapseReasoningtrueAfter thinking finishes, collapse it to a one-line summary. Ctrl+R expands it in place
showToolExecutiontrueShow tool calls as they run
modehybridrendered | hybrid | raw | quiet. Overridable with JAZZ_OUTPUT_MODE / --output

Set collapseReasoning to false to leave the full reasoning visible after it finishes. Ctrl+R is then unused — there is nothing collapsed to expand. Change it with jazz config set output.collapseReasoning false, or from Output & Display in jazz config.

scheduler

Which scheduler runs workflows: your OS scheduler (launchd on macOS, cron on Linux), or Jazz’s own in-process ticker inside jazz daemon. Defaults to "auto", which uses the OS scheduler.

{
  "scheduler": {
    "mode": "in-process"
  }
}
ValueEffect
"auto"Default. jazz workflow schedule installs a launchd/cron entry
"in-process"jazz daemon polls due schedules itself once per minute; no OS entries

Set it from Scheduler in jazz config, or directly:

jazz config set scheduler.mode in-process

in-process only matters for a host you leave running — jazz daemon must itself be running for its ticker to fire. The JAZZ_SCHEDULER=in-process environment variable still works and overrides this setting, which is useful for a one-off run without touching the saved config. See Scheduled runs for how each mode installs and fires.

webhooks

Webhook doors onto specific agents. Each entry is served at POST /webhooks/<name> by jazz daemon and runs one fixed prompt, with the request body quoted into it as data.

{
  "webhooks": [
    {
      "name": "room",
      "agentId": "default",
      "conversation": "threaded",
      "promptTemplate": "You are in a conversation. Reply to the latest message.\n\n{{payload}}"
    }
  ]
}
FieldRequiredEffect
nameyesURL segment and token lookup key. Unique
agentIdyesWhich agent the webhook wakes
promptTemplateyesPrompt for the fire. {{payload}} is replaced with the quoted body
descriptionnoNote for yourself; never sent to the model
conversationno"ephemeral" (default) starts fresh each fire; "threaded" resumes per thread key

Tokens never live in this file. Store one in the keyring with jazz config set webhooks.<name>.token, or supply JAZZ_WEBHOOK_TOKEN_<NAME> in the environment. A threaded webhook takes its thread key from the X-Jazz-Thread request header.

See Webhooks for the full behaviour.

Project Overrides: ./.jazz/config.json

Use for project-specific settings such as MCP enable/disable flags or logging level. Do not put agent storage paths here — agents always load from ~/.jazz.

Environment Variables

Override settings or provide API keys via .env or the process environment.

Paths and data

VariableEffect
JAZZ_HOMEJazz home directory (default ~/.jazz). Holds agents, history, logs, telemetry, and the model-catalog snapshot. Use it to isolate test data when developing Jazz
JAZZ_CONFIG_PATHGlobal config file path (same as --config)
JAZZ_LOG_DIRLog directory override

Network behavior

VariableEffect
JAZZ_OFFLINE1/true: make no outbound request of Jazz’s own — skips the update check and the models.dev catalog fetch. See Airgapped
JAZZ_DISABLE_UPDATE_CHECK1: skip only the npm version check
JAZZ_MODELS_DEV_URLPoint the model catalog at an internal mirror of https://models.dev/api.json
HTTPS_PROXY / HTTP_PROXYSend every outbound request — provider APIs, web tools, remote MCP servers, the update check — through an HTTP proxy. Lowercase names work too, and ALL_PROXY covers both protocols
NO_PROXYComma-separated hosts and domain suffixes to reach directly, bypassing the proxy

Only http:// and https:// proxies are supported; a SOCKS proxy is reported at startup rather than silently ignored. If the proxy terminates TLS with a private CA, point Node at that CA with NODE_EXTRA_CA_CERTS=/path/to/ca.pem.

Providers

VariableEffect
OPENAI_API_KEYOpenAI
ANTHROPIC_API_KEYAnthropic
ANTHROPIC_WORKSPACE_IDOnly needed with an identity-linked Anthropic API key (see below)
GOOGLE_GENERATIVE_AI_API_KEYGoogle Gemini
OPENROUTER_API_KEYOpenRouter
OLLAMA_BASE_URLOllama endpoint (default http://localhost:11434/api; /api is appended automatically)
LLAMACPP_BASE_URLllama.cpp endpoint (default http://localhost:8080/v1)

Other providers follow the same <PROVIDER>_API_KEY convention — see Integrations → Providers.

Anthropic has two kinds of API keys: a workspace-scoped key (the common case) needs nothing extra, but an identity-linked key — one tied to a Console user identity rather than a single workspace, typically issued by an organization — needs a workspace ID on every request. If a request fails with anthropic-workspace-id is required, set ANTHROPIC_WORKSPACE_ID or llm.anthropic.workspace_id in config.json (jazz config prompts for it right after the API key).

Output and terminal

VariableEffect
JAZZ_NO_TUI1: no terminal UI at all, plain output (same as --no-tui)
JAZZ_FULLSCREEN0: keep an interactive interface but not the alternate screen. Print-and-exit commands never enter it, so their output stays in scrollback.
JAZZ_OUTPUT_MODErendered | hybrid | raw | quiet (same as --output)
JAZZ_THEMEColour theme
JAZZ_UI_GLYPHSunicode | ascii — override glyph detection for terminals that misreport
JAZZ_TABLE_STYLETable rendering style
NO_COLORStandard: disable colour output

Scheduling

VariableEffect
JAZZ_DISABLE_CATCH_UP1: never offer missed scheduled runs on startup

Notifications

VariableEffect
JAZZ_TERMINAL_NOTIFIER / TERMINAL_NOTIFIERPath to a terminal-notifier binary for desktop notifications
JAZZ_TERMINALTerminal identity used for notification attribution

telemetry

Jazz records what each run did — agent runs, LLM requests and token usage (agent loop vs command-risk classifier), retries, tool invocations, Jazz process RSS/heap/CPU, and CLI commands — as NDJSON under ~/.jazz/telemetry/events/YYYY-MM-DD.ndjson, pruned after retentionDays. Set telemetry.otlp to also push those events to an OpenTelemetry collector. See Observability for a working collector and Langfuse setup.

{
  "telemetry": {
    "enabled": true,
    "retentionDays": 90,
    "otlp": {
      "endpoint": "http://localhost:4318",
      "headers": { "authorization": "Basic <base64>" },
      "serviceName": "jazz",
      "captureContent": false
    }
  }
}
FieldDefaultEffect
enabledtrueMaster switch. false records nothing, locally or remotely
storagePath~/.jazz/telemetryWhere the local NDJSON files live
bufferSize100Events buffered in memory before a flush
flushIntervalMs30000Periodic flush interval
retentionDays90Local files older than this are deleted
otlp.enabledtrue when an endpoint is setExplicit opt-out that keeps the endpoint configured
otlp.signals["traces"]Signals to export: traces, logs, or both
otlp.endpointCollector base URL; /v1/traces and /v1/logs are appended
otlp.tracesEndpointFull traces URL including path, overriding endpoint
otlp.logsEndpointFull logs URL including path, overriding endpoint
otlp.headers{}Extra HTTP headers, typically auth
otlp.serviceNamejazzservice.name on exported records
otlp.captureContentfalseInclude prompt, completion, and tool argument text
otlp.timeoutMs10000Per-request timeout

OTLP environment variables

Every otlp field falls back to the standard OTEL_* variable, so a Jazz process inherits an already-configured collector without touching config.json. Precedence is config → environment → default.

VariableEffect
OTEL_EXPORTER_OTLP_ENDPOINTCollector base URL. Setting this alone turns export on
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTFull traces URL, used verbatim; wins over the base URL
OTEL_EXPORTER_OTLP_LOGS_ENDPOINTFull logs URL, used verbatim; wins over the base URL
OTEL_EXPORTER_OTLP_HEADERSkey=value pairs, comma-separated, values percent-encoded
OTEL_SERVICE_NAMEservice.name on exported records

There is no environment variable for captureContent — it can only be turned on in config, and never follows from configuring an endpoint.

What gets exported

Events are sent over OTLP/HTTP with JSON encoding. By default they are exported as traces: one trace per agent run, with the run as the root span and each LLM request, retry, and tool call as a child span. Traces are what LLM-observability backends accept — Langfuse ingests OTLP traces and not logs — so this is the signal that works everywhere. Add logs to otlp.signals to also emit the same events as OTLP log records.

Where the OpenTelemetry GenAI semantic conventions define an attribute, Jazz uses it (gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens); everything else is namespaced under jazz.*.

captureContent is the one setting that turns observability into data egress. With it off (the default) Jazz drops content-bearing fields and truncates every remaining string attribute to 256 characters. Turning it on sends user prompts, model output, and tool arguments to whatever endpoint you configured. No event Jazz emits today carries content, so the flag is currently inert — it exists so that adding a content-bearing field later cannot leak it by default.

Export never blocks a run. A collector that is slow, down, or rejecting is logged as a warning and the events are dropped once the buffer ceiling is reached; the local NDJSON file is unaffected.

autoApprovedCommands

A persisted allowlist for execute_command, set at the top level of ~/.jazz/config.json:

{ "autoApprovedCommands": ["himalaya", "khal", "git status"] }

Each entry lets one command through without raising the whole approval tier. This is the right tool when a scheduled workflow needs a skill that shells out — email, calendar, and Obsidian all run through execute_command, which is unknown, so a low-risk workflow cannot use them otherwise.

Matching uses a parsed key (binary + first subcommand) with exact or word-boundary comparison, never a raw string prefix — so git status does not also permit git status && rm -rf /. See Tools & approval.

Agent Config: customTools

customTools is an optional field on an agent’s config, letting a deployment declare new tools directly in the agent’s JSON config instead of shipping code — the closest analog is the Claude Agent SDK’s custom tools (name, description, input schema, handler, registered alongside builtins). Declaring a custom tool is not enough to expose it: its name must also appear in the agent’s tools array, exactly like a builtin or MCP tool. A declared-but-unlisted custom tool is simply skipped at registration.

Each entry has a handler.type of either record or command.

record — no side effect. The call is validated and appended to the run’s toolCalls (the same field every other tool call surfaces in), so the caller embedding Jazz can read the arguments and act on them; the model itself only ever sees the fixed response. Note that toolCalls reports calls as the model sent them — including calls whose arguments failed schema validation — so callers must re-validate arguments before acting on them. This is the pattern behind confirmation-card / propose-then-confirm flows:

{
  "name": "propose_action",
  "description": "Propose an action for the user to confirm before it is carried out.",
  "parameters": {
    "type": "object",
    "properties": {
      "action": { "type": "string", "description": "Short description of the proposed action" },
      "payload": { "type": "object", "description": "Structured data needed to carry out the action" }
    },
    "required": ["action"]
  },
  "handler": {
    "type": "record",
    "response": "Proposal recorded — the user will see a confirmation card."
  }
}

command — spawns handler.command directly (an argv array, no shell) with the validated tool arguments serialized as JSON on the child’s stdin. Exit code 0 returns stdout (capped at 16 KB — tighter than execute_command’s 256 KB, because custom commands are small trusted argv programs, not a general shell) as the tool result; a non-zero exit, spawn error, or timeout produces a failure result the model sees the same way it sees a failing builtin tool. The command’s environment is sanitized the same way execute_command sanitizes its shell environment (see envAllowlist below), using the envAllowlist of the agent that DECLARED the tool, not whichever agent happens to be calling it. Security note: command tools execute with no interactive approval step — they are always registered high-risk and run whatever the deployment configured the moment the model calls them, so treat handler.command entries as deployment-authored, trusted commands, not user-supplied ones.

{
  "name": "lint_project",
  "description": "Run the project linter",
  "parameters": {
    "type": "object",
    "properties": {}
  },
  "handler": {
    "type": "command",
    "command": ["./lint.sh"],
    "timeoutMs": 30000
  }
}

timeoutMs defaults to 30 000 ms and is capped at 300 000 ms (5 minutes) when set.

Validation rules

  • name: must match ^[a-z][a-z0-9_]{1,63}$, must be unique within customTools, and must not start with the mcp_ prefix (reserved for MCP-sourced tools).
  • At most 16 entries per agent.
  • description: 1-1024 characters — this is what the model reads to decide when to call the tool.
  • parameters: a JSON Schema object; must have "type": "object". Only a subset of JSON Schema is honored by the converter that turns this into the runtime validator: unsupported keywords are silently dropped, and a property with a missing or unrecognized type degrades to an empty object schema — which then rejects a scalar argument (string/number/boolean) passed for that property. Stick to string, number, integer, boolean, null, enum, and plain nested objects/arrays of those.
  • handler.response (record): optional string, at most 1024 characters, defaults to "Recorded.".
  • handler.command (command): a non-empty array of non-empty strings.

Collisions and re-registration

A custom tool name that collides with an already-registered builtin or MCP tool name — or one of its aliases (e.g. glob) — fails the run with a configuration error rather than silently overriding the existing tool; rename the custom tool or remove the conflicting one. Because tool registration runs on every agent run (not just once per process), re-registering the exact same custom tool definition under a name that’s already registered is a no-op, not a collision; only a genuinely different definition sharing that name is rejected — and since the registry has no way to update an existing registration in place, that rejection fails the run MID-SESSION (not at process startup): restart the session, or revert the custom tool definition to match what was registered earlier.

Agent Config: envAllowlist

envAllowlist is an optional string[] field on an agent’s config that exempts specific environment variable names from the sensitive-name scrub applied to shell commands the agent runs (execute_command and custom command-handler tools share this same env-sanitization path; it does not affect grep/find/git tool spawns). By default, any variable whose name matches API|KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH (case-insensitive) is stripped before a child process is spawned. Listing a name in envAllowlist copies that variable from the process environment into the child’s environment even though it matches the scrub regex — it never invents a value that isn’t already set. SSH_*-prefixed names and the small set of base env vars Jazz always sets itself (PATH, HOME, USER, SHELL, etc.) can never be allowlisted — that block applies unconditionally, regardless of envAllowlist membership.

{
  "envAllowlist": ["MY_SERVICE_TOKEN"]
}

Validation: at most 32 names, each matching ^[A-Z][A-Z0-9_]{0,63}$ (uppercase letters, digits, and underscores, starting with a letter, up to 64 characters).

Security note: allowlisting a secret-bearing variable hands it to every shell command the agent runs — the deployment owns that trade-off.


Agent Config: temperature

temperature is an optional number field on an agent’s config that sets the model’s sampling temperature. The wizard never asks for it, so it is a file-only setting:

{
  "config": {
    "llmProvider": "anthropic",
    "llmModel": "claude-sonnet-4-5",
    "temperature": 0.2
  }
}

Leaving it unset is not the same as setting a default. When the field is absent Jazz omits the parameter from the request entirely and the provider applies its own default. Set it only when you want to override that.

Jazz does not range-check the value — the valid range differs by provider (commonly 01 or 02), and an out-of-range number surfaces as a provider error.

Models that reject a custom temperature silently ignore this field. Some models — notably several reasoning models — accept no temperature at all. Jazz reads that capability from models.dev metadata (and, for OpenRouter, the model’s supported_parameters), and drops the parameter rather than sending a request the provider would reject. The agent still runs; the setting simply has no effect. If a temperature change appears to do nothing, that is the first thing to check.

machine-readable: /docs/reference/configuration.md · /llms.txt · /llms-full.txt