Skip to content

Configuration files

Pythinker Code CLI writes all long-term preferences — which model to use, which API key to fill in, how many steps an Agent can run per turn — into TOML (a plain-text configuration format with a clear structure) files. Change them once and they take effect on every startup. Agent and runtime settings live in config.toml; terminal-UI and client preferences (theme, editor, notifications, status line, auto-update) live in a companion tui.toml.

Default location: ~/.pythinker-code/config.toml, created automatically on first run.

Config file location

The CLI reads configuration from ~/.pythinker-code/config.toml. To relocate the data directory, override it with the PYTHINKER_CODE_HOME environment variable:

sh
export PYTHINKER_CODE_HOME=/path/to/pythinker-home

The config file path then becomes $PYTHINKER_CODE_HOME/config.toml. Regardless of where the directory lives, the file name is always config.toml.

TIP

TOML field names always use snake_case, for example default_model and max_context_size. If a key contains ., you must quote it — for example [models."gpt-4.1"] — otherwise TOML treats . as a nested table separator.

Complete example

The following example covers the most commonly used configuration fields. You can copy it and adjust as needed:

toml
default_model = "pythinker-code/pythinker-for-coding"
default_thinking = true
default_permission_mode = "manual"
default_plan_mode = false
merge_all_available_skills = true
telemetry = true

[providers."managed:pythinker-code"]
type = "pythinker"
base_url = "https://api.pythinker.com/coding/v1"
api_key = ""

[models."pythinker-code/pythinker-for-coding"]
provider = "managed:pythinker-code"
model = "pythinker-for-coding"
max_context_size = 262144

[thinking]
mode = "auto"

[loop_control]
max_retries_per_step = 10
reserved_context_size = 50000

[background]
max_running_tasks = 4
keep_alive_on_exit = false

[experimental]
micro_compaction = true

