> ## Documentation Index
> Fetch the complete documentation index at: https://langchain-5e9cc07a-preview-opensw-1782332329-96d87c7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

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

Deep Agents Code 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 |
| `.env`        | Dotenv | Global API keys, secrets, and other environment variables                                         |
| `hooks.json`  | JSON   | External tool subscriptions to Deep Agents Code lifecycle events                                  |
| `.mcp.json`   | JSON   | Global MCP server definitions                                                                     |

<Note>
  Files under `~/.deepagents/.state/` hold per-machine Deep Agents Code state and are managed automatically.
</Note>

***

## Inspect configuration

The `dcode config` command group reports what configuration is in effect and where each value comes from, without starting a session. This is useful for confirming that an environment variable or `config.toml` setting is being picked up, and for sharing a redacted snapshot in a bug report.

| Command                          | Description                                                                                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `dcode config show`              | Resolve every option against the live environment and `config.toml`, printing the effective value and which source provided it                   |
| `dcode config list` (alias `ls`) | List every available option with its type, default, and where it can be set, without resolving values                                            |
| `dcode config get <key>`         | Show the effective value and source for a single option, e.g. `dcode config get interpreter.memory_limit_mb`                                     |
| `dcode config path`              | Show the on-disk config file locations (`config.toml`, project and global `.env`, `hooks.json`, and managed state files) and whether each exists |

Each option resolves from the first source that is set, in this order: a `DEEPAGENTS_CODE_`-prefixed env var, the canonical env var, `config.toml`, then the built-in default.

All four commands accept `--json` for machine-readable output.

<Warning>
  Provider credentials and other secrets are reported as configured / not configured only—their values are never printed by `config show` or `config get`, so the output is safe to paste into a bug report.
</Warning>

***

## Provider credentials

