> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Configure the Deep Agents CLI with config.toml, hooks, and MCP servers

The CLI stores its configuration in the `~/.deepagents/` directory. The main config files are:

| File          | Format | Purpose                                                                                                            |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------ |
| `config.toml` | TOML   | Model defaults, provider settings, constructor params, profile overrides, themes, update settings, MCP trust store |
| `.env`        | Dotenv | Global API keys and secrets                                                                                        |
| `hooks.json`  | JSON   | External tool subscriptions to CLI lifecycle events                                                                |
| `.mcp.json`   | JSON   | Global MCP server definitions                                                                                      |

<Note>
  Files under `~/.deepagents/.state/` hold per-machine CLI state and are managed automatically by the CLI. The `.state/` directory was introduced in `deepagents-cli>=0.0.49`.
</Note>

## Provider credentials

For every provider, the CLI checks two credential sources in order:

1. **Stored keys** — entered via `/auth` and persisted locally.
2. **Environment variables** — `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, etc., plus the same names with a [`DEEPAGENTS_CLI_`](#deepagents_cli_-prefix) prefix and any keys loaded from [`.env` files](#environment-variables).

A stored key always wins over an env-var key for the same provider.

### Use `/auth` (recommended)

Open the credential manager from any session:

```txt theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
/auth
```

The manager lists every LLM provider the CLI knows about, marks the ones that already have a key, and lets you paste, rotate, or clear credentials inline. Each provider renders as one of:

| Label                      | Meaning                                                                     |
| -------------------------- | --------------------------------------------------------------------------- |
| `✓ credentials set`        | A stored or env-var key is available                                        |
| `! missing OPENAI_API_KEY` | The named env var is unset and no key is stored — pick the row to paste one |
| `local provider`           | No credentials needed (e.g., local Ollama)                                  |
| `implicit auth`            | Credentials are implicit (e.g., Vertex AI Application Default Credentials)  |
| `custom auth`              | A `class_path` provider managing its own auth (mTLS, JWT, custom headers)   |

Press Enter on a row to paste a key, or use the row's delete affordance to clear an existing one. Saved keys are written atomically and persist across sessions and `/reload`.

<Note>
  Keys are scoped to your user account on this machine — the CLI never transmits them anywhere except to the configured provider's API.
</Note>

`/auth` only manages **LLM provider** credentials. Tool credentials such as `TAVILY_API_KEY` (web search) and `LANGSMITH_API_KEY` (tracing) are read from the environment instead — [set them in `~/.deepagents/.env` or your shell](#environment-variables).

### Environment variables (CI and headless)

For non-interactive runs, CI/CD pipelines, or anywhere a TUI isn't available, export the provider's env var or write it to `~/.deepagents/.env`:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
```

