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

PlatformGlobal 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

VariableDescription
AGENT_OF_EMPIRES_PROFILEDefault profile to use
AGENT_OF_EMPIRES_DEBUGEnable debug logging to debug.log in app data dir (1 to enable). Legacy alias for AOE_LOG_LEVEL=debug.
AOE_LOG_LEVELFile 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)
OptionDefaultDescription
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"
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"
OptionDefaultDescription
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_secs0Seconds 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_activefalseWhen 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_minutes15Minutes 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.
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_defaultfalseEnable 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_hookstrueInstall 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_renametrueAuto-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. Skipped for sandboxed sessions (a host one-shot lacks the container’s auth), agents with no one-shot mode, and command-overridden agents. 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.
opencode_preassign_session_idfalsePre-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.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\""
OptionDefaultDescription
enabledfalseRun 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_startingunsetCommand run when a session enters Starting.
on_runningunsetCommand run when a session enters Running.
on_waitingunsetCommand run when a session enters Waiting.
on_idleunsetCommand run when a session enters Idle.
on_errorunsetCommand run when a session enters Error.
on_changeunsetCommand run on every status change after the status-specific command.

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

VariableDescription
AOE_SESSION_IDSession UUID
AOE_SESSION_TITLESession title
AOE_PROJECT_PATHSession working directory
AOE_PROFILEActive profile
AOE_TOOLAgent name
AOE_GROUP_PATHGroup hierarchy path
AOE_OLD_STATUS / AOE_NEW_STATUSStatus before/after the transition
AOE_STATUS_CHANGED_ATTransition 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 the host command line for every session spawned at global scope. 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).

All forms resolve to a literal KEY=value argument on the spawned process and are therefore visible in ps. For secrets you want hidden from argv, use sandbox.environment instead. 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).

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

[host_hooks]
before_start = ['echo "GH_TOKEN=$(my-mint-tool "$AOE_REPO_SLUG")"']

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.

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).
  • The session’s sandbox environment, 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
OptionDefaultDescription
status_bar"auto""auto": apply if no ~/.tmux.conf; "enabled": always apply; "disabled": never apply
mouse"auto"Same modes as status_bar. Controls mouse support in aoe tmux sessions.
clipboard"auto"Same modes. Forwards OSC 52 clipboard escape sequences from the wrapped agent (Claude Code, OpenCode, Codex, etc.) to your terminal. Without this, “select to copy” inside the agent silently fails. Sets set-clipboard on and allow-passthrough on on the aoe tmux session (the attached path), and in live-send aoe itself extracts the agent’s OSC 52 from the pane stream and pushes it to your clipboard. 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.
socket_nameunsetRun 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_livetrueRender 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
OptionDefaultDescription
default_branch(auto-detect)Base branch for diffs
context_lines3Lines of context around changes

Updates

[updates]
update_check_mode = "notify"
OptionDefaultDescription
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.