Skip to content

Config Reference

Complete field-by-field reference for leo.yaml.

Config lives at ~/.leo/leo.yaml (the Leo home directory).

See Delegation profiles for stable work roles routed to templates by an active profile.

defaults

Settings inherited by all tasks and templates unless overridden.

Field Type Required Description
model string No Default model, validated by the resolved harness (claude and codex: any non-whitespace string — the CLI resolves aliases like sonnet/fable and full IDs like claude-fable-5 itself; opencode: must be provider/model). Defaults to sonnet. Does not cascade to a scope whose resolved harness differs from defaults.harness — see Harnesses → Cross-harness model cascade.
max_turns int No Default maximum agent turns per execution. Defaults to 15. Ignored by codex and opencode (no per-turn cap upstream).
harness string No Adapter name for this scope and everything that cascades from it. One of claude, codex, opencode. Defaults to claude. All three run every leo primitive (tasks, ephemeral agents, persistent tasks) — see Harnesses.
harness_options map No Adapter-specific options, strictly validated by the resolved harness. For claude: permission_mode, bypass_permissions, remote_control, agent, allowed_tools, disallowed_tools, append_system_prompt. For codex: permission_mode. For opencode: permission. See Harnesses for the full reference and merge rules.
environments list No Ordered named environments applied to everything that does not set its own list.
idle_suspend_after string No Idle interval (Go duration, e.g. 24h) after which an ephemeral agent is auto-stopped (dormant, auto-wakes on the next message). Empty/unset disables it. See Idle-suspend.
push.chat bool No Whether an agent's Leo Chat messages push to paired phones. Unset means on; a template's push.chat overrides it.
push.needs_input, push.finished, push.errored bool No Whether an attention transition into that state pushes to paired phones. Each state is its own switch; unset means on, so nothing changes until you turn one off. A template's own push block overrides these per state. Edits apply to the next transition with no restart.
dispatch.viewer map No Dispatch viewer settings: placement (pane, window or background, default pane), max_panes (1–6, default 3), and main_pane_height (20–90 percent, default 60).
dispatch.max_concurrent int No Cap on dispatch turns running at once (default 6, 0 = unlimited, negative is rejected). New dispatches and interactive follow-ups beyond the cap queue in one FIFO line instead of being rejected. See Dispatches → Records and limits.
dispatch.approval_timeout string No How long an interactive claude dispatch's permission prompt waits for the orchestrator's decision (Go duration, positive, at most 24h; default 30m) before the prompt falls back to the dispatch's pane. See Dispatches → Permission prompts.
dispatch.adopt_timeout string No How long a restarted daemon waits for an adopted interactive dispatch to show life (its bridge reconnecting, a hook report, or its pane changing) before closing it as daemon restarted (Go duration, positive, at most 1h; default 2m). See Dispatches → Daemon restarts.

Custom Anthropic-compatible endpoints (z.ai GLM, OpenRouter, Moonshot, DeepSeek, MiniMax, …) are configured via each scope's own env: map (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN) — see Harnesses → providers is gone.

harness_options by harness

Every scope's harness_options map is strictly validated by that scope's resolved harness: — unknown keys, wrong types, and invalid enum values are all rejected at config load and before every web-UI save. Full behavior (merge rules, resume, MCP bridge, auth, etc.) is in Harnesses.

claude (7 keys):

Key Type Meaning
permission_mode string acceptEdits, auto, bypassPermissions, default, dontAsk, plan.
bypass_permissions bool Legacy --dangerously-skip-permissions fallback; only consulted when permission_mode is empty.
remote_control bool Enables --remote-control.
agent string Path to a subagent file, passed via --agent.
allowed_tools list of strings Tool whitelist.
disallowed_tools list of strings Tool blacklist.
append_system_prompt string Extra text appended to the system prompt.

codex (1 key):

