Workspaces & config layers
Since Synthex 2.3.0, settings can live in four places: your user config, the project's shared config, your own uncommitted overrides for one repo, and the plugin's defaults. The team pins what matters in the repo, you keep personal preferences out of it, and two safety rules make sure a repo can't override what your machine has or switch on external review you turned off.
The four layers
Highest precedence first:
| Layer | Path | Scope |
|---|---|---|
| Project-local | .synthex/config.local.yaml | Your settings for this one repo. Gitignored by init/synthex:init, never committed. |
| Project (shared) | .synthex/config.yaml | The team's settings for this repo. Committed. |
| User | ~/.config/synthex/config.yaml | Your settings for every repo. Honors $XDG_CONFIG_HOME. |
| Plugin defaults | config/defaults.yaml | Shipped with Synthex. |
Put only the keys you want to change in each file. Anything you leave out falls through to the next layer. With no config files at all, every command runs on the plugin defaults.
Project settings beat user settings. This matches Claude Code's settings precedence: a repo can pin behavior for everyone who works in it.
A legacy .synthex-plus/config.yaml is still read between the project and user layers for one
major version. When a key resolves only there, Synthex prints a deprecation notice.
Two exceptions
Two narrow sets of keys don't follow that order.
User-owned: multi_model_review.per_reviewer.*
Which external models are installed on a machine, and what they are called, is a fact about the
machine, not the repo. Synthex checks the user layer first for these keys. A repo that asks for
qwen2.5-coder:32b can't override the model you actually pulled.
Restrictive wins: multi_model_review.enabled and multi_model_review.*.enabled
false in any layer you wrote disables the feature. This works the way Claude Code combines
permission deny rules instead of letting one layer replace another. A repo can't switch on
sending your code to an external CLI when you have switched it off. Plugin defaults don't count
here, so the shipped false is still only a default that a repo can turn on.
Example: a personal kill switch
Put this in your user config to disable external multi-model review in every repo, whatever a repo's config asks for:
# ~/.config/synthex/config.yaml
multi_model_review:
enabled: falseExample: a personal override for one repo
Your project-local file is for preferences the team shouldn't inherit. For example, to use cloud workers for next-priority/synthex:next-priority in one repo without changing the committed config:
# .synthex/config.local.yaml (gitignored)
next_priority:
execution_environment: cloudWhat init sets up
init/synthex:init creates .synthex/config.yaml and the document directories
(docs/reqs/, docs/plans/, docs/specs/, and so on). It also adds these per-developer paths to
.gitignore if they are missing:
# Synthex worktrees (parallel task execution)
.claude/worktrees/
# Synthex per-developer state (upgrade-nudge tracking)
.synthex/state.json
# Synthex native-looping state (per-session, per-developer)
.synthex/loops/
# Synthex cached project facts (per-clone SHAs and mtimes)
.synthex/facts.md
# Synthex project-local config (one developer's overrides for this repo)
.synthex/config.local.yaml
The worktrees entry uses your worktrees.base_path if you changed it. Running init is
optional: every command works without it.
What lives in .synthex/
Only config.yaml is meant to be committed. The rest belongs to one developer or one clone:
| Path | What it holds |
|---|---|
config.yaml | The team's shared settings. Committed. |
config.local.yaml | Your overrides for this repo. Gitignored. |
state.json | Upgrade-nudge tracking (last seen version, dismissal) and the installed plugin_root some hosts use to find the plugin. Gitignored. |
loops/<loop-id>.json | State for each native loop, so loops can run side by side and resume across sessions. Gitignored. |
facts.md | Project facts init detects (commit convention, test runner, frontend framework, spec index) with SHAs and mtimes for freshness checks. Gitignored. |
auto-decide/<session_id>.jsonl | A log of each decision --auto-decide took on Claude Code, with the commit before it. |
Worktrees: isolated workspaces on your machine
When next-priority/synthex:next-priority runs tasks in parallel locally, each task gets its own git worktree: a separate working directory on its own branch, sharing the same repository.
worktrees:
base_path: .claude/worktrees # relative to the repo root; absolute paths work too
branch_prefix: "feature/"For each task, Synthex runs:
git worktree add {base_path}/{branch_prefix}[task-id]-[short-description] \
-b {branch_prefix}[task-id]-[short-description]The worker commits only on its own branch. After the task passes validation, Synthex merges it
with git merge --ff-only, falling back to a merge commit when a fast-forward isn't possible,
and removes that worktree right away, before it touches the next task. It never force-removes a
worktree that has uncommitted changes.
init also creates a committed .worktreeinclude file (starting with .env and .env.local).
Claude Code's built-in worktree support reads it to copy those gitignored files into each new
worktree it creates.
Cloud agents: a VM instead of a worktree
On Cursor, cloud execution replaces local worktrees entirely. Each worker gets its own cloud VM,
creates the same {branch_prefix}[task-id]-[short-description] branch from the remote base,
and pushes it. Your session fetches the branch and merges it with git merge --ff-only FETCH_HEAD. Because cloud workers clone from the remote, commit and push first. See
Cloud agents for the full lifecycle.
Next
- Configuration — every key and its default
- Parallel execution — how tasks are selected and merged
- Cloud agents — run parallel tasks on Cursor cloud VMs
- Multi-model review — what the
multi_model_reviewkeys control