configuration

All configuration is TOML. ACE merges multiple layers into one effective config. Scalar, list, and map fields do not all use the same merge rule.

layer merge order

# 1. School defaults (read-only, from school.toml)
# 2. User-global config
~/.config/ace/ace.toml

# 3. Project config (checked into git, shared)
ace.toml

# 4. Project-local config (gitignored, per-machine)
ace.local.toml
Local overrides project, project overrides user-global, and user-global overrides school defaults for scalar replacement fields. env maps merge additively, while selection lists use the field-specific rules in the skills selection table. Missing and explicitly empty values can also differ, as documented for each field below.

ace.toml / ace.local.toml

Project and local config use the same file format. Local config is gitignored and holds personal settings such as trust and resume; those settings belong in local or user config, and project ace.toml ignores them. Unknown or misplaced fields are reported and ignored.

# ace.toml example
school = "ace-rs/school"
backend = "claude"
session_prompt = "Always run tests before committing."

[env]
RUSTFLAGS = "-D warnings"
KeyType / ValuesDefaultDescription
schoolstring(empty)School specifier: "owner/repo", "owner/repo:subdir" for a school in a subdirectory, or "." for a school embedded in the project. The highest-priority configured value wins across layers.
backendregistered backend nameclaudeWhich AI backend to launch. Built-in names are claude, codex, opencode, and pi; names declared under [backends.<name>] are also accepted. The highest-priority configured value wins (local → project → user).
trustdefault auto yolodefaultRequested backend permission mode. Personal-only: honored in user and local config, ignored in project ace.toml. An explicit trust = "default" overrides an inherited auto or yolo; omitting the key inherits. Support and exact behavior vary by backend; see trust modes.
session_promptstringunsetAdditional text appended to the session prompt ACE injects. The highest-priority present value wins; an explicitly empty string clears inherited text.
envmap (string → string){}Environment variables passed to the backend process. Merges additively across layers.
resumebooltrueAuto-resume the previous backend session on launch. Unset configuration resolves to true. Personal-only: honored in ace.local.toml or user-global config, ignored in project ace.toml.
skip_updateboolfalseDisable automatic version check and background upgrade. Also overridden by ACE_SKIP_UPDATE=1.
[backends.<name>]tableunsetDeclare a custom backend or override a built-in one: kind, cmd, env, model, effort. See backend tables.
yoloboolfalseDeprecated. Use trust = "yolo" instead. yolo = true still resolves to trust = "yolo"; writing trust to a layer removes a legacy yolo key from that layer.
exclude_mcplist of strings[]MCP server names to skip from the school's [[mcp]] entries. Union across all scopes. Declining a server at the registration prompt appends its name here in ace.local.toml; ace mcp register <name> removes it.
skillslist of glob patterns[]Whitelist of skills to load. Last non-empty wins across layers; empty everywhere = all discovered skills. See skills selection.
include_skillslist of glob patterns[]Always-add patterns. Union across all scopes — exception to last-wins. See skills selection.
exclude_skillslist of glob patterns[]Remove patterns from the base set before include_skills is applied. Union across all scopes — exception to last-wins. See skills selection.

backend tables

Each [backends.<name>] table declares one backend instance; the table key is the name that backend = "..." and --backend select. Any layer can declare one, including school.toml. The old [[backends]] array with a name field is rejected since 0.9.3: move each name into its table key.

# Override the model of the built-in claude backend
[backends.claude]
model = "opus"

# Custom backend, selected with backend = "work-claude"
[backends.work-claude]
kind = "claude"
effort = "high"

