> For the complete documentation index, see [llms.txt](https://dotagent.avelino.run/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dotagent.avelino.run/reference/env-vars.md).

# Environment variables

Two categories:

1. [**Injected into the agent**](#injected-into-the-agent-subprocess) — what your script sees when dotagent invokes it.
2. [**Read by dotagent itself**](#read-by-dotagent-itself) — overrides for paths, verbosity, OTel headers.

***

## Injected into the agent subprocess

When dotagent spawns your agent, these `AGENT_*` variables are set on top of the inherited environment (unless `env.inherit = false` in the manifest). They are the **only API surface** you depend on — there's no SDK to import.

| Variable                          | Type                 | Example                                                                                 | When set                                                                                                                                                                                                                                                             |
| --------------------------------- | -------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AGENT_NAME`                      | string               | `finops-weekly`                                                                         | Always.                                                                                                                                                                                                                                                              |
| `AGENT_HOME`                      | abs path             | `/Users/me/.config/dotagent/agents/finops-weekly`                                       | Always — the manifest directory.                                                                                                                                                                                                                                     |
| `AGENT_TMPDIR`                    | abs path             | `/var/folders/.../tmpAbCdEf`                                                            | Always — fresh per run, auto-cleaned on exit.                                                                                                                                                                                                                        |
| `AGENT_DRY_RUN`                   | `"true"` / `"false"` | `"false"`                                                                               | Always.                                                                                                                                                                                                                                                              |
| `AGENT_SCHEDULE_ID`               | string               | `daily`                                                                                 | Always — matches `[[schedules]].id`.                                                                                                                                                                                                                                 |
| `AGENT_SLUG`                      | string               | `period_dia-anterior`                                                                   | Always — derived from the schedule's `args`.                                                                                                                                                                                                                         |
| `AGENT_START_EPOCH`               | int                  | `1700000000`                                                                            | Always — unix epoch of `started_at`.                                                                                                                                                                                                                                 |
| `AGENT_ARGV`                      | JSON array           | `["--period","dia-anterior"]`                                                           | Always — the schedule's `args` as JSON.                                                                                                                                                                                                                              |
| `AGENT_HEARTBEAT_FILE`            | abs path             | `~/.config/dotagent/state/agents/.../slug.heartbeat.json`                               | Set when NOT dry-run.                                                                                                                                                                                                                                                |
| `AGENT_LIFECYCLE`                 | string               | `persistent`                                                                            | Only when `[lifecycle] mode = "persistent"`. Absent otherwise, so one script can support both shapes.                                                                                                                                                                |
| `AGENT_PERSIST_KEY`               | string               | `12345`                                                                                 | Persistent runs only — which slice this instance answers for (the resolved `[lifecycle] key`, or `default`).                                                                                                                                                         |
| `AGENT_TRIGGER_SOURCE`            | string               | `telegram`                                                                              | One-shot [triggered](/concepts/triggers.md) runs only. One of `telegram`, `local`, `mcp`, `cli`. **Never set in persistent mode** — see below.                                                                                                                       |
| `AGENT_TRIGGER_ACTOR`             | string               | `123456789`                                                                             | One-shot triggered runs, when the source can attest an identity. Telegram: numeric user id.                                                                                                                                                                          |
| `AGENT_TRIGGER_REPLY_TO`          | string               | `123456789`                                                                             | One-shot triggered runs, when the source can be answered. Telegram: chat id.                                                                                                                                                                                         |
| `AGENT_TRIGGER_PAYLOAD`           | JSON object          | `{"text":"/standup x","chat_id":1,"user_id":2,"command":{"name":"standup","args":"x"}}` | One-shot triggered runs. Body travels here, never in argv. `command` is present only when the sender invoked one — see [Commands](/concepts/commands.md#the-payload). `reply_to_run` is present when the sender replied to a notification dotagent sent — see below. |
| `AGENT_SESSION_ID`                | string               | `chat-9_a`                                                                              | One-shot triggered runs only, when the source has a conversation id. Persistent agents receive this as `trigger.session_id` in each request frame instead.                                                                                                           |
| `AGENT_ASSISTANT_CONTEXT_RETIRED` | `"true"`             | `"true"`                                                                                | Only on the first triggered `[assistant]` run after automatic transcript retirement. Absent after `/novo` and otherwise.                                                                                                                                             |
| `LANG`                            | string               | `en_US.UTF-8`                                                                           | Only when neither `LANG` nor `LC_ALL` was inherited — see below.                                                                                                                                                                                                     |

### `LANG`, and why a daemon has to name one

launchd and systemd start a daemon with **no locale at all**, and every agent inherits that gap. A process in the resulting `C` locale has `MB_CUR_MAX == 1`: it reads each **byte** of an environment variable as one character (Latin-1) and writes it back out as UTF-8. `AGENT_TRIGGER_PAYLOAD` carrying `é` (`c3 a9`) reaches the agent as `Ã©` (`c3 83 c2 a9`) — still valid UTF-8, so nothing errors and the agent just acts on mangled text.

So dotagent fills the gap: `en_US.UTF-8` on macOS, `C.UTF-8` elsewhere. Only when the value is missing — an inherited `LANG` or `LC_ALL` is never overridden, and `[env.extra]` wins over both:

```toml
[env.extra]
LANG = "pt_BR.UTF-8"
```

Reproduced with fish 3.7.1, whose command-substitution output is *not* affected — which is why an agent could log a mangled prompt beside a clean answer and look like a model problem.

#### `reply_to_run`

When the message answers a notification dotagent sent, the payload carries which run it came from:

```jsonc
{
  "text": "por que falhou?",
  "reply_to_text": "🚨 disk-alert/every-15min gave up after 3 attempts (exit 1)…",
  "reply_to_run": {
    "agent": "disk-alert",
    "schedule": "every-15min",
    "event": "given_up"
  }
}
```

Resolved from the replied-to message id within the inbound chat, not from the text. The wording is not a stable interface — one event names `agent/schedule` and another says only `preflight aborted by plugin preflight-warp` — and two agents can fail identically. Absent when the reply is to something else, when the notification is older than the few hundred kept in `state/notify/telegram/sent.json`.

#### Persistent agents get no per-message trigger environment

An environment is fixed at spawn. A persistent process is spawned once and answers many different messages, so those per-message variables would freeze the first one and keep serving it — and stale trigger context reads as perfectly valid, which is worse than absent.

In `[lifecycle] mode = "persistent"` the same information arrives in the `trigger` field of each request frame:

```jsonc
{ "kind": "request", "id": "1", "deadline_seconds": 600,
  "trigger": { "source": "telegram", "session_id": "chat-9_a",
                "actor": "123", "reply_to": "123",
                "payload": { "text": "…", "chat_id": 12345 } } }
```

The request's `trigger` object carries `source`, optional `session_id`, `actor`, `reply_to`, and `payload`. `session_id` is per-trigger context, not a process-wide conversation store. The local one-shot API validates local session ids against `^[A-Za-z0-9_-]{1,64}$` and defaults an omitted id to `default`. See [Local Client API](/reference/local-api.md).

`AGENT_TMPDIR` also changes lifetime: it belongs to the instance rather than to one request, so it survives between them and is removed when the instance is recycled. See [the persistent protocol](/reference/persistent-protocol.md).

The positional `argv` of your process is `[run].command` + `[run].args`

* schedule's `args`, so most scripts don't actually need `AGENT_ARGV` unless they want JSON-shaped access.

### Slug derivation

`AGENT_SLUG` is computed from the schedule's `args`:

| `args`                         | slug                  |
| ------------------------------ | --------------------- |
| `[]`                           | `default`             |
| `["--period", "dia-anterior"]` | `period_dia-anterior` |
| `["--mode", "unsubscribe"]`    | `mode_unsubscribe`    |
| `["foo bar"]`                  | `foo_bar`             |

Rules: strip leading dashes, lowercase, replace non-alphanumeric with `_`, collapse repeated `_`, trim trailing `_`. Empty input → `default`.

For triggered runs, `AGENT_SLUG` is the source slug rather than the schedule slug. Without a session it is `trigger-<source>`; with a session it is `trigger-<source>-<sanitized-session>`. The local API uses its effective `default` session when the request omits `session_id`.

### Reading these vars

**Fish**:

```fish
echo "I am $AGENT_NAME running on schedule $AGENT_SCHEDULE_ID"
test "$AGENT_DRY_RUN" = "true"; and exit 0
cd $AGENT_TMPDIR
```

**Python**:

```python
import os, json

name = os.environ["AGENT_NAME"]
argv = json.loads(os.environ.get("AGENT_ARGV", "[]"))
if os.environ.get("AGENT_DRY_RUN") == "true":
    return
```

**Go**:

```go
name := os.Getenv("AGENT_NAME")
var argv []string
json.Unmarshal([]byte(os.Getenv("AGENT_ARGV")), &argv)
```

**Bash**:

```bash
: "${AGENT_NAME:?AGENT_NAME not set — running outside dotagent?}"
cd "$AGENT_TMPDIR" || exit
```

### Extra variables you declare

`[env].extra` in the manifest is merged on top. Standard idiom for agent-tuning constants:

```toml
[env]
inherit = true                                  # default
[env.extra]
LOG_LEVEL          = "info"
PYTHONUNBUFFERED   = "1"
DISK_FREE_MIN_PCT  = "20"
```

Set `inherit = false` if you want a hermetic environment (no parent env leaks in — careful, that removes `$PATH` too unless you re-add it under `extra`).

***

## Injected into a skill script

A [skill](/concepts/skills.md) may package executables under `scripts/`, which `skill-run` executes. Those get a **different, much smaller** set of variables — a skill script is not an agent run: it has no schedule, no heartbeat and no manifest.

| Variable     | Type     | Example                                    | When                                 |
| ------------ | -------- | ------------------------------------------ | ------------------------------------ |
| `SKILL_NAME` | string   | `triage`                                   | Always.                              |
| `SKILL_DIR`  | abs path | `/Users/me/.config/dotagent/skills/triage` | Always — also the working directory. |

Everything else in the environment is inherited from whatever spawned `dotagent mcp`. Arguments arrive through argv, never a shell string.

```bash
#!/usr/bin/env bash
# scripts/report.sh — reach a sibling file without assuming the cwd.
set -euo pipefail
cat "$SKILL_DIR/references/template.md"
```

***

## Read by dotagent itself

dotagent reads these to override defaults. None are required — everything works out-of-the-box.

| Variable                     | Purpose                                                                                          | Default                      |
| ---------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------- |
| `DOTAGENT_HOME`              | Override the root directory.                                                                     | `~/.config/dotagent`         |
| `DOTAGENT_ROOT`              | Extra (colon-separated) directories to scan for manifests, prepended to the default search list. | (empty)                      |
| `DOTAGENT_PLUGIN_PATH`       | Extra (colon-separated) directories to search for plugin binaries.                               | (empty)                      |
| `RUST_LOG`                   | Tracing filter — overrides `[logging].level` in `config.toml`.                                   | `info`                       |
| `DOTAGENT_LOG_STDERR`        | Force the stderr mirror layer on (`1`/`true`/`yes`/`on`) or off (`0`/`false`/`no`/`off`).        | (unset — mirror follows TTY) |
| `NO_COLOR`                   | Set to any value to suppress ANSI escapes in the daemon and every subcommand.                    | (unset)                      |
| `OTEL_EXPORTER_OTLP_HEADERS` | OTLP auth headers (comma-separated `k=v`). Used when `[telemetry].otlp_endpoint` is set.         | (empty)                      |

### `DOTAGENT_HOME`

Moves everything dotagent owns (manifests, state, logs, audit, config) under a different root.

```bash
DOTAGENT_HOME=/var/lib/dotagent dotagent doctor
DOTAGENT_HOME=/var/lib/dotagent dotagent daemon
```

Both the daemon and CLI must agree — if you start the daemon with `DOTAGENT_HOME=A` and then run `dotagent reload` with `DOTAGENT_HOME=B`, the reload reads the **wrong** PID file (the daemon under A's `state/daemon.pid`).

When set in a launchd plist:

```xml
<key>EnvironmentVariables</key>
<dict>
    <key>DOTAGENT_HOME</key>
    <string>/var/lib/dotagent</string>
</dict>
```

When set for a systemd unit:

```ini
[Service]
Environment=DOTAGENT_HOME=/var/lib/dotagent
```

### `DOTAGENT_ROOT`

Adds **search roots** for manifest discovery, ahead of the defaults. Useful for CI / testing without touching `~/.config/dotagent/`:

```bash
DOTAGENT_ROOT=$PWD/examples dotagent doctor
DOTAGENT_ROOT=$PWD/examples dotagent run hello-fish --schedule manual
```

The full discovery order with `DOTAGENT_ROOT` set:

1. Every directory in `$DOTAGENT_ROOT`
2. `$DOTAGENT_HOME/agents/`
3. `$CWD/agents/`
4. `$CWD`

Each direct subdirectory of these roots that contains an `agent.toml` becomes one agent. Duplicates resolve to first-found by `agent.name`.

### `DOTAGENT_PLUGIN_PATH`

Adds search directories for plugin binary resolution, ahead of the defaults:

```bash
DOTAGENT_PLUGIN_PATH=$PWD/target/release dotagent plugin list
```

Full plugin discovery order:

1. Every directory in `$DOTAGENT_PLUGIN_PATH`
2. `$DOTAGENT_HOME/plugins/`
3. `/usr/local/lib/dotagent/plugins/`
4. `$PATH`

First match wins. `dotagent plugin list` shows the resolved path.

> **Daemon gotcha**: when launchd starts the daemon, your interactive shell's `$DOTAGENT_PLUGIN_PATH` is NOT inherited. Set it in the plist (`EnvironmentVariables`), or move the plugin into `$DOTAGENT_HOME/plugins/`.

### `RUST_LOG`

Overrides `[logging].level` from `config.toml`. Same `EnvFilter` syntax as the rest of the Rust ecosystem — per-target filters supported:

```bash
# Globally chatty
RUST_LOG=debug dotagent daemon

# Just the runner
RUST_LOG=info,dotagent_runner=trace dotagent daemon

# Quiet down a noisy crate
RUST_LOG=info,h2=warn,hyper_util=warn dotagent daemon
```

The `RUST_LOG` env var only affects the **CLI subcommand it's set for**. To make it sticky for the daemon, put it in the launchd plist / systemd unit `Environment=`.

### `DOTAGENT_LOG_STDERR`

Whether the daemon mirrors its `tracing` stream to stderr in addition to `logs/daemon/dotagent.log`.

The default is **only when stderr is a terminal**. Run `dotagent daemon` by hand and you get the familiar compact stream; run it under launchd or systemd and you do not — there stderr is an appended plain file (`run.avelino.dotagent-error.log`) that no rotation policy covers, so the mirror would duplicate an already-rotated log into one that grows forever. What still reaches that file is what has nowhere else to go: panics, and startup failures that happen before logging is up.

```bash
# Unit rewired to journald, which does rotate — take the stream back.
DOTAGENT_LOG_STDERR=1 dotagent daemon

# Interactive run, but you only want the JSON file.
DOTAGENT_LOG_STDERR=0 dotagent daemon
```

Accepted: `1`, `true`, `yes`, `on` / `0`, `false`, `no`, `off`. Anything else is ignored and the TTY default applies.

### `NO_COLOR`

Set to any value (including empty) and dotagent emits no ANSI escapes — in the daemon's stderr mirror and in every subcommand's output. Colour is otherwise enabled only when the stream is a terminal, so redirecting to a file already gives you plain text. Follows [no-color.org](https://no-color.org).

### `OTEL_EXPORTER_OTLP_HEADERS`

Vendor-specific authentication for the OTLP exporter. Format is comma-separated `k=v`. Used only when `[telemetry].otlp_endpoint` is non-empty in `config.toml`.

```bash
# Honeycomb
export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=YOUR_API_KEY"

# Grafana Cloud (base64-encoded basic auth)
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(printf %s "$STACK_ID:$API_TOKEN" | base64)"
```

Headers can also be declared in `[telemetry.headers]` of `config.toml` — the env var wins when both are set.

See [`observability.md`](/guides/observability.md#vendor-recipes) for full vendor recipes.

***

## What dotagent does NOT honor

Some env vars you might expect from other tools — dotagent ignores them on purpose:

| Variable          | Why dotagent ignores it                                                               |
| ----------------- | ------------------------------------------------------------------------------------- |
| `XDG_CONFIG_HOME` | dotagent uses `~/.config/dotagent` directly. Override via `DOTAGENT_HOME`.            |
| `XDG_DATA_HOME`   | dotagent doesn't separate config/data/cache — everything lives under `DOTAGENT_HOME`. |
| `EDITOR`          | dotagent has no interactive editing.                                                  |
| `LAUNCH_AGENT`    | The daemon is the launchd-managed unit; agents themselves never touch launchd.        |

***

## Quick reference

```bash
# Agent script — runtime env it sees
AGENT_NAME            finops-weekly
AGENT_HOME            ~/.config/dotagent/agents/finops-weekly
AGENT_TMPDIR          /var/folders/.../tmpXXXXXX
AGENT_DRY_RUN         false
AGENT_SCHEDULE_ID     weekly
AGENT_SLUG            default
AGENT_START_EPOCH     1700000000
AGENT_ARGV            []
AGENT_HEARTBEAT_FILE  ~/.config/dotagent/state/agents/finops-weekly/default.heartbeat.json
LANG                  en_US.UTF-8                (only if none was inherited)

# Caller — overrides for the dotagent CLI / daemon
DOTAGENT_HOME             /var/lib/dotagent          (default: ~/.config/dotagent)
DOTAGENT_ROOT             /tmp/test-agents           (prepended to manifest search)
DOTAGENT_PLUGIN_PATH      $PWD/target/release        (prepended to plugin search)
RUST_LOG                  info,dotagent_runner=debug
DOTAGENT_LOG_STDERR       1                          (default: mirror only on a TTY)
NO_COLOR                  1                          (default: colour only on a TTY)
OTEL_EXPORTER_OTLP_HEADERS x-honeycomb-team=KEY
```

***

## Related

* [`paths.md`](/reference/paths.md) — where every file actually lives
* [`agent-spec.md`](/reference/agent-spec.md) — `[env]` block in the manifest
* [`config-reference.md`](/guides/config-reference.md) — `config.toml` options that env vars can override
* [`observability.md`](/guides/observability.md) — `RUST_LOG` and OTel headers in context


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://dotagent.avelino.run/reference/env-vars.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
