Layered zdev configuration
What belongs in configuration
Section titled “What belongs in configuration”Zdev separates project settings, work records, and worker preferences:
- The project record in
.zdev/config.tomlidentifies the zdev project and controls repository topology and record handling. - Area, slice, and task files describe the work. They are domain records, not preferences.
- Worker profiles choose harness model controls. They are preferences: a user can have a useful default across repositories, and a repository can override it.
Project settings stay in the repository because they determine how zdev interprets shared work and handles cleanup. Worker preferences can have user defaults and repository overrides without changing the task record.
Initialization creates the strict .zdev/config.toml. zdev config trunk and
project integration install read, modify, and atomically replace it. Root
discovery looks only for the file; initialization checks an existing record;
cleanup, status, checks, task selection, branch operations, and integration
guidance parse it. zdev config set project.default-area writes
default_area. Area creation, binding, parenting, and managed rebase
separately write strict .zdev/<area>/area.toml records. Worker preferences
use the strict .zdev/workers.toml format described below.
Supported keys and scopes
Section titled “Supported keys and scopes”local means the initialized repository selected by discovery or --root.
global means the current user’s zdev preferences. repo-only keys can be
read through the local or effective view, but --global cannot set them.
Repository-local does not mean clone-private: the chosen personal, project,
or pull-request record policy still determines whether .zdev is shared.
| CLI key | Value | Scope | Mutation |
|---|---|---|---|
project.name |
non-empty project name | repo-only | Set by zdev init; read-only afterward. |
project.record |
personal, project, or pull-request |
repo-only | Set by zdev init; read-only afterward. |
project.trunk |
Git branch name, or absent | repo-only | Set or unset locally. |
project.default-area |
existing area tag, or absent | repo-only | Set or unset locally. |
project.guidance |
auto, agents, zdev, or a safe repository-relative Markdown path; absent means auto |
repo-only | Set or unset locally. Project integration install may continue recording the source it used. |
worker.codex.routine-implementer |
worker profile | global and local | Set or unset. |
worker.codex.implementer |
worker profile | global and local | Set or unset. |
worker.codex.verifier |
worker profile | global and local | Set or unset. |
worker.codex.advanced-implementer |
worker profile | global and local | Set or unset. |
worker.codex.planner |
worker profile | global and local | Set or unset. |
worker.claude.routine-implementer |
worker profile | global and local | Set or unset. |
worker.claude.implementer |
worker profile | global and local | Set or unset. |
worker.claude.verifier |
worker profile | global and local | Set or unset. |
worker.claude.advanced-implementer |
worker profile | global and local | Set or unset. |
worker.claude.planner |
worker profile | global and local | Set or unset. |
worker.opencode.routine-implementer |
worker profile | global and local | Set or unset. |
worker.opencode.implementer |
worker profile | global and local | Set or unset. |
worker.opencode.verifier |
worker profile | global and local | Set or unset. |
worker.opencode.advanced-implementer |
worker profile | global and local | Set or unset. |
worker.opencode.planner |
worker profile | global and local | Set or unset. |
worker.pi.routine-implementer |
worker profile | global and local | Set or unset. |
worker.pi.implementer |
worker profile | global and local | Set or unset. |
worker.pi.verifier |
worker profile | global and local | Set or unset. |
worker.pi.advanced-implementer |
worker profile | global and local | Set or unset. |
worker.pi.planner |
worker profile | global and local | Set or unset. |
worker.omp.routine-implementer |
worker profile | global and local | Set or unset. |
worker.omp.implementer |
worker profile | global and local | Set or unset. |
worker.omp.verifier |
worker profile | global and local | Set or unset. |
worker.omp.advanced-implementer |
worker profile | global and local | Set or unset. |
worker.omp.planner |
worker profile | global and local | Set or unset. |
worker.agy.routine-implementer |
worker profile | global and local | Set or unset. |
worker.agy.implementer |
worker profile | global and local | Set or unset. |
worker.agy.verifier |
worker profile | global and local | Set or unset. |
worker.agy.advanced-implementer |
worker profile | global and local | Set or unset. |
worker.agy.planner |
worker profile | global and local | Set or unset. |
A worker profile is set as a whole. It is either inherit, or a non-empty
model plus one effort from inherit, low, medium, high, xhigh, or
max. Atomic profiles avoid an invalid intermediate file containing a model
without an effort. effort = inherit means set the model but omit the harness
effort control; a whole-profile inherit omits both controls.
The built-in worker defaults for normal are described in
Worker profiles:
| Harness | Routine implementer | Implementer | Verifier | Advanced implementer |
|---|---|---|---|---|
| Codex | gpt-6.1-sol medium |
gpt-6.1-sol high |
gpt-6.1-sol high |
gpt-6.1-sol xhigh |
| Claude | haiku low |
claude-opus-5 low |
claude-opus-5 low |
claude-opus-5 high |
| OpenCode | openai/gpt-6.1-sol medium |
openai/gpt-6.1-sol high |
anthropic/claude-opus-5 inherit |
openai/gpt-6.1-sol xhigh |
| Pi | openai/gpt-6.1-sol medium |
openai/gpt-6.1-sol high |
anthropic/claude-opus-5 low |
openai/gpt-6.1-sol xhigh |
| OMP | openai/gpt-6.1-sol medium |
openai/gpt-6.1-sol high |
anthropic/claude-opus-5 low |
openai/gpt-6.1-sol xhigh |
The Oh My Pi adapter renders these two abstract fields into its native combined representation. It must validate that the pair is expressible rather than silently dropping either field.
Project identity and record policy have no default. An absent trunk has the
effective value null and means unbound. An absent default area has the
effective value null and leaves the existing unambiguous-area selection in
place. An absent guidance value has the effective value auto.
Area tag, title, objective, branch, parent, base_commit, and optional
execution_profiles remain in area.toml. Use zdev area profile AREA to
show the harness mapping, zdev area profile AREA HARNESS to inspect one
harness, zdev area profile AREA HARNESS PROFILE to set or replace it, and
zdev area profile AREA HARNESS --clear to remove it. Changing one harness
preserves the others. Slice and task identity, dependencies, status, outcomes, and
validation remain in their Markdown records. None is addressable by zdev config; their existing domain commands own their validation and transitions.
schema_version is file-format metadata, not a configurable key. Command
arguments such as output format, repository root, integration scope,
destination, and force are per-invocation controls and are not persisted.
Files and precedence
Section titled “Files and precedence”The repository files keep their current purposes:
.zdev/config.tomlis the strict project record..zdev/workers.tomlis the optional local worker-profile file.
The global worker path uses the first absolute base in this order:
- An absolute, non-empty
XDG_CONFIG_HOMEgives$XDG_CONFIG_HOME/zdev/workers.toml. - Otherwise an absolute, non-empty
HOMEgives$HOME/.config/zdev/workers.toml. - Otherwise an absolute, non-empty
USERPROFILEgives$USERPROFILE/.config/zdev/workers.toml.
An unset, empty, or relative value is unavailable and resolution continues to
the next variable. If none is absolute, the operation fails with Cannot locate global zdev configuration; set XDG_CONFIG_HOME, HOME, or USERPROFILE to an absolute path and changes nothing. Paths are lexically normalized, not
canonicalized, because the file may not exist yet. Reported global origins are
therefore always absolute. There is no zdev-specific path override.
Worker resolution is whole-profile, in this order:
- a local
.zdev/workers.tomlprofile; - the matching global profile;
- the built-in profile.
An explicit inherit overrides lower layers. It does not fall through to the next
layer. Profiles are not merged field by field. This keeps an effort from one
scope from accidentally attaching to a model from another.
Project values come only from .zdev/config.toml, followed by the explicit
defaults above where applicable. Harness-native settings may later override or
reject rendered model controls. Zdev reports its effective input to integration
rendering, not the model that a provider ultimately runs.
Both worker files use this hand-editable TOML schema:
schema_version = 1
[codex.implementer]model = "gpt-6.1-sol"effort = "high"
[codex.verifier]inherit = trueThe global and local files have the same schema. A missing worker file is an
empty layer. Removing its final profile leaves a deterministic file containing
only schema_version = 1; the command does not delete the file. The local
project file is required for local and effective operations. Schema version 1
is unchanged: the role tables are optional, so every valid legacy
implementer/verifier file retains the same meaning.
Command grammar
Section titled “Command grammar”The commands are:
zdev config show [--global | --local]zdev config get [--global | --local] <key>zdev config set [--global | --local] [--allow-divergent] <key> <value>...zdev config unset [--global | --local] <key>zdev config trunk [<branch>] [--allow-divergent]--global and --local are mutually exclusive. The existing global
--format text|json and --root options keep their current placement and
meaning. --root does not change the global file location.
Reads without a scope show the effective value. Scoped reads show only values
stored in that scope; they do not inject defaults or values from another
scope. show succeeds with an empty values array when a selected worker
scope has no entries. get of an absent scoped key fails without output.
Writes default to local, matching the common repository use and the existing
trunk command. A global write works outside a repository. A local write and an
unscoped read require an initialized repository. Using a repo-only key with
--global is an unsupported-scope error.
Scalar settings take exactly one value:
zdev config set project.trunk mainzdev config set project.default-area improvementszdev config set project.guidance docs/build.mdA worker key takes either the single value inherit, or exactly two values,
model and effort:
zdev config set --global worker.codex.implementer gpt-6.1-sol highzdev config set worker.codex.verifier inheritset replaces the one value in the selected scope. unset removes only the
selected scope’s value, exposing the next layer when one exists. Unsetting a
missing value is an error. project.name and project.record are visible to
show and get, but set and unset reject them and point to zdev init and
the record-policy documentation.
A successful worker-profile set or unset reports that its harness
integration must be refreshed; it does not refresh it. A local mutation names
zdev skill install <harness> --scope project --force, and a global mutation
names zdev skill install <harness> --scope user --force. Project-key
mutations omit the refresh fields and hint because they do not change rendered
worker controls.
zdev config trunk [<branch>] remains supported. It is the existing
branch-aware convenience command: omission means the checked-out branch. With
an explicit branch it has the same stored result as zdev config set project.trunk <branch>. The generic form never infers a value. When explicit
trunk areas exist, both forms use the locked ancestry and ownership checks in
Trunk-based area work. --allow-divergent is accepted
only for these local trunk writes and waives only a resolved false ancestry
result.
Stable output
Section titled “Stable output”Key order is the order in the scope table above: project keys first, then
harnesses in codex, claude, opencode, pi, omp, agy order, with
routine-implementer, implementer, verifier, and advanced-implementer,
and planner in that order. A scoped view omits keys not stored in that
scope. Every JSON object uses the lexical key order produced by the current
serde_json::Value map and existing serde_json::to_string_pretty renderer;
arrays retain the registry and precedence order defined here. No
preserve-order feature or config-specific serializer is needed. Every output
ends in one newline. Paths use / separators; local paths are
repository-relative and global paths are absolute.
Human show prints one effective or stored value per line. A lower-precedence
candidate follows its winner on an indented shadows line. Strings use TOML
quoting, profiles use TOML inline-table notation, and null is literal.
Effective view shape
Section titled “Effective view shape”The effective view includes every supported key. The following examples show selected rows to illustrate values, scope, and precedence. The defaults table above records the dated model choices.
Selected human rows have this form:
project.name = "checkout" [local .zdev/config.toml]project.record = "project" [local .zdev/config.toml]project.trunk = null [default]project.default-area = "payments" [local .zdev/config.toml] shadows null [default]project.guidance = "auto" [default]worker.codex.implementer = { model = "gpt-6.1-sol", effort = "high" } [local .zdev/workers.toml] shadows { model = "gpt-5.5", effort = "xhigh" } [global /home/alice/.config/zdev/workers.toml] shadows { model = "gpt-6.1-sol", effort = "high" } [default]The same selected rows have this JSON shape:
{ "schema_version": 1, "scope": "effective", "values": [ { "key": "project.name", "origin": { "path": ".zdev/config.toml", "scope": "local" }, "shadowed": [], "value": "checkout" }, { "key": "project.record", "origin": { "path": ".zdev/config.toml", "scope": "local" }, "shadowed": [], "value": "project" }, { "key": "project.trunk", "origin": { "path": null, "scope": "default" }, "shadowed": [], "value": null }, { "key": "project.default-area", "origin": { "path": ".zdev/config.toml", "scope": "local" }, "shadowed": [ { "origin": { "path": null, "scope": "default" }, "value": null } ], "value": "payments" }, { "key": "project.guidance", "origin": { "path": null, "scope": "default" }, "shadowed": [], "value": "auto" }, { "key": "worker.codex.implementer", "origin": { "path": ".zdev/workers.toml", "scope": "local" }, "shadowed": [ { "origin": { "path": "/home/alice/.config/zdev/workers.toml", "scope": "global" }, "value": { "effort": "xhigh", "model": "gpt-5.5" } }, { "origin": { "path": null, "scope": "default" }, "value": { "effort": "high", "model": "gpt-6.1-sol" } } ], "value": { "effort": "high", "model": "gpt-6.1-sol" } } ]}scope is always present: effective for an unscoped read and global or
local for a scoped show. shadowed is always present, including when empty.
An origin always contains path then scope; default origins use a null path.
Scoped views omit unstored keys instead of emitting null placeholders.
Scoped and single-key views
Section titled “Scoped and single-key views”For a fixture with two global profiles, zdev config show --global is exactly:
worker.codex.implementer = { model = "gpt-5.5", effort = "xhigh" } [global /home/alice/.config/zdev/workers.toml]worker.codex.verifier = { inherit = true } [global /home/alice/.config/zdev/workers.toml]Its JSON form is exactly:
{ "schema_version": 1, "scope": "global", "values": [ { "key": "worker.codex.implementer", "origin": { "path": "/home/alice/.config/zdev/workers.toml", "scope": "global" }, "shadowed": [], "value": { "effort": "xhigh", "model": "gpt-5.5" } }, { "key": "worker.codex.verifier", "origin": { "path": "/home/alice/.config/zdev/workers.toml", "scope": "global" }, "shadowed": [], "value": { "inherit": true } } ]}Assume the repository locally sets the implementer to gpt-6.1-sol high and
the global value above is present. zdev config get worker.codex.implementer
is exactly:
worker.codex.implementer = { model = "gpt-6.1-sol", effort = "high" } [local .zdev/workers.toml] shadows { model = "gpt-5.5", effort = "xhigh" } [global /home/alice/.config/zdev/workers.toml] shadows { model = "gpt-6.1-sol", effort = "high" } [default]Its JSON form is exactly:
{ "key": "worker.codex.implementer", "origin": { "path": ".zdev/workers.toml", "scope": "local" }, "schema_version": 1, "shadowed": [ { "origin": { "path": "/home/alice/.config/zdev/workers.toml", "scope": "global" }, "value": { "effort": "xhigh", "model": "gpt-5.5" } }, { "origin": { "path": null, "scope": "default" }, "value": { "effort": "high", "model": "gpt-6.1-sol" } } ], "value": { "effort": "high", "model": "gpt-6.1-sol" }}Successful mutation output is also stable. For example:
Set worker.codex.implementer in global /home/alice/.config/zdev/workers.toml.Refresh integration: zdev skill install codex --scope user --force{ "integration_refresh_command": "zdev skill install codex --scope user --force", "integration_refresh_required": true, "key": "worker.codex.implementer", "origin": { "path": "/home/alice/.config/zdev/workers.toml", "scope": "global" }, "schema_version": 1, "status": "set", "value": { "effort": "high", "model": "gpt-6.1-sol" }}After removing a local override whose global fallback is the fixture above, the human result is:
Unset worker.codex.implementer from local .zdev/workers.toml.Effective value: { model = "gpt-5.5", effort = "xhigh" } [global /home/alice/.config/zdev/workers.toml]Refresh integration: zdev skill install codex --scope project --forceThe JSON result is:
{ "effective": { "origin": { "path": "/home/alice/.config/zdev/workers.toml", "scope": "global" }, "value": { "effort": "xhigh", "model": "gpt-5.5" } }, "integration_refresh_command": "zdev skill install codex --scope project --force", "integration_refresh_required": true, "key": "worker.codex.implementer", "origin": { "path": ".zdev/workers.toml", "scope": "local" }, "schema_version": 1, "status": "unset"}No supported key is sensitive. Model identifiers, effort names, area tags, and repository-relative guidance paths are ordinary configuration. Zdev does not read or display credentials or other harness secrets. It rejects unknown keys, including credential-like keys.
Validation and failure behavior
Section titled “Validation and failure behavior”Both worker files and .zdev/config.toml remain strict, versioned TOML. Parsing
rejects an unsupported schema version, unknown table or key, duplicate key,
unknown harness or role, empty model, unknown effort, inherit combined with
another field, and an explicit model-effort pair the selected adapter cannot
express. Project values retain their current strict schema.
Additional key validation is narrow:
project.trunkuses the existing Git branch-name canonicalization. Without explicit trunk areas it retains the existing ability to name a branch that does not exist. With trunk areas, old and candidate tips must exist and be inspectable, the complete area ownership graph must remain valid, and the old tip must be contained by the candidate unless--allow-divergentexplicitly waives a resolved false result.project.default-areamust be a valid segment naming an existing area.project.guidanceaccepts the three named modes or the integration code’s existing safe repository-relative Markdown path rules. File presence and guidance markers are checked when an integration is installed or checked, where they matter.- Worker validation is syntactic and adapter-specific. Zdev does not contact a provider or promise account access to a model.
An effective operation validates every present source it would consult before producing output or changing a file. A scoped global operation consults only the global worker file. A scoped local operation validates the project record and the local worker file. Malformed or unreadable present files fail with the path and offending field. The command does not ignore bad configuration and does not silently fall back. The user repairs the named hand-editable file.
Unknown keys, unsupported scopes or values, a missing scoped get or unset,
an uninitialized local operation, and an unavailable configuration home all
fail through zdev’s existing text or JSON error envelope. They print no success
payload and change no file. Missing worker files and missing profile tables are
normal fallbacks on effective reads.
For a mutation, zdev validates arguments and all required input. On the first
global mutation it creates the resolved parent zdev directory, then opens
workers.lock in that directory. Failure to create the directory or lock
changes no configuration file. Local project and worker writes use the existing
Git-path zdev-state.lock. With the appropriate lock held, zdev rereads the
target, computes and serializes the complete document, writes and syncs a
temporary file in the destination directory, and atomically replaces only that
destination. Publication failure leaves the previous file bytes in place. One
command changes one file, so there is no cross-file rollback protocol.
Serialization is deterministic and preserves values, not comments or authored layout. This matches the current project-config writer. A hand edit remains a supported input; the next CLI mutation normalizes that one file.
Named execution profiles
Section titled “Named execution profiles”zdev config profile provides typed list, show, set, unset,
set-default, unset-default, and resolve commands. Definitions remain in
the schema-version-1 worker file under profiles.<name>.<harness>.<role>;
default_profile is optional. Existing top-level harness rows continue to be
the normal profile. Model and effort are written together, and the complete
document and harness controls are validated before an atomic write.
Resolution uses an explicit role profile, then --run-profile, then an
explicit local or global saved default, then normal. Local named rows win
over global rows. Missing named roles use effective normal, except an omitted
planner first uses the selected profile’s advanced implementer. Unknown names
and profiles undefined for a requested harness are errors. Read-only list,
show, and resolve commands do not rewrite either configuration file.
Changing or clearing the saved default reports the refresh command template
for each installed integration at the selected project or user scope.
Backward compatibility
Section titled “Backward compatibility”Existing .zdev/config.toml files remain valid without migration. Their field
names, schema version, strict parsing, and meaning do not change. The current
zdev config trunk command remains available. The generic commands read
and change the same typed project fields through the existing reader and
writer; they do not move project state into a preference file.
The .zdev/workers.toml shape already specified in Worker
profiles is the local layer and needs no migration. A
repository-local profile still wins exactly as documented earlier. When no
local row exists, zdev checks the global file, then the built-in default.
User-scoped integration install and check use global then built-in;
project-scoped install and check use local, global, then built-in. Explicit
inherit remains authoritative at either scope.
Install and check must use the same resolver and report the zdev origin they rendered. Existing repositories with neither worker file continue to render the same built-in profiles byte for byte. No command scans or imports harness-native configuration.