audience: [contributor, agent] source_files:
- crates/chatty-core/src/settings/repositories/mod.rs
- crates/chatty-core/src/settings/repositories/generic_json_repository.rs
- crates/chatty-core/src/settings/repositories/oauth_credential_json_repository.rs
- crates/chatty-core/src/settings/repositories/module_settings_json_repository.rs
- crates/chatty-core/src/services/mcp_token_store.rs
- crates/chatty-core/src/settings/models/ related:
- ./env-vars.md
- ./provider-matrix.md
- ./singleton-inventory.md
- ../adrs/settings-integration-map.md
Settings schema reference
When to read this: Find the JSON file, Rust model, key fields, and defaults
for a persisted setting. Source of truth:
crates/chatty-core/src/settings/repositories/ + settings/models/.
Field tables are hand-maintained in scripts/gen-docs-reference.sh; CI checks
the repository file names and the ExecutionSettingsModel / ModelConfig
field lists against the source (make docs-check-reference).
Config vs data directories
JSON settings live under dirs::config_dir()/chatty (used by
GenericJsonRepository). WASM module binaries and agent memory live
under dirs::data_dir()/chatty.
| Kind | Resolver | Linux | macOS | Windows |
|---|---|---|---|---|
| Config (this page) | dirs::config_dir()/chatty | ~/.config/chatty/ ($XDG_CONFIG_HOME/chatty) | ~/Library/Application Support/chatty/ | %APPDATA%\chatty\ |
| Data | dirs::data_dir()/chatty | ~/.local/share/chatty/ ($XDG_DATA_HOME/chatty) | ~/Library/Application Support/chatty/ | %APPDATA%\chatty\ |
On macOS and Windows, config and data resolve to the same Application Support /
AppData folder. On Linux they differ (~/.config vs ~/.local/share).
Persistence behaviour
- Single-object files (
load/save): missing file →T::default(). - List files (
load_all/save_all): missing file →[]. - Settings JSON writes are atomic: pretty-printed JSON to a sibling
*.json.<pid>.<seq>.tmp, then rename (GenericJsonRepository). Saves of one file are serialized and the newest call wins; an older save that lands late is dropped rather than written over a newer one. - Serde field names are snake_case unless a table notes otherwise.
#[serde(skip)]fields are runtime-only and never written to disk.
File inventory
| File | Shape | Model | Repository |
|---|---|---|---|
general_settings.json | object | GeneralSettingsModel | GeneralSettingsJsonRepository |
execution_settings.json | object | ExecutionSettingsModel | ExecutionSettingsJsonRepository |
search_settings.json | object | SearchSettingsModel | SearchSettingsJsonRepository |
training_settings.json | object | TrainingSettingsModel | TrainingSettingsJsonRepository |
user_secrets.json | object | UserSecretsModel | UserSecretsJsonRepository |
hive_settings.json | object | HiveSettingsModel | HiveSettingsJsonRepository |
extensions.json | object | ExtensionsModel | ExtensionsJsonRepository |
token_tracking.json | object | TokenTrackingSettings | TokenTrackingJsonRepository |
module_settings.json | object | ModuleSettingsModel | ModuleSettingsJsonRepository |
providers.json | array | ProviderConfig | JsonFileRepository |
models.json | array | ModelConfig | JsonModelsRepository |
mcp_servers.json | array | McpServerConfig | JsonMcpRepository |
a2a_agents.json | array | A2aAgentConfig | A2aJsonRepository |
mcp_oauth_<sanitized>.json | object | opaque StoredCredentials JSON | live: FileCredentialStore; also JsonOAuthCredentialRepository |
Secrets in bold must never appear in logs, traces, or LLM-facing tool output.
Secrets kept outside the JSON (AGE-741). In providers.json,
mcp_servers.json, a2a_agents.json and hive_settings.json the secret
fields marked (store) below are not written to the file. The model keeps
the plain field; on disk it becomes <field>_ref ("api_key_ref": "provider/<name>", mcp/<name>, a2a/<name>, hive/token,
hive/refresh_token) and the value lives in the SecretStore
(settings/secret_store.rs): the OS keychain under the service
chatty:<config dir>, or, when no keychain answers, the owner-only
secrets.json in the config dir (one warning line; CHATTY_SECRET_STORE=keychain
refuses the fallback, =file forces it). Docs examples use "****" or omit the field.
general_settings.json — GeneralSettingsModel
Source: settings/models/general_model.rs.
| Field | Type | Default | Notes |
|---|---|---|---|
font_size | f32 | 14.0 | UI font size (px) |
theme_name | Option<String> | null | Theme folder name; null = built-in default |
dark_mode | Option<bool> | null | null = follow OS / theme default |
{ "font_size": 14.0, "theme_name": null, "dark_mode": null }
execution_settings.json — ExecutionSettingsModel
Source: settings/models/execution_settings.rs. Master toggle enabled is
opt-in (false) for security.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Master code-execution toggle |
approval_mode | ApprovalMode | "AutoApproveSandboxed" | JSON enum: AlwaysAsk, AutoApproveSandboxed, AutoApproveAll |
workspace_dir | Option<String> | null | Absolute path required for filesystem / git tools |
filesystem_read_enabled | bool | true | Requires workspace_dir |
filesystem_write_enabled | bool | true | Requires workspace_dir + approval |
fetch_enabled | bool | true | Built-in read-only HTTP GET |
git_enabled | bool | false | Opt-in; workspace must be a git repo |
browser_enabled | bool | false | Opt-in built-in browser tools; may download a pinned Chrome on first use |
allow_private_network_access | bool | false | With internet access on, let the browser reach private/internal IPs (RFC-1918) on your own network; the SSRF guard refuses them otherwise. Cloud-metadata 169.254.0.0/16 stays refused regardless (AGE-459) |
execute_code_enabled | bool | false | Exposes execute_code to the model |
docker_code_execution_enabled | bool | false | Docker fallback for non-Monty code |
docker_host | Option<String> | null | Socket / URI; null = auto-detect |
timeout_seconds | u32 | 30 | Execution timeout |
max_output_bytes | usize | 51200 | 50 KiB cap |
network_isolation | bool | false | Sandbox network isolation when available |
sandbox_required | bool | false | Refuse to start a shell outside the sandbox instead of falling back to the host (AGE-918) |
max_agent_turns | u32 | 0 | Tool-call rounds per response; 0 = no cap (headless runs get a wall-clock budget instead, chatty-tui --max-duration) |
memory_enabled | bool | true | remember / search_memory |
warn_on_external_agent | bool | false | say so before invoke_agent sends a prompt outside the fleet (ADR-0011 C5) |
embedding_enabled | bool | false | Semantic memory search |
embedding_provider | Option<ProviderType> | null | Independent of chat provider |
embedding_model | Option<String> | null | e.g. text-embedding-3-small |
hosted_conversations_enabled | bool | false | Developer-only: offers the per-conversation move between local and hosted (AGE-308) |
tool_loading | ToolLoading | "all" | How tools are offered to the model. JSON: all (every allowed tool on every request) or dynamic (a small core, plus groups the model loads with load_tools). Omitted from the file while all. CLI override: chatty-tui --tool-loading |
ask_user_enabled | bool | true | Offer the ask_user tool (ADR-0011 C7). Omitted from the file while true. CLI override: chatty-tui --disable ask-user (unattended runs, so a stray call fails fast instead of blocking) |
terminal_access | bool | false | Offer the read-only terminal_read tool over the user’s tmux panes (AGE-577); registered only while a tmux server has panes. Omitted from the file while false. CLI: chatty-tui --enable terminal |
ApprovalMode has no rename_all — JSON uses the Rust variant names above.
search_settings.json — SearchSettingsModel
Source: settings/models/search_settings.rs. Also stores Browser Use and
Daytona keys (not only web search).
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Master web-search toggle |
active_provider | SearchProvider | "Tavily" | JSON: Tavily or Brave |
tavily_api_key | Option<String> | null | Secret |
brave_api_key | Option<String> | null | Secret |
max_results | usize | 5 | Search hit cap |
browser_use_enabled | bool | true | Key alone is enough to activate; set false to disable without deleting the key |
browser_use_api_key | Option<String> | null | Secret |
daytona_enabled | bool | true | Same pattern as browser-use |
daytona_api_key | Option<String> | null | Secret |
rerank_url | Option<String> | null | Cohere/Jina-style /rerank endpoint; keyless search only, needs rerank_model too |
rerank_model | Option<String> | null | Model name sent to rerank_url |
{
"enabled": false,
"active_provider": "Tavily",
"max_results": 5,
"browser_use_enabled": true,
"daytona_enabled": true
}
training_settings.json — TrainingSettingsModel
Source: settings/models/training_settings.rs. Both flags opt-in.
| Field | Type | Default | Notes |
|---|---|---|---|
atif_auto_export | bool | false | ATIF JSON after each completed assistant turn |
jsonl_auto_export | bool | false | JSONL (SFT + DPO) after each completed turn |
user_secrets.json — UserSecretsModel
Source: settings/models/user_secrets_store.rs. Injected into shell sessions
as env vars; never sent to the LLM.
| Field | Type | Default | Persisted? |
|---|---|---|---|
secrets | [{key, value}] | [] | yes — values are secrets |
revealed_keys | HashSet<String> | empty | no (#[serde(skip)]; UI-only) |
{ "secrets": [ { "key": "EXAMPLE_TOKEN", "value": "****" } ] }
hive_settings.json — HiveSettingsModel
Source: settings/models/hive_settings.rs.
| Field | Type | Default | Notes |
|---|---|---|---|
registry_url | String | http://localhost:8080 | DEFAULT_REGISTRY_URL |
runner_url | String | http://localhost:8081 | DEFAULT_RUNNER_URL |
token | Option<String> | null | One-hour access JWT. Secret (store: token_ref → hive/token). is_logged_in() = token present |
refresh_token | Option<String> | null | 30-day refresh token, rotated on every refresh. Secret (store: refresh_token_ref → hive/refresh_token) |
expires_at | Option<DateTime> | null | When token expires |
username | Option<String> | null | Cached for UI |
email | Option<String> | null | Cached for re-login |
extensions.json — ExtensionsModel
Source: settings/models/extensions_store.rs. Unified install list for MCP,
WASM, and A2A.
| Field | Type | Default | Persisted? |
|---|---|---|---|
extensions | [InstalledExtension] | [] | yes |
mcp_auth_statuses | map | empty | no (#[serde(skip)]) |
a2a_statuses | map | empty | no (#[serde(skip)]) |
InstalledExtension:
| Field | Type | Default | Notes |
|---|---|---|---|
id | String | required | Unique slug |
display_name | String | required | UI name |
description | String | required | |
kind | ExtensionKind | required | Internally tagged kind: mcp (flattened McpServerConfig), wasm, a2a (flattened A2aAgentConfig) |
source | ExtensionSource | required | Internally tagged type: hive (module_name, version) or custom |
pricing_model | Option<String> | null | Hive marketplace classification |
enabled | bool | true |
token_tracking.json — TokenTrackingSettings
Source: settings/models/token_tracking_settings.rs. The compaction share —
ADR-0007’s utilisation key. Ratio fields (high_threshold,
critical_threshold) are fractions of the model’s context window, never a
character count.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | true | Show the token context bar |
response_reserve | usize | 4096 | Tokens reserved for model output |
high_threshold | f64 | 0.70 | Ratio 0.0–1.0; bar turns amber |
critical_threshold | f64 | 0.90 | Ratio 0.0–1.0; bar turns red |
auto_summarize | bool | false | Auto-summarize on crossing critical_threshold |
summarization_model_id | Option<String> | null | null = use the conversation’s own model |
cap_usd | Option<f64> | null | Monthly spend cap (USD); hive-only (AGE-416 / ADR-0010) |
module_settings.json — ModuleSettingsModel
Source: settings/models/module_settings.rs. Load path
(ModuleSettingsJsonRepository) runs normalize_module_dir(): empty values
and the legacy .chatty/modules fallback become the platform data-dir default.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | WASM module runtime |
module_dir | String | platform data dir + /chatty/modules | Linux ~/.local/share/chatty/modules; macOS ~/Library/Application Support/chatty/modules; Windows %APPDATA%\chatty\modules. Last-resort fallback: .chatty/modules |
gateway_port | u16 | 8420 | Local protocol gateway |
default_endpoint_budget | usize | 1 | Delegated workers the broker runs at once against one model endpoint when nothing more specific is known (ADR-0011 C6, AGE-305). Ollama’s OLLAMA_NUM_PARALLEL / a provider’s num_parallel beat it |
endpoint_budgets | HashMap<String, usize> | {} | Per-endpoint overrides keyed by the server’s base URL (http://localhost:11434); beats the default and what the provider reports. Omitted when empty |
virtual_agents | [string] | [] | The broker’s named workers (ADR-0011 C10, AGE-377), by agent spec name (AGE-614): each is <name>.toml in .chatty/agents/, the data directory’s chatty/agents/, or the presets. Agent objects (the old VirtualAgentConfig shape) fail to load. Empty = local-agent and every exposed spec (PL-U5). A --team run replaces this roster for that run only. Field table: Agents and specs |
team | TeamConfig | {} | What the team shares: verification — a shell command run in each worker’s worktree at task end, whose exit code and tail go into the evidence envelope (ADR-0011 C12, AGE-406); isolate — each worker of a roster no team claims gets a git worktree of its own (default false, AGE-822). Omitted when empty |
providers.json — [ProviderConfig]
Source: settings/models/providers_store.rs. See also
Provider matrix.
| Field | Type | Default | Notes |
|---|---|---|---|
name | String | required | Display name |
provider_type | ProviderType | required | JSON: open_router, ollama, azure_openai. Serde rename_all = "snake_case" on the variant OpenRouter yields open_router — the string openrouter does not deserialize. Legacy aliases open_ai, open_a_i, anthropic, gemini, mistral deserialize as OpenRouter |
api_key | Option<String> | omitted if null | Secret. Never expose to the LLM (store: api_key_ref → provider/<name>) |
base_url | Option<String> | omitted if null | Ollama / Azure / OpenAI-compat |
extra_config | Map<String, String> | {} (omitted if empty) | Azure: auth_method = api_key (default) or entra_id |
configured_providers(): Ollama always; Azure needs non-empty base_url plus
API key or Entra ID; others need a non-empty API key.
models.json — [ModelConfig]
Source: settings/models/models_store.rs.
| Field | Type | Default | Notes |
|---|---|---|---|
id | String | required | Internal UUID (not the API model name) |
name | String | required | UI label |
provider_type | ProviderType | required | Same enum as providers (open_router / ollama / azure_openai) |
model_identifier | String | required | API / Ollama model id |
temperature | f32 | 1.0 | |
preamble | String | "" | System prompt / GEPA target |
max_tokens | Option<i32> | omitted if null | |
top_p | Option<f32> | omitted if null | |
extra_params | Map<String, String> | {} (omitted if empty) | Azure: api_version (default 2025-03-01-preview) |
cost_per_million_input_tokens | Option<f64> | omitted if null | USD |
cost_per_million_output_tokens | Option<f64> | omitted if null | USD |
cost_per_million_cache_read_tokens | Option<f64> | omitted if null | USD; falls back to the input rate when unset |
cost_per_million_cache_write_tokens | Option<f64> | omitted if null | USD; falls back to the input rate when unset |
supports_images | bool | false | New models inherit ProviderType::default_capabilities() |
supports_pdf | bool | false | |
supports_temperature | bool | true | Off for some reasoning models |
max_context_window | Option<i32> | omitted if null | Token-bar budget |
source | ModelSource | "User" | User (added by hand or from a catalogue pick) or Sync (owned by a provider sync) |
is_favorite | bool | false | Starred rows sort to the top of the roster |
is_default | bool | false | Model new conversations start with |
mcp_servers.json — [McpServerConfig]
Source: settings/models/mcp_store.rs. HTTP MCP endpoints only (the app does
not spawn stdio servers). Writes are serialized with MCP_WRITE_LOCK.
| Field | Type | Default | Notes |
|---|---|---|---|
name | String | required | Unique id |
url | String | required | e.g. http://localhost:3000/mcp |
api_key | Option<String> | omitted if null | Bearer token. Secret. LLM-facing copies must not include the real value (store: api_key_ref → mcp/<name>) |
enabled | bool | true | |
is_module | bool | false (omitted when false) | Auto-registered WASM gateway entry |
Runtime McpAuthStatus is not persisted.
MASKED_API_KEY_SENTINEL is "****": on edit, that string means “keep the
stored key”. There is no env map on McpServerConfig in current source.
a2a_agents.json — [A2aAgentConfig]
Source: settings/models/a2a_store.rs.
| Field | Type | Default | Notes |
|---|---|---|---|
name | String | required | Also the /agent <name> token |
url | String | required | A2A base URL |
api_key | Option<String> | omitted if null | Bearer token. Secret (store: api_key_ref → a2a/<name>) |
enabled | bool | true | |
skills | [String] | [] (omitted if empty) | Cached from the remote agent card |
Runtime A2aAgentStatus is not persisted.
mcp_oauth_<sanitized>.json — OAuth credentials
One file per MCP server name in the config dir. Non-alphanumeric
characters in the server name (except - _) become _.
Example: server my server/with:special.chars →
mcp_oauth_my_server_with_special_chars.json.
The live writer is FileCredentialStore (mcp_token_store.rs), used by
McpService. JsonOAuthCredentialRepository uses the same filenames but is
not registered in init_repositories(). FileCredentialStore writes the
JSON directly (not the temp-file rename used by settings repos).
Shape is opaque rmcp::StoredCredentials JSON (client_id,
token_response, granted_scopes, …). Do not paste real token files
into issues, PRs, or docs. Corrupt files are deleted on load.
Not persisted as JSON (yet)
| Store | Typical path | Notes |
|---|---|---|
TokenTrackingSettings | in-memory GPUI global | Defaults: enabled true, response_reserve 4096, high_threshold 0.70, critical_threshold 0.90, auto_summarize false, summarization_model_id omitted. Source comment: JSON persistence is a follow-up. |
| Conversations | config dir chatty/conversations.db | SQLite, not settings JSON |
| Provider keys, MCP/A2A tokens, Hive tokens | OS keychain, service chatty:<config dir>; fallback config dir chatty/secrets.json | Referenced from the JSON by <field>_ref (AGE-741) |
| Agent memory / skills | data dir chatty/ | memvid + skill files |
| WASM module binaries | data dir chatty/modules/ | See module_dir |
| Pdfium native lib | data dir chatty/lib/ | See env-vars |
| Math SVG cache | data dir chatty/math_cache/ |
Planned research settings (FlowSettingsModel, playbook store) are not
implemented — see settings integration map.
openrouter_curated.json also lives in the config dir (GPUI OpenRouter catalog
override: [{id, name}, …]). It is not a settings repository.
Related
- Settings integration map — research ↔ settings
- Environment variables —
CHATTY_*/XDG_* - Provider matrix — auth and default capabilities
- Singleton inventory — repository accessors