[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"

[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.pythinker-code/hooks/check-bash.mjs"
timeout = 5

Top-level fields

Fields in the config file fall into two categories: top-level scalars that directly control default behavior, and nested tables (providers, models, thinking, etc.) that each have their own structure, described individually in the sections below.

FieldTypeDefaultDescription
default_modelstringDefault model alias; must be defined in models
default_thinkingbooleanfalseWhether new sessions enable Thinking (deep reasoning) mode by default; can be toggled from the model menu inside a session. Even when set to true, [thinking].mode = "off" will still force Thinking off
default_permission_modestringmanualDefault permission mode for new sessions; one of manual (prompt each time), yolo (auto-approve tool actions, but the agent may still ask questions), or auto (fully autonomous — the agent decides everything without asking)
default_plan_modebooleanfalseWhether new sessions start in Plan mode (produce a plan before executing) by default
merge_all_available_skillsbooleantrueWhether to merge Agent Skills from all available directories
extra_skill_dirsarray<string>Extra skill search directories, layered on top of the default directories
telemetrybooleantrueWhether anonymous telemetry is enabled; disabled only when explicitly set to false
providerstable{}API provider table → providers
modelstableModel alias table → models
thinkingtableDefault parameters for Thinking mode → thinking
loop_controltableAgent loop control parameters → loop_control
backgroundtableBackground task runtime parameters → background
experimentaltableExperimental feature overrides → experimental
servicestableBuilt-in external service configuration → services
permissiontableInitial permission rules → permission
hooksarray<table>Lifecycle hooks; see Hooks

The following sections cover each of the nested tables in turn: providers, models, thinking, loop_control, background, experimental, services, and permission.

providers

Each entry in the providers table defines an API provider, keyed by a unique name. Shell credentials are not guessed automatically: set api_key_env_var to opt a provider into reading one named shell variable (see Config overrides).

FieldTypeRequiredDescription
typestringYesProvider type: pythinker, anthropic, openai, openai_responses, google-genai, vertexai
api_keystringNoAPI key, written in plain text in the config file
api_key_env_varstringNoName of a shell environment variable containing the API key; the name is persisted, not its value
base_urlstringNoAPI base URL
oauthtableNoOAuth credential reference (storage and key fields); injected automatically by the login flow — normally no need to write this by hand
envtable<string, string>NoFallback source for provider credentials; see below
custom_headerstable<string, string>NoCustom HTTP headers attached to each request
sourcetableNoCatalog or custom-registry refresh metadata written by provider commands; normally not edited by hand

api_key_env_var: The provider resolves this exact name from the process environment when it is used. Missing or blank values fail before a provider request, and redacted configuration APIs report only whether a value is available:

toml
[providers.deepseek]
type = "openai"
base_url = "https://api.deepseek.com"
api_key_env_var = "DEEPSEEK_API_KEY"

env sub-table: You can write provider-conventional key names (such as PYTHINKER_API_KEY) inside [providers.<name>.env] as a fallback source for api_key / base_url. This sub-table is read only from the config file and does not modify the shell environment:

toml
[providers.pythinker.env]
PYTHINKER_API_KEY = "sk-xxx"
PYTHINKER_BASE_URL = "https://api.pythoughts.ai/v1"

Priority: api_key field > shell value named by api_key_env_var > env sub-table key > if all are absent, startup fails with an error. base_url retains its existing priority: direct field, then the provider-conventional key in the env sub-table.

models

Each entry in the models table defines a model alias (the name used in default_model or the -m flag), keyed by a unique name.

FieldTypeRequiredDescription
providerstringYesName of the provider to use; must be defined in providers
modelstringYesModel identifier sent to the server when calling the API
max_context_sizeintegerYesMaximum context length in tokens; must be at least 1
max_output_sizeintegerNoPer-request output token cap (maps to max_tokens). Currently only the anthropic provider honors it; recognized Claude models are automatically clamped to the server-side maximum
capabilitiesarray<string>NoCapability tags to add explicitly: thinking, image_in, video_in, audio_in, tool_use, fast_mode. Unioned with the capabilities auto-detected by the provider — entries can only be added, never removed. Add fast_mode to a compatible custom gateway only when it implements the provider's native Fast request contract
display_namestringNoName shown in the UI; falls back to model when unset
reasoning_keystringNoopenai provider only. Override the field name used for reasoning content when the gateway returns it under a non-standard name; by default reasoning_content, reasoning_details, and reasoning are auto-detected
adaptive_thinkingbooleanNoanthropic provider only. Force adaptive thinking on or off, overriding the version inference based on the model name. Omit to infer automatically (Claude ≥ 4.6 uses adaptive)

When an alias contains ., use a quoted key:

toml
[models."gpt-4.1"]
provider = "openai"
model = "gpt-4.1"
max_context_size = 1047576

You can also switch models temporarily without touching the config file — by setting PYTHINKER_MODEL_* environment variables, the CLI synthesizes a temporary provider in memory that does not persist after restart. See Define a model from environment variables.

thinking

thinking sets the global default behavior for Thinking mode. mode = "off" forces Thinking off even when the top-level default_thinking = true.

FieldTypeDefaultDescription
modestringTrigger policy: auto (decided by the model), on (always on), off (force off)
effortstringhighThinking effort level: low, medium, high, xhigh, max; the levels actually available depend on the provider

loop_control

loop_control governs the step count limit, per-step retry count, and the threshold that triggers automatic context compaction in the Agent execution loop.

FieldTypeDefaultDescription
max_steps_per_turnintegerMaximum steps per turn; unset or 0 means unlimited
max_retries_per_stepinteger10Maximum retries after a step failure
reserved_context_sizeintegerNumber of tokens reserved for model output; automatic compaction is triggered when the remaining context window falls below this value

background

background controls the concurrency behavior of background tasks (launched via the Bash tool or the Agent tool's run_in_background=true parameter).

FieldTypeDefaultDescription
max_running_tasksintegerMaximum number of background tasks running concurrently
keep_alive_on_exitbooleanfalseWhether to keep still-running background tasks when the session closes. By default, Pythinker Code requests that all background tasks stop before the process exits; set this to true only when you want tasks to outlive the session

keep_alive_on_exit can be overridden by the PYTHINKER_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT environment variable, which takes higher priority than config.toml.

experimental

experimental stores persistent overrides for experimental-feature flags. Currently, micro_compaction is the only user-facing entry and defaults to true; set it to false only when you need to disable automatic trimming of older large tool results.

FieldTypeDefaultDescription
micro_compactionbooleantrueTrim older large tool results from context while preserving recent conversation

services

services configures two built-in services: web search (pythoughts_search) and web fetch (pythoughts_fetch). Only these two fixed keys are recognized; other keys are ignored. Both entries share the same fields:

FieldTypeRequiredDescription
base_urlstringNoService API URL
api_keystringNoAPI key
oauthtableNoOAuth credential reference, same structure as providers.*.oauth
custom_headerstable<string, string>NoCustom HTTP headers attached to each request
toml
[services.pythoughts_search]
base_url = "https://api.pythoughts.com/v1/search"
api_key = "sk-xxx"

[services.pythoughts_fetch]
base_url = "https://api.pythoughts.com/v1/fetch"
api_key = "sk-xxx"

permission

permission sets permission rules that are automatically loaded when a session starts, controlling whether the Agent needs user confirmation before calling a tool. Rules are written as a [[permission.rules]] array of tables, matched in order — the first matching rule takes effect.

FieldTypeRequiredDescription
decisionstringYesAction on match: allow (permit immediately), deny (reject immediately), ask (prompt each time)
scopestringNoRule scope: turn-override, session-runtime, project, user; defaults to user
patternstringYesMatch pattern in the form ToolName or ToolName(arg-pattern), e.g. Read or Bash(rm -rf*)
reasonstringNoRule description for debugging and auditing

Built-in tool names are listed in Built-in tools. Most built-in tools that accept rule arguments define their own matching subject, such as Bash(command-pattern) or Read(path-pattern). DynamicWorkflow, MCP tools, and custom tools can only be matched by tool name — argument patterns are not supported for them.

toml
[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "allow"
pattern = "Grep"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"

[[permission.rules]]
decision = "ask"
pattern = "Bash"

TIP

MCP server declarations are configured in ~/.pythinker-code/mcp.json or the project-local .pythinker-code/mcp.json, not in config.toml. The interactive configuration entry point is /mcp-config; see Model Context Protocol.

tui.toml

Alongside config.toml, the CLI keeps terminal-UI and client preferences, including status-line visibility, in a companion tui.toml in the same directory (~/.pythinker-code/tui.toml, or $PYTHINKER_CODE_HOME/tui.toml when overridden). It is created with defaults on first run, and the interactive commands /config, /theme, and /editor write to it for you — so you rarely need to edit it by hand. If the file is malformed, the CLI falls back to defaults and shows a notice instead of failing to start.

FieldTypeDefaultDescription
themestringautoColor theme: auto (follow the terminal), dark, light, or the name of a custom theme
layoutstringfixedScreen layout: fixed (full-height screen with the input box pinned to the bottom; the mouse wheel scrolls the transcript and drag-selecting text copies it to the clipboard) or inline (legacy flow that grows with the terminal's native scrollback)
[editor].commandstring""External editor command for composing long input; empty falls back to $VISUAL / $EDITOR
[notifications].enabledbooleantrueWhether desktop notifications are sent
[notifications].notification_conditionstringunfocusedWhen to notify: unfocused (only when the terminal is not focused) or always
[upgrade].auto_installbooleantrueWhether new versions are installed automatically
[status_line].show_modelbooleantrueShow the model name and session spend
[status_line].show_effortbooleantrueShow Thinking effort when show_model is also true
[status_line].show_token_speedbooleantrueShow live token speed when show_model is also true
[status_line].show_context_barbooleantrueShow the context gauge, percentage, and token totals
[status_line].show_gitbooleantrueShow the Git branch, changes, and pull request badge
[status_line].show_modesbooleantrueShow Dynamic Workflow, Auto, YOLO, and Plan mode indicators, plus the ↯ fast suffix when the model is visible
[status_line].show_elapsedbooleantrueShow elapsed time while a request is active
[status_line].show_goalbooleantrueShow the goal badge and make it keyboard-focusable
[status_line].show_background_tasksbooleantrueShow both shell-task and background-agent badges and make them keyboard-focusable
toml
# ~/.pythinker-code/tui.toml
theme = "auto" # "auto" | "dark" | "light" | custom theme name
layout = "fixed" # "fixed" | "inline"

[editor]
command = "" # empty uses $VISUAL / $EDITOR

[notifications]
enabled = true
notification_condition = "unfocused" # "unfocused" | "always"

[upgrade]
auto_install = true

[status_line]
show_model = true
show_effort = true
show_token_speed = true
show_context_bar = true
show_git = true
show_modes = true
show_elapsed = true
show_goal = true
show_background_tasks = true

Every [status_line] field is optional and defaults to true. show_effort and show_token_speed take effect only while show_model is enabled, and show_background_tasks controls both shell-task and background-agent badges. show_modes also controls the dedicated red YOLO mode indicator; its ↯ fast suffix appears only when show_model is enabled too.

This table only hides items already present in the compact status row. It does not control transient hints, validation or activity rows, composer content, welcome-banner tips, working-directory text, or a clock.

Changes apply on the next start, or immediately with /reload-tui (which reloads only tui.toml); /reload reloads both config.toml and tui.toml.

Next steps