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

# Advanced Configuration

Use these options when you need more control over providers, policies, and integrations. For a quick start, see [Config basics](/config-file/config-basic).

For background on project guidance, reusable capabilities, custom slash commands, subagent workflows, and integrations, see [Customization](/customization/overview). For configuration keys, see [Configuration Reference](/config-file/config-reference).

## Profiles

Profiles let you save named configuration layers and switch between them from
the CLI. When you pass `--profile profile-name`, ZeroCode loads
`~/.zerotwo/config.toml`, then overlays `~/.zerotwo/profile-name.config.toml`.
Profile names can contain letters, numbers, hyphens, and underscores.

Create a separate TOML file for each profile. Use top-level config keys in the
profile file; don't nest them under `[profiles.profile-name]`.

```toml theme={null}
# ~/.zerotwo/deep-review.config.toml
model = "gpt-5.5"
model_reasoning_effort = "xhigh"
approval_policy = "on-request"
model_catalog_json = "/Users/me/.zerotwo/model-catalogs/deep-review.json"
```

```shell theme={null}

ZeroTwo desktop runs --profile deep-review "review this change"
```

Because the profile file is a layer above your base user config and below
project and CLI config, it only needs the values that differ from your base
config. Profile files can also override `model_catalog_json`; ZeroCode uses the
profile value when both files set it.

In ZeroCode 0.134.0 and later, `--profile` no longer reads `[profiles.profile-name]`
from `config.toml`, and the top-level `profile = "profile-name"` selector is no
longer supported. Move legacy profile settings into
`~/.zerotwo/profile-name.config.toml`, then remove the matching
`[profiles.profile-name]` table and `profile = "profile-name"` selector from
`config.toml`.

## One-off overrides from the CLI

In addition to editing `~/.zerotwo/config.toml`, you can override configuration for a single run from the CLI:

* Prefer dedicated flags when they exist (for example, `--model`).
* Use `-c` / `--config` when you need to override an arbitrary key.

Examples:

```shell theme={null}
# Dedicated flag


# Generic key/value override (value is TOML, not JSON)



```

Notes:

* Keys can use dot notation to set nested values (for example, `mcp_servers.context7.enabled=false`).
* `--config` values are parsed as TOML. When in doubt, quote the value so your shell doesn't split it on spaces.
* If the value can't be parsed as TOML, ZeroCode treats it as a string.

## Config and state locations

ZeroCode stores its local state under `ZEROTWO_HOME` (defaults to `~/.zerotwo`).

Common files you may see there:

* `config.toml` (your local configuration)
* `auth.json` (if you use file-based credential storage) or your OS keychain/keyring
* `history.jsonl` (if history persistence is enabled)
* Other per-user state such as logs and caches

For authentication details (including credential storage modes), see [Authentication](/quickstart). For the full list of configuration keys, see [Configuration Reference](/config-file/config-reference).

For shared defaults, rules, and skills checked into repos or system paths, see [Team Config](/configuration).

If you just need to point the built-in ZeroTwo provider at an LLM proxy, router, or data-residency enabled project, set `openai_base_url` in `config.toml` instead of defining a new provider. This changes the base URL for the built-in `openai` provider without requiring a separate `model_providers.<id>` entry.

```toml theme={null}
openai_base_url = "https://us.api.zerotwo.ai/v1"
```

## Project config files (`.zerotwo/config.toml`)

In addition to your user config, ZeroCode reads project-scoped overrides from `.zerotwo/config.toml` files inside your repo. ZeroCode walks from the project root to your current working directory and loads every `.zerotwo/config.toml` it finds. If multiple files define the same key, the closest file to your working directory wins.

For security, ZeroCode loads project-scoped config files only when the project is trusted. If the project is untrusted, ZeroCode ignores project `.zerotwo/` layers, including `.zerotwo/config.toml`, project-local hooks, and project-local rules. User and system layers remain separate and still load.

Relative paths inside a project config (for example, `model_instructions_file`) are resolved relative to the `.zerotwo/` folder that contains the `config.toml`.

Project config files can't override settings that redirect credentials, alter
host-owned app request metadata, change provider auth, select config profiles,
or run machine-local notification/telemetry commands. ZeroCode ignores the
following keys in project-local `.zerotwo/config.toml` and prints a startup
warning when it sees them: `openai_base_url`, `chatgpt_base_url`,
`apps_mcp_product_sku`, `model_provider`, `model_providers`, `notify`,
`profile`, `profiles`, `experimental_realtime_ws_base_url`, and `otel`. Set
provider, notification, and telemetry keys in your user-level
`~/.zerotwo/config.toml`; select config profiles with `--profile profile-name`
and `~/.zerotwo/profile-name.config.toml`.

## Hooks

ZeroCode can also load lifecycle hooks from either `hooks.json` files or inline
`[hooks]` tables in `config.toml` files that sit next to active config layers.

In practice, the four most useful locations are:

* `~/.zerotwo/hooks.json`
* `~/.zerotwo/config.toml`
* `<repo>/.zerotwo/hooks.json`
* `<repo>/.zerotwo/config.toml`

Project-local hooks load only when the project `.zerotwo/` layer is trusted.
User-level hooks remain independent of project trust.

Inline TOML hooks use the same event structure as `hooks.json`:

