Reference

Configuration

TOML

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.

LayerPathRole
state$XDG_DATA_HOME/panto/state.toml · else ~/.local/share/panto/state.tomlRemembered picker selections and extensions.rocks; delete it to reset them.
base$XDG_DATA_HOME/panto/config.toml · else ~/.local/share/panto/config.tomlGenerated defaults; base agent resources live beside it.
user$XDG_CONFIG_HOME/panto/config.toml · else ~/.config/panto/config.tomlPersonal settings for the machine.
project./.panto/config.tomlProject defaults.
local./.panto/local/config.tomlPersonal 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.

config.tomltoml
[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.

stylestring enum
openai_chat, anthropic_messages, or openai_responses. The internal Codex and extension-protocol tags cannot be selected here.
dialectstring
Only dialect = "codex" with style = "openai_responses" is valid. Other pairings invalidate the provider.
base_urlstring
Base API URL. It is required on each network endpoint, but may be omitted at provider level when endpoints are configured.
authstringrequired
Name of an existing [auth.<name>] session.
endpoints.<style>table
Each endpoint supplies its base_url and may override inherited auth, extra_headers, and compatible network options.

Shared network options

model_catalog_namestring
Models.dev lookup key for panto models sync. Defaults to the configured provider name.
prompt_cachebool
Defaults to true. For anthropic_messages, controls the advancing cache_control marker; it is ignored by other network transports. A wrong type is an invalid provider.
extra_headerstable of string → string
Headers added to model requests and used as identity headers on OAuth calls. Values may contain ${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

protocolnon-empty stringrequired
Name of a Lua-registered host protocol. The extension owns serialization, transport, and authentication.
model_catalog_namestring
Optional models.dev lookup key, defaulting to the provider name. The configured provider name and wire model remain separate from the protocol name.

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.

config.tomltoml
[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

typestring
api_key, explicit or inferred from key.
keystringrequired
Literal or substituted credential. An absent or empty value leaves the session unresolved.

OAuth device sessions

typestring
oauth_device, explicit or inferred from client_id.
dialecttoken | codex
Defaults to token. Selects the device-flow response and polling shape. Not substituted.
client_id / device_code_url / token_urlsubstituted stringrequired
Client ID and device-code endpoint. token_url is the token-dialect polling/refresh endpoint or the Codex exchange/refresh endpoint.
device_poll_urlsubstituted string
Required for the codex dialect; optional otherwise.
verification_url / scopesubstituted string
Optional browser URL and requested scope. Token dialect normally gets the verification URL from its device-code response; the codex dialect has no such fallback, so login fails without verification_url.
token_request_formatform | json
Defaults to form for token-dialect device and polling bodies. Codex uses JSON regardless. Not substituted.
redirect_uri / account_id_jwt_claimsubstituted string
Optional Codex exchange redirect URI and ID-token claim path emitted as chatgpt-account-id. The default redirect URI is https://auth.openai.com/deviceauth/callback.
exchange_urlsubstituted string
Optional post-login token-exchange URL; omitting it disables exchange.
exchange_method / exchange_token_path / exchange_expires_path / exchange_base_url_pathsubstituted string
Exchange method, defaulting to 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]

modelstring
Must be exactly "<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.
reasoningstring label
Session-default reasoning or effort label. It applies at boot or model switch only when the alias has no explicit 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.

editorstring
Overrides $VISUAL / $EDITOR for Ctrl+G. Wrong types are ignored.
tools_collapsedbool
Initial Ctrl+O tool-output collapse state. Wrong types are ignored; a newly generated base config sets it to 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.

timeoutnon-negative integer
Total request lifetime in seconds; the value is a non-negative integer fitting u32. Parser default is 0, which disables the deadline; a newly generated base config sets 300.
retriesnon-negative integer
Retries after the initial request; the value is a non-negative integer fitting 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.

allow / denyarray of strings
Accumulated glob rules over declared entry names. The default is allow. Non-arrays act as empty and non-string members are skipped.
pathsarray of strings
Accumulated extra source directories; relative values resolve against the current working directory.
rocksarray of strings
Accumulated LuaRocks dependency specs. The first whitespace token names an extension source module; empty rock specs are skipped during extension discovery and are not valid module sources.

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.

config.tomltoml
[extensions]
deny  = ["**", "std.shell"]
allow = ["mytool"]
paths = ["./dev/panto-ext"]
rocks = ["panto-agent 1.4.2-1"]

[compaction]

keep_verbatimpositive integer
Upper bound, in tokens, for the recent whole-turn suffix retained verbatim. The turn crossing the boundary is summarized. Absent or invalid values use the effective default of 20,000 tokens; the newly generated base config sets 20,000.
modelstring
Optional exact 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

modelstring
Wire model ID; defaults to the alias. A wrong type also falls back to the alias.
context_windowpositive integer (u32)
Approximate input-context metadata for UI and pickers. Invalid values are unknown.
max_tokenspositive integer (u32)
Per-request output cap. An invalid or absent entry becomes null, then the provider default is 64,000.
input / output / cache_read / cache_writeinteger or float
USD per million tokens. Omitted values are unknown; explicit zero is known zero, including for cache_write. Negative or non-finite values convert to known zero.

OpenAI-style knobs

reasoningstring enum
Provider-neutral default label: 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.
reasoning_levelsarray of strings
Explicit supported OpenAI-style levels: 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

api_versionstring
Anthropic version header; defaults to 2023-06-01 when the Anthropic config is built. A wrong type behaves as absent.
thinkingstring enum
Anthropic mode: 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.
effortstring enum
Anthropic adaptive effort: low, medium, high, xhigh, or max. Defaults to medium; invalid or wrong-type values also become medium.
thinking_budget_tokenspositive integer
Manual enabled-thinking budget. Null is interpreted as max_tokens - 1; a constructed model with no entry uses 32,000. It is ignored outside enabled mode.
thinking_interleavedbool
Defaults to 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.

PathContents
config.toml / models.tomlBase configuration and model metadata.
agent/Bundled base prompt, extensions, and tools.
auth/<name>.jsonOAuth 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.lockPersistent 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.