[backends.work-claude.env]
ANTHROPIC_BASE_URL = "https://proxy.example.com"
KeyType / ValuesDefaultDescription
kindclaude codex opencode piinferredBuilt-in backend this entry behaves as. A table keyed by a built-in name overrides that built-in. A custom name needs kind unless its cmd binary matches a built-in.
cmdlist of stringsbuilt-in binaryExecutable plus any wrapper prefix used to launch the backend. Supports path placeholders.
envmap (string → string){}Environment variables for this backend. Supports path placeholders.
modelstringunsetModel passed to the backend's native model setting for sessions and ace -p. Opaque: passed through unchanged. Unset leaves the backend's own default.
effortstringunsetReasoning effort passed to the backend's native effort setting. Opaque and not validated by ACE; accepted values are whatever the backend accepts. Unset leaves the backend's own default.

Custom backends inherit their kind's mapping of model and effort to native flags. Arguments passed after -- come last, so they can override either value for one run. See custom backends.

inspecting and editing

ace config                    # effective config as one TOML document
ace config get <key>          # resolved value of one key
ace config set <key> <value>  # write a key to a layer
ace config explain [key]      # provenance per layer

Keys: school, backend, trust, resume, skip_update, session_prompt, env.<NAME>, backends.<name>.model, and backends.<name>.effort. skills, include_skills, exclude_skills, and exclude_mcp are read-only here: get and explain show them, set rejects them. Pass --user, --project, or --local to pick the layer.

Bare ace config includes backend model/effort and the selection lists; school metadata is not included, so read it from the school's school.toml. ace config set validates before writing and keeps comments, unrelated fields, inline tables, and dotted keys, including fields ACE doesn't recognize. It says when a higher-precedence layer overrides the value you wrote.

skills selection

Three fields control which of the school's skills are loaded by the backend at session start. Each has one consistent rule across all scopes; the field name signals intent, the scope is mechanical.

FieldMerge ruleEffect
skillslast-wins replace (local > project > user, first non-empty)Sets the base set. Empty everywhere → base = all discovered skills.
include_skillsunion across all scopesAlways added on top of the base.
exclude_skillsunion across all scopesRemoved from the base before include_skills is added.

Resolution: effective = (skills_base − exclude_skills) ∪ include_skills. Exclude is applied before include, so include_skills is authoritative when an item appears in both.

# user ace.toml — global "always-add" preferences
include_skills = ["issue-*"]

# project ace.toml — remove issue-* from the base selection
skills = ["rust-coding", "issue-*"]
exclude_skills = ["issue-*"]

# Result: include_skills wins, so issue-* is still loaded.

Glob patterns use * as a wildcard (no ** or character classes). An invalid pattern is warned and skipped rather than failing the run. Foreign entries in <backend>/skills/ (real files or symlinks pointing outside the school clone) are left alone unless their name collides with a desired skill, in which case the link is skipped with a warning.

CLI

ace skills [--all] [--names]              # list resolved skills
ace skills include <pattern>...           # append to include_skills
ace skills exclude <pattern>...           # append to exclude_skills
ace skills reset [--include] [--exclude]  # set list(s) back to empty
ace explain <name>                        # provenance + trace for one skill

Mutating verbs default to project scope; pass --user or --local to target another layer. There is no ace skills set verb — the skills whitelist is config-only; edit ace.toml directly. The CLI exposes the union-merge fields because those are what users typically tweak per session.

user-global ace.toml

Same schema as project ace.toml. Holds your personal defaults across every ACE-managed repo — typically trust, resume, or a fallback school and backend. Project config overrides it; local config overrides project.

A user-level ace.toml resolves a project on its own: name a school here and a repo needs no ace.toml of its own. When the school comes from the user or local layer rather than the repo, bare ace announces which school it resolved, so a school that isn't the repo's own is never silent.

# ~/.config/ace/ace.toml
school = "ace-rs/school"
trust = "auto"
resume = true

school.toml

Lives at the root of a school repository. Defines the school name, backend preference, backend tables, MCP servers, projects, and skill imports.

# school.toml example
name = "acme-school"
backend = "claude"
session_prompt = "Follow ACME conventions."

[env]
EDITOR = "nvim"

[[mcp]]
name = "github"
url = "https://mcp.example.com/github"
instructions = "Use for all GitHub operations."