See [Environment variables](#environment-variables) below for loading order, the `DEEPAGENTS_CLI_` prefix, and per-project overrides.

## Environment variables

The CLI loads environment variables from dotenv files so you don't need to `export` API keys in your shell profile or duplicate `.env` files across projects.

### Loading order and precedence

Two `.env` files are loaded at startup:

1. **Project `.env`** — the `.env` file in your current working directory (if present)
2. **Global `~/.deepagents/.env`** — a single shared file that acts as a fallback for all projects

The effective precedence is: **shell environment > project `.env` > global `.env`**. Values already set in the shell are never overwritten—including on `/reload`.

### `DEEPAGENTS_CLI_` prefix

All CLI-specific environment variables use a `DEEPAGENTS_CLI_` prefix (e.g., `DEEPAGENTS_CLI_AUTO_UPDATE`, `DEEPAGENTS_CLI_DEBUG`). See the [CLI environment variable reference](#cli-environment-variable-reference) for the full list.

The prefix also works as an override mechanism for any environment variable the CLI reads, including third-party credentials. The CLI checks `DEEPAGENTS_CLI_{NAME}` first, then falls back to `{NAME}`:

```bash title="~/.deepagents/.env" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
# Override OPENAI_API_KEY only for the CLI, without affecting other tools
DEEPAGENTS_CLI_OPENAI_API_KEY=sk-cli-only

# Block a shell-exported key within the CLI by setting the prefixed var to empty
DEEPAGENTS_CLI_ANTHROPIC_API_KEY=
```

On `/reload`, the CLI re-reads `.env` files and picks up prefixed values, so you can rotate keys without restarting.

### Example

Store API keys once in `~/.deepagents/.env`:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
LANGSMITH_API_KEY=lsv2_...
TAVILY_API_KEY=tvly-...

# If OPENAI_API_KEY is already exported in your shell for other tools,
# use the prefix to give the CLI its own key without conflict
DEEPAGENTS_CLI_OPENAI_API_KEY=sk-cli-only-...
```

Then override per-project where needed by placing a `.env` in the project directory.

### Enable web search with Tavily

The built-in `web_search` tool uses [Tavily](https://tavily.com). The CLI shows a "Web search disabled — `TAVILY_API_KEY` is not set" notification on startup until you provide a key. Unlike model provider keys, Tavily is **not** managed by `/auth`; it is read directly from the environment.

<Steps>
  <Step title="Get a key">
    Sign up at [tavily.com](https://tavily.com) and copy the key (it starts with `tvly-`). The free tier is sufficient for most CLI usage.
  </Step>

  <Step title="Add it to your environment">
    Add the key to `~/.deepagents/.env` so every session picks it up:

    ```bash title="~/.deepagents/.env" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    TAVILY_API_KEY=tvly-...
    ```

    Or export it in your shell for a one-off session:

    ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    export TAVILY_API_KEY=tvly-...
    ```

    Shell exports take precedence over `.env` values (see [Loading order and precedence](#loading-order-and-precedence)). To scope a key to the CLI only without affecting other tools that read `TAVILY_API_KEY`, use the [`DEEPAGENTS_CLI_` prefix](#deepagents_cli_-prefix): `DEEPAGENTS_CLI_TAVILY_API_KEY=tvly-...`.
  </Step>

  <Step title="Reload or restart">
    In an existing session, run `/reload` to re-read `.env` files. On the next launch, the "Web search disabled" notification goes away and the agent can call `web_search`.
  </Step>
</Steps>

***

## Config file

`~/.deepagents/config.toml` lets you customize model providers, set defaults, and pass extra parameters to model constructors.

### Default and recent model

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models]
default = "ollama:qwen3:4b"             # your intentional long-term preference
recent = "google_genai:gemini-3.1-pro-preview"   # last /model switch (written automatically)
```

`[models].default` always takes priority over `[models].recent`. The `/model` command only writes to `[models].recent`, so your configured default is never overwritten by mid-session switches. To remove the default, use `/model --default --clear` or delete the `default` key from the config file.

### Default and recent agent

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[agents]
default = "backend-dev"  # your intentional long-term preference (Ctrl+S in /agents picker)
recent = "frontend-dev"  # last /agents switch (written automatically)
```

`[agents].default` always takes priority over `[agents].recent`. Selecting an agent in the `/agents` picker with `Enter` writes to `recent`; pressing `Ctrl+S` on the highlighted row pins it as `default`. Pressing `Ctrl+S` again on the same row clears the default.

Explicit `-a`/`--agent` always overrides both, and `-r`/`--resume` bypasses both so the thread's original agent is restored. See [Command reference](/oss/javascript/deepagents/cli/overview#command-reference) for related flags.

### Provider configuration

Each provider is a TOML table under `[models.providers]`:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.<name>]
models = ["gpt-4o"]
api_key_env = "OPENAI_API_KEY"
base_url = "https://api.openai.com/v1"
class_path = "my_package.models:MyChatModel"
enabled = true

[models.providers.<name>.params]
temperature = 0
max_tokens = 4096

[models.providers.<name>.params."gpt-4o"]
temperature = 0.7
```

Providers have the following configuration options:

<ResponseField name="models" type="string[]" post={["optional"]}>
  A list of model names to show in the interactive `/model` switcher for this provider. For providers that already ship with model profiles, any names you add here appear alongside the bundled ones, useful for newly released models that haven't been added to the package yet. For [arbitrary providers](#arbitrary-providers), this list is the only source of models in the switcher.

  Models listed here **bypass** the profile-based [filtering criteria](/oss/javascript/deepagents/cli/providers#which-models-appear-in-the-switcher) and always appear in the switcher. This makes it the recommended way to surface models that are excluded because their profile lacks `tool_calling` support or doesn't exist yet.

  This key is optional. You can always pass any model name directly to `/model` or `--model` regardless of whether it appears in the switcher; the provider validates the name at request time.
</ResponseField>

<ResponseField name="api_key_env" type="string" post={["optional"]}>
  The **name** of the environment variable that holds the API key (e.g., `"OPENAI_API_KEY"`), not the key itself. The CLI reads the credential from this env var at startup to verify access before creating the model. Most chat model packages read from a default env var automatically. See the [Provider reference](/oss/javascript/deepagents/cli/providers#provider-reference) table for which variable each provider checks.
</ResponseField>

<ResponseField name="base_url" type="string" post={["optional"]}>
  Override the base URL used by the provider, if supported. Refer to your provider packages' [reference docs](https://reference.langchain.com/python/integrations/) for more info.
</ResponseField>

<ResponseField name="params" type="object" post={["optional"]}>
  Extra keyword arguments forwarded to the model constructor. Flat keys (e.g., `temperature = 0`) apply to every model from this provider. Model-keyed sub-tables (e.g., `[params."gpt-4o"]`) override individual values for that model only; the merge is shallow (model wins on conflict).

  Do not put credentials (e.g., `api_key`) in `params`. Use [`api_key_env`](#provider-configuration) to point at an environment variable instead.
</ResponseField>

<ResponseField name="profile" type="object" post={["optional"]}>
  (Advanced) Override fields in the model's runtime [profile](/oss/javascript/langchain/models#model-profiles) (e.g., `max_input_tokens`). Flat keys apply to every model from this provider. Model-keyed sub-tables (e.g., `[profile."claude-sonnet-4-5"]`) override individual values for that model only; the merge is shallow (model wins on conflict). These overrides are applied after the model is created, so they take effect for context-limit display, auto-summarization, and any other feature that reads the profile.
</ResponseField>

<ResponseField name="class_path" type="string" post={["optional"]}>
  Used for [arbitrary model](#arbitrary-providers) providers. Fully-qualified Python class in `module.path:ClassName` format. When set, the CLI imports and instantiates this class directly for provider `<name>`. The class must be a `BaseChatModel` subclass.
</ResponseField>

<ResponseField name="enabled" type="boolean" default="true" post={["optional"]}>
  Whether this provider appears in the `/model` selector. Set to `false` to hide a provider that was auto-discovered from an installed package (e.g., a transitive dependency you don't want cluttering the switcher). You can still use a disabled provider directly via `/model provider:model` or `--model`.
</ResponseField>

### Model constructor params

Any provider can use the `params` table to pass extra arguments to the model constructor:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.ollama.params]
temperature = 0
num_ctx = 8192
```

#### Per-model overrides

If a specific model needs different params, add a model-keyed sub-table under `params` to override individual values without duplicating the entire provider config:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.ollama]
models = ["qwen3:4b", "llama3"]

[models.providers.ollama.params]
temperature = 0
num_ctx = 8192

[models.providers.ollama.params."qwen3:4b"]
temperature = 0.5
num_ctx = 4000
```

With this configuration:

* `ollama:qwen3:4b` gets `{temperature: 0.5, num_ctx: 4000}` — model overrides win.
* `ollama:llama3` gets `{temperature: 0, num_ctx: 8192}` — no override, provider-level params only.

The merge is shallow: any key present in the model sub-table replaces the same key from the provider-level params, while keys only at the provider level are preserved.

<Tip>
  For one-off adjustments without editing `config.toml`, pass a JSON object via `--model-params` at launch or mid-session with `/model`. CLI flags take highest priority over the config file. See [Model parameters](/oss/javascript/deepagents/cli/providers#model-parameters) on the providers page for syntax and provider-specific examples.
</Tip>

### Profile overrides (Advanced)

Override fields in the model's runtime profile to change how the CLI interprets model capabilities. The most common use case is lowering `max_input_tokens` to trigger auto-summarization earlier — useful for testing or for constraining context usage:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
# Apply to all models from this provider
[models.providers.anthropic.profile]
max_input_tokens = 4096
```

Per-model sub-tables work the same way as `params` — the model-level value wins on conflict:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.anthropic.profile]
max_input_tokens = 4096