```toml theme={null}
[[hooks.PreToolUse]]
matcher = "^Bash$"

[[hooks.PreToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.zerotwo/hooks/pre_tool_use_policy.py"'
timeout = 30
statusMessage = "Checking Bash command"
```

If a single layer contains both `hooks.json` and inline `[hooks]`, ZeroCode loads
both and warns. Prefer one representation per layer.

For the current event list, input fields, output behavior, and limitations, see
[Hooks](/hooks).

## Agent roles (`[agents]` in `config.toml`)

For subagent role configuration (`[agents]` in `config.toml`), see [Subagents](/agent-configuration/subagents).

## Project root detection

ZeroCode discovers project configuration (for example, `.zerotwo/` layers and `AGENTS.md`) by walking up from the working directory until it reaches a project root.

By default, ZeroCode treats a directory containing `.git` as the project root. To customize this behavior, set `project_root_markers` in `config.toml`:

```toml theme={null}
# Treat a directory as the project root when it contains any of these markers.
project_root_markers = [".git", ".hg", ".sl"]
```

Set `project_root_markers = []` to skip searching parent directories and treat the current working directory as the project root.

## Custom model providers

A model provider defines how ZeroCode connects to a model (base URL, wire API, authentication, and optional HTTP headers). Custom providers can't reuse the reserved built-in provider IDs: `openai`, `ollama`, and `lmstudio`.

Define additional providers and point `model_provider` at them:

```toml theme={null}
model = "gpt-5.6-terra"
model_provider = "proxy"

[model_providers.proxy]
name = "ZeroTwo using LLM proxy"
base_url = "http://proxy.example.com"
env_key = "OPENAI_API_KEY"

[model_providers.local_ollama]
name = "Ollama"
base_url = "http://localhost:11434/v1"

[model_providers.mistral]
name = "Mistral"
base_url = "https://api.mistral.ai/v1"
env_key = "MISTRAL_API_KEY"
```

If a custom provider supports the standalone web search endpoint, advertise
that capability in its provider configuration:

```toml theme={null}
[model_providers.proxy]
name = "ZeroTwo using LLM proxy"
base_url = "https://proxy.example.com/v1"
env_key = "OPENAI_API_KEY"
supports_standalone_web_search = true
```

The setting defaults to `false` for custom providers. Standalone web search is
under development and off by default. Setting the provider capability to `true`
doesn't enable it: the provider must support a compatible endpoint,
and the selected model and runtime must support standalone search. The
configured [`web_search` mode](/web-search) and
managed search restrictions still apply.

Add request headers when needed:

```toml theme={null}
[model_providers.example]
http_headers = { "X-Example-Header" = "example-value" }
env_http_headers = { "X-Example-Features" = "EXAMPLE_FEATURES" }
```

Use command-backed authentication when a provider needs ZeroCode to fetch bearer tokens from an external credential helper:

```toml theme={null}
[model_providers.proxy]
name = "ZeroTwo using LLM proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"

[model_providers.proxy.auth]
command = "/usr/local/bin/fetch-zerocode-token"
args = ["--audience", "zerocode"]
timeout_ms = 5000
refresh_interval_ms = 300000
```

The auth command receives no `stdin` and must print the token to stdout. ZeroCode trims surrounding whitespace, treats an empty token as an error, and refreshes proactively at `refresh_interval_ms`; set `refresh_interval_ms = 0` to refresh only after an authentication retry. Don't combine `[model_providers.<id>.auth]` with `env_key`, `experimental_bearer_token`, or `requires_openai_auth`.

### Amazon Bedrock provider

ZeroCode includes a built-in `amazon-bedrock` model provider. Set it directly as
`model_provider`; unlike custom providers, this built-in provider supports only
the nested AWS profile and region overrides.

```toml theme={null}
model_provider = "amazon-bedrock"
model = "<bedrock-model-id>"

[model_providers.amazon-bedrock.aws]
profile = "default"
region = "eu-central-1"
```

If you omit `profile`, ZeroCode uses the standard AWS credential chain. Set
`region` to the supported Bedrock region that should handle requests.

For the full setup flow, authentication options, supported models, and feature
availability, see [Use ZeroTwo Work and ZeroCode with Amazon
Bedrock](/models).

## OSS mode (local providers)

ZeroCode can run against a local "open source" provider such as Ollama or LM
Studio when you pass `--oss`. Choose one for a single run with
`--local-provider`, or set `oss_provider` as the default. If neither is set, the
interactive CLI prompts you to choose; `ZeroTwo desktop runs` exits with an error.

```toml theme={null}
# Default local provider used with `--oss`
oss_provider = "ollama" # or "lmstudio"
```

## Azure provider and per-provider tuning

```toml theme={null}
[model_providers.azure]
name = "Azure"
base_url = "https://YOUR_PROJECT_NAME.openai.azure.com/openai"
env_key = "AZURE_OPENAI_API_KEY"
query_params = { api-version = "2025-04-01-preview" }
wire_api = "responses"
request_max_retries = 4
stream_max_retries = 10
stream_idle_timeout_ms = 300000
```

To change the base URL for the built-in ZeroTwo provider, use `openai_base_url`; don't create `[model_providers.openai]`, because you can't override built-in provider IDs.

## ZeroTwo customers using data residency

