# Settings

Elyra uses JSON settings files with project settings overriding global settings.

| Location | Scope |
|----------|-------|
| `~/.elyra/agent/settings.json` | Global (all projects) |
| `.elyra/settings.json` | Project (current directory) |

Edit directly or use `/settings` for common options.

## All Settings

### Model & Thinking

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `defaultProvider` | string | - | Default provider (e.g., `"anthropic"`, `"openai"`) |
| `defaultModel` | string | - | Default model ID |
| `defaultThinkingLevel` | string | - | `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"` |
| `hideThinkingBlock` | boolean | `false` | Hide thinking blocks in output |
| `thinkingBudgets` | object | - | Custom token budgets per thinking level |

#### thinkingBudgets

```json
{
  "thinkingBudgets": {
    "minimal": 1024,
    "low": 4096,
    "medium": 10240,
    "high": 32768
  }
}
```

### UI & Display

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `theme` | string | `"dark"` | Theme name (`"dark"`, `"light"`, or custom) |
| `quietStartup` | boolean | `false` | Hide startup header |
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
| `enableInstallTelemetry` | boolean | `true` | Send an anonymous install/update version ping after first install or changelog-detected updates. This does not control update checks |
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
| `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
| `showHardwareCursor` | boolean | `false` | Show terminal cursor |

### Telemetry and update checks

`enableInstallTelemetry` only controls the anonymous install/update ping to `https://elyracode.app/api/report-install`. Opting out of telemetry does not disable update checks; Elyra can still fetch `https://elyracode.app/api/latest-version` to look for the latest version.

