For the complete documentation index, see llms.txt. This page is also available as Markdown.

Environment variables

Two categories:

  1. Injected into the agent — what your script sees when dotagent invokes it.

  2. 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 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. 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:

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:

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:

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.

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.

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:

Python:

Go:

Bash:

Extra variables you declare

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

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 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.


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.

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:

When set for a systemd unit:

DOTAGENT_ROOT

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

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:

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:

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.

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.

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.

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

See observability.md 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


  • paths.md — where every file actually lives

  • agent-spec.md[env] block in the manifest

  • config-reference.mdconfig.toml options that env vars can override

  • observability.mdRUST_LOG and OTel headers in context

Last updated

Was this helpful?