Skip to Content
synth

Configuration

Files

Three files, split by what they hold.

fileholdswhere
config.jsonwhat you set by hand$XDG_CONFIG_HOME/synth/
auth.jsonAPI keys, one per provider$XDG_DATA_HOME/synth/
synth.dbsessions, messages, approvals, the provider and model in use$XDG_DATA_HOME/synth/

Every config.json key is optional. An absent one keeps the built-in default.

keytypedefaultwhat it does
system_promptstringthe built-in briefReplaces the base instructions entirely.
thinkbooltrueAsk for reasoning output. Turn off for models that do not support it.
auto_approve_safe_commandsbooltrueLet a preset of read-only shell commands run without a prompt. Off makes every command a question, however harmless.
approvalsstring"ask"Who decides a call that would otherwise ask: ask puts it to you, auto has the session's model judge it (see Auto approvals), all runs everything, paths outside the project included - for a machine set aside for the agent. PreToolUse hooks still run under each. Turns still stop at their limits; to let one run until the work is done, set the three below to 0 as well.
debug_logstringnoneAppend every request and reply to this file.
max_turn_msnumber1800000How long one turn may run. 0 disables it.
max_turn_tokensnumber2000000Tokens one turn may spend, prompt and completion together. 0 disables it.
max_stall_msnumber120000How long the provider may send nothing before the turn is given up on. 0 disables it. With all three of these at 0, the agent's cap on model calls per turn is lifted too.
skill_pathsarray of strings[]Extra directories to look for skills in, searched in order.
database_pathstringsynth.db beside the other dataWhere sessions live.
mcpobjectnoneMCP servers. See MCP.
hooksobjectnoneCommands run on selected agent lifecycle events. See Hooks.
hook_timeout_msnumber30000How long a hook command may run before it is killed.
webobjectnoneSettings for the web tools. See below.
pruneobjectshed after 30 daysWhat a prune throws away. See Pruning.
bellstringunfocusedWhen a finished turn says so: unfocused, always or never. See below.
{
  "think": true,
  "auto_approve_safe_commands": true,
  "max_turn_tokens": 500000,
  "skill_paths": ["/srv/team/skills"]
}

The provider, its host, the model and the theme are not in here. They are set by using the app and kept in the database.

debug_log is the first thing to reach for when a model goes quiet: it shows whether a request was sent at all, and how large it had grown.

Ollama picks the context window a model is loaded with, and what it picks is usually far below what the model can do. ollama.num_ctx is what synth asks for; leave it out and it asks for the model's advertised maximum, and 0 leaves the choice to the server. Whether the server can give what was asked for is its own business: /api/ps is the last word on what the runner actually got, and that is what the sidebar plans against.

Some settings can be overridden per run by the environment: SYNTH_PROVIDER, SYNTH_DB, SYNTH_SOCKET_DIR, SYNTH_DEBUG_LOG, OLLAMA_HOST, OLLAMA_MODEL, OLLAMA_API_KEY, OLLAMA_THINK, OLLAMA_NUM_CTX, OPENAI_BASE_URL, OPENAI_API_KEY, BRAVE_API_KEY, SYNTH_SEARCH_API_KEY, SYNTH_COPILOT_INTEGRATION_ID. VISUAL and EDITOR decide what ctrl+e opens. Which host-and-key pair applies depends on the protocol the chosen provider speaks, so having both sets exported does not hand one server the other's settings.

GitHub Copilot's experimental direct-HTTP provider uses the copilot-sdk integration profile for model discovery and inference. It keeps synth's own OAuth application and user-agent identity; it does not require the Copilot CLI. Override the profile with SYNTH_COPILOT_INTEGRATION_ID. Values must be nonempty visible ASCII without spaces. GitHub selects model availability by integration profile as well as account policy, so changing the value can restrict the catalog. This compatibility profile is not a guarantee of GitHub support for direct clients.

web_search needs no key: without one it reads DuckDuckGo's HTML results page. That works, but DuckDuckGo throttles it after a handful of searches and answers with a page that has no results on it, which the tool reports as such. A Brave Search key in BRAVE_API_KEY switches it to an API that does not throttle.

A debug build keeps all three files in the working directory, so a checkout never touches installed state.

"bell" decides when a finished turn says so: unfocused (the default), always, or never. It sends both a desktop notification and BEL, because neither lands everywhere.

The default needs the terminal to report focus, which synth asks for at startup; a terminal that does not answer never rings, so use always there. Under tmux both halves need turning on, and neither is the default:

set -g focus-events on
set -g allow-passthrough on

Pruning

The database keeps every transcript, and most of its weight is stored tool output: the full result behind each card, plus the model's reasoning. Left alone that grows without bound.

A prune runs at startup and reclaims it in two steps, each an age in days since a session was last touched:

{
  "prune": {
    "shed_after_days": 30,
    "delete_after_days": 0
  }
}

shed_after_days keeps an old session's transcript but drops the payloads behind it, and the file checkpoints /rewind would use. The cards still read: what the model was shown is on the tool call itself, and only the expanded view loses its full text. This is where nearly all the space goes, so 30 days is the default.

delete_after_days removes an old session outright, messages and all. It defaults to 0, meaning never: losing a transcript is not something to do by accident. Set it if the machine is short of disk.

Zero switches either half off. /prune applies the same policy on demand, and synth db does the same from the shell:

synth db status        what it holds, and what a prune would free
synth db prune [days]  shed sessions idle that many days, or use the config
synth db vacuum        hand freed pages back to the filesystem

db status is a dry run: it says what a prune would take without taking it. A day count given to db prune only ever sheds, so losing a transcript stays something config.json has to ask for.

Edit on GitHub

On this page