Set `ELYRA_SKIP_VERSION_CHECK=1` to disable the Elyra version update check. Use `--offline` or `ELYRA_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.

### Warnings

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `warnings.anthropicExtraUsage` | boolean | `true` | Show a warning when Anthropic subscription auth may use paid extra usage. Shown once per install; Elyra records `warnings.anthropicExtraUsageShown` after displaying it |

```json
{
  "warnings": {
    "anthropicExtraUsage": false
  }
}
```

### Compaction

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `compaction.enabled` | boolean | `true` | Enable auto-compaction |
| `compaction.reserveTokens` | number | `16384` | Tokens reserved for LLM response |
| `compaction.keepRecentTokens` | number | `20000` | Recent tokens to keep (not summarized) |

```json
{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  }
}
```

### Context Pruning

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `contextPruning.enabled` | boolean | `true` | Replace stale, large tool outputs with a short placeholder before each provider request |
| `contextPruning.keepRecentTurns` | number | `6` | A tool result is stale once this many assistant messages follow it |
| `contextPruning.minChars` | number | `2000` | Only tool results with at least this many characters of text are pruned |

Context pruning is a request-time transform. Before each provider request, tool results that are older than `keepRecentTurns` assistant turns and larger than `minChars` have their content replaced with a one-line placeholder (for example `[read src/foo.ts, 250 lines – pruned from context; re-read if needed]`). Session history, `/tree`, exports, and compaction all see the original output; only what is sent to the model changes. Error results, results containing images, and small results are never pruned. Because `read` output includes a stable per-line hash prefix, the model can cheaply re-read a file when it needs the details again. Set `enabled: false` to disable.

```json
{
  "contextPruning": {
    "enabled": true,
    "keepRecentTurns": 6,
    "minChars": 2000
  }
}
```

### Cache Warming

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `cacheWarming.enabled` | boolean | `true` | Keep the Anthropic prompt cache warm while you are idle |
| `cacheWarming.delayMs` | number | `240000` | Milliseconds after a turn ends before the first warm ping (4 minutes; Anthropic's default TTL is 5) |
| `cacheWarming.maxPings` | number | `2` | Pings per idle period before giving up (2 covers about 13 minutes) |
| `cacheWarming.minPrefixTokens` | number | `20000` | Skip warming when the last request's prompt was smaller than this |

Anthropic's prompt cache expires five minutes after the last read or write. While you read the answer or type the next prompt, the cache goes cold and the next turn rewrites the whole prefix at 1.25x input price instead of reading it at 0.1x. A warm ping replays the exact same request (system prompt, tools, history, thinking settings) with a one-character user message and `max_tokens: 1`; that read costs one cache read of the prefix and resets the TTL. On a 100k-token prefix with Sonnet that is roughly $0.03 per ping versus $0.375 for a cold rewrite, so warming pays for itself if you come back within about ten minutes. For longer breaks the one-hour TTL (`ELYRA_CACHE_RETENTION=long`, a one-time 2x write premium) is cheaper, which is why the warmer gives up after `maxPings`.

Warming only runs for `anthropic-messages` models with an API key (not Claude subscription OAuth, where pings would spend rate-limit quota), only when the agent is idle, and only when the last request was at least `minPrefixTokens`. It stops as soon as you send a prompt, switch models, or abort, and it never keeps the process alive. Pings are not added to the conversation or the session file; `/session` shows their count and cost separately from turn cost. Set `enabled: false` to turn it off.

```json
{
  "cacheWarming": {
    "enabled": true,
    "delayMs": 240000,
    "maxPings": 2,
    "minPrefixTokens": 20000
  }
}
```

### Tools

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `stableToolSet` | boolean | `true` | Keep the tool list sent to the provider stable when tools are toggled mid-session, so the prompt cache survives |

On Anthropic the tools block is the root of the prompt-cache hierarchy, and on OpenAI it is part of the hashed prefix, so removing a tool from the request normally throws away the cached system prompt and conversation. With `stableToolSet`, deactivating a tool (via `/tools`, an extension's `setActiveTools`, or smart routing) leaves its definition in the request and in the system prompt; the agent blocks the call instead and tells the model the tool is disabled. Activating a tool that was never sent still adds it, which costs one cache miss, as before. Startup restrictions (`--tools`, `--no-tools`) are unaffected because they shape the initial set. Set to `false` to send only the active tools; the prompt shrinks but every toggle invalidates the cache.

### Branch Summary

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `branchSummary.reserveTokens` | number | `16384` | Tokens reserved for branch summarization |
| `branchSummary.skipPrompt` | boolean | `false` | Skip "Summarize branch?" prompt on `/tree` navigation (defaults to no summary) |

### Retry

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `retry.enabled` | boolean | `true` | Enable automatic agent-level retry on transient errors |
| `retry.maxRetries` | number | `3` | Maximum agent-level retry attempts |
| `retry.baseDelayMs` | number | `2000` | Base delay for agent-level exponential backoff (2s, 4s, 8s) |
| `retry.provider.timeoutMs` | number | SDK default | Provider/SDK request timeout in milliseconds |
| `retry.provider.maxRetries` | number | SDK default | Provider/SDK retry attempts |
| `retry.provider.maxRetryDelayMs` | number | `60000` | Max server-requested delay before failing (60s) |

When a provider requests a retry delay longer than `retry.provider.maxRetryDelayMs` (e.g., Google's "quota will reset after 5h"), the request fails immediately with an informative error instead of waiting silently. Set to `0` to disable the cap.

```json
{
  "retry": {
    "enabled": true,
    "maxRetries": 3,
    "baseDelayMs": 2000,
    "provider": {
      "timeoutMs": 3600000,
      "maxRetries": 0,
      "maxRetryDelayMs": 60000
    }
  }
}
```

### Message Delivery

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `steeringMode` | string | `"one-at-a-time"` | How steering messages are sent: `"all"` or `"one-at-a-time"` |
| `followUpMode` | string | `"one-at-a-time"` | How follow-up messages are sent: `"all"` or `"one-at-a-time"` |
| `transport` | string | `"sse"` | Preferred transport for providers that support multiple transports: `"sse"`, `"websocket"`, or `"auto"` |

### Terminal & Images

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `terminal.showImages` | boolean | `true` | Show images in terminal (if supported) |
| `terminal.imageWidthCells` | number | `60` | Preferred inline image width in terminal cells |
| `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when content shrinks (can cause flicker) |
| `terminal.hyperlinks` | string | `"auto"` | OSC 8 hyperlink support: `"auto"`, `"on"`, `"off"` |
| `terminal.images` | string | `"auto"` | Inline image protocol: `"auto"`, `"kitty"`, `"iterm2"`, `"off"` |
| `terminal.trueColor` | string | `"auto"` | 24-bit color support: `"auto"`, `"on"`, `"off"` |
| `images.autoResize` | boolean | `true` | Resize images to 2000x2000 max |
| `images.blockImages` | boolean | `false` | Block all images from being sent to LLM |

`terminal.hyperlinks`, `terminal.images`, and `terminal.trueColor` override capability auto-detection. Detection is conservative: unrecognized terminals get hyperlinks and images disabled, and tmux/screen forces hyperlinks off. Use these to force-enable features on capable but unrecognized terminals, or to disable a misdetected capability. `"auto"` (the default) keeps the detected value.

```json
{
  "terminal": {
    "hyperlinks": "on",
    "images": "kitty",
    "trueColor": "on"
  }
}
```

### Shell

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `shellPath` | string | - | Custom shell path (e.g., for Cygwin on Windows) |
| `shellCommandPrefix` | string | - | Prefix for every bash command (e.g., `"shopt -s expand_aliases"`) |
| `npmCommand` | string[] | - | Command argv used for npm package lookup/install operations (e.g., `["mise", "exec", "node@20", "--", "npm"]`) |