Deep Agents Code needs an API key for each model provider you use. The recommended way to add one is the [`/auth`](#use-%2Fauth-recommended) credential manager. For non-interactive runs, manage the same stored keys from the shell with [`dcode auth`](#manage-credentials-from-the-shell-dcode-auth) or set [environment variables](#environment-variables-ci-and-headless) instead.

If the same key is set in more than one place, see [Key resolution order](#key-resolution-order) for which one wins.

### Use `/auth` (recommended)

Open the credential manager from any session:

```txt theme={null}
/auth
```

The manager lists the LLM providers available in your installation, plus non-model services such as Tavily web search, and marks the ones that already have a key set. Select a provider to add or replace its key, or remove one you have already stored. Keys you save here persist across sessions.

<Accordion title="Provider row labels" icon="list-check">
  Each row shows the provider name followed by where its key comes from:

  | Label            | Meaning                                                                                                                             |
  | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
  | `[stored]`       | A key saved in this manager via `/auth`                                                                                             |
  | `[env: VARNAME]` | The key comes from environment variable `VARNAME` (the resolved name, such as `DEEPAGENTS_CODE_OPENAI_API_KEY` or `OPENAI_API_KEY`) |
  | `[missing]`      | No key is stored and the env var is unset; select the row to paste one                                                              |
</Accordion>

The `/auth` prompt also has an optional **base URL** field. Leave it blank to use the provider's default endpoint, or set a custom one to use with this key. The base URL is saved alongside the key. See [Endpoints, keys, and gateways](#endpoints-keys-and-gateways) for how endpoints resolve, including with gateways.

<Warning>
  A stored base URL is not a secret and may be logged; the key paired with it is never logged.
</Warning>

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

#### Sign in with ChatGPT

Selecting the `openai_codex` provider in `/auth` starts a browser sign-in instead of prompting for an API key, letting you use OpenAI models with a ChatGPT subscription. To re-authenticate or sign out, select `openai_codex` again. See [Sign in with ChatGPT (Codex models)](/oss/python/deepagents/code/providers) for the full flow.

`/auth` manages **LLM provider** credentials and the **Tavily web-search key**. A Tavily key entered here is stored alongside your provider keys and activates web search on the next launch — see [Enable web search with Tavily](#enable-web-search-with-tavily). Other tool credentials such as `LANGSMITH_API_KEY` (tracing) are read from the environment instead — [set them in `~/.deepagents/.env` or your shell](#environment-variables).

### Manage credentials from the shell (`dcode auth`)

The `dcode auth` command group is the scriptable equivalent of the `/auth` manager: it manages the same stored credentials without launching the TUI, which makes it usable for dotfile bootstrap, CI/CD, and setting a key on a remote box over SSH. The subcommands mirror the modal's verbs:

| Command                                                 | Description                                                   |
| ------------------------------------------------------- | ------------------------------------------------------------- |
| `dcode auth list` (alias `ls`)                          | List every known provider and where its key resolves from     |
| `dcode auth status <provider>`                          | Print the resolution source for one provider                  |
| `dcode auth set <provider>`                             | Store an API key, read from stdin by default                  |
| `dcode auth remove <provider>` (aliases `rm`, `delete`) | Delete a stored credential                                    |
| `dcode auth path`                                       | Print the resolved path to the credential store (`auth.json`) |

`set` reads the key from **stdin** by default, so it never lands in shell history or `argv`. Pipe the key in, or use `--from-env VAR` to copy it from a process environment variable:

```bash theme={null}
# Pipe the key in (stdin)
echo "$ANTHROPIC_API_KEY" | dcode auth set anthropic

# Copy it from an existing environment variable
dcode auth set openai --from-env OPENAI_API_KEY
```

<Note>
  `set` refuses to run in an interactive terminal so an accidental invocation cannot hang waiting on input — pipe the key via stdin or use `--from-env VAR`. Stored keys go through the same store as `/auth`, so warnings (for example, about file permissions on `auth.json`) are printed to stderr.
</Note>

Remove a stored key or print the store location:

```bash theme={null}
dcode auth remove anthropic
dcode auth path
```

<Note>
  `dcode auth set` manages API keys only. The `openai_codex` provider uses a ChatGPT browser sign-in rather than an API key, so run [`/auth` and select `openai_codex`](#sign-in-with-chatgpt) to sign in instead. `dcode auth remove openai_codex` does sign you out.
</Note>

### 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 in your shell:

```bash theme={null}
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."

# Prefix with DEEPAGENTS_CODE_ to scope a key to Deep Agents Code only,
# leaving a shared key used by other CI steps untouched
export DEEPAGENTS_CODE_OPENAI_API_KEY="sk-..."
```

To keep keys in a file instead, define them in a [`.env` file](#environment-variables).

### Key resolution order

When a provider's key is set in more than one place, Deep Agents Code uses the first of these that is set:

1. **`DEEPAGENTS_CODE_`-prefixed env var** — for example `DEEPAGENTS_CODE_OPENAI_API_KEY` as an inline shell export. The [`DEEPAGENTS_CODE_` prefix](#deepagents_code_-prefix) is the explicit "use this key in Deep Agents Code" override.
2. **App-stored key** — entered in the `/auth` credential manager.
3. **Plain provider env var** — for example `OPENAI_API_KEY`, from your shell or `.env` files.

An app-stored key wins over a plain env-var key for the same provider, but a `DEEPAGENTS_CODE_`-prefixed key wins over an app-stored key. The prefix is the way to override an already-stored key for a single run, without clearing it:

```bash theme={null}
# With a key already stored via /auth, a plain env var does not override it.
# dcode still uses the app-stored key for this run:
OPENAI_API_KEY=sk-xxxx dcode -n "..."

# The DEEPAGENTS_CODE_ prefix does override it, for this run only:
DEEPAGENTS_CODE_OPENAI_API_KEY=sk-xxxx dcode -n "..."
```

This layering exists for the common case where your machine already exports a plain provider variable for some other purpose — a shared `OPENAI_API_KEY` used by other tools, scripts, or CI — that you do not want Deep Agents Code to reuse. An app-stored key or a `DEEPAGENTS_CODE_`-prefixed variable gives Deep Agents Code its own value while leaving the unprefixed one untouched for everything else, so the two never mix.

Each provider's API key and its endpoint (`base_url`) resolve as a pair from the same source. See [Endpoints, keys, and gateways](#endpoints-keys-and-gateways).

### Enable web search with Tavily

The built-in `web_search` tool uses [Tavily](https://tavily.com). Deep Agents Code shows a "Web search disabled" notification on startup until you provide a key. You can store the key in the [`/auth`](#use-%2Fauth-recommended) credential manager, where Tavily appears as a non-model service, or set the `TAVILY_API_KEY` environment variable.

<Tabs>
  <Tab title="Use /auth (recommended)">
    Get a key from [tavily.com](https://tavily.com) (it starts with `tvly-`; the free tier is sufficient for most Deep Agents Code usage), then store it in the credential manager:

    ```txt theme={null}
    /auth
    ```

    Select **Tavily** from the list and paste the key. You can also reach this prompt directly from the "Web search disabled" notification by choosing **Enter API key**.
  </Tab>

  <Tab title="Set an environment variable">
    <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 Deep Agents Code 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={null}
        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 Deep Agents Code only without affecting other tools that read `TAVILY_API_KEY`, use the [`DEEPAGENTS_CODE_` prefix](#deepagents_code_-prefix): `DEEPAGENTS_CODE_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>
  </Tab>
</Tabs>

***

## Environment variables

In addition to shell exports, Deep Agents Code reads environment variables from dotenv files, so you can keep API keys out of your shell profile and avoid duplicating `.env` files across projects.

```bash title="~/.deepagents/.env" theme={null}
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
```

### Loading order and precedence

At startup, Deep Agents Code reads the nearest project `.env`, found by searching the directory you launch from and walking up through its parents (the first `.env` found wins), then `~/.deepagents/.env` as a global fallback for all projects. A project `.env` wins over the global one, and neither overrides a value already set in your shell. Running `/reload` re-reads both `.env` files so you can change keys without restarting, with shell values still taking precedence. This applies to every variable Deep Agents Code reads (for example, `TAVILY_API_KEY` or the `DEEPAGENTS_CODE_*` settings). Provider API keys have additional resolution rules; see [Provider credentials](#provider-credentials).

### `DEEPAGENTS_CODE_` prefix

All Deep Agents Code-specific environment variables use a `DEEPAGENTS_CODE_` prefix (e.g., `DEEPAGENTS_CODE_AUTO_UPDATE`, `DEEPAGENTS_CODE_DEBUG`). See the [environment variable reference](#environment-variable-reference) for the full list.

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

```bash title="~/.deepagents/.env" theme={null}
# Give Deep Agents Code its own value, without affecting other tools
DEEPAGENTS_CODE_OPENAI_API_KEY=sk-cli-only

# Or set it empty so Deep Agents Code ignores a key exported in your shell
DEEPAGENTS_CODE_ANTHROPIC_API_KEY=
```

***

## Config file

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

* **Defaults**: pin a [default model](#default-and-recent-model) or [agent](#default-and-recent-agent).
* **Provider setup**: the [`[models.providers.<name>]` table](#provider-configuration), [constructor params](#model-constructor-params), [retries](#retries), [profile overrides](#profile-overrides-advanced), and [adding models to the `/model` switcher](#adding-models-to-the-interactive-switcher).
* **Custom endpoints and providers**: [custom base URLs](#custom-base-url), [OpenAI- or Anthropic-compatible APIs](#compatible-apis), and [arbitrary providers](#arbitrary-providers).
* **Endpoints and gateways**: how [API keys and base URLs resolve together](#endpoints-keys-and-gateways), including through a managed gateway.

### Default and recent model

```toml theme={null}
[models]
default = "ollama:qwen3:4b"             # your intentional long-term preference
recent = "google_genai:gemini-3.5-flash"   # 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={null}
[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/python/deepagents/code/overview#command-reference) for related flags.

### Provider configuration

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

```toml theme={null}
[models.providers.<name>]
display_name = "My Provider"
api_key_url = "https://provider.example/keys"
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 the provider defined as `<name>`. For providers that already ship with model profiles, any names you add here appear in addition to 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** any applied profile-based [filtering criteria](/oss/python/deepagents/code/providers#which-models-appear-in-the-switcher), always appearing 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"`). Deep Agents Code 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/python/deepagents/code/providers#provider-reference) table for which variable name each built-in provider checks. For a provider not in that table, set `api_key_env` to its variable name (see [Arbitrary providers](#arbitrary-providers)).
</ResponseField>

<ResponseField name="display_name" type="string" post={["optional"]}>
  Human-readable provider name shown in auth UI. Use this for arbitrary providers whose config key is optimized for machines (for example, `my_gateway`) but whose UI label should include spaces or brand capitalization.
</ResponseField>

<ResponseField name="api_key_url" type="string" post={["optional"]}>
  URL for the provider page where users create or manage API keys. The `/auth` modal links to this page before the API-key input. This value is a URL, not a credential.
</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.

  See [Compatible APIs](#compatible-apis) for pointing a built-in provider at a wire-compatible endpoint, or [Arbitrary providers](#arbitrary-providers) for one configured via `class_path`.
</ResponseField>

<ResponseField name="base_url_env" type="string" post={["optional"]}>
  Name of the environment variable that holds this provider's base URL, parallel to `api_key_env`. Reach for this instead of `base_url` when the endpoint comes from the environment rather than a fixed value — for example a gateway URL that differs by machine or CI job — so it can change without editing `config.toml` and can take part in endpoint resolution and key/endpoint pairing (see [Endpoints, keys, and gateways](#endpoints-keys-and-gateways)). It also extends those to providers outside the [built-in set](/oss/python/deepagents/code/providers#provider-reference); see [Arbitrary providers](#arbitrary-providers).

  If both are set, the static `base_url` wins:

  ```toml theme={null}
  [models.providers.example]
  base_url = "https://fixed.example/v1"   # used
  base_url_env = "EXAMPLE_BASE_URL"        # ignored while base_url is set
  ```
</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/python/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. See [Profile overrides](#profile-overrides-advanced) for examples and the `--profile-override` flag.
</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, Deep Agents Code 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 model switcher). You can still use a disabled provider directly via `/model provider:model` or `--model`.
</ResponseField>

### Model constructor params

The [`params` field](#provider-configuration) forwards extra arguments to the model constructor. To give one model different values, add a model-keyed sub-table so you do not have to duplicate the whole provider config:

```toml theme={null}
[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/python/deepagents/code/providers#model-parameters) on the providers page for syntax and provider-specific examples.
</Tip>

### Retries

Configure retry counts for transient model provider errors with the top-level `[retries]` section. Deep Agents Code passes these values through to provider integrations that accept retry-count constructor kwargs. If you omit this section, the provider SDK default applies.

```toml theme={null}
[retries]
max_retries = 2

[retries.fireworks]
max_retries = 3

[retries.anthropic]
max_retries = 0
```

The global `[retries].max_retries` value applies to all supported providers. A provider-specific table, such as `[retries.fireworks]`, overrides the global value for that provider. Values must be integers greater than or equal to `0`.

Most supported providers receive the retry count as `max_retries`. Some integrations use a different constructor kwarg. For an arbitrary provider, or to override the registered kwarg for a known provider, set `param` in the provider-specific retries table:

```toml theme={null}
[retries]
max_retries = 2

[retries.my_custom]
param = "retries"
max_retries = 4
```

`param` must be a valid Python identifier string, such as `"max_retries"` or `"retries"`. Deep Agents Code ignores unknown providers that do not set `param`, because passing the wrong retry kwarg can break model creation.

`[retries]` is lower precedence than constructor parameters. The complete precedence order is:

1. `--max-retries N`, applied under the provider's resolved retry kwarg
2. `--model-params` with the provider's retry kwarg, such as `'{"max_retries": N}'` or `'{"retries": N}'`
3. `[models.providers.<provider>.params]` with the provider's retry kwarg
4. `[retries.<provider>].max_retries`
5. `[retries].max_retries`
6. Provider SDK default

### Profile overrides (Advanced)

Override fields in the model's runtime profile to change how Deep Agents Code interprets model capabilities. See [`ModelProfile`](https://reference.langchain.com/python/langchain-core/language_models/model_profile/ModelProfile) for the full list of overridable fields. 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={null}
# 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={null}
[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.

<Accordion title="CLI profile overrides with --profile-override" icon="terminal">
  To override model profile fields at runtime without editing the config file, pass a JSON object via `--profile-override`:

  ```bash theme={null}
  dcode --profile-override '{"max_input_tokens": 4096}'

  # Combine with --model
  dcode --model google_genai:gemini-3.5-flash --profile-override '{"max_input_tokens": 4096}'

  # In non-interactive mode
  dcode -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.
</Accordion>

### Adding models to the interactive switcher

Some providers (e.g. `langchain-ollama`) don't bundle model profile data (see [Provider reference](/oss/python/deepagents/code/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={null}
[models.providers.ollama]
models = ["gemma4", "qwen3.6", "granite4.1:3b"]
```

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={null}
/model ollama:qwen3.6:27b
```

<Note>
  When `langchain-ollama` is installed and the daemon is reachable, Deep Agents Code 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_CODE_OLLAMA_DISCOVERY=0` to opt out.
</Note>

### 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={null}
[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={null}
[models.providers.openai]
base_url = "https://api.example.com/v1"
api_key_env = "EXAMPLE_API_KEY"
models = ["my-model"]
```

```toml theme={null}
[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={null}
  [models.providers.openai.params]
  use_responses_api = false
  ```
</Warning>

### Arbitrary providers

Deep Agents Code works with any tool calling LLM available as a [LangChain `BaseChatModel`](https://reference.langchain.com/python/langchain_core/language_models/#langchain_core.language_models.BaseChatModel). The [built-in providers](/oss/python/deepagents/code/providers#provider-reference) work out of the box; a less common or in-house model takes a little more setup. Point `class_path` at its `BaseChatModel` subclass and Deep Agents Code imports and instantiates the class directly.

```toml theme={null}
[models.providers.my_custom]
display_name = "My Custom Provider"
api_key_url = "https://my-provider.example.com/keys"
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. `display_name` and `api_key_url` customize the provider name and key-acquisition link shown by `/auth`; omit them to fall back to the provider config key and provider setup docs. To read the endpoint from an environment variable instead of hardcoding `base_url`, use [`base_url_env`](#provider-configuration); it then resolves and pairs with the key the same way as for the built-in providers (see [Endpoints, keys, and gateways](#endpoints-keys-and-gateways)).

`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={null}
[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>
  Deep Agents Code requires **tool calling** support. If your custom model supports tool calling but Deep Agents Code doesn't know about it, declare it in the provider profile:

  ```toml theme={null}
  [models.providers.xyz.profile]
  tool_calling = true
  max_input_tokens = 128000
  ```

  Although optional, setting `max_input_tokens` to your model's context window is strongly encouraged. Without it, Deep Agents Code cannot show how full the context is, and auto-summarization falls back to a fixed trigger (around 170,000 tokens) instead of a fraction of your model's window. For a model with a smaller window, summarization may not run before you reach the model's hard limit, so requests start failing once the conversation grows.
</Note>

Because Deep Agents Code imports the `class_path` class at startup, the package that defines it must be importable from the same environment that runs `dcode`. Built-in providers ship as [install extras](/oss/python/deepagents/code/providers#quickstart), but a custom or in-house package is not one. Install it into the `dcode` environment with the `--package` flag:

```bash theme={null}
dcode --install my_package --package
```

In a session, run `/install my_package --package --force`. Both install the package alongside `dcode`. If the package is missing or cannot be imported, Deep Agents Code skips the provider and its models do not appear in `/model`.

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={null}
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.

### Endpoints, keys, and gateways

An API key and the endpoint it is sent to have to match: the endpoint has to accept that key, or the request will likely fail. Deep Agents Code resolves the key and endpoint together, so overriding one updates the other to match. For example, if you replace a gateway-provisioned key with your own, Deep Agents Code also drops the gateway endpoint, so your key goes to the provider directly instead of to a gateway that would reject it.

#### How `base_url` resolves

Deep Agents Code resolves a provider's endpoint in this order (first match wins):

1. **`base_url` in `config.toml`** for the provider.
2. **The `DEEPAGENTS_CODE_`-prefixed endpoint variable.**
3. **The plain endpoint variable** in the environment (for example, `OPENAI_BASE_URL`).
4. **The endpoint saved with a `/auth` credential.** This step applies the saved endpoint for a provider that has no endpoint variable—such as a provider you add without declaring [`base_url_env`](#provider-configuration). Steps 2-3 have no variable to read for these, so the saved endpoint is used directly here. For a provider that does have an endpoint variable, the saved endpoint already took effect at step 2 or 3 (it is written to that variable), so this step changes nothing. Either way, an endpoint entered in `/auth` applies.
5. **The provider SDK's own default endpoint**, when none of the above is set.

<Note>
  Resolved endpoints are delivered to the model as the `base_url` constructor argument.
</Note>

As with API keys, the [`DEEPAGENTS_CODE_` prefix](#deepagents_code_-prefix) scopes the endpoint to Deep Agents Code without affecting other tools. For any other provider, declare the name with [`base_url_env`](#provider-configuration) and the endpoint resolves and pairs the same way:

```toml theme={null}
[models.providers.myprovider]
api_key_env = "MYPROVIDER_API_KEY"
base_url_env = "MYPROVIDER_BASE_URL"
models = ["my-model"]
```

A literal `base_url` wins over `base_url_env`, so set only the one you need:

```toml theme={null}
[models.providers.myprovider]
base_url = "https://fixed.example/v1"   # used
base_url_env = "MYPROVIDER_BASE_URL"    # ignored while base_url is set
```

#### Overrides keep the pair together

When you store a key with `/auth`, the endpoint you enter (or the provider's default, if left blank) is applied together with the key. Storing a key with a blank base URL also clears any endpoint already set in your environment (for example, a gateway `OPENAI_BASE_URL` your shell exports), so your key goes to the provider's default endpoint instead of to that gateway.

```bash title="Scope both the key and the endpoint to Deep Agents Code" theme={null}
DEEPAGENTS_CODE_OPENAI_API_KEY=sk-cli-only
DEEPAGENTS_CODE_OPENAI_BASE_URL=https://api.openai.com/v1
```

#### Managed gateways

On a machine provisioned with a model gateway (for example, the LangSmith gateway), the gateway typically exports a gateway key and the matching endpoint variable (`OPENAI_BASE_URL`, `ANTHROPIC_BASE_URL`, or `GOOGLE_GEMINI_BASE_URL`) together. Deep Agents Code uses that pair by default, so no configuration is needed.

To use your own key instead, store it with `/auth` (leave the base URL blank for the provider default, or set it explicitly), or set the `DEEPAGENTS_CODE_` prefixed key and endpoint. Both override the gateway pair without leaving a mismatched endpoint behind.

***

## Skill directory allowlist

By default, when Deep Agents Code loads skills it validates that a resolved skill file path stays inside one of the standard [skill directories](/oss/python/deepagents/code/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={null}
  [skills]
  extra_allowed_dirs = [
      "~/shared-skills",
      "/opt/team-skills",
  ]
  ```
</ResponseField>

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

```bash theme={null}
export DEEPAGENTS_CODE_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`.

Deep Agents Code 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={null}
[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={null}
[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={null}
[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]`. Deep Agents Code matches the shell's `TERM_PROGRAM` and applies the mapped theme automatically:

```toml theme={null}
[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

  Deep Agents Code resolves a theme on every launch using this precedence:

  1. `DEEPAGENTS_CODE_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

Deep Agents Code automatically checks for and installs updates by default.

To opt out of automatic updates:

<Tabs>
  <Tab title="Config file">
    ```toml theme={null}
    [update]
    auto_update = false
    ```
  </Tab>

  <Tab title="Environment variable">
    ```bash theme={null}
    export DEEPAGENTS_CODE_AUTO_UPDATE=0
    ```
  </Tab>
</Tabs>

The environment variable takes precedence over the config file.

When enabled (default), Deep Agents Code checks PyPI for a newer version at session start and automatically upgrades. When disabled, Deep Agents Code shows an update hint with the appropriate install command instead.

To suppress automatic update checks entirely:

<Tabs>
  <Tab title="Config file">
    ```toml theme={null}
    [update]
    check = false
    ```
  </Tab>

  <Tab title="Environment variable">
    ```bash theme={null}
    export DEEPAGENTS_CODE_NO_UPDATE_CHECK=1
    ```
  </Tab>
</Tabs>

Disabling update checks also prevents automatic update installs at startup.

You can still check for and install updates manually at any time with the `/update` slash command, which runs an on-demand check and reports success or failure inline.

After an upgrade, Deep Agents Code 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.

***

## Uninstall

To remove the `dcode` and `deepagents-code` binaries and the isolated tool environment, run:

```bash theme={null}
uv tool uninstall deepagents-code
```

The uninstall command does not remove user configuration or session data. Deep Agents Code stores those files under `~/.deepagents/`, including `config.toml`, `hooks.json`, the global `.env`, and `.state/` contents such as saved sessions and credentials. To delete that data as well, run:

```bash theme={null}
rm -rf ~/.deepagents
```

***

## Managed deployments

The [install script](https://github.com/langchain-ai/deepagents/blob/main/libs/code/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.

### Pin the install with environment variables

The install script reads environment variables that let you pin a version, select extras, and choose a Python version fleet-wide. Set them on the same line as the piped install:

```bash theme={null}
# Pin an exact version for reproducible installs across the fleet
curl -LsSf https://langch.in/dcode | DEEPAGENTS_CODE_VERSION="0.1.16" bash
```

<ResponseField name="DEEPAGENTS_CODE_VERSION" type="string" post={["optional"]}>
  Exact package version to install, e.g. `0.1.0` (or a pre-release such as `0.1.0rc1`). Mutually exclusive with `DEEPAGENTS_CODE_PRERELEASE` — setting both is an error, since an exact pin already selects a single version.
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_PRERELEASE" type="string" post={["optional"]}>
  uv pre-release strategy applied when resolving the latest version: `disallow`, `allow`, `if-necessary`, `explicit`, or `if-necessary-or-explicit`. Mutually exclusive with `DEEPAGENTS_CODE_VERSION`.
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_EXTRAS" type="string" post={["optional"]}>
  Comma-separated pip extras to install, e.g. `ollama`, `ollama,groq`, or `daytona`. See [`pyproject.toml`](https://github.com/langchain-ai/deepagents/blob/main/libs/code/pyproject.toml) for the available extras.
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_PYTHON" type="string" default="3.13" post={["optional"]}>
  Python version to use for the install.
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_SKIP_OPTIONAL" type="string" post={["optional"]}>
  Set to `1` to skip optional tool checks.
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_VERBOSE" type="string" post={["optional"]}>
  Set to `1` to show uv's raw stderr (timing lines, unfiltered package diff) and the quiet-by-default status lines (optional-tool checks, post-install footer). Useful when debugging an install.
</ResponseField>

<ResponseField name="UV_BIN" type="string" post={["optional"]}>
  Path to the uv binary. Auto-detected if unset.
</ResponseField>

Auto-update is enabled by default for managed installs. To opt out, set `DEEPAGENTS_CODE_AUTO_UPDATE=0` in the user's shell profile or deploy a `config.toml` with `[update] auto_update = false` to `~/.deepagents/config.toml`. To suppress automatic updates and update checks entirely, set `DEEPAGENTS_CODE_NO_UPDATE_CHECK=1` or deploy `[update] check = false`.

To route every user's model traffic through a managed gateway (provisioning a gateway key and base URL fleet-wide), see [Managed gateways](#managed-gateways).

***

## Environment variable reference

All Deep Agents Code-specific environment variables use the `DEEPAGENTS_CODE_` prefix. See [`DEEPAGENTS_CODE_` prefix](#deepagents_code_-prefix) for how the prefix also works as an override for third-party credentials.

<ResponseField name="DEEPAGENTS_CODE_AUTO_UPDATE" type="string" post={["optional"]}>
  Toggle automatic Deep Agents Code updates. Enabled by default; set to `0`, `false`, `no`, or `off` to opt out.
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_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_CODE_DEBUG_FILE" type="string" default="/tmp/deepagents_debug.log" post={["optional"]}>
  Path for the debug log file.
</ResponseField>

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

<ResponseField name="DEEPAGENTS_CODE_LANGSMITH_PROJECT" type="string" post={["optional"]}>
  Override the LangSmith project name for Deep Agents Code's own agent traces. Shell commands still run with the user's original `LANGSMITH_PROJECT`, so app, test, or script traces can appear in a separate project. See [Trace with LangSmith](/oss/python/deepagents/code/overview#trace-with-langsmith).
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_LANGSMITH_REPLICA_PROJECTS" type="string" post={["optional"]}>
  A second LangSmith project to *also* write agent traces to. When set and tracing is active, each agent run is dual-written to the primary project (from `DEEPAGENTS_CODE_LANGSMITH_PROJECT`, or `deepagents-code` by default) and this project. Off by default. See [Dual-write traces to a second project](/oss/python/deepagents/code/overview#trace-with-langsmith).
</ResponseField>

<ResponseField name="DEEPAGENTS_CODE_NO_UPDATE_CHECK" type="string" post={["optional"]}>
  Disable automatic update checking when set. This also prevents automatic update installs at startup.
</ResponseField>

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

<ResponseField name="DEEPAGENTS_CODE_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. Deep Agents Code 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 Deep Agents Code blocks until you close the file.

```bash theme={null}
# 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 Deep Agents Code lifecycle events. Configure commands in `~/.deepagents/hooks.json` and it 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 Deep Agents Code and failures are logged without interrupting your session.

### Setup

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

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

Now every time a session starts or ends, Deep Agents Code 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={null}
{
  "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 Deep Agents Code emits.

### Payload format

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

```json theme={null}
{
  "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 Deep Agents Code 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 Deep Agents Code.
* **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={null}
  {
    "hooks": [
      {
        "command": ["bash", "-c", "jq -c . >> ~/.deepagents/hook-events.jsonl"],
        "events": []
      }
    ]
  }
  ```
</Accordion>

<Accordion title="Desktop notification on task completion (macOS)">
  ```json theme={null}
  {
    "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={null}
  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={null}
  {
    "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/code/configuration.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>
</div>
