Repository navigation
10_Configuration
Imza's settings live in one JSON file, config.json. API keys normally do
not: they are kept in the OS keychain (see Credentials).
Imza's own dialogs write the file for you, and their workflows are documented
on the feature pages. This page is the reference for the file itself: what
each key means, its default, and what Imza does with it, for the times you
edit it by hand, share it between machines, or script a setup.
| Platform | Path |
|---|---|
| Linux |
$XDG_DATA_HOME/imza/config.json, or $HOME/.local/share/imza/config.json when unset |
| macOS | $HOME/Library/Application Support/imza/config.json |
| Windows | %APPDATA%\imza\config.json |
imza --configImza creates the file when it is missing and opens it in $VISUAL, falling
back to $EDITOR, then to the platform default. Every key is optional, so an
empty object is a valid configuration.
The first key in an Imza-written file is $schema, pointing at
https://imza.run/config.schema.json. Editors that read $schema offer
completion and flag mistakes as you type.
Other files sit beside config.json in the platform data directory:
| File | Purpose |
|---|---|
presets.json |
Cached model catalog. |
input-history.json |
Last 100 submitted prompts. |
update.json |
Cached update-check results. |
sessions/ |
Saved sessions. |
prompts/ |
Optional Markdown prompt overrides. |
$XDG_CONFIG_HOME is only used for discovering project instruction files
(AGENTS.md, CLAUDE.md, GEMINI.md) and project skills.
-
Bad values stop startup. One unparseable field rejects the whole file:
Imza prints
config error (<code>): <message>and exits. It does not run with a partially applied configuration. Typical culprits are amodelsentry naming a provider that is not inproviders, or an unknown skill policy name. -
Two sections tolerate stale entries. An
allow.directoriespath that no longer exists, and aninstructionsfile that is missing, are skipped instead of failing startup, so one file can be shared across machines. - Unknown keys are ignored on read and dropped on write. Imza rebuilds the document from its own model whenever it saves, so a key it does not recognize does not survive the next save.
- Imza writes this file too. The dialogs listed below save through it, and MCP sign-ins store tokens there. Your manual edits and another running Imza instance do not overwrite each other: each write takes an exclusive lock, re-reads the file, applies its change, and replaces the file only when something changed.
- Provider credentials live in your OS keychain. API keys and subscription tokens are not written to this file. See Credentials.
-
Files are owner-only. On Linux and macOS Imza writes its files with
mode
0600and creates the data directory with0700. - Saved files stay minimal. Imza omits keys that hold their default value, so a value you set can disappear from the file once it matches the default.
Most sections are managed by a dialog; the rest are edited by hand.
| Section | Managed by | Documented in |
|---|---|---|
providers |
/connect |
Connecting a model |
models.main |
/model, /variant
|
Connecting a model |
models.builder, models.researcher, models.basic
|
/subagents |
Subagents |
skills.global, skills.projects
|
/skills |
Skills and instructions |
mcp_servers |
/mcp |
MCP servers |
allow |
/permissions (--allow-dir / --allow-cmd for one run) |
Permissions |
preferences |
by hand | this page |
sidebar, instructions, prompt overrides |
by hand | this page |
Approving a request in a modal grants it for the session in memory only;
it never writes the file. To make an approval permanent, use the
/permissions dialog, which writes this section for you; editing allow
by hand works too. See Permission grants.
API keys and subscription tokens are not stored in config.json. They live in
your operating system's keychain: Secret Service on Linux, Keychain on macOS,
Credential Manager on Windows. Use preferences.credentials to change that:
{ "preferences": { "credentials": "os" } }| Value | Meaning |
|---|---|
os |
Default. Secrets go to the keychain and are left out of this file. |
plaintext |
Keep secrets in this file instead. |
Any other value is a configuration error.
Containers, CI runners, and SSH logins without a desktop session have no keychain. Imza still starts: it keeps secrets in this file for that run and warns you at startup.
The warning repeats on every start until a keychain appears, because Imza
never changes your setting on its own. Installing a keyring later therefore
needs no action: the next start finds it and moves the secrets out of the file.
On a machine that will never have one, set "plaintext" yourself to choose
that storage and stop the warning.
A config.json that still contains api_key or refresh_token is migrated
on the first start that finds a keychain. The values move into it and the file
is rewritten without them. The keychain wins, so a hand-edited plaintext key
never overrides a stored one. The same happens when you switch from
plaintext back to os.
MCP tokens are not covered by this section. They stay in mcp_servers, under
bearer_token and oauth.
providers is the list of model endpoints Imza can talk to. Add entries in the
/connect dialog; the keys below matter when you edit the file by hand, share a
config between machines, or script a setup.
{
"providers": [
{ "id": "openai" },
{ "id": "custom", "label": "my ollama",
"endpoint": "http://localhost:11434/v1" },
{ "id": "anthropic",
"dialects": { "claude-opus-4-1": "anthropic" } }
]
}The entries have no api_key: that field is written only in plaintext
mode.
| Key | Type | Default | Meaning |
|---|---|---|---|
id |
string | required | Provider identity, taken from the built-in catalog, or custom for your own endpoint. Empty is a configuration error. |
label |
string | none | Distinguishes several connections that share an id, for example two Ollama servers. Cannot contain /. |
endpoint |
string | catalog default | Base URL of a custom endpoint. A trailing /chat/completions is stripped. Leave it out for hosted providers. |
api_key |
string | none | Key sent with each request. Omit it for endpoints that need none. Normally in the keychain; written here only in plaintext mode. |
dialects |
object | none | Per-model wire protocol override: model id to openai, openai-responses or anthropic. Use it when a server deviates from what Imza infers for that model. |
refresh_token |
string | none | Subscription sign-in credential. Imza writes it, normally to the keychain; written here only in plaintext mode. |
expires_at |
integer | 0 |
When that credential expires, in Unix seconds. Imza writes it. |
account_id |
string | none | Subscription account identifier. Imza writes it. |
An entry's identity is its id, or id/label when a label is present. That
combined form is what models and other sections use to name a connection. Two
entries with the same identity are relabelled deterministically at load, so the
second one becomes 2 and the third 3.
Removing the last remaining connection from the file leaves Imza with no model;
the /connect dialog refuses to remove it for the same reason.
See Connecting a model.
models selects the model for the main conversation and for each subagent role.
The main entry also carries the reasoning effort for the conversation.
{
"models": {
"main": { "provider": "openai", "model": "gpt-5.2", "reasoning_effort": "high" },
"builder": { "provider": "openai", "model": "gpt-5.2", "reasoning_effort": "default" },
"researcher": { "reasoning_effort": "low" },
"basic": { "provider": "anthropic", "model": "claude-haiku-4-5" }
}
}| Key | Type | Default | Meaning |
|---|---|---|---|
models.main |
object | none | Active model for the conversation. Set it with /model and /variant. |
models.builder |
object | main model | Model for Builder subagents, which edit files. |
models.researcher |
object | main model | Model for Research subagents, which are read-only. |
models.basic |
object | main model | Model for the one-shot passes: session titles, AI Review, and handoff. |
<role>.provider |
string | none | Connection identity, id or id/label. Must match an entry in providers. |
<role>.model |
string | none | Model identifier as the provider reports it. Requires provider. |
<role>.reasoning_effort |
string | per role |
off, low, default or high (medium is accepted as the wire spelling of default; Imza stores default and sends medium). For a subagent role, anything else is a configuration error. |
Per-role effort defaults are default for builder, low for researcher, and
off for basic. A role with no provider falls back to the main model and
keeps its own effort. The basic role defaults to off because its passes are
extraction tasks, but an effort you set there is used.
Whether an effort reaches the provider depends on the model's catalog row,
which is what marks a model as supporting reasoning. Anthropic models receive
a thinking budget instead of an effort field; a model with no catalog row
(every model on a custom connection, or one the catalog does not list yet)
is treated as unable to reason, and nothing is sent whatever you set. The
selection is stored and displayed even when nothing is sent, which is why
reasoning can look ignored while the status line still shows your chosen
effort.
An effort on models.main is stored as read and not checked at startup, so a
typo there is ignored rather than reported; the subagent roles are checked,
and a rejected value fails the load. Set the main effort with /variant.
To override the selection for a single run without touching the file, use
imza --model <model> and imza --variant <variant>.
See Connecting a model and Subagents.
skills records the activation policy for each $skill, which you set in the
/skills dialog.
{
"skills": {
"global": { "code-review": "allow", "unslop": "ask" },
"projects": { "/home/me/Source/work/secrets-tool": { "deploy-helper": "deny" } }
}
}| Key | Type | Default | Meaning |
|---|---|---|---|
skills.global |
object | empty | Skill name to policy, for skills discovered outside the project. |
skills.projects |
object | empty | Project root path to its own skill name to policy map. |
| policy | string | ask |
allow activates silently, ask opens an approval modal, deny hides the skill from the model. |
A project entry overrides the global entry for the same skill name. Keys in
skills.projects are matched against the detected project root, so a policy
written on one machine needs the same path to apply on another. Unknown skill
names are harmless: the entry waits until a skill with that name appears.
Bundled skills, such as the imza-docs manual, ignore this section: they
are always allowed and never removable. A policy naming one has no effect.
mcp_servers is a map keyed by server id. Manage servers in the /mcp dialog.
{
"mcp_servers": {
"github": { "catalog_id": "github", "bearer_token": "${GITHUB_TOKEN}" },
"docs": { "url": "https://mcp.example.com/docs", "autoload": true,
"timeout_secs": 60, "headers": { "x-api-key": "${DOCS_KEY}" } },
"local": { "type": "stdio", "command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem"],
"env": { "DEBUG": "1" }, "working_directory": "/tmp" }
}
}| Key | Type | Default | Meaning |
|---|---|---|---|
type |
string | http |
http connects to an endpoint; stdio launches command as a child process. Any other value is a configuration error. |
catalog_id |
string | none | Entry in the bundled catalog. Its URL and label resolve at use time, so the address follows app updates. |
url |
string | none | Endpoint for a custom HTTP server. Plain http:// is upgraded to https://. |
command |
string | none | Program to launch for a stdio server, looked up on PATH and run without a shell. Required when type is stdio. |
args |
array of string | empty | Arguments for command, passed verbatim. |
env |
object | empty | Extra environment variables for the child process, merged over the inherited environment. |
working_directory |
string | inherited | Working directory for the child process. |
headers |
object | empty | Extra HTTP request headers. A name must be non-empty and contain no colon. |
bearer_token |
string | none | Sent as the request bearer token. |
enabled |
boolean | true |
A disabled server never connects and is hidden from the agent. |
autoload |
boolean | false |
Connect at startup. Only an enabled server can autoload. For one run instead of every start, use --allow-mcp <id>. |
timeout_secs |
integer | 0 |
Per-request timeout in seconds, for HTTP requests and for waiting on a stdio server's reply. 0 uses the client default of 25 seconds; a negative value is a configuration error. |
label |
string | catalog or id | Display name in the /mcp list. |
description |
string | catalog | Description shown in the /mcp list. |
oauth |
object | none | Browser sign-in state. Imza writes and refreshes it; see below. |
An http entry needs a url or a catalog_id, and an explicit url wins over
the reference. An entry with neither is a configuration error. A catalog_id
that a later update removes makes the server fail to connect with an explanatory
reason, and the entry starts working again when the catalog regains it.
url, bearer_token, header names and values, args and env values expand
${NAME} and ${NAME:-fallback} from the environment when the connection is
made, so a secret can stay out of the file. The expansion happens at connect
time, not at parse time.
oauth holds the browser sign-in state: the registered client, the
discovered authorization-server endpoints, and the current tokens. Treat it
as Imza-managed state rather than something to hand-edit: background token
refresh overwrites it, and it is only valid on an http server whose
endpoints are https.
See MCP servers.
allow pre-approves work at startup, so the same directories and commands stop
asking on every run. It is the persistent counterpart to the --allow-*
command-line flags, which merge into the same startup grants.
{
"allow": {
"directories": ["/home/me/Source/shared-assets", "scratch"],
"commands": [
{ "program": "rg" },
{ "program": "git", "subcommand": "push" }
]
}
}| Key | Type | Default | Meaning |
|---|---|---|---|
allow.directories |
array of string | empty | Directories the agent may read and edit without asking, including everything beneath them. Absolute, or relative to the directory Imza starts in. |
allow.commands |
array of object | empty | Shell programs the agent may run without asking. |
allow.commands[].program |
string | required | Executable name. Empty is a configuration error. |
allow.commands[].subcommand |
string | all | Narrows the grant to one subcommand. Omit it to approve every subcommand of that program. An empty string is a configuration error. |
A grant here authorizes exactly what it names. Everything else is evaluated as
before, and a grant cannot override Plan mode, a disabled tool, or a malformed
request. Deletion commands such as rm cannot be granted at all; the agent
removes files through its own tool, which always asks. A directories entry
that is missing, or that is not a directory, is skipped rather than failing
startup; an empty string, by contrast, is a configuration error.
Grants you approve during a session are not written here. They live in memory and are cleared when you switch sessions, change directory, or exit; only this section persists them.
See Permissions.
sidebar hides individual side panel widgets. Imza reads it once at startup, so
an edit takes effect on the next launch.
{ "sidebar": { "changed_files": false, "context": false } }| Key | Type | Default | Widget |
|---|---|---|---|
documents |
boolean | true |
Plans and their revisions. |
review_comments |
boolean | true |
Comments anchored in the review diff. |
tasks |
boolean | true |
The agent's task list. Appears only when there are items. |
changed_files |
boolean | true |
Files changed in the working tree. Wide layouts, and only with a Git repository that has changes. |
context |
boolean | true |
Instruction file, attachments, loaded skill names, connected MCP servers. Wide layouts only. |
permissions |
boolean | true |
Active session grants. Wide layouts, and only when a grant changes the defaults. |
Omitting a key keeps that widget visible, and Imza writes only the keys that
differ from true. These flags gate visibility; a widget with nothing to show
stays hidden whatever you set.
instructions names extra Markdown files appended to the system prompt after the
project's AGENTS.md, CLAUDE.md or GEMINI.md. Use it for conventions you do
not want to commit to the repository, or for guidance that lives outside it.
{ "instructions": ["CONTRIBUTING.md", "docs/style.md", "/home/me/rules.md"] }Paths are relative to the project root, or to the working directory when there is no project root. An absolute path is used as given. Files load in the listed order, each capped at 32 KiB, the same limit as the project instruction file. A file that does not exist is skipped, so one config can name files that only exist on some machines. An empty entry is a configuration error.
These files are added to the prompt; they do not replace the project instruction
file, and the first non-empty AGENTS.md, CLAUDE.md or GEMINI.md still wins
as described in Skills and instructions.
Imza's built-in prompts can be replaced with your own Markdown files in a
prompts directory inside the data directory (see
Where the file is). Each file overrides one prompt
fragment: the main system prompt, the subagent prompts, title generation,
history compaction, AI review, the handoff document, the Review to Plan
handoff, and the revise-plan prompt.
Ready-made copies of every default fragment ship in the repository under
misc/prompts/.
That directory is the current list; copy the ones you want to tweak into your
prompts/ directory as a starting point.
A blank or missing file falls back to the embedded default, so you can override just the fragments you care about. Imza picks up the files the next time it starts.
Older files can still contain these. Imza reads them and rewrites them in the current spelling on the next save, so prefer the modern key when you edit by hand.
A file written by an older version also has api_key and refresh_token
inside providers. Imza moves them into the keychain; see
Credentials.
| Old key | Replacement |
|---|---|
providers[].provider_id |
providers[].id, used only when id is absent |
sidebar.plans |
sidebar.documents, used only when documents is absent |
models.reviewer |
models.basic, used only when basic is absent |
{
"$schema": "https://imza.run/config.schema.json",
"preferences": { "credentials": "os" },
"providers": [
{ "id": "openai" },
{ "id": "custom", "label": "ollama", "endpoint": "http://localhost:11434/v1" }
],
"models": {
"main": { "provider": "openai", "model": "gpt-5.2", "reasoning_effort": "high" },
"builder": { "reasoning_effort": "default" },
"researcher": { "provider": "custom/ollama", "model": "qwen3-coder", "reasoning_effort": "low" },
"basic": { "reasoning_effort": "off" }
},
"skills": {
"global": { "code-review": "allow" },
"projects": { "/home/me/Source/work/secrets-tool": { "deploy-helper": "deny" } }
},
"mcp_servers": {
"github": { "catalog_id": "github", "bearer_token": "${GITHUB_TOKEN}", "autoload": true },
"local": { "type": "stdio", "command": "npx", "args": ["-y", "some-mcp-server"] }
},
"allow": {
"directories": ["scratch"],
"commands": [{ "program": "rg" }, { "program": "git", "subcommand": "status" }]
},
"sidebar": { "context": false },
"instructions": ["CONTRIBUTING.md", "/home/me/rules.md"]
}