```json
{
  "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
}
```

`npmCommand` is used for all npm package-manager operations, including installs, uninstalls, and dependency installs inside git packages. Use argv-style entries exactly as the process should be launched. When `npmCommand` is configured, git package dependency installs use plain `install` to avoid npm-specific flags in wrappers or alternate package managers.

Normally the package manager's global modules location is queried using `root -g`. As a special case, if the first element of `npmCommand` is `"bun"`, the modules location will instead be queried with `pm bin -g`.

### Sessions

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `sessionDir` | string | - | Directory where session files are stored. Accepts absolute or relative paths, plus `~`. |

```json
{ "sessionDir": ".elyra/sessions" }
```

When multiple sources specify a session directory, precedence is `--session-dir`, `ELYRA_CODING_AGENT_SESSION_DIR`, then `sessionDir` in settings.json.

### Memory

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `decisionLog` | string | `"confirm"` | Architecture decision log: `"off"` disables it; `"confirm"` asks before the agent records a decision; `"auto"` records silently |

#### Decision log

Decisions live in `.elyra/memory/decisions/`, one Markdown file per decision (`<date>-<slug>.md` with YAML frontmatter). The directory is meant to be committed to git so the whole team shares the same history. Decisions are never deleted: when a newer decision replaces an older one, the old file is marked `superseded` and keeps a back-reference, so the reasoning behind a reversal stays available. Browse the log with `/decisions`, or switch the mode with `/decisions off|confirm|auto`.

### Git

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `gitCostTrailer` | boolean | `true` | Append an `Elyra-Cost:` trailer to git commits the agent makes, showing spend since the previous commit |

When the agent runs `git commit` through the bash tool, Elyra appends a `--trailer` argument summarizing assistant spend since the last trailered commit (or since the session started for the first one): total cost, the model(s) used, the number of turns, and how many times the model changed (failovers or manual switches). The trailer is skipped for `--fixup`/`--squash` commits and when no turns have completed since the last one. The result shows up in `git log`:

```
Elyra-Cost: $0.42 (glm-5.3, 4 turns, 1 model switch)
```

### Model Cycling

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `enabledModels` | string[] | - | Model patterns for Ctrl+P cycling (same format as `--models` CLI flag) |

```json
{
  "enabledModels": ["claude-*", "gpt-4o", "gemini-2*"]
}
```

### Markdown

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `markdown.codeBlockIndent` | string | `"  "` | Indentation for code blocks |

### Resources

These settings define where to load extensions, skills, prompts, and themes from.

Paths in `~/.elyra/agent/settings.json` resolve relative to `~/.elyra/agent`. Paths in `.elyra/settings.json` resolve relative to `.elyra`. Absolute paths and `~` are supported.

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `packages` | array | `[]` | npm/git packages to load resources from |
| `extensions` | string[] | `[]` | Local extension file paths or directories |
| `skills` | string[] | `[]` | Local skill file paths or directories |
| `prompts` | string[] | `[]` | Local prompt template paths or directories |
| `themes` | string[] | `[]` | Local theme file paths or directories |
| `enableSkillCommands` | boolean | `true` | Register skills as `/skill:name` commands |

Arrays support glob patterns and exclusions. Use `!pattern` to exclude. Use `+path` to force-include an exact path and `-path` to force-exclude an exact path.

#### packages

String form loads all resources from a package:

```json
{
  "packages": ["elyra-skills", "@org/my-extension"]
}
```

Object form filters which resources to load:

```json
{
  "packages": [
    {
      "source": "elyra-skills",
      "skills": ["brave-search", "transcribe"],
      "extensions": []
    }
  ]
}
```

See [packages.md](packages.md) for package management details.

## Example

```json
{
  "defaultProvider": "anthropic",
  "defaultModel": "claude-sonnet-4-20250514",
  "defaultThinkingLevel": "medium",
  "theme": "dark",
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  },
  "retry": {
    "enabled": true,
    "maxRetries": 3
  },
  "enabledModels": ["claude-*", "gpt-4o"],
  "warnings": {
    "anthropicExtraUsage": true
  },
  "packages": ["elyra-skills"]
}
```

## Project Overrides

Project settings (`.elyra/settings.json`) override global settings. Nested objects are merged:

```json
// ~/.elyra/agent/settings.json (global)
{
  "theme": "dark",
  "compaction": { "enabled": true, "reserveTokens": 16384 }
}

// .elyra/settings.json (project)
{
  "compaction": { "reserveTokens": 8192 }
}

// Result
{
  "theme": "dark",
  "compaction": { "enabled": true, "reserveTokens": 8192 }
}
```
