Configuration Reference

AoE uses a layered configuration system. Settings are resolved in this order:

  1. Global config: ~/.agent-of-empires/config.toml (or ~/.config/agent-of-empires/config.toml on Linux)
  2. Profile config: ~/.agent-of-empires/profiles/<name>/config.toml
  3. Repo config: .agent-of-empires/config.toml in the project root

Later layers override earlier ones. Only explicitly set fields override; unset fields inherit from the previous layer.

All settings below can also be edited from the TUI settings screen (press s or access via the menu).

File Locations

Platform Global Config
Linux $XDG_CONFIG_HOME/agent-of-empires/config.toml (defaults to ~/.config/agent-of-empires/)
macOS ~/.agent-of-empires/config.toml by default, or $XDG_CONFIG_HOME/agent-of-empires/config.toml when you opt into the XDG layout (see below)

On macOS, AoE reads from $XDG_CONFIG_HOME/agent-of-empires/ (e.g. ~/.config/agent-of-empires/) when you set XDG_CONFIG_HOME, or whenever that directory already exists, so a dotfile manager like chezmoi can share one config path with Linux. Otherwise it uses ~/.agent-of-empires/. Nothing is moved automatically: an existing ~/.agent-of-empires/ keeps being used even after you set XDG_CONFIG_HOME, until you relocate it yourself.

~/.agent-of-empires/
  config.toml              # Global configuration
  state.toml               # Runtime/UI bookkeeping (auto-managed, see below)
  trusted_repos.toml       # Hook trust decisions (auto-managed)
  .schema_version          # Migration tracking (auto-managed)
  profiles/
    default/
      sessions.json        # Session data
      groups.json          # Group hierarchy
      config.toml          # Profile-specific overrides
  logs/                    # Session execution logs

state.toml

Sits alongside config.toml in the same app dir. It holds global-only runtime/UI bookkeeping, such as “seen the welcome tour”, the last browse directory, sort order, and dismissed-tip/update tracking, none of which is a user-facing setting, so it has no profile or repo layer and is never part of the settings TUI or the web dashboard’s settings schema. GET /api/settings still exposes these fields under the app_state.* key for backwards-compatible reads; only their on-disk home moved. That exposure is read-only: PATCH /api/settings rejects writes to app_state.* with a 400, because AppStateConfig is not a settings-schema section and the patch validator treats it as an unknown one.

state.toml is machine-owned runtime bookkeeping, but it is written with the same locked, read-modify-write guarantee as config.toml: both go through storage::locked_update, so a concurrent writer’s changes survive and two aoe processes (the TUI and an aoe serve daemon) never lose an update. It lives in a separate file, with its own lock, so its highest-churn writes (every sidebar toggle, every tip dismissal) do not contend with a real settings save on config.toml.

Environment Variables

Variable Description
AGENT_OF_EMPIRES_PROFILE Default profile to use
AGENT_OF_EMPIRES_DEBUG Enable debug logging to debug.log in app data dir (1 to enable). Legacy alias for AOE_LOG_LEVEL=debug.
AOE_LOG_LEVEL File log level: trace, debug, info, warn, error.

Theme