# This model gets a higher limit
[models.providers.anthropic.profile."claude-sonnet-4-5"]
max_input_tokens = 8192
```

Profile overrides are merged into the model's profile after creation. Any feature that reads the profile — context-limit display in the status bar, auto-summarization thresholds, capability checks — will see the overridden values.

#### CLI profile overrides with `--profile-override` (Advanced)

To override model profile fields at runtime without editing the config file, pass a JSON object via `--profile-override`:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
deepagents --profile-override '{"max_input_tokens": 4096}'

# Combine with --model
deepagents --model google_genai:gemini-3.1-pro-preview --profile-override '{"max_input_tokens": 4096}'

# In non-interactive mode
deepagents -n "Summarize this repo" --profile-override '{"max_input_tokens": 4096}'
```

These are merged on top of config file profile overrides (CLI wins). The priority chain is: model default \< config.toml profile \< CLI `--profile-override`.

`--profile-override` values persist across mid-session `/model` hot-swaps — switching models re-applies the override to the new model.

### Custom base URL

Some provider packages accept a `base_url` to override the default endpoint. For example, `langchain-ollama` defaults to `http://localhost:11434` via the underlying `ollama` client. To point it elsewhere, set `base_url` in your configuration:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.ollama]
base_url = "http://your-host-here:port"
```

Refer to your provider's reference documentation for compatibility information and additional considerations.

### Compatible APIs

For providers that expose APIs that are wire-compatible with OpenAI or Anthropic, you can use the existing `langchain-openai` or `langchain-anthropic` packages by pointing `base_url` at the provider's endpoint:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.openai]
base_url = "https://api.example.com/v1"
api_key_env = "EXAMPLE_API_KEY"
models = ["my-model"]
```

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.anthropic]
base_url = "https://api.example.com"
api_key_env = "EXAMPLE_API_KEY"
models = ["my-model"]
```

<Note>
  Any features added on top of the official spec by the provider will not be captured. If the provider offers a dedicated LangChain integration package, prefer that instead.
</Note>

<Warning>
  The OpenAI provider defaults to the [Responses API](https://platform.openai.com/docs/api-reference/responses), which most OpenAI-compatible gateways do not implement. If your provider only supports the Chat Completions API, invocation will likely fail. Disable the Responses API explicitly:

  ```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  [models.providers.openai.params]
  use_responses_api = false
  ```
</Warning>

### Adding models to the interactive switcher

Some providers (e.g. `langchain-ollama`) don't bundle model profile data (see [Provider reference](/oss/javascript/deepagents/cli/providers#provider-reference) for full listing). When this is the case, the interactive `/model` switcher won't list models for that provider. You can fill in the gap by defining a `models` list in your config file for the provider:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.ollama]
models = ["llama3", "mistral", "codellama"]
```

