Reference
Configuration
panto reads layered config.toml files for providers, auth, defaults, TUI settings, API deadlines, compaction, and extension policy. Model aliases and pricing live beside them in a separate layered models.toml.
The loader is permissive: unknown top-level tables and unknown keys are generally ignored. Some wrong types use the fallback behavior noted below; [tui].theme wrong or empty values, a non-table [tui], a non-table [api], and invalid provider fields invalidate configuration.
Files & layers
state.toml is read first as a lower-precedence remembered layer, followed by four config.toml layers from lowest to highest precedence. Missing files are skipped. State is at $XDG_DATA_HOME/panto/state.toml (or ~/.local/share/panto/state.toml). It accepts only tui.theme, defaults.model, defaults.reasoning, and extensions.rocks; panto extension install rock:<spec> writes its rock spec there. Malformed or missing state is ignored, and picker writes are atomic.
| Layer | Path | Role |
|---|---|---|
| state | $XDG_DATA_HOME/panto/state.toml · else ~/.local/share/panto/state.toml | Remembered picker selections and extensions.rocks; delete it to reset them. |
| base | $XDG_DATA_HOME/panto/config.toml · else ~/.local/share/panto/config.toml | Generated defaults; base agent resources live beside it. |
| user | $XDG_CONFIG_HOME/panto/config.toml · else ~/.config/panto/config.toml | Personal settings for the machine. |
| project | ./.panto/config.toml | Project defaults. |
| local | ./.panto/local/config.toml | Personal per-project overrides. |
Tables merge recursively. A higher scalar or array replaces the lower value, so separately named providers and auth sessions accumulate while individual fields override; model aliases in models.toml follow the same rule. The [extensions] arrays are the exception across config layers: allow, deny, paths, and rocks accumulate. State is not a general config layer; it contributes only tui.theme, defaults.model, defaults.reasoning, and extensions.rocks, with its rocks additionally added to extension sources.
The base file is created on the first bootstrap only when it does not exist. Upgrades do not overwrite an existing base file. A newly generated base sets [api] timeout = 300, retries = 3, [compaction] keep_verbatim = 20000, [tui] tools_collapsed = true, OpenAI and Anthropic API-key provider entries, and empty extension arrays. There is no PANTO_HOME environment-variable override; the data-home paths are described in PANTO_HOME layout.
[defaults]
model = "anthropic:sonnet"
reasoning = "high"
[tui]
editor = "code -w"
tools_collapsed = false
[api]
timeout = 300
retries = 3
[providers.anthropic]
style = "anthropic_messages"
base_url = "https://api.anthropic.com"
auth = "anthropic_api"
[auth.anthropic_api]
key = "${env:ANTHROPIC_API_KEY}"[providers.<name>]
A provider is either a built-in network transport or an extension-owned protocol. Its name is the left half of a provider:alias model reference. The loader is permissive about unknown keys, but a provider must be one of these two valid forms.
Network providers
A single-endpoint provider may set style, base_url, and auth at provider level. A provider with multiple endpoint styles may omit provider-level style and base_url and instead define [providers.<name>.endpoints.<style>] tables; each endpoint requires its own base_url, and a sole endpoint can be selected implicitly.
openai_chat, anthropic_messages, or openai_responses. The internal Codex and extension-protocol tags cannot be selected here.dialect = "codex" with style = "openai_responses" is valid. Other pairings invalidate the provider.[auth.<name>] session.base_url and may override inherited auth, extra_headers, and compatible network options.Shared network options
panto models sync. Defaults to the configured provider name.true. For anthropic_messages, controls the advancing cache_control marker; it is ignored by other network transports. A wrong type is an invalid provider.${session:id}, expanded per conversation. Provider headers are inherited by endpoints; endpoint headers override them case-insensitively. Non-string members are skipped.Extension-protocol providers
Exactly one of style and protocol must be present. Extension-protocol providers must not set endpoints, base_url, auth, dialect, prompt_cache, or extra_headers. Configure their routing with [providers.<name>] protocol = "<registered-name>". Network providers need an existing auth block; API-key providers with an empty resolved key are removed, while OAuth providers resolve credentials when a turn runs.
[providers.copilot]
style = "openai_chat"
base_url = "https://api.individual.githubcopilot.com"
auth = "github_copilot"[auth.<name>]
Auth sessions are named, reusable credential sources. type may be api_key or oauth_device. If it is omitted, client_id implies OAuth device auth and otherwise key implies API-key auth. An explicit unknown type, or a block with neither inference key, is invalid.
Substitution
Substitution is limited to consumed string fields in an auth block: key; client_id, device_code_url, token_url, device_poll_url, verification_url, scope, redirect_uri, and account_id_jwt_claim; and exchange_url, exchange_method, exchange_token_path, exchange_expires_path, and exchange_base_url_path. It does not apply to type, dialect, token_request_format, or arbitrary unused values.
${env:VAR} reads the environment. ${name} reads the raw string value of a sibling key in the same auth table, including an otherwise schema-ignored helper such as domain; missing values insert "". Sibling values are not recursively substituted, and an unterminated ${ is retained literally. After substitution, an empty complete field is absent: missing required OAuth fields invalidate the session, and an empty API key removes providers that use it.
API-key sessions
api_key, explicit or inferred from key.OAuth device sessions
oauth_device, explicit or inferred from client_id.token. Selects the device-flow response and polling shape. Not substituted.token_url is the token-dialect polling/refresh endpoint or the Codex exchange/refresh endpoint.codex dialect; optional otherwise.codex dialect has no such fallback, so login fails without verification_url.form for token-dialect device and polling bodies. Codex uses JSON regardless. Not substituted.chatgpt-account-id. The default redirect URI is https://auth.openai.com/deviceauth/callback.GET; dotted JSON paths for the exchanged credential (default token), expiry, and dynamic provider base URL.OAuth tokens are stored at <data-home>/auth/<auth-name>.json, refreshed within 120 seconds of expiry, and optional exchange credentials are regenerated when stale.
[defaults]
"<provider>:<alias>" with one colon, both parts non-empty, and a provider that survives resolution. An alias does not have to be present in models.toml.reasoning, thinking, or effort; an explicit --effort wins. Unsupported values are warned and ignored. Extension protocols receive the label unchanged.Model selection order is explicit --model (a qualified reference or a unique bare alias), then [defaults].model, then the sole alias of the sole resolved provider. Otherwise selection fails.
[tui]
theme is a required non-empty string when present. It is applied after extensions load; --theme overrides it, and an unknown name falls back to Panto. The six core themes are Panto, Tidepool Tumble, Mossmallow, Peachy Pudding, Plum Pips, and Gilded Inkblot; the optional panto-popular-themes rock adds Tokyo Night, Catppuccin, Gruvbox, Solarized, Nord, Dracula, and Kanagawa themes.
$VISUAL / $EDITOR for Ctrl+G. Wrong types are ignored.true.[api]
These settings are copied into the active panto.Config, including configs resolved for extension-created agents, and apply to HTTP provider requests. A non-table [api], invalid types, negative values, or values that overflow the field are invalid configuration.
u32. Parser default is 0, which disables the deadline; a newly generated base config sets 300.u16. Parser default is 0; panto makes total attempts retries + 1. A newly generated base config sets 3.The lower-level library supplies fixed retry delays: 500 ms initially, doubling with full jitter and a 10-second cap. Those delay settings are not TOML keys.
--no-extensions skips Lua and LuaRocks entirely, including shipped tools.
[extensions] policy & sources
[extensions] controls every Lua entry, including shipped std.read, std.write, std.edit, and std.shell. There is no TOML [tools] table: tools are Lua-authored entries, and tools/ and extensions/ feed the same declared-name namespace. An unknown top-level [tools] table is ignored.
Extensions can register slash commands and protocol providers. Use panto.ext.dirs for the resolved base/user/project/local directories exposed to them; panto.ext.register_protocol is selected by [providers.<name>] protocol. The built-in slash commands are /help, /quit, /model, /theme, /reasoning, /new, /resume, /tree, and /status.
Glob matching
Rules match the declared entry name, not its filename. Matching is case-sensitive, consumes the whole name, and uses dot-separated segments: literal segments match exactly; * as a whole segment matches exactly one segment; ** as a whole segment matches one or more segments; and embedded stars such as rea* are literal.
Rules from all layers accumulate. No match allows an entry. Matching rules sort by layer, then specificity (more literal segments, more total segments, fewer **), with allow before deny. The last matching rule wins: higher layers beat lower layers even when the lower rule is more specific; within one layer, the more-specific rule wins; an exact allow/deny tie goes to deny. A whitelist is deny = ["**"] plus specific allows.
Source precedence
Canonical directories are <base>/agent/{extensions,tools}, <user>/{extensions,tools}, <cwd>/.panto/{extensions,tools}, and <cwd>/.panto/local/{extensions,tools}. Across layers, local outranks project, which outranks user, which outranks base. Within a layer, canonical directories outrank extensions.paths, which outrank extensions.rocks. Equal-rank duplicate declared names are errors. Sources are .lua files or directories containing init.lua; dotfiles and underscore-prefixed Lua files are skipped.
A git install clones into $XDG_DATA_HOME/panto/dist/<name>/ and creates a symlink under $XDG_CONFIG_HOME/panto/extensions/<name>. The clone root is used when it has init.lua; otherwise <name>/init.lua is used, otherwise installation fails. If the repository has a v<semver> tag, the newest such tag is checked out; otherwise the default branch remains checked out. On update, a tagged checkout moves to a newer tag, a branch checkout uses a fast-forward-only pull, and a detached checkout is skipped. Git installs do not install rockspec dependencies; use panto extension install rock:<spec> for those.
Denied entries are discovered but not activated. This policy is about feature availability, not security: rock installation and source evaluation happen before allow/deny activation filtering.
[extensions]
deny = ["**", "std.shell"]
allow = ["mytool"]
paths = ["./dev/panto-ext"]
rocks = ["panto-agent 1.4.2-1"][compaction]
20,000 tokens; the newly generated base config sets 20,000.provider:alias compaction model. Its provider must survive resolution. If compaction with this model fails, libpanto falls back to the active chat model.The compaction prompt is not a TOML key; it comes from layered COMPACTION.md. See System prompt.
models.toml
models.toml uses the same four layers and precedence as config.toml: base at $XDG_DATA_HOME/panto/models.toml (or ~/.local/share/panto/models.toml), user at $XDG_CONFIG_HOME/panto/models.toml (or ~/.config/panto/models.toml), project at ./.panto/models.toml, and local at ./.panto/local/models.toml. Missing files are skipped; tables merge recursively and higher scalars or arrays replace lower values.
Entries are [<provider>.<alias>] and may set a style to select a configured provider endpoint; quoted table keys are safest when names contain punctuation. Lookup uses provider plus alias, and ambiguous multi-endpoint providers fail without a provider default or model-level style. The provider must correspond to a configured provider when selected. If no entry exists, the alias is sent as the wire model with default knobs and unknown pricing; an entry that merely omits model still contributes its own knobs and pricing under the alias. Pricing is indexed by provider plus wire model, not alias.
Common keys
64,000.cache_write. Negative or non-finite values convert to known zero.OpenAI-style knobs
default, off/none, minimal, low, medium, high, xhigh, or max. Matching is case-sensitive; off/none are accepted case-insensitively. Invalid values or types invalidate model loading.off/none, minimal, low, medium, high, xhigh, or max. Absent means capability unknown; none normalizes to off, while default and invalid values are rejected. A non-default reasoning value must be listed. An empty array means no listed effort modes.Anthropic-style knobs
2023-06-01 when the Anthropic config is built. A wrong type behaves as absent.disabled, enabled, or adaptive. An unrecognized string becomes disabled; a non-string value falls back to the absent-key default. If effort is present and thinking is omitted, that default is adaptive; otherwise it is disabled.low, medium, high, xhigh, or max. Defaults to medium; invalid or wrong-type values also become medium.max_tokens - 1; a constructed model with no entry uses 32,000. It is ignored outside enabled mode.false. Sends the interleaved-thinking beta header only for manual enabled mode; adaptive interleaving is automatic. Wrong types become false.Any explicit reasoning, thinking, or even effort key makes an alias's reasoning configuration explicit, so [defaults].reasoning does not override it.
panto models sync
panto models sync takes no extra arguments. It loads the current layered config, fetches https://models.dev/api.json, and rebuilds the base-layer models.toml; user, project, and local files remain separate. Only providers that survive config resolution are processed. The optional model_catalog_name selects the models.dev provider key, falling back to the configured name.
For network providers, sync may probe authenticated GET <base_url-with-slashes-trimmed>/models and intersect catalog entries by model ID and endpoint compatibility (/chat/completions, /v1/messages, or /responses). Probe, auth, or format failures skip filtering. Extension protocols are not probed. Generated aliases are models.dev IDs, with no redundant model key, and include positive context/output metadata, recognized costs, and reasoning_levels. Catalog none normalizes to off; false reasoning or metadata without effort options emits an empty list. Sync does not synthesize Anthropic thinking or effort defaults.
A panto run bundle imports only ambient auth, providers, and [defaults].model; it then uses the bundle config for policy, sources, UI, API, and compaction, without reading or writing ambient state.
System prompt
There is no system-prompt TOML key. Prompt files are layered across these directories: base at <data-home>/agent/, user at $XDG_CONFIG_HOME/panto/ (or ~/.config/panto/), project at ./.panto/, and local at ./.panto/local/.
SYSTEM.md uses replacement precedence: the highest existing file wins, local > project > user > base. If none exists, the hardcoded fallback is You are a helpful assistant., although bootstrap normally stages a bundled base file. Every existing APPEND_SYSTEM.md is retained and emitted after the seed in high-to-low order: local, project, user, base.
COMPACTION.md follows the replacement precedence of SYSTEM.md; it does not accumulate. A bundled or hardcoded fallback exists. On a fresh session, these blocks become persisted conversation system state. On resume, changed files append one replacement seed plus append blocks without rewriting history.
The welcome/session header reports the winning SYSTEM.md path (or (built-in)) followed by the selected APPEND_SYSTEM.md paths in emission order.
PANTO_HOME layout
In these sources, “Panto home” means the XDG data home: $XDG_DATA_HOME/panto, or $HOME/.local/share/panto when XDG_DATA_HOME is unset. The layout resolver honors only XDG_DATA_HOME and HOME.
| Path | Contents |
|---|---|
| config.toml / models.toml | Base configuration and model metadata. |
| agent/ | Bundled base prompt, extensions, and tools. |
| auth/<name>.json | OAuth credentials. Treat these files as sensitive. |
| rocks/lua-5.4.7/ | Versioned embedded LuaRocks runtime tree; older Lua-version trees are retained for rollback. |
| .bootstrap.lock | Persistent inode used for the advisory staging lock. |
| dist/<name>/ | Git-installed extension clone roots. |
| extensions/<name> | Config-home symlinks into managed git extension clones. |
| sessions/ / debug/ | Sessions honor PANTO_SESSION_DIR and may be outside the data home; debug logs remain under the data home. |
The current runtime uses Lua 5.4.7 and LuaRocks 3.13.0. Its tree contains bin/lua (a wrapper for panto lua), staged headers under include/, libuv headers under libuv/include/ and an empty stub archive at libuv/lib/libuv.a (libuv itself is compiled into the panto binary), pure Lua rocks under share/lua/5.4/, C modules under lib/lua/5.4/ including panto.so and its SHA-256 marker, rock metadata under lib/luarocks/rocks-5.4/, and regenerated LuaRocks configuration at etc/luarocks/config-5.4.lua.
The runtime prepends its Lua paths ahead of system paths and confines LuaRocks to this tree. The only automatic dependency is luv 1.52.1-0, rebuilt when the host-libuv ABI marker changes. extensions.rocks specs install at startup when missing; exact pins check their version, while bare or range specs accept any installed version until panto extension update. That command asks LuaRocks to install every configured spec; panto update updates the panto release itself.