Reference
Configuration
Configure panto with layered TOML: config.toml for settings, models.toml for model aliases and pricing. Prompt files and rigs add instructions and resources.
Core generally ignores unknown keys. Lua extensions see the whole merged document.
Files & layers
Four config.toml layers stack over a remembered-state file. Higher rows win.
| Layer | Path | Holds |
|---|---|---|
| local | ./.panto/local/config.toml | Personal project overrides; usually git-ignored. |
| project | ./.panto/config.toml | Team-shared project settings. |
| user | ~/.config/panto/config.toml | Your settings. |
| base | ~/.local/share/panto/config.toml | Generated defaults. Bundled resources sit in base/ beside it. |
| state | ~/.local/share/panto/state.toml | Remembered UI choices, extension-owned extension_state values, and installed rock specs. |
$XDG_CONFIG_HOME and $XDG_DATA_HOME replace the two home directories when set. The data home is the data directory plus panto/.
- Tables merge recursively; higher scalars and arrays replace lower ones.
- Exception: the four
[extensions]arrays accumulate. - Missing files are skipped; missing or invalid state is ignored.
models.toml, prompt files, and Lua directories use the same layers.- The base file is written only when absent; upgrades never overwrite it. It seeds most defaults below (shown as “base N” where they differ) plus API-key providers reading
OPENAI_API_KEYandANTHROPIC_API_KEY.
[providers.anthropic]
style = "anthropic_messages"
base_url = "https://api.anthropic.com"
auth = "anthropic"
[auth.anthropic]
key = "${env:ANTHROPIC_API_KEY}"
[defaults]
model = "anthropic:sonnet"[providers.<name>]
A provider is the left half of a provider:alias reference. Network providers use a built-in API style; protocol providers hand everything to a Lua extension.
Network providers
| Key | Type | Default | Meaning |
|---|---|---|---|
| style | enum | — | openai_chat, openai_responses, or anthropic_messages. Required in flat form; picks the default endpoint otherwise. |
| base_url | string | required | API base URL. |
| auth | string | required | Name of an existing [auth.<name>] session; OAuth credentials resolve when needed. |
| dialect | string | — | Only "codex", with openai_responses. |
| endpoints.<style> | table | — | Multi-endpoint form: one table per style. |
| prompt_cache | bool | true | Anthropic prompt caching. |
| extra_headers | string table | — | Extra request headers, also sent on OAuth calls. ${session:id} becomes the conversation ID. |
| model_catalog_name | string | provider name | models.dev key for panto models sync. |
| Case | Rule |
|---|---|
| Endpoint form | Put base_url, dialect, and prompt_cache on each endpoint, not the provider. prompt_cache is valid only on the Anthropic endpoint. |
| Several endpoints | Set a provider or per-model style. A sole endpoint is implicit. |
| Headers | Endpoint headers override same-named provider headers (case-insensitive). Non-strings are skipped. |
| Prompt caching | A non-boolean value on an Anthropic endpoint invalidates the provider. Flat non-Anthropic providers ignore it. |
| Unresolved key | An API-key session with no key drops every provider using it. |
Extension-protocol providers
| Key | Type | Meaning |
|---|---|---|
| protocol | string | Non-empty protocol name an extension registered. |
| model_catalog_name | string | Optional models.dev key. |
Do not mix protocol and network settings; the catalog name is allowed. The extension owns transport and auth. See Lua extensions.
[providers.example_extension]
protocol = "example.protocol"
[providers.multi]
auth = "service_key"
style = "openai_chat"
[auth.service_key]
key = "${env:SERVICE_API_KEY}"
[providers.multi.endpoints.openai_chat]
base_url = "https://service.example/v1"
[providers.multi.endpoints.anthropic_messages]
base_url = "https://service.example"
prompt_cache = true[auth.<name>]
A named credential that providers reference by auth. Omit type to infer it:
| type | Inferred from | Use |
|---|---|---|
| api_key | key | Static key, usually from the environment. |
| oauth_device | client_id | Device-code login via panto auth login. |
If both inference keys exist, OAuth wins. An unknown type or neither key without a type is invalid.
API-key auth
| Key | Type | Meaning |
|---|---|---|
| key | string | Credential, usually ${env:VAR}. Empty or unset makes the session unavailable. |
OAuth device auth
| Key | Type | Default | Meaning |
|---|---|---|---|
| dialect | enum | token | token or codex. |
| client_id | string | required | OAuth client ID. |
| device_code_url | string | required | Device-code endpoint. |
| token_url | string | required | Token endpoint (codex: exchange and refresh). |
| device_poll_url | string | — | Codex only, required: polled for the code. |
| verification_url | string | — | Browser URL. Codex requires it; token reads it from the device-code response. |
| scope | string | — | Requested scope (token only). |
| token_request_format | enum | form | form or json bodies (token only; codex sends JSON). |
| redirect_uri | string | OpenAI callback | Codex redirect; default https://auth.openai.com/deviceauth/callback. |
| account_id_jwt_claim | string | — | Literal ID-token claim name containing chatgpt_account_id; sends that field as the account-ID header. |
| exchange_url | string | — | Post-login token exchange; absent disables it. |
| exchange_method | string | GET | Exchange method: POST sends POST; other values send GET (case-insensitive). |
| exchange_token_path | string | token | Dotted JSON path to the credential. |
| exchange_expires_path | string | — | Dotted JSON path to the Unix-seconds expiry. |
| exchange_base_url_path | string | — | Dotted JSON path to a dynamic base URL. |
Substitution and storage
| Syntax | Expands to |
|---|---|
| ${env:VAR} | Environment variable. |
| ${name} | Raw value of a sibling key in the same table, even one core ignores. |
- Works in
keyand all OAuth string fields; not in enum fields. - Missing references become empty. No recursion. An unterminated
${stays literal. - OAuth tokens are saved as JSON under the data home's
auth/. Treat them as secrets.
[auth.openai]
key = "${env:OPENAI_API_KEY}" # inferred: api_key
[auth.gh]
client_id = "abc" # inferred: oauth_device
domain = "github.com"
device_code_url = "https://${domain}/login/device/code"
token_url = "https://${domain}/login/oauth/access_token"[defaults]
Default model and reasoning for new sessions.
| Key | Type | Meaning |
|---|---|---|
| model | string | Exactly one colon with non-empty parts: provider:alias. The provider must resolve; the alias needn't be in models.toml. |
| reasoning | string | Label such as high. Applies only if the model entry sets no reasoning, thinking, or effort; unsupported defaults warn and are ignored. |
| Case | Rule |
|---|---|
| Model selection | CLI override → configured default → remembered model → sole alias of the sole provider. Otherwise, select a model explicitly. A unique bare alias works on the CLI. |
| Reasoning selection | --reasoning beats per-alias settings, configured defaults, and remembered choices. An unsupported CLI value fails startup. |
| Remembered choices | Model and reasoning pickers save to state, below explicit config. See the CLI reference. |
[defaults]
model = "anthropic:sonnet"
reasoning = "high"[keys]
Override or add TUI command shortcuts. Each key maps to a slash-command invocation; registered extension commands are available too.
| Value | Meaning |
|---|---|
| "/command args" | Run that command when the chord is pressed. |
| "" | Unbind a default command shortcut. |
Use canonical lowercase chords: ctrl+<a-z>, alt+<a-z>, or ctrl+alt+<a-z>. Host-reserved chords are rejected: Ctrl+C/D/Z/H/I/J/M, Ctrl+A/E/U/W/G, and Alt+B/F. Any invalid entry (including a malformed chord, a reserved key, or a value other than a slash-command string or "") fails config loading; it is not ignored. A configured chord replaces its default binding; other chords add bindings.
Defaults: Ctrl+T /model, Ctrl+R /reasoning, Ctrl+Y /theme, Ctrl+P /proceed, Ctrl+O /collapse, Ctrl+K /thinking, and Ctrl+L /redraw. During a live turn, only native commands registered for mid-turn dispatch run immediately; other configured command invocations are ignored, not queued.
[keys]
"ctrl+t" = "/reasoning"
"alt+m" = "/model"
"ctrl+y" = ""[tui]
Startup UI behavior. See Key bindings for controls.
| Key | Type | Default | Meaning |
|---|---|---|---|
| theme | string | Panto | Startup theme; case-sensitive except panto also selects Panto. --theme overrides. |
| tree_view | enum | no_tool_use | /tree view: no_tool_use, no_tools, user, all, or named. |
| show_thinking | bool | true | Show thinking. Ctrl+K toggles. |
| tools_collapsed | bool | true | Collapse tool output. Ctrl+O toggles. |
| editor | string | $VISUAL, $EDITOR, vi | Ctrl+G editor command. |
- Core themes: Panto, Tidepool Tumble, Mossmallow, Peachy Pudding, Plum Pips, Gilded Inkblot. Extensions add more; unknown names fall back to Panto.
- The theme picker (Ctrl+Y) previews choices; Enter accepts. It, the tree picker, and Ctrl+K save choices in state; explicit config wins.
- Blank or non-string themes, invalid tree views, and non-boolean thinking values invalidate config. Unknown theme names fall back; wrong editor or collapse types are ignored.
[tui.diagrams]
Terminal rendering of Mermaid diagrams.
| Key | Type | Default | Meaning |
|---|---|---|---|
| rounded_borders | bool | true | Rounded box corners. |
| box_border_padding | integer ≥ 0 | 0 | Padding inside flowchart, class, and ER boxes. |
| graph_padding | integer ≥ 0 | 2 | Gap between graph boxes. |
| sequence_spacing | integer ≥ 0 | 3 | Gap between sequence participants. |
Non-table diagram settings and bad sizes invalidate config. A non-boolean rounded_borders is ignored.
[tui]
theme = "Tidepool Tumble"
editor = "code -w"
[tui.diagrams]
rounded_borders = false[api]
Timeouts and retries for model requests.
| Key | Type | Default | Meaning |
|---|---|---|---|
| timeout | u32 | 0 (base 600) | Seconds per attempt; 0 disables. |
| retries | u16 | 0 (base 3) | Retries after the first attempt. |
Bad types or ranges invalidate config. panto serve ignores this section and makes one attempt per call.
[api]
timeout = 600
retries = 3[server]
Listener and limits for panto serve.
| Key | Type | Default | Meaning |
|---|---|---|---|
| host | string | 127.0.0.1 | Non-empty bind address, without surrounding whitespace. --host overrides. |
| port | 1–65535 | 8080 | Listen port. --port overrides. |
| auth_token_env | string | — | Env var holding the inbound token. Unset or empty supplies no token. |
| max_body_bytes | positive u32 | 33554432 | Decoded request body. |
| max_header_bytes | 1024–1048576 | 65536 | Request headers. |
| max_response_bytes | positive u32 | 33554432 | Buffered response. |
| max_connections | 1–64 | 16 | Open connections. |
| max_active_requests | positive u16 | 16 | Active requests; ≤ connections. |
| max_queued_requests | u16 | 0 | Must be 0: overload is rejected, not queued. |
| max_retained_contexts | u16 | 8 | Warm contexts; 0 disables. |
| context_idle_seconds | positive u32 | 120 | Retained-context idle lifetime. |
| request_deadline_seconds | positive u32 | 600 | Request deadline and upstream timeout. |
| header_deadline_seconds | positive u32 | 15 | Header-read deadline. |
| stalled_write_deadline_seconds | positive u32 | 30 | Stalled-write deadline. |
Strict validation
- Wrong types and out-of-range values invalidate config.
- Token variable names start with a letter or underscore, then use letters, digits, or underscores.
- Connections × body size and active requests × response size are each capped at 512 MiB.
- A non-loopback host needs a non-empty token, never sent upstream.
[server]
host = "0.0.0.0"
auth_token_env = "PANTO_SERVER_TOKEN"[extensions] policy & sources
Choose which Lua entries activate, shipped tools included, and add sources. Tools and extensions share one namespace; there is no separate tools section.
| Key | Type | Meaning |
|---|---|---|
| allow | string array | Declared-name globs to allow, not filenames. |
| deny | string array | Name globs to deny. |
| paths | string array | Extra source directories, relative to the working directory. |
| rocks | string array | LuaRocks specs; first word is the module. Missing rocks install at startup; remembered rock specs are included. |
All four default to empty and accumulate. Empty policy allows everything; non-array values act as empty and non-string members are skipped.
Matching and precedence
- Globs are case-sensitive, dot-separated, and whole-name.
*matches one segment;**matches one or more. A star inside a segment is literal. - Unmatched names are allowed. Higher layers override lower ones. In a layer, the more specific rule wins; ties go to deny.
- Whitelist with
deny = ["**"]plus specific allows.
Source directories
| Layer | Directories |
|---|---|
| local | ./.panto/local/{extensions,tools}/ |
| project | ./.panto/{extensions,tools}/ |
| user | ~/.config/panto/{extensions,tools}/ |
| base | <data-home>/base/{extensions,tools}/ |
- Higher rows win. In a layer: canonical directories, then
paths, thenrocks. - Sources are
.luafiles or directories withinit.lua. Dot-prefixed entries and_-prefixed Lua files are skipped. - Duplicate declared names at equal precedence are errors.
- Manage rocks and git extensions with
panto extensions(CLI).
Policy is not a sandbox
Deny only blocks activation. Sources are evaluated and rocks installed first.[extensions]
# whitelist: only the read and edit tools
deny = ["**"]
allow = ["std.read", "std.edit"][compaction]
Choose how panto summarizes long conversations.
| Key | Type | Default | Meaning |
|---|---|---|---|
| keep_verbatim | positive u32 | 20000 | Token cap on recent whole turns kept verbatim; the boundary-crossing turn is summarized. Missing, wrong-type, or non-positive values use the default. |
| model | string | chat model | provider:alias to compact with; its provider must resolve at config load. Later resolution or request failures fall back to the chat model. |
The prompt comes from COMPACTION.md; see System prompt.
[compaction]
keep_verbatim = 20000
model = "anthropic:haiku"models.toml
Per-alias wire IDs, limits, reasoning, and pricing. Merges across the layers.
[anthropic.sonnet]defines the aliasanthropic:sonnet. Quote names that aren't bare TOML keys, such as ones with dots.- An alias with no entry still works, using the alias as the wire ID.
- Pricing is keyed by provider and wire ID.
panto models syncrebuilds the base catalog.
Common keys
| Key | Type | Default | Meaning |
|---|---|---|---|
| model | string | alias | Wire model ID. |
| style | enum | provider's | Endpoint override; see providers. |
| context_window | positive u32 | unknown | Input context, shown in the UI. |
| max_tokens | positive u32 | 64000 | Output cap per request. |
| input, output, cache_read, cache_write | number | unknown | USD per million tokens. 0 is a known zero. |
OpenAI-style reasoning
| Key | Type | Default | Meaning |
|---|---|---|---|
| reasoning | enum | default | default, off/none, minimal, low, medium, high, xhigh, or max. |
| reasoning_levels | string array | unknown | Supported effort names above, excluding default. Missing means unknown; empty means no declared levels. When present, non-default reasoning must be listed. |
Anthropic-style settings
| Key | Type | Default | Meaning |
|---|---|---|---|
| api_version | string | 2023-06-01 | Version header. |
| thinking | enum | disabled | disabled, enabled, or adaptive. If thinking is omitted, effort implies adaptive. |
| effort | enum | medium | low, medium, high, xhigh, or max (adaptive). |
| thinking_budget_tokens | positive u32 | max_tokens − 1 | Budget for enabled mode; 32000 without an entry. |
| thinking_interleaved | bool | false | Interleaved-thinking beta header (enabled mode). |
| Case | Rule |
|---|---|
| Invalid style or reasoning | Wrong types or unknown values fail model loading. Invalid capability lists or a selection outside a declared list also fail. |
| Missing or invalid values | Wire ID uses the alias; context is unknown; output cap, API version, thinking budget, effort, and interleaving use their shown fallbacks. An unknown thinking string disables thinking. |
| Pricing | Missing or unparseable rates are unknown; negative or non-finite rates become known zero. |
| Per-alias reasoning | Any explicit reasoning, thinking, or effort key prevents the session default from replacing it. |
[anthropic.sonnet]
model = "claude-sonnet-4-20250514"
max_tokens = 8192
thinking = "enabled"
thinking_budget_tokens = 4096
input = 3.0
output = 15.0
cache_read = 0.3
cache_write = 3.75
[anthropic.opus]
model = "claude-opus-4-8"
thinking = "adaptive"
effort = "high"
[openai.gpt]
model = "gpt-4o"
reasoning_levels = ["off", "low", "medium", "high"]System prompt
Markdown files in each layer directory set the system and compaction prompts.
| File | Rule | Fallback |
|---|---|---|
| SYSTEM.md | Highest layer wins. | You are a helpful assistant. |
| APPEND_SYSTEM.md | All appended after it, local first. | — |
| COMPACTION.md | Highest layer wins. | Built-in prompt |
User, project, and local prompts sit beside their config files. Base prompts live in the data home's base/, which ships system and compaction defaults.
- Changed files apply on resume as a new seed and append blocks; old messages stay.
- The welcome header shows which files loaded.
- A rig's prompts layer above the base prompts; your user, project and local prompts are not read.
Rigs
A rig is a trusted, self-contained bundle of config and resources. Pass it to a session or panto serve with --rig.
my-rig/
├── config.toml # rig settings
├── models.toml # extra or overriding aliases
├── PROMPT.md # first user message
├── SYSTEM.md # also APPEND_SYSTEM.md, COMPACTION.md
├── extensions/
└── tools/| Path | Purpose |
|---|---|
| config.toml | Optional. Merges over the base config and your user-level providers, auth, and default model. |
| models.toml | Layers over the base and user model catalogs. |
| extensions/, tools/ | Layer above the base's; the base and rig paths and rocks both load. Built-in tools apply unless denied (deny = ["std.*"]). Relative paths use the working directory, not the rig root. |
| PROMPT.md | Optional first message for a fresh agent session; never replayed on resume. Missing, empty, or whitespace-only adds nothing; invalid UTF-8 fails startup. |
| SYSTEM.md etc. | Override the base prompts. Without SYSTEM.md, panto's default applies, never yours. |
| Your setting | Carries into rig? |
|---|---|
| User auth, providers, models.toml | Yes, under the rig's |
| User [defaults].model | Yes; serve ignores it as a fallback |
| Remembered theme, view, thinking, model, reasoning | Yes, under the rig's config, and saved as usual; not in serve |
| Installed rocks | No |
| Project and local layers | No |
| User extension directories, [extensions] | No |
| User [tui], [api], [compaction], [server] | No |
- A directory is used in place without archive validation; writes persist.
- Any other path is a ZIP (
.rig): validated, extracted privately, deleted on exit. Put files at the archive root. - First message: explicit prompt → piped stdin (print mode) → rig prompt file.
--ignore-rig-promptskips only the rig prompt file.- Remembered picks are read from and saved to
state.toml;panto serveneither reads nor writes it. panto serveignoresPROMPT.md. Examples are in the repo'sexamples/rigs/.- Configuration hooks use a rig-specific scope: only base and rig extension sources run, not user, project, or local extension hooks. Their
panto.ext.configsnapshot contains the merged base and rig settings, plus only the inherited user connection settings and (when remembered state is enabled) the selected UI fields described above; serve does not load remembered state.
Archive limits
Rejects unsafe paths, symlinks, special or colliding entries, over 10,000 entries, files over 64 MiB, or over 256 MiB total.Data-home layout
The data home stores generated resources and state; there is no PANTO_HOME override.
| When | Data home |
|---|---|
| XDG_DATA_HOME is set | $XDG_DATA_HOME/panto |
| Otherwise | $HOME/.local/share/panto |
| Path | Contents |
|---|---|
| config.toml, state.toml | Base config and remembered state. |
| models.toml | Base catalog from panto models sync. |
| base/ | Bundled prompts, tools, and extensions. |
| auth/<name>.json | OAuth credentials. Secret. |
| rocks/lua-<version>/ | LuaRocks tree per Lua version; old trees kept. |
| dist/<name>/ | Git-managed extension clones, linked from your user extensions/. |
| sessions/<encoded-cwd>/ | Sessions per working directory. PANTO_SESSION_DIR overrides the sessions base directory. |
| debug/<session-id>.log | Log when PANTO_DEBUG is non-empty and not 0. |
| .bootstrap.lock | Bootstrap lock. |
| state.toml.lock | Persistent lock for state updates. Leave it in place while panto may be writing state. |