Key Type Meaning
permission_mode string read-only (codex default), workspace-write, danger-full-access, or approve-for-me (workspace-write, with escalations routed to codex's automatic approval reviewer rather than a human).

opencode (1 key):

Key Type Meaning
permission map Per-tool allow/ask/deny, or a nested pattern map of the same. Delivered via a per-spawn OPENCODE_CONFIG_CONTENT overlay, not argv.

web

Field Type Required Default Description
enabled bool No false Enable the web dashboard.
port int No 8370 TCP port for the web UI.
bind string No 127.0.0.1 Bind address. Loopback-only by default.
allowed_hosts list of strings No [] Extra hostnames/IPs accepted in the Host and Origin headers, in addition to loopback. Required when bind is non-loopback. Entries must not include a port.
trusted_proxies list of strings No [] IPs or CIDRs whose requests with a non-empty Remote-User header are authenticated as the operator.
public_urls list of strings No [] Origins a paired phone reaches this daemon at (e.g. https://leo.example.com behind a Cloudflare Tunnel). leo pair advertises them; each one's exact host[:port] is accepted in Host/Origin. Absolute http(s) URLs with no path, query or credentials. Empty = the listener's LAN addresses.

When enabled, the daemon serves a web dashboard with agent monitoring, task management, agent dispatch, config editing, and cron preview.

Authentication

Both browser and API access require the same token. On first start the daemon mints a random 64-hex-char token and writes it to ~/.leo/state/api.token (mode 0600).

Browser login. Visit the dashboard and you'll be redirected to /login. Paste the token there and a 7-day session cookie is set (HttpOnly, SameSite=Strict). For convenience:

leo web login-url

prints a one-click URL (http://<bind>:<port>/login?token=...) — the login page auto-submits if the token is in the query string. The URL contains the token; don't share it.

Click Sign out at the bottom of the sidebar to destroy the session.

API access. /api/* endpoints take the token in an Authorization: Bearer header:

curl -H "Authorization: Bearer $(cat ~/.leo/state/api.token)" \
  http://127.0.0.1:8370/api/status

Rotate the token by deleting api.token and restarting the daemon. Existing browser sessions remain valid until they expire (7 days).

Token scope. There are two tokens, with different privileges:

Token File Accepted on Held by
Operator ~/.leo/state/api.token everything — /login, the browser UI, /api/*, /web/* you, your scripts, channel plugins
Agent ~/.leo/state/agent.token /api/* and /web/agent/{name}/{message,send,interrupt} only every supervised agent, via LEO_API_TOKEN
Device ~/.leo/state/devices/devices.json (SHA-256 hashes only) everywhere the operator token is as a bearer, except /login a phone paired with leo pair

The operator token grants access to the full daemon API — including routes that restart the service, mutate config, and write prompt files. Treat it like a root credential.

Agents get the narrower token so a credential that escapes an agent — into a transcript, a log, a channel message — cannot be exchanged for a web session. It is rejected at /login and on the config editor, which renders template env: values in full. Rotate either by deleting its file and restarting the daemon.

This bounds blast radius; it is not a sandbox. Agents run as your user and can read ~/.leo/leo.yaml directly, so any credential in config is reachable by an agent that goes looking. Don't install a channel plugin you wouldn't trust with the contents of your config.

Non-loopback access

bind defaults to 127.0.0.1. To expose the web UI on your LAN:

web:
  enabled: true
  bind: 0.0.0.0
  port: 8370
  allowed_hosts:
    - 192.0.2.10      # the IP your LAN will use to reach this host
    - leo.local      # or a hostname

allowed_hosts entries are checked against the incoming Host and Origin headers to defend against DNS-rebinding and drive-by cross-origin POSTs. Entries must be bare hostnames or IPs — no port, no scheme. allowed_hosts is required when bind is non-loopback; leo validate will fail otherwise.

For an Authelia, oauth2-proxy, or similar ForwardAuth proxy, configure its peer IPs or CIDRs in trusted_proxies. Requests arriving directly from those peers with a non-empty Remote-User header are treated as the operator. The proxy must overwrite or strip any client-supplied Remote-User header (for example, Authelia ForwardAuth with Traefik's authResponseHeaders does this). The proxy's public hostname must also be in allowed_hosts; its forwarded port is ignored for trusted peers. Zoned IPv6 addresses such as fe80::1%en0 are rejected by validation.

web:
  enabled: true
  bind: 0.0.0.0
  port: 8370
  allowed_hosts: [leo.example.com]
  trusted_proxies: [192.0.2.9, 198.51.100.5]

Never trust a range that includes untrusted LAN hosts: anyone who can reach the port directly from a trusted address could forge the header.

The daemon prints a startup warning when bind is non-loopback.

Pairing a phone

leo pair prints a QR code and a one-line JSON payload — {daemon_id, name, urls, pairing_code} — for the Leo iOS app (leo pair --json prints the payload with warnings, expires_at and the exact qr_payload string instead; see leo pair). leo web enable turns the listener on first if it is off. The code works once and expires after 5 minutes. The app redeems it at POST /api/v1/pair (the one unauthenticated route; rate-limited to 5 attempts per minute per peer and 20 overall) for its own device token. leo devices list shows paired phones; leo devices revoke <id> invalidates a token immediately; the device's open streams (SSE, attach) are closed within about 2 seconds, and its push registration goes with it.

Push. When an agent's attention moves into needs_input, finished or errored, the daemon encrypts a small alert (AES-256-GCM, aad leo-push-v1, under the key the phone registered) and POSTs it to {relay_url}/push for every registered device, asynchronously with a bounded queue, a 10 s timeout and one retry on a 5xx or network error. A 410 from the relay deletes that registration. The agent name never reaches the relay or Apple unencrypted (the collapse id is a hash). Repeats of the same state do not re-notify. The alert's title is the short state line ("Has a question", "Needs permission: Bash", "Finished", "Hit an error"); an optional body says what needs attention — the question, the command or path awaiting permission, or the finished turn's summary (clipped to 400 characters, omitted when there is nothing to show, and older app builds ignore it). The content stays end-to-end encrypted to the phone, but it appears in the notification, so what shows on a locked screen follows iOS's Show Previews setting (When Unlocked keeps it off the lock screen). Which states push is configurable per state, daemon-wide and per template, via push (see defaults and templates); a change applies live. If the config cannot be read when a transition arrives, the push is sent anyway.

Routes for the app (bearer auth; device or operator token):

Route Behaviour
PUT /api/v1/devices/{id}/push {grant, key, relay_url} push registration (key: base64 of 32 bytes, AES-256; relay_url must be https://). A device may write only its own; the operator api.token may write any.
DELETE /api/v1/devices/{id}/push Unregisters push for that device (same auth; idempotent).
GET /api/v1/agents/{name}/attach WebSocket onto tmux attach -r. Binary frames: terminal output. Text frames: JSON control — ping/pong (the daemon pings every 30 s) and {"type":"resize","cols":N,"rows":M}. Optional ?cols=&rows=. Binary input frames close the socket with 1008.

chat

Leo Chat settings.

Field Type Required Default Description
whisper_model string No -- whisper.cpp ggml model for voice notes (~ expands). Unset: voice notes are delivered untranscribed.
permission_timeout duration No 120s How long a Claude permission prompt waits for an answer in chat, at most 1h.
question_timeout duration No 1h How long an AskUserQuestion questions card waits for an answer in chat, at most 1h.

client

Remote-host definitions used by the leo agent CLI when leo is invoked as a client of a different machine. Empty on server configs.

Field Type Required Default Description
default_host string No — Host name to use when --host and LEO_HOST are unset.
hosts map No {} Named host definitions keyed by short name.

Each entry under hosts has:

Field Type Required Description
ssh string Yes SSH target passed verbatim (e.g. user@host, or a Host alias from ~/.ssh/config).
ssh_args list No Extra arguments inserted between the target and the remote command (e.g. ["-p", "2222"]).
leo_path string No Absolute path to leo on the remote host. Defaults to $HOME/.local/bin/leo (matches install.sh). Override when leo is installed elsewhere or the remote's non-interactive SSH shell doesn't have it on PATH.
tmux_path string No Path to tmux on the remote host. Used by agent attach and agent logs --follow. Defaults to tmux (relies on PATH). Set to /opt/homebrew/bin/tmux for macOS arm64 homebrew remotes, /usr/local/bin/tmux for macOS intel.
client:
  default_host: prod
  hosts:
    prod:
      ssh: alice@leo.example.com
      ssh_args: ["-p", "2222"]
      leo_path: /usr/local/bin/leo
      tmux_path: /opt/homebrew/bin/tmux
    dev:
      ssh: alice@devbox.local

Why leo_path exists: SSH runs a non-interactive shell on the remote, which doesn't source .zshrc / .bashrc. If leo lives in ~/.local/bin and PATH is only extended in .zshrc, bare leo won't resolve. The default full path avoids that; set leo_path explicitly when the remote installs leo elsewhere (Homebrew, /usr/local/bin, etc.).

Resolution order for the target host: --host flag → LEO_HOST env → default_host → first entry sorted by key → localhost (only when no hosts are configured). --host localhost is a hard override. On a host with a follow block, the "first entry" step is skipped: with no default_host the CLI targets localhost, since the lead's entry is there to be followed, not to be the CLI's remote. See the Remote CLI guide.

api_clients

External API clients — agents or jobs Leo does not supervise — each with a scoped bearer token. Manage them with leo client add|list|rm; see API Clients.

Field Type Required Description
can_message list One of the two Agent names or globs the client may start a turn in (POST /web/agent/<target>/message).
can_post list One of the two Agent names or globs the client may post chat text to, with no turn (POST /web/agent/<target>/chat/post).
api_clients:
  pipe:
    can_post: ["pipe-*"]

An empty scope denies everything, and a client with neither list is rejected at load.

Channels

Leo does not ship with any built-in messaging channel. Channels are Claude Code plugins the user installs separately (e.g. Telegram, Slack, webhook). In leo.yaml they are referenced by plugin ID strings like plugin:telegram@claude-plugins-official on the channels: field of templates and tasks.

Leo passes the resolved list to the spawned Claude process via the LEO_CHANNELS environment variable. The plugin owns its own credentials, routing, and inbound-message handling.

To install a channel plugin:

claude plugin install telegram@claude-plugins-official

Then reference it:

templates:
  assistant:
    workspace: ~/agents/assistant
    channels: [plugin:telegram@claude-plugins-official]

Development Channels

For channel plugins that aren't yet published to a registry (or for local plugin development), templates and tasks accept a parallel dev_channels: field. Leo passes each entry to Claude Code via --dangerously-load-development-channels <id> and exports the list in LEO_DEV_CHANNELS.

templates:
  assistant:
    workspace: ~/agents/assistant
    channels: [plugin:blackpaw-telegram@blackpaw-plugins]
    dev_channels: [plugin:blackpaw-telegram@blackpaw-plugins]

Validation matches channels — each entry must be a valid plugin ID.

Claude Code displays a confirmation prompt before loading development channels. For supervised agents, Leo watches the tmux pane and auto-accepts the prompt so the agent starts non-interactively. Silent/nonexistent entries are ignored by Claude Code without warning — verify spellings carefully.

environments

Top-level map of named env maps (environments.<name>.<KEY>: value), referenced by name from defaults, templates, tasks and leo agent spawn --environment. See Environments. A value may be a secret reference resolved at launch.

appearance_rules

Ordered list of rules that give agents a palette color and/or an emoji, e.g. - when: { field: environments, op: contains, value: prod } with color: red. Fields are name, environments, template and harness; ops are is, contains, starts_with and glob. A value set by hand on an agent wins over every rule. See Agent Appearance.

tasks

Each task is a named entry under the tasks map. Tasks are invoked by the in-process cron scheduler or manually via leo run <task>.

tasks:
  daily-briefing:
    schedule: "0 7 * * *"
    timezone: America/New_York
    prompt_file: prompts/daily-briefing.md
    enabled: true
Field Type Required Default Description
workspace string No ~/.leo/workspace/ Working directory.
schedule string Yes -- 5-field cron expression.
timezone string No System default IANA timezone (e.g., America/New_York).
prompt_file string Yes -- Path to prompt file, relative to workspace.
model string No defaults.model Model override, validated by the resolved harness. Does not fall back to defaults.model when this task's harness differs from defaults.harness — see Cross-harness model cascade.
harness string No defaults.harness Adapter override for this task. claude, codex, and opencode all support one-shot and runtime: persistent tasks (persistent tasks deliver into agents, which all three harnesses support). See Harnesses.
harness_options map No merged with defaults.harness_options (same harness only) Adapter-specific options — for claude: permission_mode, bypass_permissions, allowed_tools, disallowed_tools, append_system_prompt. bypass_permissions at task scope is honored (not defaults-only). For codex: permission_mode. For opencode: permission. See Harnesses.
max_turns int No defaults.max_turns Max turns override. Ignored by codex/opencode.
environments list No defaults.environments Ordered named environments for this task's run (or implicit persistent agent). Not allowed on a persistent task that names a template:.
timeout string No 30m Max duration before kill (e.g., 30m, 1h).
retries int No 0 Retry attempts on failure.
channels list No -- Channel plugin IDs used by the channel notification of notify_on_fail (oneshot tasks) or reply delivery (persistent tasks). Only valid on a channel-supporting harness. For a runtime: persistent task with template: set, must be a subset of the target template's channels:.
dev_channels list No -- Unpublished channel plugin IDs loaded via --dangerously-load-development-channels.
notify_on_fail bool No false On a oneshot task that fails (all attempts exhausted, not interrupted), post a failure card to the owner's Leo Chat thread, and, when channels: is set, also spawn a short child claude invocation to notify those channels. On a persistent task, enqueue a follow-up failure-notice prompt into the target agent (requires channels:).
enabled bool No false Whether the scheduler should run this task.
silent bool No false Prepend silent-mode preamble to prompt.
runtime enum No oneshot oneshot spawns a fresh claude -p per firing; persistent injects the prompt into a supervised agent instead. See Persistent Tasks.
template string No -- Only meaningful with runtime: persistent. Names a templates: entry — the task's target is that template's agent. Omit for an implicit target: an agent named after the task, synthesized from the task's own workspace/model/channels/dev_channels. See Persistent Tasks.
queue_max int No 5 Only meaningful with runtime: persistent. Max queued firings per target agent; overflow is rejected with "queue full". 0 also means "use the default".
owner string No see Leo Chat Agent whose Leo Chat thread gets this task's cards (leo_chat_post from a run). Validated as an agent name only; resolved when a card posts. Unset: a persistent task's own agent, else the agent whose workspace matches, else assistant.

Silent Mode

When silent: true, Leo prepends a preamble instructing the agent to work without narration. The agent should deliver its final message via a configured channel plugin or output NO_REPLY if there's nothing to report.

templates

Templates are reusable blueprints for spawning ephemeral agents. Dispatch them via the HTTP API, a channel plugin that exposes agent commands, or the web UI.

templates:
  coding:
    model: sonnet
    workspace: ~/agents
    harness_options:
      remote_control: true
      permission_mode: auto
Field Type Required Default Description
workspace string No ~/.leo/agents/ Base directory for agent workspaces. Repos are cloned as subdirectories.
channels list No -- Channel plugin IDs for spawned agents. Only valid on a channel-supporting harness.
dev_channels list No -- Unpublished channel plugin IDs loaded via --dangerously-load-development-channels.
model string No defaults.model Model, validated by the resolved harness.
harness string No defaults.harness Adapter override for this template. All three harnesses support ephemeral agents. See Harnesses.
harness_options map No merged with defaults.harness_options (same harness only) Adapter-specific options — for claude: permission_mode, bypass_permissions, remote_control, agent, allowed_tools, disallowed_tools, append_system_prompt. remote_control cascades like the rest (template, then defaults.harness_options) and defaults to true when neither sets it. See Harnesses.
max_turns int No defaults.max_turns Max turns.
mcp_config string No -- Path to MCP config file.
add_dirs list No -- Additional directories.
env map No -- Environment variables. Wins over environments. A value may be a secret reference.
environments list No defaults.environments Ordered named environments; replaces (does not extend) the inherited list.
idle_suspend_after string No defaults.idle_suspend_after Idle interval (Go duration) before agents from this template are auto-stopped (dormant, auto-wakes on the next message). Empty inherits the default.
isolation string No -- worktree runs every dispatch and consult of this template from a throwaway managed Git worktree at the caller's committed HEAD — the same as passing isolation: "worktree" to leo_dispatch, which takes precedence when given. The subagent's prompt names the worktree and warns that uncommitted changes in the caller's tree are not visible. Lets a reviewer run tests with a writable sandbox (e.g. codex permission_mode: workspace-write) without touching the real tree. See Dispatches.
push.chat bool No defaults.push.chat Per-template override of whether this template's agents push their Leo Chat messages.
push.needs_input, push.finished, push.errored bool No defaults.push.* Per-state override of whether agents from this template push to paired phones. Resolution per state: this template, then defaults.push, then on. Agents with no template (implicit persistent-task agents) use defaults.push.
permissions map No -- Narrows the leo MCP tool surface for agents spawned from this template (deny_tools) and which agents/templates they may message, spawn, or consult (can_message, can_spawn, can_consult). See Permissions.

When dispatching with a repo (/agent coding owner/repo via a channel plugin, or leo agent spawn coding --repo owner/repo), Leo clones the repo into <workspace>/<repo> using gh. The agent session is named leo-<template>-<owner>-<repo>. A repo is optional — leo agent spawn coding with no repo runs the template as-is directly in workspace, and the agent is named after the template (coding in this example).

Templates also back persistent tasks (runtime: persistent): a task with template: <name> delivers its prompts into that template's agent instead of spawning claude -p, spawning/resuming it on demand rather than at leo service boot. Multiple tasks can share one template: and therefore one agent and one FIFO queue. A runtime: persistent task with no template: gets an implicit target instead — an agent named after the task, synthesized from the task's own fields. See Persistent Tasks for the full mechanics (ensure-exists, queueing, completion reporting, leo agent reset).

Sharing templates between machines (templates_file)

Set templates_file to keep agent templates in a separate file (for example in a git repo you sync across machines) while the rest of leo.yaml stays per-machine:

# leo.yaml
templates_file: ~/dotfiles/leo/templates.yaml
# ~/dotfiles/leo/templates.yaml: one top-level key, same schema as `templates:` above
templates:
  coding:
    model: opus
    environments: [work]
  • Path. ~ is expanded; a relative path resolves against the directory holding leo.yaml. The file must exist and parse, or Leo refuses to load the config and names the path. Any top-level key other than templates is an error.
  • Merge. Effective templates are the shared ones, then the local leo.yaml templates by name. A local template replaces a shared one of the same name entirely; fields are never merged.
  • Per-machine values belong in local environments (referenced by name from shared templates) or in ~ paths. A shared template that references an environment the machine lacks fails validation like any other.
  • Source. The web UI shows a shared, local or local · overrides shared badge on each template, and leo template list gains a SOURCE column (--json adds source).
  • Writes. Editing or deleting a shared template writes the shared file; editing or deleting a local template writes leo.yaml. Each file is rewritten only when its content changed, so editing a local template never dirties your repo. Deleting a local override uncovers the shared template again. Renaming a shared template keeps it in the shared file. New templates go to leo.yaml, unless you tick shared on the web create form or pass leo template add --shared. Comments in either file are not preserved on a rewrite.
  • Revisions. The config revision (the web/API If-Match) covers both files, so a git pull that changes the shared file makes a stale edit fail its revision check.
  • Reload. Leo does not watch the shared file and never runs git. After a git pull, run leo service reload, use the web reload, or make any save.
  • Trust. The shared file has the same trust level as leo.yaml: templates can set harness_options (for example, bypass permissions). Only point templates_file at a repo you control.
  • Renaming a shared template rewrites task references in this machine's leo.yaml only; other machines whose tasks use the old name fail validation after their next pull.

Following a lead host (follow)

A host can mirror another Leo host's templates, environments, delegation and defaults over ssh instead of sharing a file. See Following a lead host for setup, the cache and errors.

client:
  hosts:
    server: {ssh: evan@10.0.2.10}
follow:
  host: server
  interval: 5m
Field Type Required Default Description
host string Yes — A client.hosts name: the lead. Not localhost or a loopback ssh target.
interval duration No 5m How often the daemon re-pulls the lead (minimum 30s). The cache is called stale after 3 × interval without a successful pull.
  • Layering, lowest to highest: the lead, the follower's templates_file (templates only), the follower's leo.yaml. A local template, environment or delegation profile replaces the lead's entry of the same name entirely. In defaults and the other delegation keys, a key set locally replaces the lead's value for that key (defaults.dispatch.* and defaults.push.* one level deeper; harness_options whole; an empty local value overrides too, except defaults.model: "", defaults.max_turns: 0 and delegation.active_profile: "", which count as unset).
  • Trust. The follower runs leo config export --follow on the lead over ssh with the client.hosts entry, the same trust as leo agent --host. The lead needs no config. Lead entries can set harness_options (for example, bypass permissions): only follow a host you control.
  • Writes. Lead entries are read-only on the follower (owned_by_lead); leo template override <name> (and the web Override locally button) copies one into leo.yaml for editing.
  • Invalid entries. A lead entry that fails on the follower (unknown harness, harness binary not on PATH, a missing CLAUDE_CONFIG_DIR, ...) is listed as lead · invalid and unusable instead of failing the whole config.
  • CLI target. With follow set and no client.default_host, the CLI's default host is localhost.

Idle-suspend

Ephemeral agents can be auto-stopped after a period of inactivity to free local resources (the claude process and tmux session are killed) while preserving the record, workspace, and stored conversation — an agent that goes dormant this way is no different from a manually stopped one, except that it auto-wakes on the next message. Off by default — enable it by setting an interval:

defaults:
  idle_suspend_after: "24h"      # global default

templates:
  reviewer:
    idle_suspend_after: "30m"    # per-template override

Or per spawn: leo agent spawn reviewer owner/repo --idle-suspend 24h.

The cascade is spawn flag → template → defaults; the resolved interval is stamped onto the agent at spawn time. Behavior:

  • Activity is measured by the agent's tmux session_activity — injected prompts, interactive typing in an attached pane, and the agent's own output all count.
  • An agent with a client attached is never auto-stopped, even past the interval (so reading scrollback won't yank the session out from under you).
  • An idle-stopped agent shows as stopped in leo agent list, same as any other dormant agent. It auto-wakes on the next incoming message (e.g. leo_send_message), rejoining its prior conversation via --resume. You can also start or stop it manually:
leo agent stop <name>
leo agent start <name>

A manual leo agent stop does not auto-wake — that behavior is specific to the idle sweep. Start it explicitly when you're ready to resume it.

  • Dormant agents stay dormant across daemon restarts (they are not resurrected at boot), and their worktrees are never removed — only leo agent delete removes a record or worktree.

State directory

Leo's runtime state lives under ~/.leo/state/ (or <home>/state for a non-default leo home): sessions.json (session-id bookkeeping — including the post-hoc-discovered codex/opencode session ids, see Harnesses → Session driver semantics), history.json, api.token (see Authentication), and on a follower follow-cache.json and follow-status.json (see Following a lead host). There is no more per-harness state directory — codex and opencode drive a resident tmux TUI the same way claude does, so their session ids live in the same sessions.json store rather than separate per-tmux-session files.

If you're updating from a pre-uniform-tmux-TUI build, stale state/opencode/*.json and state/transcripts/*.log files from the old model are inert and safe to delete; see the migration note in Harnesses.

Override Cascade

Task and template settings override defaults:

effective value = task/template value OR defaults value