Projects created with [data residency](https://help.zerotwo.ai/en/articles/9903489-data-residency-and-inference-residency-for-chatgpt) enabled can create a model provider to update the base\_url with the [correct prefix](https://platform.zerotwo.ai/docs/guides/your-data#which-models-and-features-are-eligible-for-data-residency).

```toml theme={null}
model_provider = "openaidr"
[model_providers.openaidr]
name = "ZeroTwo Data Residency"
base_url = "https://us.api.zerotwo.ai/v1" # Replace 'us' with domain prefix
```

## Model reasoning, verbosity, and limits

```toml theme={null}
model_reasoning_summary = "none"          # Disable summaries
model_verbosity = "low"                   # Shorten responses
model_supports_reasoning_summaries = true # Force reasoning
model_context_window = 128000             # Context window size
```

`model_verbosity` applies only to providers using the Responses API. Chat Completions providers will ignore the setting.

## Approval policies and sandbox modes

Pick approval strictness (affects when ZeroCode pauses) and sandbox level (affects file/network access).

For operational details to keep in mind while editing `config.toml`, see [Common sandbox and approval combinations](/agent-approvals-security#common-sandbox-and-approval-combinations), [Protected paths in writable roots](/agent-approvals-security#protected-paths-in-writable-roots), and [Network access](/agent-approvals-security#network-access).

For beta permission profiles that configure filesystem and network access together, see [Permissions](/permissions).

You can also use a granular approval policy (`approval_policy = { granular = { ... } }`) to allow or auto-reject individual prompt categories. This is useful when you want normal interactive approvals for some cases but want others, such as `request_permissions` or skill-script prompts, to fail closed automatically.

Set `approvals_reviewer = "auto_review"` to route eligible interactive approval
requests through automatic review. This changes the reviewer, not the sandbox
boundary.

Use `[auto_review].policy` for local reviewer policy instructions. Managed
`guardian_policy_config` takes precedence.

```toml theme={null}
approval_policy = "untrusted"   # Other options: on-request, never, or { granular = { ... } }
approvals_reviewer = "user"     # Or "auto_review" for automatic review
sandbox_mode = "workspace-write"
allow_login_shell = false       # Optional hardening: disallow login shells for shell tools

# Example granular approval policy:
# approval_policy = { granular = {
#   sandbox_approval = true,
#   rules = true,
#   mcp_elicitations = true,
#   request_permissions = false,
#   skill_approval = false
# } }

[sandbox_workspace_write]
exclude_tmpdir_env_var = false  # Allow $TMPDIR
exclude_slash_tmp = false       # Allow /tmp
writable_roots = ["/Users/YOU/.pyenv/shims"]
network_access = false          # Opt in to outbound network

[auto_review]
policy = """
Use your organization's automatic review policy.
"""
```

### Named permission profiles

For built-in profiles, custom profile syntax, and the full filesystem and
network configuration model, see [Permissions](/permissions).

For the complete key list and requirements constraints, see
[Configuration Reference](/config-file/config-reference) and
[Managed configuration](/configuration).

In workspace-write mode, some environments keep `.git/` and `.zerotwo/`
read-only even when the rest of the workspace is writable. This is why
commands like `git commit` may still require approval to run outside the
sandbox. If you want ZeroCode to skip specific commands (for example, block `git
  commit` outside the sandbox), use
[rules](/agent-configuration/rules).

Disable sandboxing entirely (use only if your environment already isolates processes):

```toml theme={null}
sandbox_mode = "danger-full-access"
```

## Shell environment policy

`shell_environment_policy` controls which environment variables ZeroCode passes to
spawned commands. Start with an empty environment using `inherit = "none"`, or
inherit a trimmed set using `inherit = "core"`. Add explicit values and keyed
filters to avoid passing unnecessary secrets to spawned commands.

```toml theme={null}
[shell_environment_policy]
inherit = "core"
set = { MY_FLAG = "1" }
ignore_default_excludes = false

[shell_environment_policy.filters]
"AWS_*" = "exclude"
"AZURE_*" = "exclude"
```

Filter patterns are case-insensitive and support `*` and `?`. Use `"exclude"`
to remove matching variables. When any pattern uses `"include"`, ZeroCode keeps
only variables matching an include pattern. Includes don't restore variables
that were already excluded. Filter keys merge case-insensitively across
configuration layers.

`ignore_default_excludes` defaults to `true`, so ZeroCode doesn't automatically
remove variable names containing `KEY`, `SECRET`, or `TOKEN`. Set it to `false`
to apply those automatic exclusions before your explicit filters run.

ZeroCode applies automatic exclusions first, then custom exclusions, values from
`set`, and finally the include-pattern allowlist. Because `set` runs after
exclusions, it can restore an excluded variable. An include-pattern allowlist
can still remove that restored value.

The older `exclude` and `include_only` arrays remain supported for existing
configurations. Don't combine either array with
`[shell_environment_policy.filters]` in the same configuration layer; ZeroCode
rejects that combination.

## MCP servers

See the dedicated [MCP documentation](/extend/mcp) for configuration details.

## Observability and telemetry

Enable OpenTelemetry (OTel) log export to track ZeroCode runs (API requests, SSE/events, prompts, tool approvals/results). Disabled by default; opt in via `[otel]`:

```toml theme={null}
[otel]
environment = "staging"   # defaults to "dev"
exporter = "none"         # set to otlp-http or otlp-grpc to send events
log_user_prompt = false   # redact user prompts unless explicitly enabled
```

Choose an exporter:

```toml theme={null}
[otel]
exporter = { otlp-http = {
  endpoint = "https://otel.example.com/v1/logs",
  protocol = "binary",
  headers = { "x-otlp-api-key" = "${OTLP_TOKEN}" }
}}
```

```toml theme={null}
[otel]
exporter = { otlp-grpc = {
  endpoint = "https://otel.example.com:4317",
  headers = { "x-otlp-meta" = "abc123" }
}}
```

If `exporter = "none"` ZeroCode records events but sends nothing. Exporters batch asynchronously and flush on shutdown. Event metadata includes service name, CLI version, env tag, conversation id, model, sandbox/approval settings, and per-event fields (see [Config Reference](/config-file/config-reference)).

### What gets emitted

ZeroCode emits structured log events for runs and tool usage. Representative event types include:

* `zerocode.conversation_starts` (model, reasoning settings, sandbox/approval policy)
* `zerocode.api_request` (attempt, status/success, duration, and error details)
* `zerocode.sse_event` (stream event kind, success/failure, duration, plus token counts on `response.completed`)
* `zerocode.websocket_request` and `zerocode.websocket_event` (request duration plus per-message kind/success/error)
* `zerocode.user_prompt` (length; content redacted unless explicitly enabled)
* `zerocode.tool_decision` (approved/denied and whether the decision came from config vs user)
* `zerocode.tool_result` (duration, success, output snippet)

### OTel metrics emitted

When the OTel metrics pipeline is enabled, ZeroCode emits counters and duration histograms for API, stream, and tool activity.

Each metric below also includes default metadata tags: `auth_mode`, `originator`, `session_source`, `model`, and `app.version`.

| Metric                                   | Type      | Fields              | Description                                                       |
| ---------------------------------------- | --------- | ------------------- | ----------------------------------------------------------------- |
| `zerocode.api_request`                   | counter   | `status`, `success` | API request count by HTTP status and success/failure.             |
| `zerocode.api_request.duration_ms`       | histogram | `status`, `success` | API request duration in milliseconds.                             |
| `zerocode.sse_event`                     | counter   | `kind`, `success`   | SSE event count by event kind and success/failure.                |
| `zerocode.sse_event.duration_ms`         | histogram | `kind`, `success`   | SSE event processing duration in milliseconds.                    |
| `zerocode.websocket.request`             | counter   | `success`           | WebSocket request count by success/failure.                       |
| `zerocode.websocket.request.duration_ms` | histogram | `success`           | WebSocket request duration in milliseconds.                       |
| `zerocode.websocket.event`               | counter   | `kind`, `success`   | WebSocket message/event count by type and success/failure.        |
| `zerocode.websocket.event.duration_ms`   | histogram | `kind`, `success`   | WebSocket message/event processing duration in milliseconds.      |
| `zerocode.tool.call`                     | counter   | `tool`, `success`   | Tool invocation count by tool name and success/failure.           |
| `zerocode.tool.call.duration_ms`         | histogram | `tool`, `success`   | Tool execution duration in milliseconds by tool name and outcome. |

For more security and privacy guidance around telemetry, see [Security](/agent-approvals-security#monitoring-and-telemetry).

### Metrics

By default, ZeroCode periodically sends a small amount of anonymous usage and health data back to ZeroTwo. This helps detect when ZeroCode isn't working correctly and shows what features and configuration options are being used, so the ZeroCode team can focus on what matters most. These metrics don't contain any personally identifiable information (PII). Metrics collection is independent of OTel log/trace export.

If you want to disable metrics collection entirely across the ZeroTwo desktop app, and desktop app on a machine, set the analytics flag in your config:

```toml theme={null}
[analytics]
enabled = false
```

Each metric includes its own fields plus the default context fields below.

#### Default context fields (applies to every event/metric)

* `auth_mode`: `swic` | `api` | `unknown`.
* `model`: name of the model used.
* `app.version`: ZeroCode version.

#### Metrics catalog

Each metric includes the required fields plus the default context fields above. Metric names below omit the `zerocode.` prefix.
Most metric names are centralized in `zerocode-rs/otel/src/metrics/names.rs`; feature-specific metrics emitted outside that file are included here too.
If a metric includes the `tool` field, it reflects the internal tool used (for example, `apply_patch` or `shell`) and doesn't contain the actual shell command or patch `ZeroTwo` is trying to apply.

#### Runtime and model transport

| Metric                                          | Type      | Fields               | Description                                                  |
| ----------------------------------------------- | --------- | -------------------- | ------------------------------------------------------------ |
| `api_request`                                   | counter   | `status`, `success`  | API request count by HTTP status and success/failure.        |
| `api_request.duration_ms`                       | histogram | `status`, `success`  | API request duration in milliseconds.                        |
| `sse_event`                                     | counter   | `kind`, `success`    | SSE event count by event kind and success/failure.           |
| `sse_event.duration_ms`                         | histogram | `kind`, `success`    | SSE event processing duration in milliseconds.               |
| `websocket.request`                             | counter   | `success`            | WebSocket request count by success/failure.                  |
| `websocket.request.duration_ms`                 | histogram | `success`            | WebSocket request duration in milliseconds.                  |
| `websocket.event`                               | counter   | `kind`, `success`    | WebSocket message/event count by type and success/failure.   |
| `websocket.event.duration_ms`                   | histogram | `kind`, `success`    | WebSocket message/event processing duration in milliseconds. |
| `responses_api_overhead.duration_ms`            | histogram |                      | Responses API overhead timing from WebSocket responses.      |
| `responses_api_inference_time.duration_ms`      | histogram |                      | Responses API inference timing from WebSocket responses.     |
| `responses_api_engine_iapi_ttft.duration_ms`    | histogram |                      | Responses API engine IAPI time-to-first-token timing.        |
| `responses_api_engine_service_ttft.duration_ms` | histogram |                      | Responses API engine service time-to-first-token timing.     |
| `responses_api_engine_iapi_tbt.duration_ms`     | histogram |                      | Responses API engine IAPI time-between-token timing.         |
| `responses_api_engine_service_tbt.duration_ms`  | histogram |                      | Responses API engine service time-between-token timing.      |
| `transport.fallback_to_http`                    | counter   | `from_wire_api`      | WebSocket-to-HTTP fallback count.                            |
| `remote_models.fetch_update.duration_ms`        | histogram |                      | Time to fetch remote model definitions.                      |
| `remote_models.load_cache.duration_ms`          | histogram |                      | Time to load the remote model cache.                         |
| `startup_prewarm.duration_ms`                   | histogram | `status`             | Startup prewarm duration by outcome.                         |
| `startup_prewarm.age_at_first_turn_ms`          | histogram | `status`             | Startup prewarm age when the first real turn resolves it.    |
| `cloud_requirements.fetch.duration_ms`          | histogram |                      | Workspace-managed cloud requirements fetch duration.         |
| `cloud_requirements.fetch_attempt`              | counter   | See note             | Workspace-managed cloud requirements fetch attempts.         |
| `cloud_requirements.fetch_final`                | counter   | See note             | Final workspace-managed cloud requirements fetch outcome.    |
| `cloud_requirements.load`                       | counter   | `trigger`, `outcome` | Workspace-managed cloud requirements load outcome.           |

The `cloud_requirements.fetch_attempt` metric includes `trigger`, `attempt`, `outcome`, and `status_code` fields. The `cloud_requirements.fetch_final` metric includes `trigger`, `outcome`, `reason`, `attempt_count`, and `status_code` fields.

#### Turn and tool activity

| Metric                                 | Type      | Fields                                                                    | Description                                                                                                      |
| -------------------------------------- | --------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `turn.e2e_duration_ms`                 | histogram |                                                                           | End-to-end time for a full turn.                                                                                 |
| `turn.ttft.duration_ms`                | histogram |                                                                           | Time to first token for a turn.                                                                                  |
| `turn.ttfm.duration_ms`                | histogram |                                                                           | Time to first model output item for a turn.                                                                      |
| `turn.network_proxy`                   | counter   | `active`, `tmp_mem_enabled`                                               | Whether the managed network proxy was active for the turn.                                                       |
| `turn.memory`                          | counter   | `read_allowed`, `feature_enabled`, `config_use_memories`, `has_citations` | Per-turn memory read availability and memory citation usage.                                                     |
| `turn.tool.call`                       | histogram | `tmp_mem_enabled`                                                         | Number of tool calls in the turn.                                                                                |
| `turn.token_usage`                     | histogram | `token_type`, `tmp_mem_enabled`                                           | Per-turn token usage by token type (`total`, `input`, `cached_input`, `output`, or `reasoning_output`).          |
| `tool.call`                            | counter   | `tool`, `success`                                                         | Tool invocation count by tool name and success/failure.                                                          |
| `tool.call.duration_ms`                | histogram | `tool`, `success`                                                         | Tool execution duration in milliseconds by tool name and outcome.                                                |
| `tool.unified_exec`                    | counter   | `tty`                                                                     | Unified exec tool calls by TTY mode.                                                                             |
| `approval.requested`                   | counter   | `tool`, `approved`                                                        | Tool approval request result (`approved`, `approved_with_amendment`, `approved_for_session`, `denied`, `abort`). |
| `mcp.call`                             | counter   | See note                                                                  | MCP tool invocation result.                                                                                      |
| `mcp.call.duration_ms`                 | histogram | See note                                                                  | MCP tool invocation duration.                                                                                    |
| `mcp.tools.list.duration_ms`           | histogram | `cache`                                                                   | MCP tool-list duration, including cache hit/miss state.                                                          |
| `mcp.tools.fetch_uncached.duration_ms` | histogram |                                                                           | Duration of MCP tool fetches that miss the cache.                                                                |
| `mcp.tools.cache_write.duration_ms`    | histogram |                                                                           | Duration of ZeroCode Apps MCP tool-cache writes.                                                                 |
| `hooks.run`                            | counter   | `hook_name`, `source`, `status`                                           | Hook run count by hook name, source, and status.                                                                 |
| `hooks.run.duration_ms`                | histogram | `hook_name`, `source`, `status`                                           | Hook run duration in milliseconds.                                                                               |

The `mcp.call` and `mcp.call.duration_ms` metrics include `status`; normal tool-call emissions also include `tool`, plus `connector_id` and `connector_name` when available. Blocked ZeroCode Apps MCP calls may emit `mcp.call` with only `status`.

#### Threads, tasks, and features

| Metric                            | Type      | Fields                | Description                                                                      |
| --------------------------------- | --------- | --------------------- | -------------------------------------------------------------------------------- |
| `feature.state`                   | counter   | `feature`, `value`    | Feature values that differ from defaults (emit one row per non-default).         |
| `status_line`                     | counter   |                       | Session started with a configured status line.                                   |
| `model_warning`                   | counter   |                       | Warning sent to the model.                                                       |
| `thread.started`                  | counter   | `is_git`              | New thread created, tagged by whether the working directory is in a Git repo.    |
| `conversation.turn.count`         | counter   |                       | User/assistant turns per thread, recorded at the end of the thread.              |
| `thread.fork`                     | counter   | `source`              | New thread created by forking an existing thread.                                |
| `thread.rename`                   | counter   |                       | Thread renamed.                                                                  |
| `thread.side`                     | counter   | `source`              | Side conversation created.                                                       |
| `thread.skills.enabled_total`     | histogram |                       | Number of skills enabled for a new thread.                                       |
| `thread.skills.kept_total`        | histogram |                       | Number of enabled skills kept after prompt rendering.                            |
| `thread.skills.truncated`         | histogram |                       | Whether skill rendering truncated the enabled skills list (`1` or `0`).          |
| `task.compact`                    | counter   | `type`                | Number of compactions per type (`remote` or `local`), including manual and auto. |
| `task.review`                     | counter   |                       | Number of reviews triggered.                                                     |
| `task.undo`                       | counter   |                       | Number of undo actions triggered.                                                |
| `task.user_shell`                 | counter   |                       | Number of user shell actions (`!` in the TUI for example).                       |
| `shell_snapshot`                  | counter   | See note              | Whether taking a shell snapshot succeeded.                                       |
| `shell_snapshot.duration_ms`      | histogram | `success`             | Time to take a shell snapshot.                                                   |
| `skill.injected`                  | counter   | `status`, `skill`     | Skill injection outcomes by skill.                                               |
| `plugins.startup_sync`            | counter   | `transport`, `status` | Curated plugin startup sync attempts.                                            |
| `plugins.startup_sync.final`      | counter   | `transport`, `status` | Final curated plugin startup sync outcome.                                       |
| `multi_agent.spawn`               | counter   | `role`                | Agent spawns by role.                                                            |
| `multi_agent.resume`              | counter   |                       | Agent resumes.                                                                   |
| `multi_agent.nickname_pool_reset` | counter   |                       | Agent nickname pool resets.                                                      |

The `shell_snapshot` metric includes `success` and, on failures, `failure_reason`.

#### Memory and local state

| Metric                         | Type      | Fields                    | Description                                               |
| ------------------------------ | --------- | ------------------------- | --------------------------------------------------------- |
| `memory.phase1`                | counter   | `status`                  | Memory phase 1 job counts by status.                      |
| `memory.phase1.e2e_ms`         | histogram |                           | End-to-end duration for memory phase 1.                   |
| `memory.phase1.output`         | counter   |                           | Memory phase 1 outputs written.                           |
| `memory.phase1.token_usage`    | histogram | `token_type`              | Memory phase 1 token usage by token type.                 |
| `memory.phase2`                | counter   | `status`                  | Memory phase 2 job counts by status.                      |
| `memory.phase2.e2e_ms`         | histogram |                           | End-to-end duration for memory phase 2.                   |
| `memory.phase2.input`          | counter   |                           | Memory phase 2 input count.                               |
| `memory.phase2.token_usage`    | histogram | `token_type`              | Memory phase 2 token usage by token type.                 |
| `memories.usage`               | counter   | `kind`, `tool`, `success` | Memory usage by kind, tool, and success/failure.          |
| `external_agent_config.detect` | counter   | See note                  | External agent config detections by migration item type.  |
| `external_agent_config.import` | counter   | See note                  | External agent config imports by migration item type.     |
| `db.backfill`                  | counter   | `status`                  | Initial state DB backfill results (`upserted`, `failed`). |
| `db.backfill.duration_ms`      | histogram | `status`                  | Duration of the initial state DB backfill.                |
| `db.error`                     | counter   | `stage`                   | Errors during state DB operations.                        |

The `external_agent_config.detect` and `external_agent_config.import` metrics include `migration_type`; skills migrations also include `skills_count`.

#### Windows sandbox

| Metric                                           | Type      | Fields                                    | Description                                           |
| ------------------------------------------------ | --------- | ----------------------------------------- | ----------------------------------------------------- |
| `windows_sandbox.setup_success`                  | counter   | `originator`, `mode`                      | Windows sandbox setup successes.                      |
| `windows_sandbox.setup_failure`                  | counter   | `originator`, `mode`                      | Windows sandbox setup failures.                       |
| `windows_sandbox.setup_duration_ms`              | histogram | `result`, `originator`, `mode`            | Windows sandbox setup duration.                       |
| `windows_sandbox.elevated_setup_success`         | counter   |                                           | Elevated Windows sandbox setup successes.             |
| `windows_sandbox.elevated_setup_failure`         | counter   | See note                                  | Elevated Windows sandbox setup failures.              |
| `windows_sandbox.elevated_setup_canceled`        | counter   | See note                                  | Canceled elevated Windows sandbox setup attempts.     |
| `windows_sandbox.elevated_setup_duration_ms`     | histogram | `result`                                  | Elevated Windows sandbox setup duration.              |
| `windows_sandbox.elevated_prompt_shown`          | counter   |                                           | Elevated sandbox setup prompt shown.                  |
| `windows_sandbox.elevated_prompt_accept`         | counter   |                                           | Elevated sandbox setup prompt accepted.               |
| `windows_sandbox.elevated_prompt_use_legacy`     | counter   |                                           | User chose legacy sandbox from the elevated prompt.   |
| `windows_sandbox.elevated_prompt_quit`           | counter   |                                           | User quit from the elevated prompt.                   |
| `windows_sandbox.fallback_prompt_shown`          | counter   |                                           | Fallback sandbox prompt shown.                        |
| `windows_sandbox.fallback_retry_elevated`        | counter   |                                           | User retried elevated setup from the fallback prompt. |
| `windows_sandbox.fallback_use_legacy`            | counter   |                                           | User chose legacy sandbox from the fallback prompt.   |
| `windows_sandbox.fallback_prompt_quit`           | counter   |                                           | User quit from the fallback prompt.                   |
| `windows_sandbox.legacy_setup_preflight_failed`  | counter   | See note                                  | Legacy Windows sandbox setup preflight failure.       |
| `windows_sandbox.setup_elevated_sandbox_command` | counter   |                                           | Elevated sandbox setup command invoked.               |
| `windows_sandbox.createprocessasuserw_failed`    | counter   | `error_code`, `path_kind`, `exe`, `level` | Windows `CreateProcessAsUserW` failures.              |

The elevated setup failure metrics include `code` and `message` when Windows setup failure details are available, and may include `originator` when emitted from the shared setup path. The `windows_sandbox.legacy_setup_preflight_failed` metric includes `originator` when emitted from the shared setup path, but fallback-prompt preflight failures may not include any fields.

### Feedback controls

By default, local clients let users send feedback from `/feedback`. To disable feedback collection across the ZeroTwo desktop app, and desktop app on a machine, update your config:

```toml theme={null}
[feedback]
enabled = false
```

When disabled, `/feedback` shows a disabled message and ZeroCode rejects feedback submissions.

### Hide or surface reasoning events

If you want to reduce noisy "reasoning" output (for example in CI logs), you can suppress it:

```toml theme={null}
hide_agent_reasoning = true
```

If you want to surface raw reasoning content when a model emits it:

```toml theme={null}
show_raw_agent_reasoning = true
```

Enable raw reasoning only if it's acceptable for your workflow. Some models/providers (like `gpt-oss`) don't emit raw reasoning; in that case, this setting has no visible effect.

## Notifications

Use `notify` to trigger an external program whenever ZeroCode emits supported events (currently only `agent-turn-complete`). This is handy for desktop toasts, chat webhooks, CI updates, or any side-channel alerting that the built-in TUI notifications don't cover.

```toml theme={null}
notify = ["python3", "/path/to/notify.py"]
```

Example `notify.py` (truncated) that reacts to `agent-turn-complete`:

```python theme={null}
#!/usr/bin/env python3
import json, subprocess, sys

def main() -> int:
    notification = json.loads(sys.argv[1])
    if notification.get("type") != "agent-turn-complete":
        return 0
    title = f"ZeroCode: {notification.get('last-assistant-message', 'Turn Complete!')}"
    message = " ".join(notification.get("input-messages", []))
    subprocess.check_output([
        "terminal-notifier",
        "-title", title,
        "-message", message,
        "-group", "zerocode-" + notification.get("thread-id", ""),
        "-activate", "com.googlecode.iterm2",
    ])
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

The script receives a single JSON argument. Common fields include:

* `type` (currently `agent-turn-complete`)
* `thread-id` (session identifier)
* `turn-id` (turn identifier)
* `cwd` (working directory)
* `input-messages` (user messages that led to the turn)
* `last-assistant-message` (last assistant message text)

Place the script somewhere on disk and point `notify` to it.

#### `notify` vs `tui.notifications`

* `notify` runs an external program (good for webhooks, desktop notifiers, CI hooks).
* `tui.notifications` is built in to the TUI and can optionally filter by event type (for example, `agent-turn-complete` and `approval-requested`).
* `tui.notification_method` controls how the TUI emits terminal notifications (`auto`, `osc9`, or `bel`).
* `tui.notification_condition` controls whether TUI notifications fire only when
  the terminal is `unfocused` or `always`.

In `auto` mode, ZeroCode prefers OSC 9 notifications (a terminal escape sequence some terminals interpret as a desktop notification) and falls back to BEL (`\x07`) otherwise.

See [Configuration Reference](/config-file/config-reference) for the exact keys.

## History persistence

By default, ZeroCode saves local session transcripts under `ZEROTWO_HOME` (for example, `~/.zerotwo/history.jsonl`). To disable local history persistence:

```toml theme={null}
[history]
persistence = "none"
```

To cap the history file size, set `history.max_bytes`. When the file exceeds the cap, ZeroCode drops the oldest entries and compacts the file while keeping the newest records.

```toml theme={null}
[history]
max_bytes = 104857600 # 100 MiB
```

## Clickable citations

If you use a terminal/editor integration that supports it, ZeroCode can render file citations as clickable links. Configure `file_opener` to pick the URI scheme ZeroCode uses:

```toml theme={null}
file_opener = "vscode" # or cursor, windsurf, vscode-insiders, none
```

Example: a citation like `/home/user/project/main.py:42` can be rewritten into a clickable `vscode://file/...:42` link.

## Project instructions discovery

ZeroCode reads `AGENTS.md` (and related files) and includes a limited amount of project guidance in the first turn of a session. Two knobs control how this works:

* `project_doc_max_bytes`: how much to read from each `AGENTS.md` file
* `project_doc_fallback_filenames`: additional filenames to try when `AGENTS.md` is missing at a directory level

For a detailed walkthrough, see [Custom instructions with AGENTS.md](/agent-configuration/agents-md).

## Desktop

Options in this section apply only to the ZeroTwo desktop app.

### Add custom file handlers

In your user-level `~/.zerotwo/config.toml`, add entries under
`desktop.custom_file_handlers` to open files in editors or internal launchers
that the ZeroTwo desktop app doesn't support by default. Each entry adds an
editor target to the app's **Open in** menus. The app lists the target when
`command` is an existing absolute path or resolves from the app's `PATH`.

The following example shows three ways to pass a file to a handler:

```toml theme={null}
# Append the opened path directly after the command.
[desktop.custom_file_handlers.vscodium]
label = "VSCodium"
icon = "/Users/you/.zerotwo/icons/vscodium.png"
command = "codium"

# Place fixed arguments before the opened path.
[desktop.custom_file_handlers.textedit]
label = "TextEdit"
icon = "/Users/you/.zerotwo/icons/textedit.png"
command = "/usr/bin/open"
args = ["-a", "TextEdit"]

# Append one JSON argument with the path and editor context.
[desktop.custom_file_handlers.company_editor]
label = "Company Editor"
icon = "/opt/company/editor/icon.png"
command = "/opt/company/bin/editor"
input = "json_argument"
```

Save `config.toml`, then restart the ZeroTwo desktop app.

The handler ID is the final segment of the TOML table header. It must contain
1–64 characters, start with an ASCII letter or number, and otherwise contain
only ASCII letters, numbers, periods, underscores, or hyphens. The app exposes
the ID with a `custom:` prefix; for example, `company_editor` becomes
`custom:company_editor`. Quote an ID that contains a period so TOML doesn't
interpret it as a nested table. For example:

```toml theme={null}
[desktop.custom_file_handlers."company.editor"]
label = "Company Editor"
icon = "/opt/company/editor/icon.png"
command = "/opt/company/bin/editor"
```

Each handler supports these fields:

| Field          | Required | Description                                                                                                                                                                              |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label`        | Yes      | Display name in the app.                                                                                                                                                                 |
| `icon`         | Yes      | Bundled app icon such as `apps/vscode.png`, base64 `data:image/...` URL, `file:` URI, or absolute local image path. An unsupported source uses the default the ZeroTwo desktop app icon. |
| `command`      | Yes      | Executable path or command name to detect and launch.                                                                                                                                    |
| `args`         | No       | String array inserted between `command` and the file input. Defaults to `[]`.                                                                                                            |
| `input`        | No       | How the app sends file input: `path`, `json_argument`, or `json_stdin`. Defaults to `path`.                                                                                              |
| `supports_ssh` | No       | Whether to offer the handler for files in SSH workspaces. Defaults to `false`. Use `json_stdin` when the handler needs remote host and path details.                                     |

The `input` value controls what follows `args`:

* `path` appends the path as the final command argument.
* `json_argument` appends a JSON object with `target`, `path`, `appPath`, and
  `location`. The `location` value is an object with 1-based `line` and
  `column` values, or `null`.
* `json_stdin` writes the JSON object to standard input instead of adding an
  argument. It also includes `hostConfig`, `remoteWorkspaceRoot`, and
  `remotePath`; these fields are `null` when they don't apply.

For example, `company_editor` can receive this argument when the user opens a
specific source location:

```json theme={null}
{
  "target": "custom:company_editor",
  "path": "/repo/src/index.ts",
  "appPath": null,
  "location": { "line": 12, "column": 3 }
}
```

Selecting a custom handler as the preferred editor persists the choice the same
way as selecting a built-in editor, including per-project preferences.

## TUI options

Running `ZeroTwo` with no subcommand launches the interactive terminal UI (TUI). ZeroCode exposes some TUI-specific configuration under `[tui]`, including:

* `tui.notifications`: enable/disable notifications (or restrict to specific types)
* `tui.notification_method`: choose `auto`, `osc9`, or `bel` for terminal notifications
* `tui.notification_condition`: choose `unfocused` or `always` for when
  notifications fire
* `tui.animations`: enable/disable ASCII animations and shimmer effects
* `tui.alternate_screen`: control alternate screen usage (set to `never` to keep terminal scrollback)
* `tui.show_tooltips`: show or hide onboarding tooltips on the welcome screen

`tui.notification_method` defaults to `auto`. In `auto` mode, ZeroCode prefers OSC 9 notifications (a terminal escape sequence some terminals interpret as a desktop notification) when the terminal appears to support them, and falls back to BEL (`\x07`) otherwise.

See [Configuration Reference](/config-file/config-reference) for the full key list.
