Skip to main content

Agent Presets

How agent presets resolve in Daintree: the three preset sources, worktree-scoped defaults, display titles, tri-state behavior overrides, fallback chains, shared project presets, and provider templates.

Reviewed

Presets

A preset is a named launch configuration for a single agent: environment variables, extra CLI arguments, a couple of behavior overrides, a color, and an optional display title. Launching with a preset starts the same CLI you always start, configured differently.

Presets are agent-scoped. For multi-panel workspace layouts (several agents, terminals, and panels laid out across a worktree), see Recipes instead. Recipes launch agents, and a recipe launch honors whichever preset the agent resolves to.

What a Preset Is

Every preset is an AgentPreset: a stable ID, a name, and optional overrides.

{
  "id": "zai",
  "name": "Z.AI",
  "description": "Anthropic-compatible via Z.AI",
  "displayTitle": "Claude [Z.ai]",
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
    "API_TIMEOUT_MS": "3000000"
  },
  "args": ["--mode", "plan"],
  "customFlags": "--verbose",
  "dangerousMode": "off",
  "inlineMode": "inherit",
  "color": "#36CE94",
  "fallbacks": ["anthropic-native"]
}
FieldMeaning
idRequired. Restricted to [a-zA-Z0-9_.-], capped at 100 characters. Presets you create get a generated user-<uuid> ID.
nameRequired. Trimmed, capped at 200 characters, rejected if it contains < or >.
descriptionShown under the preset in Settings.
displayTitleOptional free-form label that replaces the composed panel title. See Display Title and Color.
envEnvironment variables layered on top of the agent's global env at launch.
argsCLI arguments appended to the generated launch command.
customFlagsA flag string that replaces the agent-level custom flags when the preset defines one.
dangerousModeTri-state permission-bypass override. See Behavior Overrides Are Tri-States.
inlineModeTri-state alt-screen override, same three values.
colorCSS hex (#rgb, #rgba, #rrggbb, #rrggbbaa). Anything else is dropped.
fallbacksUp to three preset IDs to try when this preset's provider is unreachable.

dangerousEnabled is the legacy boolean that dangerousMode replaced. Presets written before the tri-state landed still work: a stored true reads as "on", and false or absent reads as "inherit", never as a veto.

Where Presets Come From

Daintree merges three sources per agent and keeps the first entry it sees for any given ID:

  1. Custom: your own presets, created in Settings and stored per user.
  2. Project: shared JSON files in .daintree/presets/{agentId}/, committed alongside your code.
  3. Registry and CCR: presets shipped with the agent definition, plus Claude Code Router routes discovered at runtime from ~/.claude-code-router/config.json. CCR routes arrive as ccr-* IDs named CCR: <route>; the prefix is stripped for display everywhere.

Custom beats project beats registry on an ID collision. That is the escape hatch for overriding a teammate's shared preset locally without touching the committed JSON. Mistral Vibe is the only agent shipping registry presets today: Default, Plan, Accept edits, Auto-approve, and Local (llama.cpp).

Every preset from every source is sanitized on merge. Values that fail validation are dropped silently, so a preset never carries a shell injection into a launch. See What Gets Sanitized.

How a Launch Resolves a Preset

This is the part that surprises people, so it is worth being exact. Daintree resolves a preset ID first, then looks that ID up in the merged list:

  1. An explicit preset on the launch. A dropdown row, a context-menu entry, or a recipe step naming a preset forwards that ID directly. The Agent default row forwards an explicit "no preset", which skips the lookup entirely.
  2. The worktree-scoped override for the active worktree, if one is set.
  3. The agent-level default, if one is set.
  4. A registry-declared default, for an agent whose definition names one. No shipped agent declares one today.
  5. No preset: the bare agent CLI, configured only by the agent's own Default scope. This is what "Agent default" means.

There is no "whatever sorts first" step. Before v0.20 the resolver fell back to the first merged preset, which meant creating a single custom route silently promoted it into the primary button's launch while the dropdown checkmark still sat on Agent default. It now resolves to no preset, so the button and the checkmark always agree.

Note
Merely having presets no longer changes what the plain agent button launches: only a pick does. Two scopes carry a pick: the worktree override, written by the dropdown gutter, and the agent-level default, written by the Settings scope picker. Note that creating or duplicating a preset in Settings selects it in that picker, which is the same control as the agent-level default.

When a Preset ID Goes Stale

Deleting a preset that a worktree still points at does not launch you preset-free. If the worktree-scoped pick no longer resolves and the agent-level default still does, the launch uses the agent-level default and clears the dead worktree slot so the next launch resolves directly. Recipe launches take the same fallback but never write to settings: a recipe run does not mutate your agent configuration.

Picking a Preset From the Toolbar

An agent button with at least one preset splits into a launch button and a chevron. The button label carries the active preset name in its tooltip (Start Claude · Z.AI), and the icon takes the preset's color.

Each preset row in the chevron dropdown has two click zones:

  • The row itself launches immediately with that preset and closes the menu. This is also what keyboard activation does.
  • The left gutter (the narrow strip holding the checkmark) sets that preset as the worktree default without launching, and leaves the menu open. The checkmark marks the effective preset — the one the next plain launch would use: the worktree override when one is set, and otherwise the agent-level default.

The Agent default row follows the same split: its label launches the bare CLI, and its gutter clears both scopes (the worktree override and the agent-level default), so nothing is left behind for the resolver to fall through to.

Rows are grouped by source (CCR Routes, Project Shared, Custom) whenever more than one group is present, and the menu ends with Manage Presets…. Right-clicking the button opens a context menu with the same list under Launch with Preset, and the searchable launcher expands each agent into the same preset rows.

Tip
The gutter writes to the worktree scope, not the global one. Setting a preset on one worktree does not change what launches on another. An agent-wide default is a Settings action.
Each preset row splits: the label launches, the gutter sets the worktree default

Display Title and Color

Launching with a preset renames the panel. By default Daintree composes the agent name with the preset name in brackets (Claude [Z.ai]), so the active route is visible in the tab without opening anything. Set displayTitle and that string is used verbatim instead.

Either way the title is pinned as a custom title, so the agent's own title detection cannot overwrite it mid-session.

A display title shows up in:

  • the panel tab and the docked terminal entry
  • the preset rows in the agent-button dropdown and in the launcher
  • the agent button's tooltip, next to the agent name
  • the Settings scope picker and the "Editing:" scope banner above the preset editor

The field is sanitized on the way in: control characters are stripped, a value containing < or > is rejected outright, the result is trimmed and capped at 100 characters, and an empty result means "no custom title". Duplicating a preset deliberately drops the display title so the copy does not share a label with its source.

color tints the agent icon on the toolbar button, the dropdown row, the dock entry, the grid tab, and the Pilot row, and is carried onto the launched panel so restart and resume keep it. A preset color is treated as a deliberate choice: it bypasses the dark-theme contrast fallback that would otherwise render a low-contrast mark on a near-white tile.

Behavior Overrides Are Tri-States

Both behavior overrides are three-valued, not boolean. The preset's value is layered on top of the agent's resolved value, and the preset wins unless it defers:

ValueEffect
"on"Force it on for this preset, overriding an agent or global "off".
"off"Force it off for this preset. A true veto: it beats an agent or global "on".
"inherit"Defer to the agent's Default scope, which in turn defers to the global switch. This is also what an absent value means.

The scopes stack the same way for both settings:

ScopeSet fromBeaten by
Global switchSettings > CLI agents > General, under "Global agent settings"An explicit agent value
Agent Default scopeSettings > CLI agents > agent > Runtime settings, scope DefaultAn explicit preset value
PresetThe same editor with a custom preset selectedNothing

dangerousMode controls whether the agent's permission-bypass flag is injected, for agents that declare one. "inherit" falls through to the global "Skip permission prompts for agents" switch. The legacy dangerousEnabled boolean maps true → "on" and false or absent → "inherit": an explicit veto is reachable only through dangerousMode, so an old settings file never gains a veto it did not ask for. See Security for what bypass actually turns off.

inlineMode controls whether the agent renders inline in the terminal or takes the alternate screen. "on" is inline, "off" is alt-screen, and "inherit" defers to a per-agent registry default if the agent declares one (none do today) and then to the global "Use alt-screen mode by default" switch. Here a legacy boolean is read literally (false → "off", not "inherit") because this field was historically seeded with concrete per-agent choices that a fall-through mapping would have quietly handed to the global switch.

Env, Args, and Flags

Environment layers in one direction: the agent's global env is the base, the preset's env overrides it key by key, and a caller-supplied env (a recipe step's env, or a session-bound token) overrides both. args are appended to the generated launch flags, and skipped if the same contiguous sequence is already present. customFlags is a replacement rather than an addition: when the preset sets one, it stands in for the agent-level flag string entirely, and the editor offers a reset back to the agent value.

Creating and Editing Presets

Open Settings > CLI agents, pick an agent, and find the Runtime settings card. The scope picker at the top is the single control for both "what am I editing" and "what is this agent's default": Default (all worktrees) edits the agent's own settings, and selecting a preset edits that preset and makes it the agent-level default. The banner underneath names the current scope and badges it custom, project, or auto.

Add preset offers three starting points:

  • Blank: an empty preset named "New preset".
  • Clone current: copies the selected preset's env, args, flags, and behavior overrides, but not its fallback chain.
  • From template: pre-fills a provider template's non-secret settings. API keys are left blank for you to fill in.

The custom-preset editor covers the color swatch, an inline rename, the display title, the env block, the behavior controls, the custom-flags override, and the fallback chain. Duplicate and delete live in the same header. Because creating or duplicating a preset selects it, the newly created preset becomes the agent-level default until you switch the scope back to Default.

Provider Templates

Claude Code ships six templates: Anthropic (native), Z.AI, OpenRouter, DeepSeek, Ollama (local), and Custom (OpenAI-compatible). Each pre-fills the base URL, model aliases, and timeout it needs: the Z.AI template, for instance, sets ANTHROPIC_BASE_URL, the three model aliases, and a long API_TIMEOUT_MS. No template carries a credential.

A Z.AI template preset. The name is still Z.AI; only the display title reads GLM · Reconciliation. The behavior tri-states, custom flags and fallback chain sit below the fold.

Fallback Chains

A preset's fallbacks list names up to three other presets for the same agent, tried in order when the primary preset's provider goes down. The chain is read from the original preset, so an agent that has already fallen through one entry advances to the next rather than restarting the chain.

Daintree classifies the tail of the agent's output when the process exits. Only two classes trigger a fallback:

ClassExamplesFalls back?
Connection5xx, ECONNREFUSED, DNS failure, timeout, "Unable to connect to API"Yes
Auth401/403, "Not logged in", invalid or revoked tokenYes
Rate limit429, quota exceeded, RESOURCE_EXHAUSTEDNo
User error"Prompt is too long", context window exceeded, request too largeNo
CleanExit code 0, or nothing matchedNo

Rate limits and prompt errors are yours or the provider's to fix, and silently rerouting to a different model would hide them. A terminal you killed yourself never triggers a fallback either.

When a chain is exhausted, or when an entry no longer resolves to a real preset, you get one error notification with a link into agent settings rather than an endless respawn loop. A panel running on a fallback carries a marker on its tab, and that marker is captured in the session bookmark so a resumed session still tells you which preset it is actually on. See Agent States for the rest of the terminal's state vocabulary.

Self-references, duplicates, and IDs that match no known preset are stripped when the chain loads, and the list is truncated to three. A misconfigured entry does not break the launcher: it just never fires.

Shared Project Presets

Daintree reads shared presets from .daintree/presets/{agentId}/{preset-id}.json in the active project's root: one JSON file per preset, with the same shape as above. Only id and name are required. The directory name must be a valid agent ID; anything else is skipped. See The .daintree Directory for the rest of what lives there.

Because these files are committed, a teammate's git pull is all it takes to hand the whole team a working provider route. Daintree re-reads the directory every 30 seconds (paused while the window is hidden, refreshed the moment it comes back), so new presets surface without an app restart.

One agent directory per agent ID, one file per preset:

.daintree/presets/
├── claude/
│   ├── zai-glm.json
│   └── haiku-cheap.json
├── codex/
│   └── gpt-high-effort.json
└── gemini/
    └── flash-review.json

Two files claiming the same id in the same agent directory would resolve differently on different machines, since directory order is not stable, so Daintree keeps the first one it reads and logs a warning naming the file to rename. Malformed JSON is skipped with a structural summary rather than the parsed object, because a preset's env can hold secrets and logs are not the place for them.

Note
Project presets carry a Project Shared badge and are read-only in the UI. Use Duplicate as custom to fork one into an editable copy, or edit the JSON file directly.

What Gets Sanitized

Presets can arrive from a repository you did not write, so every field is validated before it reaches a launch. Rejected values are dropped rather than reported, which is worth knowing when a preset appears to do nothing:

FieldRule
idMust match [a-zA-Z0-9_.-] and be at most 100 characters, or the whole preset is discarded.
nameNon-empty after trimming, at most 200 characters, no angle brackets, or the whole preset is discarded.
env keysPATH, LD_PRELOAD, LD_LIBRARY_PATH, DYLD_LIBRARY_PATH and prototype keys are dropped.
env valuesStrings only. Dropped if they contain $(, a backtick, ; or |, or exceed 10,000 characters.
args, customFlagsEntries containing shell metacharacters, or longer than 10,000 characters, are dropped.
colorMust be a hex color; anything else is ignored.
fallbacksSelf-references, duplicates, and unknown IDs stripped; capped at three.