[mcp.headers]
Authorization = "Bearer {{ github_token }}"

[[projects]]
name = "ace"
repo = "ace-rs/ace"
description = "ACE CLI and backend integration code."

[[imports]]
skill = "docker"
source = "ace-rs/school"

[[imports]]
skill = "*"
source = "company/school"
include_experimental = true

top-level keys

KeyType / ValuesDefaultDescription
namestring(empty)School display name. Must be non-empty (validated after load).
backendregistered backend nameunsetDefault backend for projects using this school. Built-in and custom names are accepted; ace.toml layers can override it. See custom backends.
session_promptstring(empty)Prompt text injected into every session using this school.
envmap (string → string){}Shared environment variables, not secrets. Merged with ace.toml env (ace.toml wins on conflict).
[backends.<name>]tableunsetSame shape as in ace.toml; see backend tables.

[[mcp]]

MCP (Model Context Protocol) server declarations. Each entry registers an MCP server for the backend to use.

KeyTypeDefaultDescription
namestring(empty)Server identifier. Must be unique across all MCP entries.
urlstring(empty)Remote MCP server URL.
headersmap (string → string){}HTTP headers sent with requests. Values support {{ placeholder }} syntax for user-prompted secrets.
instructionsstring(empty)Usage instructions passed to the backend for this server.

[[projects]]

Optional project catalog entries that give the AI context about available repositories.

KeyTypeDefaultDescription
namestring(empty)Short project identifier.
repostring(empty)Git-cloneable repository URL or shorthand.
descriptionstring(empty)Project description written for AI consumption.
envmap (string → string){}Per-project environment variables exposed to the backend when this project is active.

[[imports]]

Import skills from other school repositories. Skill discovery scans four tiers under skills/: the default skills/<name> and skills/.curated/<name> (both Curated), plus skills/.experimental/ and skills/.system/. Explicit names resolve from any tier. Glob patterns match only Curated by default, and skip skills marked internal: true unless include_internal is set. ace import writes these entries; ace school pull refreshes them.

KeyTypeDefaultDescription
skillsnames or glob patterns(empty)Skills to import, by name or glob (e.g. "*-coding", "*"). Written by ace import, which merges new picks into an existing entry.
skillstring(empty)Single skill name or glob pattern. An entry needs skills or skill.
exclude_skillsnames or glob patterns(empty)Skills to leave out of this import.
sourcestring(empty)Where to import from — any git URL (SSH and self-hosted remotes included) or an owner/repo shorthand, e.g. "ace-rs/school". Sources cache under imports/{host}/{path}, so same-named repos on different hosts don't collide.
include_experimentalboolfalseFor glob entries: also match skills under skills/.experimental/.
include_systemboolfalseFor glob entries: also match skills under skills/.system/.
include_internalboolfalseFor glob entries: also match skills whose SKILL.md frontmatter sets internal: true. An explicit name imports them regardless.

placeholder syntax

Several string fields support {{ placeholder }} substitution. Two placeholder families share the same syntax but resolve at different times.

WhereResolves toWhen
[[mcp]] headersUser-supplied secret, prompted on first run and cachedruntime
[backends.<name>] cmd / envPath placeholders — school_dir, project_dir, backend_dir, homewhen ACE resolves the backend
# MCP header — prompts user for "github_token" on first run
Authorization = "Bearer {{ github_token }}"

# Backend cmd — resolves school_dir when ACE resolves the backend
[backends.codex-ace]
kind = "codex"
cmd  = ["{{ school_dir }}/skills/ace-connect/scripts/codex.sh"]

# Whitespace is flexible
"{{name}}"  "{{ name }}"  "{{  name  }}"  # all equivalent

# Names must be alphanumeric + underscore
"{{ my_api_key_2 }}"  # valid
Path placeholders are a closed set — no shell-style $VAR or ~ expansion. Unknown names render to empty strings. See Backends › templated cmd and env for the full list.