The `/model` switcher will now include an Ollama section with these models listed.

This is entirely optional. You can always switch to any model by specifying its full name directly:

```txt theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
/model ollama:llama3
```

<Note>
  When `langchain-ollama` is installed and the daemon is reachable, the CLI auto-discovers locally pulled models and merges them into the switcher—no `models` list required. Run `/reload` to refresh after pulling new models, or set `DEEPAGENTS_CLI_OLLAMA_DISCOVERY=0` to opt out.
</Note>

### Arbitrary providers

You can use any [LangChain `BaseChatModel`](https://reference.langchain.com/python/langchain_core/language_models/#langchain_core.language_models.BaseChatModel) subclass using `class_path`. The CLI imports and instantiates the class directly — no built-in provider package required.

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.my_custom]
class_path = "my_package.models:MyChatModel"
api_key_env = "MY_API_KEY"
base_url = "https://my-endpoint.example.com"

[models.providers.my_custom.params]
temperature = 0
max_tokens = 4096
```

`api_key_env` and `base_url` are optional. `class_path` providers are expected to handle their own authentication internally — useful when your model uses custom auth (JWT tokens, proprietary headers, mTLS, etc.) rather than a standard API key:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[models.providers.xyz]
class_path = "abc.integrations.deepagents:DeepAgentsXYZChat"
models = ["abc-xyz-1"]

[models.providers.xyz.params]
bypass_auth = true
temperature = 0
```

