Skip to content

Configuration: models, providers, and themes

Mayfly's configuration lives on two layers: in-app slash commands (the everyday surface) and dsh's file system (settings.yaml, .credentials.yaml, profile patches — the persistent source of truth). The in-app commands write to those same files; the two layers never fight. This page walks both.

What you wantIn-appWhere it lands
API key/provider add, the Providers panel's edit form~/.dsh/.credentials.yaml
Display languagethe first locale group in /settingslocale.preference in settings.yaml (omit to follow the system)
Default model / thinking effort/model, /effort, Alt+Mthe agent-default-model: section of settings.yaml
New provider / custom gateway/provider addthe llm-pi-ai: section of settings.yaml + the credentials file
DeepSeek official endpoint tuning— (files only)the llm-deepseek: section of settings.yaml
Theme/theme (session-level); /settings or the file for the persisted defaultthe mayfly: section of settings.yaml (Theming)
Mayfly preferences (update check, fold defaults, …)/settingsthe mayfly: section of settings.yaml
Plugin rows / compositionthe profile's cordis.patch.yml (Profiles & directories)

Minimal setup: one DEEPSEEK_API_KEY

The out-of-the-box assembly talks to the DeepSeek official API (provider route deepseek-official, endpoint https://api.deepseek.com, default model deepseek-v4-flash, thinking on at effort high). So getting from zero to your first reply takes exactly one key:

sh
export DEEPSEEK_API_KEY=sk-...
dsh --profile mayfly

To skip the export every time, write the key into ~/.dsh/.credentials.yaml — the whole document is a plain reference → value mapping and nothing else:

yaml
DEEPSEEK_API_KEY: sk-...

One key can sit in four places, in priority order:

PrioritySourceUse it for
1Process environment (DEEPSEEK_API_KEY=… dsh …)Per-run override: CI secrets, a temporary key
2~/.dsh/.credentials.yamlThe normal home; keys stored by /provider add land here
3./.env in the launch directoryProject-level
4~/.dsh/.envUser-level fallback

First-run setup is built in

Once the session is ready, Mayfly checks the credentials for every registered provider. If none has a usable key, it opens a DeepSeek quick-setup form. Enter only DEEPSEEK_API_KEY; Mayfly uses the deepseek-official route at https://api.deepseek.com. Press Esc to skip setup for this run and use /provider add for another provider. Mayfly prompts again on the next launch while no usable key exists.

Keys still resolve per request, so adding or rotating one while Mayfly is running needs no restart. MISSING_CREDENTIAL remains the fallback when a credential is removed or becomes unavailable.

~/.dsh is the Harness home; DSH_HOME relocates it (full directory table in Profiles & directories).

In-app configuration (the everyday surface)

Model and thinking effort

  • /model — no argument opens the model picker; with an id it switches directly. The panel's Set as default action persists the selection.
  • Alt+M — cycles models without opening the panel.
  • Use for this session (panel action) — switch the next step immediately without persisting a new default.
  • /effort (alias /thinking) — switch the current model's thinking effort; default restores the provider default.

/model persistence lands in the agent-default-model: section of settings.yaml (shape below).

Providers: list, switch, add

/provider                  # open the Providers panel (configured routes + Add)
/provider list             # list providers and the current route
/provider switch <name>    # switch routes
/provider add              # the add-provider wizard

Selecting a configured route in the Providers panel opens its edit form: display name, baseURL, and key (empty keeps the stored value); Ctrl+D deletes the route after a typed confirmation. The built-in deepseek-official route has no stored profile to edit (the panel says "nothing to edit") — tune it through the llm-deepseek: section of settings.yaml.

/provider add branches two ways:

  • Known provider (anthropic, openai, …) — pick a vendor from the host's configurable directory and enter only the key. Base URL is not editable; the host catalog's vendor endpoint is always used.
  • Custom endpoint (self-hosted gateways, any OpenAI-compatible surface) — declare protocol and address:
    • one of three protocols: anthropic-messages / openai-completions / openai-responses;
    • baseURL conventions: the anthropic protocol takes no trailing /v1 (the client appends /v1/messages itself); the openai protocols need the /v1;
    • the wizard interrogates the endpoint live for its model list (GET /models) for you to pick from, then enriches context windows and efforts from the models.dev catalog — whatever it cannot describe, it asks you once (both fields skippable with Enter);
    • the key goes into the credentials file under a ref derived from the route id: my-gatewayMY_GATEWAY_API_KEY.

After the add completes, the new route's model picker opens; cancelling it keeps the provider (visible via /provider list).

The file system: settings.yaml and credentials

Beyond the in-app commands, all of dsh's configuration sits in a handful of files under the Harness home, and external edits hot-apply (the watcher is on by default):

FileContents
~/.dsh/settings.yamlEvery plugin's settings section (one document, all namespaces)
~/.dsh/.credentials.yamlCredentials (enforced mode 0600 under a 0700 directory)
~/.dsh/.envUser-level environment layer
~/.dsh/profiles/<name>/cordis.patch.ymlThe profile's composition overlay (see Profiles & directories)

A document that exists but fails to parse fails boot (loud); an invalid edit while running keeps the last good snapshot and warns. Hand edits to settings.yaml keep their comments (writes diff at the leaf level).

locale: display language

Mayfly supports English and Simplified Chinese. With no explicit preference it follows LC_ALL, LC_MESSAGES, LANG, then Intl; every zh-* variant uses Simplified Chinese and unsupported locales fall back to English. The first /settings group cycles Follow system, 中文, and English, applying immediately while preserving the open panel's selection and form draft.

For a manual override, write the preference below; remove it to follow the system again:

yaml
locale:
  preference: zh  # zh | en; omit to follow the system

The three core settings.yaml sections

yaml
# Default model — /model and /effort write here
agent-default-model:
  provider: deepseek-official
  model: deepseek-v4-flash
  reasoningEffort: high      # optional

# DeepSeek official endpoint (the llm-deepseek adapter) — every field optional
llm-deepseek:
  apiKeyEnv: DEEPSEEK_API_KEY    # credential ref (this is the default)
  baseURL: https://api.deepseek.com
  thinking: enabled              # enabled | disabled (disabled locks effort to off)
  reasoningEffort: high          # off | high | max
  maxTokens: 256000              # per-request output cap
  defaultContextWindow: 1000000  # fallback when a model declares no capacity
  models:                        # omit for the built-in V4 Flash / V4 Pro pair
    - id: deepseek-v4-flash
      name: DeepSeek-V4-Flash
      contextWindow: 1000000

# Custom provider routes (the pi-ai adapter) — /provider add writes here
llm-pi-ai:
  providers:
    my-gateway:
      displayName: Company gateway
      api: openai-completions          # anthropic-messages | openai-completions | openai-responses
      baseURL: https://gw.example.com/v1
      apiKeyEnv: MY_GATEWAY_API_KEY    # credential ref — the key in .credentials.yaml
      models:
        - id: glm-5.3
          contextWindow: 1000000       # capacity the catalog/endpoint did not declare
          maxTokens: 131072
          reasoningEfforts:            # offered efforts → their wire spellings
            low: low
            high: high
            max: max

Notes:

  • llm-deepseek: and llm-pi-ai: are two independent adapters: the former owns the deepseek-official route to the official API; the latter registers arbitrary routes from its providers: dict (the route name is the dict key, lowercase kebab-case). They coexist fine.
  • A user settings section overrides the composition baseline (the bundle's defaults) field by field; omitted fields keep the baseline value.
  • pi-ai routes take further fields: modelOverrides: (reshape one catalog model without replacing the list), compat: (reasoning-parameter format switches), defaultContextWindow: / defaultMaxTokens: (route-wide fallbacks), and more — the complete list lives in the upstream config catalog.
  • List fields (like models:) replace wholesale, they never merge entry by entry.

mayfly: Mayfly's own settings section

The /settings panel writes the fields below. Every key is optional; defaults are shown:

yaml
mayfly:
  updateCheck: true        # the boot update check (false is the offline switch)
  updateChannel: rc        # the dist-tag the update check follows
  theme: dark              # persisted default theme: dark | light | ocean | paper | auto (applied at startup)
  collapseThinking: true   # thinking blocks start collapsed
  collapseToolCalls: true  # tool output starts collapsed (ctrl+o toggles in the session)
  windowTurns: 15          # transcript window: only the newest N completed turns stay mounted
  recentStepsRetention: 30 # in-turn step folding: keep the newest N steps' cards expanded
  expandTurns: 3           # ctrl+o expansion scope (turns counted from the end)
  userFoldLines: 10        # long user message fold threshold (lines)
  userFoldChars: 1000      # long user message fold threshold (chars)
  editorCommand: ''        # external editor command (empty = auto-detect via $VISUAL/$EDITOR)
  pasteImageBackend: auto  # Linux clipboard backend: auto | wayland | x11

The panel is two-level: level one starts with locale and groups rows by namespace (host sections like shell:, agent-loop:, and web-search-deepseek: included), Enter steps into level two's per-key rows, and Enter/Space there steps the preset value with every change landing on disk; language and mayfly.theme changes both apply live (/theme stays the session-level switch — see Theming), and folding-default and transcript-number changes apply to the running session just as immediately (an active Ctrl-O expansion still dominates). Level one's last row opens the whole settings.yaml in $EDITOR.

Verifying your edits

sh
dsh --profile mayfly --dump-config        # print the fully assembled plugin tree

settings.yaml effects show up right in the UI: the /model panel lists each route's models, /status shows the current route and model.

Themes

/theme dark|light|ocean|paper|auto switches instantly; /theme custom <path> mounts a custom JSON palette — hot switches never lose your draft. /theme is the session-level switch; the persisted default theme is set through the /settings panel or mayfly.theme in settings.yaml (applied at startup). The full semantic token table and the custom file format are in Theming.

Other configuration surfaces

  • Permissions & sandbox — permission presets (workspace-write / danger-full-access), approval policies, see Modes & permissions; in-session Shift+Tab cycles normal → plan → yolo through native /plan and /permission commands.
  • Agent presets/preset switches tool surface and persona across upstream shipped standard / minimal / ptc / cordis and Mayfly's mayfly-cordis (blank sessions only). Mayfly does not provide the old code alias.
  • Skills — user-level skills live under ~/.dsh/skills/, see Skills.
  • MCP — wiring MCP servers is covered in MCP setup.

Environment variable quick reference

VariablePurposeDefault behavior
DEEPSEEK_API_KEYDeepSeek official API key (the default apiKeyEnv ref of llm-deepseek)Missing → first request fails with MISSING_CREDENTIAL
DEEPSEEK_BASE_URLDeepSeek official endpoint fallbackhttps://api.deepseek.com
DSH_HOMEHarness home directory~/.dsh
DSH_PERMISSION_MODEProcess-level permission fallback: read-only / workspace-write / danger-full-access (the last also skips approvals)workspace-write
DSH_TELEMETRY_DISABLEDAny non-empty value (including '0' / 'false') hard-disables session telemetryTelemetry is off by default (DISABLED)
DSH_MAYFLY_ATTACHMENT_DIRMayfly attachment storage$DSH_HOME/attachments/
DSH_AGENTS_HOMEShared agent config root (the ~/.agents layer of skill discovery)~/.agents

What cannot live in .env

DEEPSEEK_API_KEY resolves from all four layers (.env included), but DEEPSEEK_BASE_URL and every DSH_*-prefixed variable are bootstrap variables — a .env file entry is rejected outright (with an "export it instead" notice); set them in the launching environment. Credential environment variables always beat the file layers — to swap a key for one run: DEEPSEEK_API_KEY=sk-… dsh --profile mayfly.

Preview · v0.1.0-alpha.1