[theme]
name = "default"   # default, empire, phosphor, tokyo-night-storm, catppuccin-latte, dracula, rose-pine, deep-ocean
color_mode = "truecolor"   # truecolor | palette (TUI only)
Option Default Description
name "default" Color theme. Applies to both the TUI and the web dashboard. Available builtins: default (neutral zinc/amber), empire (warm navy/copper), phosphor (green), tokyo-night-storm (dark blue/purple), catppuccin-latte (light pastel), dracula (dark purple/pink), rose-pine (dark muted purple/pink), deep-ocean (Material Theme Deep Ocean, dark navy/cyan). Custom TOML themes in ~/.agent-of-empires/themes/*.toml also appear in the picker. An empty name resolves to default.
color_mode "truecolor" TUI only. palette downsamples to xterm-256 for transports that mangle 24-bit RGB (e.g. some mosh setups). The web dashboard always renders truecolor.

Custom themes

Drop a TOML file in ~/.agent-of-empires/themes/<name>.toml (or $XDG_CONFIG_HOME/agent-of-empires/themes/ on Linux). The file appears in the theme picker under its filename stem. Export a builtin as a starting point:

aoe theme export empire             # writes ~/.agent-of-empires/themes/custom-empire.toml
aoe theme export dracula -o my.toml # writes to my.toml
aoe theme list                      # show all available themes
aoe theme dir                       # print the custom themes directory

The schema is flat and every field is optional. Missing color fields fall back to the Empire baseline; an omitted appearance or [syntax].shiki_theme is derived from the theme’s background luminance rather than copied from Empire. Color fields cover background, borders, text, status semantics, diff colors, branch/sandbox chips, and accent. appearance = "dark" | "light" and [syntax].shiki_theme control the web dashboard’s surface ramp and code-block syntax theme.

Session

[session]
default_tool = "claude"   # any supported agent name
yolo_mode_default = false
agent_status_hooks = true
smart_rename = true
smart_rename_agent = ""    # "" = use the session's own agent; e.g. "codex"
# smart_rename_model: per-agent title model, e.g. { claude = "haiku" } (see table below)
opencode_preassign_session_id = false  # pre-create opencode's session via a throwaway `opencode serve`
auto_stop_idle_secs = 0   # 0 disables; e.g. 7200 = stop after 2h idle
row_tag = "branch"       # none | auto | profile | sandbox | branch
prevent_sleep_when_active = false      # daemon only; keep the OS awake while sessions are active
prevent_sleep_idle_grace_minutes = 15  # release once every session has been idle this long (0-240)

# Per-agent structured-view defaults live under [acp], not [session].
[acp.acp_defaults.opencode]
model = "openai/gpt-5.5"
effort = "high"           # default thinking level
mode = "plan"             # default mode, applied when the agent advertises one

[acp.acp_defaults.opencode.effort_by_model]
"openai/gpt-5.5" = "high"  # overrides `effort` when this model is resolved

# Trusted global/profile hook event to AoE status overrides.
[agents.claude.status_map]
Stop = "idle"
Notification = "waiting"
Option Default Description
default_tool (auto-detect) Default agent for new sessions. Falls back to the first available tool if unset or unavailable. Can be set to a custom agent name.
auto_stop_idle_secs 0 Seconds a plain tmux session may sit Idle before it is auto-stopped: its tmux session and any sandbox container are killed, leaving a restartable Stopped row. 0 disables it; no session is ever auto-stopped for inactivity. Idle age is measured from the later of the last transition into Idle and the last user interaction, and a session with an attached tmux client is always spared, so a session you are reading is never reaped. Evaluated about once a minute (by the TUI and by aoe serve), so the stop can lag the threshold by up to a minute. Structured view workers use the separate acp.auto_stop_idle_secs. See #1689 and #1690.
prevent_sleep_when_active false When enabled, the aoe serve daemon holds an OS assertion that prevents user-idle system sleep (the display still sleeps) while any session is active, releasing it once every session has been idle past prevent_sleep_idle_grace_minutes. Opt-in, daemon only: a TUI-only user without a running aoe serve gets no inhibition. Global toggle, not profile-overridable, since it drives a single process-wide assertion. Backed by caffeinate -i on macOS and systemd-inhibit --what=idle:sleep on Linux; hosts without those tools (or without logind) warn once and no-op. See #2733.
prevent_sleep_idle_grace_minutes 15 Minutes a session must stay idle before the sleep-inhibit assertion may be released. Only consulted when prevent_sleep_when_active is on. Range 0 to 240; 0 releases as soon as every session leaves an active status. The grace period only begins once a session goes Idle, so a session that never reaches Idle keeps holding the assertion: Waiting on an unanswered prompt, or Creating on a container, worktree, or submodule setup that never returns, can hold sleep indefinitely. A Starting session is bounded by a short (~3s) launch guard and then re-resolves.
row_tag "branch" Controls the compact metadata shown next to each TUI session title: none shows nothing; auto shows the profile code only in all-profiles view; profile always shows the profile code; sandbox shows sb on sandboxed sessions; branch shows a compact worktree or workspace branch tag.
yolo_mode_default false Enable YOLO mode by default for new sessions (skip permission prompts). Works with or without sandbox. In tmux mode this passes --dangerously-skip-permissions to the agent CLI; in structured view it maps to ACP bypassPermissions (see Structured view: Permission modes and YOLO for the adapter caveat).
agent_status_hooks true Install status-detection hooks into the agent’s config file. Codex uses the [hooks] table in its resolved config.toml (typically ~/.codex/config.toml); other JSON-based agents use their settings JSON. Config-dir overrides are honored: CODEX_HOME (Codex), CLAUDE_CONFIG_DIR (Claude), or CURSOR_CONFIG_DIR (Cursor) set in the session’s profile environment or in AoE’s own environment redirects hooks to that directory instead of the ~/.codex / ~/.claude / ~/.cursor default. When disabled, status detection falls back to tmux pane content parsing. Codex is hook-first, but known hook gaps are reconciled from pane content.
smart_rename true Auto-rename a new structured view (ACP) session from its first turn, using the session’s own agent in one-shot mode (claude -p, codex exec, opencode run, gemini -p). Runs only while the session still carries its auto-generated civilization name; a manually named session is never touched. Title only: the worktree directory is not moved, since the running agent holds it. A sandboxed session runs the one-shot inside its own container, where that agent’s credentials are mounted, and defers while the container is stopped. Skipped for agents with no one-shot mode, command-overridden agents, and sandboxed sessions whose smart_rename_agent is a different agent (only the session agent’s credentials are mounted in the container). Best-effort: a failed or timed-out call leaves the generated name and never affects the prompt.
smart_rename_agent "" Agent used for one-shot utility calls (the smart-rename title and the conversation summary). Empty means use the session’s own agent. Set it to a different one-shot-capable agent (claude, codex, opencode, gemini) to point those calls at a cheaper or more obedient model without changing the session’s working agent. An unknown or one-shot-incapable value falls back to the session’s own agent behavior. For a sandboxed session, a value resolving to any agent other than the session’s own makes smart rename ineligible rather than falling back: the container mounts only the session agent’s credentials. An empty value still means the session’s own agent, so sandboxed sessions auto-name normally by default.
smart_rename_model {} Per-agent model for the throwaway smart-rename title one-shot, keyed by agent name (e.g. claude = "haiku"). A three-to-five-word title should not bill the CLI’s default frontier model. An absent key uses the agent’s built-in default (claude pins its cheap haiku alias, others the CLI default); an empty value forces the CLI default; a non-empty value pins that model via the agent’s model flag (--model / -m). Ids are free-form and not validated, so an id the CLI rejects simply keeps the generated name. Does not affect the conversation summary, which always uses the CLI default. Only agents with a one-shot mode are tunable; a configured value for an agent with no model flag is ignored. The web dashboard sets the built-in-default and pinned states; the empty-string “force CLI default” state is settable via the TUI or this file.
opencode_preassign_session_id false Pre-assign opencode’s session id before launch instead of capturing it afterward by polling opencode’s SQLite store. When on, AoE spawns a throwaway opencode serve, creates the session through POST /api/session, then launches opencode --session <id>, so the id is known before the first prompt (symmetric with Claude’s --session-id). Eliminates the post-launch capture race, at the cost of a short server boot (~2s) on each new host opencode launch. Host sessions only: a sandboxed agent cannot reach the loopback server, so containers keep polling. Best-effort: any failure falls back to the SQLite poller.
agent_extra_args {} Per-agent extra arguments appended after the binary (e.g., { opencode = "--port 8080" }).
agent_command_override {} Per-agent command override replacing the binary entirely (e.g., { claude = "my-claude-wrapper" }).
custom_agents {} User-defined agents: name to command mapping. Custom agent names appear in the TUI agent picker alongside built-in agents.
agent_detect_as {} Status detection mapping: maps an agent name to a built-in agent whose status heuristics should be used.
agent_acp_cmd {} ACP launch command for a custom agent, enabling it to run in structured view (e.g., { "oc-superpowers" = "ocp run sp acp" }). A custom agent with an entry here is structured view-capable; without one it stays tmux-only. Unlike custom_agents, the value is split into argv and run directly, with no shell.
acp.restrict_agents false Restrict structured view sessions to acp.allowed_agents. Off leaves every registered agent available. Read from the global config only: a profile override cannot widen it, so a shared or locked-down deployment cannot be loosened by its own users. Changing the web value requires the passphrase step-up.
acp.allowed_agents [] ACP registry keys a structured view session may run while acp.restrict_agents is on, e.g. ["claude", "codex"]. These are registry keys, not binary names, and each alias counts separately (allowing claude does not allow claude-code). With the restriction on, an empty list denies every agent. Governs the structured view only; a terminal session runs in a pane where any binary can be launched, so it is not constrained here. A policy change applies to new sessions immediately and to an already-running worker when it next respawns or when the daemon restarts, at which point a worker on a now-disallowed agent is terminated rather than reattached.
acp.acp_defaults {} Per-agent defaults for structured view startup (under the [acp] section, not [session]). model is forwarded when the worker starts; effort (thinking) and mode are applied through the agent’s ACP config options (thought_level, mode) when advertised, and skipped with a warning otherwise. effort_by_model (a {model = effort} map) overrides effort for the resolved model. Editable per agent from the web dashboard (Structured view tab, Structured View Defaults). Example: [acp.acp_defaults.opencode] model = "openai/gpt-5.5" effort = "high" mode = "plan".
agents.<name>.status_map {} Trusted global/profile-only hook event to AoE status mappings. Valid statuses are running, waiting, idle, and error. Entries apply by event name to built-in hook defaults, so duplicate event names with different matchers all receive the same status; new event names are added to the installed hooks when the agent format supports event keys. Existing hook files update on the next hook install, usually a new or restarted session. Agent processes with installed status hooks receive AOE_PROFILE, so hook scripts can query the resolved map with aoe -p "$AOE_PROFILE" profile show --status-map <agent> --json.

For Codex, AoE preserves existing [hooks.state] trust data and writes ~/.codex/config.toml through config.toml.lock plus an atomic replace. This keeps repeated or concurrent AoE launches from duplicating hook blocks or leaving partial TOML.

Status Hooks

Status hooks run local shell commands when the TUI sees a session status change. They are disabled by default and are intended for personal machine behavior such as desktop notifications.

[status_hooks]
enabled = true
on_waiting = "notify-send -a aoe 'AoE: Waiting' \"$AOE_SESSION_TITLE is waiting for input\""
on_idle = "notify-send -a aoe 'AoE: Idle' \"$AOE_SESSION_TITLE is idle\""
on_error = "notify-send -u critical -a aoe 'AoE: Error' \"$AOE_SESSION_TITLE errored\""
Option Default Description
enabled false Run configured status hook commands from the TUI. Commands fire once a status has stayed stable for a short built-in debounce (100ms), so rapid flickers don’t spam hooks.
on_starting unset Command run when a session enters Starting.
on_running unset Command run when a session enters Running.
on_waiting unset Command run when a session enters Waiting.
on_idle unset Command run when a session enters Idle.
on_error unset Command run when a session enters Error.
on_change unset Command run on every status change after the status-specific command.

Commands run in the session project directory and receive context through environment variables:

Variable Description
AOE_SESSION_ID Session UUID
AOE_SESSION_TITLE Session title
AOE_PROJECT_PATH Session working directory
AOE_PROFILE Active profile
AOE_TOOL Agent name
AOE_GROUP_PATH Group hierarchy path
AOE_OLD_STATUS / AOE_NEW_STATUS Status before/after the transition
AOE_STATUS_CHANGED_AT Transition timestamp

When both a status-specific hook and on_change fire for the same transition, AoE runs them sequentially (status-specific first). Hook commands are best-effort, non-blocking, and never block status updates or sound playback. They are configurable in global and profile settings only, not repo config, because they run arbitrary local commands.

Custom Agents

Custom agents let you name commands for agents that AoE cannot detect as built-in binaries, such as SSH wrappers, local scripts, or remote Claude sessions. Configure them once in custom_agents, then select the configured name from the TUI picker, aoe add --tool <name>, or the Web session wizard.

[session]
default_tool = "lenovo-claude"
custom_agents = { "lenovo-claude" = "ssh -t lenovo claude" }
agent_detect_as = { "lenovo-claude" = "claude" }
  • custom_agents: Maps a display name to the shell command AoE runs in a tmux pane when that agent is selected. Names appear in the TUI picker alongside built-ins like claude, opencode, and codex, and work with aoe add --tool <name>.
  • agent_detect_as (optional): Reuses a built-in agent’s status detection for the custom agent. Without it, custom agents default to Idle.
  • agent_acp_cmd (optional): ACP launch command that lets the agent run in the structured view (see below).
  • default_tool (optional): Can point at a custom-agent name to default new sessions to it.

Custom agents are always shown as available in the picker since their command may target a remote host or wrapper. All three maps are editable in config files or the TUI settings screen and support profile/repo overrides; profile/repo values fully replace the global map (redeclare any agents you want to keep). The Web wizard can select a configured custom agent but does not expose or edit the command strings.

Running a custom agent in the structured view

Give an agent an ACP launch command in agent_acp_cmd to run it in the structured view UI instead of tmux. The agent must speak the Agent Client Protocol; the command is what AoE execs to start the ACP server.

[session.custom_agents]
"oc-superpowers" = "ocp run sp"

[session.agent_acp_cmd]
"oc-superpowers" = "ocp run sp acp"

The agent_acp_cmd value is split into argv and executed directly with no shell, so for shell features wrap explicitly, e.g. "sh -lc 'source ~/.profile && ocp run sp acp'". The name must match a custom_agents entry and cannot shadow a built-in. A custom agent with no agent_acp_cmd runs in the terminal view.

Host Environment

environment = [
    "CLAUDE_CONFIG_DIR=/Users/me/.claude-accounts/work",
    "GH_TOKEN=$AOE_GH_TOKEN",
    "TERM",
]

Top-level environment injects env vars into every host (non-sandboxed) session spawned at global scope, in both the terminal and the structured view. Useful for pinning a Claude/Codex/Gemini config dir per profile, forwarding an API token, or otherwise scoping per-agent state without exporting variables shell-wide.

Each entry follows the same grammar as sandbox.environment:

  • KEY=value: literal value, passed through verbatim. ~ is not expanded; use an absolute path.
  • KEY=$VAR: read $VAR from the host env at spawn time (skipped with a warning if $VAR is unset).
  • KEY=$$literal: escape; emits KEY=$literal.
  • KEY (bare): passthrough from the host env (skipped with a warning if unset).

In the terminal view every form resolves to a literal KEY=value prefix on the pane command and is therefore visible in ps; for secrets you want hidden from argv there, use sandbox.environment instead. The structured view applies the same list to the agent process’s environment rather than its argv, so values do not appear in ps. Host and sandbox sessions take disjoint code paths: a sandboxed session reads only sandbox.environment, an unsandboxed session reads only the top-level environment. Set both lists if you want a variable available regardless of how the session launches.

Profile-scoped environment replaces the global list entirely (matching the sandbox.environment override semantics).

What host sessions inherit automatically

Independent of the environment list, AoE forwards a fixed set of desktop and session vars from its own environment into every host session, in both the terminal and the structured view: DISPLAY, WAYLAND_DISPLAY, XAUTHORITY, DBUS_SESSION_BUS_ADDRESS, SSH_AUTH_SOCK, and every XDG_* var. Without this, a browser an agent launches (an OIDC login, say) has no way to reach your desktop, since tmux carries only its own narrow update-environment set and the structured view starts its agent from a cleared environment.

Worth knowing what that grants: DISPLAY plus XAUTHORITY is X11 access to your whole session, which means an agent can capture the screen and inject input, not just open a browser window. That is the point of forwarding them, and it has been the terminal view’s behavior since #3079, but it is the tradeoff. A sandboxed session never receives them.

To forward everything else too, rather than naming each var in environment:

[session]
inherit_host_environment = true

Every var AoE itself holds then reaches host sessions, so a GOPATH or CARGO_HOME you exported in your shell is simply there. AOE_* and AGENT_OF_EMPIRES_* keys are never forwarded (they are AoE’s own wiring and credentials), and TERM stays owned by tmux so a pane’s terminal type is not degraded. Off by default: it widens what every agent process can read, including any API token you exported in your shell, so it is opt-in per profile.

In the terminal view the forwarded pairs ride the short-lived tmux new-session invocation as -e KEY=value, so a secret is briefly visible in ps while that command runs. That is narrower than environment, whose values sit in the pane command’s argv for the pane’s whole life, but it is the reason to prefer sandbox.environment for genuine secrets.

When AoE has no environment to forward

Both mechanisms above read AoE’s own environment. Forwarding is a passthrough, not a store, so AoE can only hand a session what it holds itself. If the process that starts AoE has no DISPLAY, neither does your agent.

That matters when something other than your shell starts the daemon. A systemd unit gets a near-empty environment by default, so give it your vars explicitly:

[Service]
# Either name the vars to inherit from the systemd user manager...
PassEnvironment=DISPLAY XAUTHORITY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
# ...or load them from a file you maintain.
EnvironmentFile=%h/.config/agent-of-empires/env

For a user unit, run this from your graphical session to populate the manager AoE then inherits from, then restart the unit so it picks the values up (import-environment does not touch already-running units):

systemctl --user import-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
systemctl --user restart agent-of-empires

That is runtime-only and lost on reboot; ~/.config/environment.d/*.conf is the persistent equivalent. The same principle applies to launchd, cron, and a bare SSH command: the launch context owns its environment, and AoE forwards whatever that is.

To check what a running daemon can actually forward, read its environment directly. On Linux:

tr '\0' '\n' < /proc/$(cat ~/.config/agent-of-empires/serve.pid)/environ

macOS has no /proc, so use ps there:

ps eww -o command= -p "$(cat ~/.agent-of-empires/serve.pid)"

Worktree

The [worktree] block controls automatic git worktree creation for new sessions. Common keys:

[worktree]
enabled = false                                       # auto-enable worktrees for new sessions
path_template = "../{repo-name}-worktrees/{branch}"   # template vars: {repo-name}, {branch}, {session-id}
auto_cleanup = true                                   # prompt to remove the worktree on session delete

See Git Worktrees for the full key reference (bare_repo_path_template, delete_branch_on_cleanup, init_submodules) and template details.

Sandbox (Docker)

The [sandbox] block configures Docker sandboxing for sessions. Common keys:

[sandbox]
enabled_by_default = false                                    # auto-enable sandbox for new sessions
default_image = "ghcr.io/agent-of-empires/aoe-sandbox:latest" # container image
environment = ["GH_TOKEN=$AOE_GH_TOKEN"]                      # env vars forwarded into the container

See Docker Sandbox for the full key reference (cpu_limit, memory_limit, port_mappings, extra_volumes, volume_ignores, volume_ignores_strategy, auto_cleanup, default_terminal_mode), the environment grammar, and credential handling. For env vars on host (non-sandboxed) sessions, use Host Environment instead; the two lists are disjoint.

Host Hooks

The [host_hooks] block declares hooks that run on the host (not inside the sandbox container). Unlike [hooks], which for sandboxed sessions runs inside the container, host hooks run in your host shell and can compute a value with host-only tooling and credentials, then hand only that value to the agent.

[host_hooks]
before_start   = ['echo "GH_TOKEN=$(my-mint-tool "$AOE_REPO_SLUG")"']  # sandboxed sessions
before_session = ['my-account-switcher env']                            # host sessions

The two fields mirror the split the static env lists already make: before_start serves sandboxed sessions (the dynamic counterpart of sandbox.environment), before_session serves host sessions (the counterpart of the top-level environment). A launch runs exactly one of them, chosen by whether the session is sandboxed, so the same key is never minted twice.

before_start runs each time a sandbox container comes up (on create and on restart, so short-lived values are refreshed before the agent launches). It re-mints when the container is created fresh or restarted from a stopped state (including after a Docker daemon restart leaves it stopped); attaching to an already-running container reuses the values from the last run and only backfills if none are stashed yet, so it is not re-run on every reattach. Each KEY=VALUE line the command prints to stdout is injected into the container environment as an inherited variable: the value is passed to the docker invocation through the process environment, never in argv, so it does not appear in ps. Lines that are not KEY=VALUE are ignored, and the hook’s stdout is never logged, so it is safe to print a secret. A non-zero exit aborts bringing the container up.

before_session runs each time a host (non-sandboxed) session is launched, before the agent starts, and applies its KEY=VALUE lines to the agent’s own environment. Same stdout contract as before_start: other lines are ignored, stdout is never logged, and a non-zero exit aborts the launch. It re-runs on every host launch, including restart and a view switch that respawns the agent, and nothing is persisted between launches, so a short-lived value is refreshed rather than replayed.

Minted pairs are applied after the static environment list, so a freshly minted value wins over a same-keyed config entry. Both views honor that: the structured view appends the pairs to the agent process’s environment, and the terminal view passes them through tmux new-session -e while dropping any same-keyed environment entry, which would otherwise shadow them via the shell-assignment prefix.

On secrecy, the terminal view is better than the static environment list but not airtight: a static entry becomes a shell-assignment prefix on the pane command and is therefore visible in ps for the pane’s whole life, whereas a minted value rides tmux new-session -e instead, so it never enters the pane command’s argv. That value is still not private, though: tmux stores it in the session’s own environment for as long as the session exists, and any client with access to the tmux server can read it back with tmux show-environment -t <session>, so it is only as secret as access to that tmux server. For a value that must stay out of both argv and the tmux session environment, use a sandboxed session and before_start, which passes values to docker through the process environment.

The canonical use case is resolving which identity a session runs as at spawn time rather than pinning it in config: an account or provider switcher prints the config dir and endpoint for the account currently selected, refreshing a rotated token in the same step.

[host_hooks]
before_session = ['my-account-switcher env --profile "$AOE_PROFILE"']
CLAUDE_CONFIG_DIR=/Users/me/.claude-accounts/profiles/work
ANTHROPIC_BASE_URL=http://127.0.0.1:8317

Scope note: before_session applies to the agent launch, matching the static environment list. A plain tool session (the extra shell terminal in a session) is not an agent launch and does not run it.

The command’s environment carries:

  • Lifecycle vars: AOE_SESSION_ID, AOE_SESSION_TITLE, AOE_PROJECT_PATH, AOE_PROFILE, AOE_TOOL, AOE_GROUP_PATH, AOE_SESSION_BRANCH (worktree sessions only), and AOE_REPO_SLUG (the owner/repo of the project’s origin remote, when it parses; useful for minting a repo-scoped credential without parsing the path yourself). In the structured view before_session receives the subset available at that spawn site: AOE_SESSION_ID, AOE_PROFILE, AOE_TOOL, and AOE_PROJECT_PATH.
  • The session’s sandbox environment (before_start only), so a per-session value reaches the hook. Set TEST_VAR=foo in the session’s sandbox env (the new-session dialog’s env list accepts KEY=VALUE), and the hook reads $TEST_VAR; a different session can set a different value. This is the per-session input channel (the host process env, e.g. TEST_VAR=foo aoe add ..., only varies per CLI invocation, so in the long-running TUI it would otherwise be fixed for every session). This env is resolved from the per-session list (or profile/global sandbox.environment) but not from a repo’s .agent-of-empires/config.toml, keeping the same host/repo trust boundary as host_hooks itself.

The canonical use case is per-session, repo-scoped, short-lived credentials: mint a one-hour, single-repo token on the host (where the broad credential lives) and inject only the narrow token, so the minting tool and host credential never enter the container.

host_hooks is global/profile only: it is never honored from a repo’s .agent-of-empires/config.toml, because a checked-out repository must not be able to run host commands. Declare it in your global or profile config.toml.

tmux

[tmux]
status_bar = "auto"
mouse = "auto"
clipboard = "auto"
# socket_name = "aoe"
vt_live = true
Option Default Description
status_bar "auto" Paints aoe’s themed status bar (session title, branch, sandbox, detach hint) on its own sessions. "auto" steps aside whenever you have a tmux config at all, because the bar is a whole theme rather than one option and a half-merge of yours with aoe’s would please nobody; "enabled" always paints it; "disabled" never does. Not painting it reverts aoe’s session-scoped status* overrides so your own config governs, so "disabled" means “stop styling the bar”, not “hide it”.
mouse "auto" Sets tmux mouse on aoe’s sessions, which is what turns a wheel scroll (or the Web dashboard’s touch scroll) into tmux copy-mode scrollback. "auto" leaves the option untouched when your own tmux config sets mouse, so your set -g mouse ... governs, and enables it otherwise (including when your tmux config exists but never mentions mouse, since tmux’s own default is off). "enabled" always turns it on; "disabled" always turns it off, for aoe’s sessions only.
clipboard "auto" Forwards OSC 52 clipboard escape sequences from the wrapped agent (Claude Code, OpenCode, Codex, etc.) to your terminal or Web dashboard. Without this, “select to copy” inside the agent silently fails. Sets set-clipboard on and allow-passthrough on for the aoe session (the attached path), and in live-send aoe itself extracts the agent’s OSC 52 from the pane stream and pushes it to the native or browser clipboard. "auto" steps aside only when your own tmux config sets one of those two options, the same per-option rule as mouse; "enabled" always applies them; "disabled" never does. Live-send forwarding is on for "auto" and "enabled" (your tmux config cannot affect aoe’s in-process transport, so "auto" does not defer to it here); "disabled" turns it off.

Detection for the per-option modes (mouse, clipboard) reads ~/.tmux.conf, $XDG_CONFIG_HOME/tmux/tmux.conf, and ~/.config/tmux/tmux.conf, looking for a set / setw of the option. It is deliberately conservative: an option reached via source-file, wrapped in if-shell, guarded by a false %if, or set from inside a key binding (bind m set -g mouse) is not detected, and aoe applies its own value. Set the mode to "disabled" if you keep yours in one of those places. /etc/tmux.conf is not consulted; it is not your file. | socket_name | unset | Run aoe’s sessions on a private tmux server with this socket name (passed as tmux -L <name>), so your own tmux ls and hand-managed sessions stay separate from aoe’s. Leave unset to share the default tmux server (the current behavior). Must be a bare name, not a path; a value with a / or \ is ignored. Takes effect on the next aoe start. Global/profile only. | | vt_live | true | Render live views (the TUI live preview and the web/mobile live terminal) from a persistent VT channel: tmux pipe-pane streams the pane into an in-process terminal grid, and keystrokes go back over the same socket. Needs tmux 3.4+; panes that cannot arm a channel fall back to the polling capture-pane / send-keys path automatically. Disable only to troubleshoot the VT transport; the fallback is slower and loses agent clipboard forwarding in live-send. Applies in place: the TUI picks a change up on the next capture cycle, web connections on their next reconnect. |

Diff

[diff]
default_branch = "main"
context_lines = 3
Option Default Description
default_branch (auto-detect) Base branch for diffs
context_lines 3 Lines of context around changes

Updates

[updates]
update_check_mode = "notify"
Option Default Description
update_check_mode "notify" One of auto, notify, off. See below.

Checks hit GitHub at most once a day (a built-in server-side cache TTL); the web dashboard re-polls the cached status hourly while open.

update_check_mode

  • auto: when a new release is detected, install it silently in the background using the same tarball install path as aoe update. The new binary is picked up on the next launch (no mid-session restart). Only fires when the install location is writable; Homebrew installs fall through to manual brew upgrade.
  • notify (default): show the TUI banner and the CLI eprintln nag. Press Ctrl+x on the banner to snooze for the current latest version; the banner returns automatically when a newer release ships.
  • off: skip every check, banner, fetch, and dashboard poll. Use this on offline / restricted networks.

The TUI banner snooze is persisted to app_state.dismissed_update_version (in state.toml, see above), so dismissing on v1.5.3 keeps the banner hidden across aoe restarts until v1.5.4 (or later) ships. See #1140.

Configs written for older aoe versions used a check_enabled boolean and an orphaned auto_update field. Migration v009 runs once on startup and rewrites check_enabled = false to update_check_mode = "off", check_enabled = true (or missing) to "notify", and drops auto_update entirely. The former check_interval_hours, notify_in_cli, and web_poll_interval_minutes knobs are now fixed built-ins; migration v022 drops them from saved configs.

Tools

The [tools.*] block configures dev tools tied to each agent session’s working directory. Each entry has a required command, an optional hotkey in Alt+<single-char> format, and optional background = true for fire-and-forget commands that should not create a tmux tool session.

[tools.lazygit]
command = "lazygit"
hotkey = "Alt+g"

[tools.yazi]
command = "yazi"
hotkey = "Alt+f"

[tools.github]
command = "gh repo view --web"
hotkey = "Alt+o"
background = true

See Tool Sessions for the full reference, hotkey rules, and lifecycle.

Profiles

Profiles provide separate workspaces with their own sessions and groups. Each profile can override any of the settings above.

aoe                 # Uses "default" profile
aoe -p work         # Uses "work" profile
aoe profile create client-xyz
aoe profile list
aoe profile default work   # Set "work" as default

Profile overrides go in ~/.agent-of-empires/profiles/<name>/config.toml and use the same format as the global config.

Repo Config

Per-repo settings go in .agent-of-empires/config.toml at your project root. Run aoe init to generate a template.

Repo config supports: [hooks], [session], [sandbox], and [worktree] sections. It does not support [tmux], [updates], [claude], or [diff] (those are personal settings).

See Repo Config & Hooks for details.