With this config, switch to the model with `/model xyz:abc-xyz-1` or `--model xyz:abc-xyz-1`.

<Note>
  The Deep Agents CLI requires **tool calling** support. If your custom model supports tool calling but the CLI doesn't know about it, declare it in the provider profile:

  ```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  [models.providers.xyz.profile]
  tool_calling = true
  max_input_tokens = 128000
  ```

  Set `max_input_tokens` to what your model supports to enable accurate context length tracking and auto-summarization.
</Note>

The provider package must be installed in the same Python environment as `deepagents-cli`:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
# If deepagents-cli was installed with uv tool:
uv tool install deepagents-cli --with my_package
```

When you switch to `my_custom:my-model-v1` (via `/model` or `--model`), the model name (`my-model-v1`) is passed as the `model` kwarg:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
MyChatModel(model="my-model-v1", base_url="...", api_key="...", temperature=0, max_tokens=4096)
```

<Warning>
  `class_path` executes arbitrary Python code from your config file. This has the same trust model as `pyproject.toml` build scripts — you control your own machine.
</Warning>

Your provider package may optionally provide model profiles at a `_PROFILES` dict in `<package>.data._profiles` in lieu of defining them under the `models` key. See LangChain [model profiles](https://github.com/langchain-ai/langchain/tree/master/libs/model-profiles) for more info.

***

## Skills extra allowed directories

By default, when the CLI loads skills it validates that a resolved skill file path stays inside one of the standard [skill directories](/oss/javascript/deepagents/data-locations#skills). This prevents symlinks inside skill directories from reading arbitrary files outside those roots.

If you store shared skill assets in a non-standard location and use symlinks from a standard skill directory to reference them, you can add that location to the containment allowlist. This does **not** add a new skill discovery location: skills are still only discovered from the standard directories.

<ResponseField name="extra_allowed_dirs" type="string[]" post={["optional"]}>
  Paths added to the skill containment allowlist. Supports `~` expansion.

  ```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  [skills]
  extra_allowed_dirs = [
      "~/shared-skills",
      "/opt/team-skills",
  ]
  ```
</ResponseField>

Alternatively, set the `DEEPAGENTS_CLI_EXTRA_SKILLS_DIRS` environment variable as a colon-separated list:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export DEEPAGENTS_CLI_EXTRA_SKILLS_DIRS="~/shared-skills:/opt/team-skills"
```

When the environment variable is set, it takes precedence over the config file value. Changes take effect on `/reload`.

***

## Themes

Use `/theme` to open an interactive theme selector. Navigate the list to preview themes in real-time, press `Enter` to persist your choice to `config.toml`.

The CLI ships with many built-in themes. The default theme is `langchain`, a dark theme with LangChain-branded colors. The selected theme is persisted under `[ui]`:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[ui]
theme = "langchain-dark"
```

### User-defined themes

Define custom themes under `[themes.<name>]` sections in `config.toml`. Each section requires `label` (str). `dark` (bool) defaults to `false` if omitted — set to `true` for dark themes. All color fields are optional — omitted fields fall back to the built-in dark or light palette based on the `dark` flag.

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[themes.my-solarized]
label = "My Solarized"
dark = true
primary = "#268BD2"
warning = "#B58900"

# Theme names with spaces require TOML quoting
[themes."ocean breeze"]
label = "Ocean Breeze"
primary = "#0077B6"
background = "#CAF0F8"
```

User-defined themes appear alongside built-in themes in the `/theme` selector.

### Override built-in theme colors

To tweak a built-in theme's colors without creating a new theme, use a `[themes.<builtin-name>]` section. Only color fields are read — `label` and `dark` are inherited from the built-in:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[themes.langchain]
primary = "#FF5500"
```

Omitted color fields retain the existing built-in values.

Changes to `[themes.*]` sections take effect on `/reload`.

### Map themes to terminals

If you switch between terminals with different color schemes (for example, a dark iTerm and a light Apple Terminal), map each one to a theme under `[ui.terminal_themes]`. The CLI matches the shell's `TERM_PROGRAM` and applies the mapped theme automatically:

```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
[ui.terminal_themes]
"Apple_Terminal" = "langchain-light"
"iTerm.app" = "langchain"
```

Press `T` in the `/theme` picker to save the highlighted theme for the current terminal, or run `echo $TERM_PROGRAM` to find your terminal's identifier and add it by hand.

<Accordion title="Advanced: picker shortcuts, resolution order, terminal identifiers">
  #### Picker shortcuts

  In the `/theme` selector:

  * `N` toggles between display labels and canonical registry keys—the keys are what `[ui] theme` and `[ui.terminal_themes]` accept.
  * `T` saves the highlighted theme into `[ui.terminal_themes]` for the current `TERM_PROGRAM`. The mapped theme is badged `(default)` in the picker.

  #### Common `TERM_PROGRAM` values

  Keys are matched verbatim against the environment variable—quote them in TOML when they contain dots or special characters.

  | Terminal                    | `TERM_PROGRAM`   |
  | --------------------------- | ---------------- |
  | Apple Terminal              | `Apple_Terminal` |
  | iTerm2                      | `iTerm.app`      |
  | WezTerm                     | `WezTerm`        |
  | VS Code integrated terminal | `vscode`         |
  | Ghostty                     | `ghostty`        |

  #### Resolution order

  The CLI resolves a theme on every launch using this precedence:

  1. `DEEPAGENTS_CLI_THEME` environment variable (explicit override).
  2. `[ui.terminal_themes]` mapping for the current `TERM_PROGRAM`.
  3. `[ui] theme` saved preference (set by `/theme`).
  4. The built-in default (`langchain`).
</Accordion>

***

## Auto-update

The CLI can automatically check for and install updates.

<Tabs>
  <Tab title="Config file">
    ```toml theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    [update]
    auto_update = true
    ```
  </Tab>

  <Tab title="Environment variable">
    ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    export DEEPAGENTS_CLI_AUTO_UPDATE=1
    ```
  </Tab>
</Tabs>

The environment variable takes precedence over the config file.

When enabled, the CLI checks PyPI for a newer version at session start and automatically upgrades using the detected install method (uv, Homebrew, or pip). When disabled (default), the CLI shows an update hint with the appropriate install command instead.

You can also check for and install updates manually at any time with the `/update` slash command, which bypasses the cache and reports success or failure inline.

After an upgrade, the CLI shows a "what's new" banner on the next launch with a link to the changelog.

At session exit, if a newer version was detected during the session, an update banner is displayed as a reminder.

***

## Managed deployments

The [install script](https://github.com/langchain-ai/deepagents/blob/main/libs/cli/scripts/install.sh) supports running as root, targeting macOS MDM tools (Kandji, Jamf, etc.) that execute scripts in a minimal root environment.

When `id -u` is `0`, the script:

1. Resolves the real console user's `HOME` (via `/dev/console` or a `/Users` directory scan)
2. `chown`s all created files back to the target user after each install step

Non-root installs are unaffected: all root-specific code paths short-circuit when not running as root.

To pre-configure auto-update for managed installs, set `DEEPAGENTS_CLI_AUTO_UPDATE=1` in the user's shell profile or deploy a `config.toml` with `[update] auto_update = true` to `~/.deepagents/config.toml`. To suppress automatic updates and update checks entirely, set `DEEPAGENTS_CLI_NO_UPDATE_CHECK=1`.

***

## CLI environment variable reference

All CLI-specific environment variables use the `DEEPAGENTS_CLI_` prefix. See [`DEEPAGENTS_CLI_` prefix](#deepagents_cli_-prefix) for how the prefix also works as an override for third-party credentials.

<ResponseField name="DEEPAGENTS_CLI_AUTO_UPDATE" type="string" post={["optional"]}>
  Enable automatic CLI updates (`1`, `true`, or `yes`).
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_DEBUG" type="string" post={["optional"]}>
  Enable verbose debug logging to a file. Accepts `1`, `true`, `yes`, `on` (case-insensitive) as enabled; `0`, `false`, `no`, `off`, empty string, or unset disables it. When enabled, the per-session server log file is preserved on shutdown and its path is printed to stderr for triage.
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_DEBUG_FILE" type="string" default="/tmp/deepagents_debug.log" post={["optional"]}>
  Path for the debug log file.
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_EXTRA_SKILLS_DIRS" type="string" post={["optional"]}>
  Colon-separated paths added to the [skill containment allowlist](#skills-extra-allowed-directories).
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_LANGSMITH_PROJECT" type="string" post={["optional"]}>
  Override the LangSmith project name for agent traces. See [Trace with LangSmith](/oss/javascript/deepagents/cli/overview#trace-with-langsmith).
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_NO_UPDATE_CHECK" type="string" post={["optional"]}>
  Disable automatic update checking when set.
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_SHELL_ALLOW_LIST" type="string" post={["optional"]}>
  Comma-separated shell commands to allow (or `recommended` / `all`).
</ResponseField>

<ResponseField name="DEEPAGENTS_CLI_USER_ID" type="string" post={["optional"]}>
  Attach a user identifier to LangSmith trace metadata.
</ResponseField>

***

## External editor

Press `Ctrl+X` or type `/editor` to compose prompts in an external editor. The CLI checks `$VISUAL`, then `$EDITOR`, then falls back to `vi` (macOS/Linux) or `notepad` (Windows). GUI editors (VS Code, Cursor, Zed, Sublime Text, Windsurf) automatically receive a `--wait` flag so the CLI blocks until you close the file.

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
# Set in your shell profile (~/.zshrc, ~/.bashrc, etc.)
export VISUAL="code"    # GUI editor (--wait auto-injected)
export EDITOR="nvim"    # Terminal fallback
```

***

## Hooks

Hooks let external programs react to CLI lifecycle events. Configure commands in `~/.deepagents/hooks.json` and the CLI pipes a JSON payload to each matching command's stdin whenever an event fires.

Hooks run fire-and-forget in a background thread — they never block the CLI and failures are logged without interrupting your session.

### Setup

Create `~/.deepagents/hooks.json`:

```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  "hooks": [
    {
      "command": ["bash", "-c", "cat >> ~/deepagents-events.log"],
      "events": ["session.start", "session.end"]
    }
  ]
}
```

Now every time a session starts or ends, the CLI appends the event payload to `~/deepagents-events.log`.

### Hook configuration

The config file contains a single `hooks` array. Each entry has:

<ResponseField name="command" type="list[str]" required>
  Command and arguments to run. No shell expansion: use `["bash", "-c", "..."]` if needed.
</ResponseField>

<ResponseField name="events" type="list[str]" post={["optional"]}>
  Event names to subscribe to. Omit or leave empty to receive **all** events.
</ResponseField>

```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  "hooks": [
    {
      "command": ["python3", "my_handler.py"],
      "events": ["session.start", "task.complete"]
    },
    {
      "command": ["bash", "log_everything.sh"]
    }
  ]
}
```

The second hook above has no `events` filter, so it receives every event the CLI emits.

### Payload format

Each hook command receives a JSON object on stdin with an `"event"` key plus event-specific fields:

```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  "event": "session.start",
  "thread_id": "abc123"
}
```

### Events reference

#### `session.start`

Fired when an agent session begins (both interactive and non-interactive modes).

<ResponseField name="thread_id" type="string" required>
  The session thread identifier.
</ResponseField>

#### `session.end`

Fired when a session exits.

<ResponseField name="thread_id" type="string" required>
  The session thread identifier.
</ResponseField>

#### `user.prompt`

Fired in interactive mode when the user submits a chat message.

No additional fields.

#### `input.required`

Fired when the agent requires human input (human-in-the-loop interrupt).

No additional fields.

#### `permission.request`

Fired before the approval dialog when one or more tool calls need user permission.

<ResponseField name="tool_names" type="list[str]" required>
  Names of the tools requesting approval.
</ResponseField>

#### `tool.error`

Fired when a tool call returns an error.

<ResponseField name="tool_names" type="list[str]" required>
  Names of the tool(s) that errored.
</ResponseField>

#### `task.complete`

Fired when the agent finishes its current task (the streaming loop ends without further interrupts).

<ResponseField name="thread_id" type="string" required>
  The session thread identifier.
</ResponseField>

#### `context.compact`

Fired before the CLI compacts (summarizes) the conversation context.

No additional fields.

### Execution model

* **Background thread**: Hook subprocesses run in a thread via `asyncio.to_thread` so the main event loop is never blocked.
* **Concurrent dispatch**: When multiple hooks match an event, they run concurrently in a thread pool.
* **5-second timeout**: Each command has a 5-second timeout. Commands that exceed this are killed.
* **Fire-and-forget**: Errors are caught per-hook and logged at debug/warning level. A failing hook never crashes or stalls the CLI.
* **Lazy loading**: The config file is read once on the first event dispatch and cached for the rest of the session.
* **No shell expansion**: Commands are executed directly (not through a shell). Wrap in `["bash", "-c", "..."]` if you need shell features like pipes or variable expansion.

### Hook examples

<Accordion title="Log all events to a file">
  ```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "hooks": [
      {
        "command": ["bash", "-c", "jq -c . >> ~/.deepagents/hook-events.jsonl"],
        "events": []
      }
    ]
  }
  ```
</Accordion>

<Accordion title="Desktop notification on task completion (macOS)">
  ```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "hooks": [
      {
        "command": [
          "bash", "-c",
          "osascript -e 'display notification \"Agent finished\" with title \"Deep Agents\"'"
        ],
        "events": ["task.complete"]
      }
    ]
  }
  ```
</Accordion>

<Accordion title="Python handler">
  Write a handler script that reads the JSON payload from stdin:

  ```python title="my_handler.py" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import json
  import sys

  payload = json.load(sys.stdin)
  event = payload["event"]

  if event == "session.start":
      print(f"Session started: {payload['thread_id']}", file=sys.stderr)
  elif event == "permission.request":
      print(f"Approval needed for: {payload['tool_names']}", file=sys.stderr)
  ```

  ```json title="~/.deepagents/hooks.json" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "hooks": [
      {
        "command": ["python3", "my_handler.py"],
        "events": ["session.start", "permission.request"]
      }
    ]
  }
  ```
</Accordion>

### Security considerations

Hooks follow the same trust model as Git hooks or shell aliases — any user who can write to `~/.deepagents/hooks.json` can execute arbitrary commands. This is by design:

* **No command injection**: Payload data flows only to stdin as JSON, never to command-line arguments. `json.dumps` handles escaping.
* **No shell by default**: Commands run with `shell=False`, preventing shell injection.
* **Malformed config**: Invalid JSON or unexpected types produce logged warnings, not security issues.

<Warning>
  Only add hooks from sources you trust. A hook has the same permissions as your user account.
</Warning>

***

<div className="source-links">
  <Callout icon="terminal-2">
    [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
  </Callout>

  <Callout icon="edit">
    [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/deepagents/cli/configuration.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>
</div>
