Commit .dz/maintainer/maintainer.yml to configure the maintainer bot.
The root .maintainer.yml is the legacy path: still read until it is deprecated,
and ignored when the .dz/ file exists. The file is YAML; all fields are optional
and default-safe, an empty file with only version: 1 is valid.
Schema autocomplete. Add
# yaml-language-server: $schema=https://developerz.ai/schemas/maintainer.v1.jsonas the first line of the file to get IDE validation and autocomplete.
Minimal example
# yaml-language-server: $schema=https://developerz.ai/schemas/maintainer.v1.json
version: 1
Full example
# yaml-language-server: $schema=https://developerz.ai/schemas/maintainer.v1.json
version: 1
tone:
voice: friendly
language: en
triage:
ask_for_repro: true
required_fields: [steps, expected, actual, version]
dedupe:
enabled: true
similarity_threshold: 0.85
qualify_into: [bug, feature, dupe, question]
handoff:
mode: webhook
webhook_ref: my-claude-code-hook
ask_reporter_first: true
pr:
wait_for_other_bots: [coderabbit, copilot]
required_checks: [ci]
large_threshold_lines: 500
auto_merge: true
require_review: true
human_merge:
branches: [main]
paths: [db/migrations/**]
release:
manager: release-please
channels: [github-release]
escalate:
always: [hostile_tone, direct_mention, paid_customer]
ask_before_acting: [breaking_change, major_dep_bump, first_time_contributor, large_pr]
kb:
repo_notes:
- note: "Auth lives in packages/auth; ignore the legacy auth-old/ tree."
by: "@maintainer"
pages:
- title: Architecture
purpose: How the worker dispatches jobs.
memory:
stale_after_days: 90
delete_unused_after_days: 40
review:
profile: chill
learnings: true
anti_noise:
severity_floor: minor
max_comments: 10
path_filters: ["!**/*.lock", "!dist/**"]
Root fields
| Field | Type | Required | Description |
|---|---|---|---|
version |
1 |
yes | Schema version. Must be 1. |
schema |
URL string | no | JSON schema URL for editor tooling. |
format_command |
string (shell command) | no | Formatter the bot runs before git add on the BYOVM (e.g. biome check --write ., gofmt -w .). Non-zero exit fails the commit rather than failing CI later. |
services |
redis | postgres[] |
no | Services this repo’s own check needs running before a box can grade a diff. A closed vocabulary (redis, postgres), never an image or a command. Each name becomes a claim requirement, so a box that cannot reach the service never picks up this repo’s coding work; it provisions nothing. Empty by default, and empty means a box claims regardless, which is what every repo did before this key. |
tone
Controls the bot's voice and language in all comments.
| Field | Type | Default | Description |
|---|---|---|---|
voice |
friendly | formal | terse |
friendly |
Persona used in all bot comments. |
language |
BCP-47 string | en |
BCP-47 language tag for bot-generated copy (e.g. en, de, pt-BR). |
triage
Governs how new issues are triaged: reproduction requests, deduplication, and label outcomes.
| Field | Type | Default | Description |
|---|---|---|---|
ask_for_repro |
boolean | true |
Bot asks reporters for a reproduction before qualifying the issue. |
required_fields |
steps | expected | actual | version | environment | logs[] |
[steps, expected, actual, version] |
Checklist items the bot requests when ask_for_repro is enabled. |
non_goals |
string[] (max 10, 200 chars each) | [] |
Things this project deliberately will not take, one short line each in your own words (e.g. no Windows support). Empty by default, and empty is meaningful: this list is the only thing that lets the bot decline a request as out of scope, so if you declare none it never will. When an issue directly matches an entry, the bot posts one comment quoting that line as your stated position and suggesting a fork, and the issue stays open: it is not closed, locked or labelled. A pull request whose change matches an entry is closed with a comment quoting that line. The bot never invents or paraphrases a stance you did not write. |
dedupe.enabled |
boolean | true |
Similarity deduplication of new work against in-flight tasks (and their open PRs). |
dedupe.similarity_threshold |
number 0–1 | 0.85 |
Similarity cutoff (0–1). New work scoring at or above this against an in-flight task is dropped as a duplicate and cross-linked to the surviving task. |
shipped_lookback_days |
integer 0–90 | 14 |
How far back (days) the scheduler looks for recently shipped work when placing a new report in the queue. 0 disables it. Distinct from dedupe, which only sees work in motion (open tasks and their PRs). This is the finished-work half. A match defers the report (claimed after the ordinary backlog), it never drops it: a signal that recurs after a fix shipped is a regression, not a duplicate. |
qualify_into |
bug | feature | dupe | question | docs | chore[] |
[bug, feature, dupe, question] |
Allowed outcome labels after triage. |
labels.qualification |
map: bug | feature | dupe | question → string 1–64 |
- | Rename the GitHub label the bot writes for a qualification verdict; the classification never changes, only the label NAME gh.issues.label applies. Map bug / feature / dupe / question (the four verdicts the bot ever labels) to your own name, e.g. bug: type:defect. Each mapped label name is 1–64 characters; a longer or empty value is refused at parse. An omitted verdict keeps the platform default (bug / enhancement / duplicate / question), so a repo that sets none of these is unaffected. |
confidence_threshold |
number 0–1 | 0.6 |
Minimum confidence (0–1) the scoped judgment must reach before the bot acts on an issue. Below it the bot escalates to a human instead of proceeding or handing off, so raising this makes the bot quieter and more deferential, lowering it makes it act on thinner evidence. |
stale_after_days |
integer > 0 | no sweep | How long an issue may sit with no activity before the stale sweep speaks. A whole number of days, greater than zero. This sweep does not ask whom a quiet issue is waiting on; it does leave out issues opened by project members when skip_project_members is on. Omit it and it does not run: there is deliberately no default, because any number here would start acting on repos whose maintainers never asked. Issues with a milestone, a linked open pull request, a ready-for-agent or ready-for-human label or work in flight are never touched, and with skip_project_members on neither is an issue opened by a project member, or one whose author’s permission could not be read that day. When close_unanswered_repro_after_days is also set, an unanswered reproduction request is left to that setting. Must be set together with stale_action; either key alone is refused at parse, naming the missing half. |
stale_action |
comment_then_close | comment_only |
no sweep | What the sweep does when the window elapses. comment_then_close comments naming the window and then closes; comment_only comments and leaves the issue open, a nudge rather than a verdict. There is no silent-close option: the bot always discloses. Must be set together with stale_after_days. |
close_unanswered_repro_after_days |
integer > 0 | false |
false |
Off by default: omit it and no issue is closed on a clock. We recommend 14. How many days a reproduction request may go unanswered by the reporter before the issue is closed as not planned. It applies only to an open issue that carries the needs-repro label, which the bot applies when ask_for_repro asks for the missing details. Only the reporter's reply stops the clock: a comment from anyone else does not. At the halfway point the bot posts one reminder that names the close date. The close comment says the report was not judged and that a reply with the requested details reopens it. Issues with a milestone, a linked open pull request, a ready-for-agent or ready-for-human label or work in flight are never closed, and neither is an issue opened by a bot (this one included) or by a project member (write or admin access to the repository, where GitHub reports maintain as write, read from GitHub’s collaborator permission whether or not skip_project_members is on), or one whose author’s permission could not be read that day. false is the default written out. |
skip_project_members |
boolean | false |
Off by default. Set true and the bot leaves an issue opened by a project member alone: it files no ask from it, does not label, comment on or close it, does not import it from the backlog, and the stale sweep never touches it. A project member is someone with write or admin access to the repository (GitHub reports maintain as write). The bot reads that from GitHub’s GET /repos/{owner}/{repo}/collaborators/{username}/permission, not from how GitHub labels the author on the issue, so the bot’s GitHub App installation must be able to read the repository’s collaborator permission. An answer is remembered for 10 minutes. An @-mention of the bot on a member’s issue, by anyone, still wakes it: in the body of a newly opened issue, in an edit that adds it, or in a new comment or a comment edited to add it. A mention inside quoted text, code, an HTML comment or a link does not count, and a label, assignment or close on an issue that once mentioned the bot wakes nothing. That session is told the author is a project member and answers only the mention: it does not label, close, ask for a reproduction or hand the issue off unless the mention asks for it. Any other comment on a member’s issue, including an outside contributor’s, does not wake the bot. When the author’s permission cannot be read: a newly opened issue is retried (the delivery is re-attempted with backoff for several hours, then dropped and recorded), any other event on the issue is left alone that time, and the backlog import, the escalated-issue pickup and the stale sweep skip it for that pass and decide again on the next. Issues opened by people without write access, and every pull request, are handled as before. |
handoff
After qualification, the bot can hand off the issue to a coding agent.
mode = webhook fires a signed POST to the configured webhook; mode = label applies a
GitHub label your CI or coding agent can pick up; mode = fleet runs the work on your own
enrolled boxes (BYOVM, never our hardware: the assigner claims the durable tasks row onto
one of your boxes; no external webhook fires); mode = none disables handoff.
When
mode = webhook,webhook_refis required and the webhook must be registered in the dashboard.mode = fleetdoes not requirewebhook_ref.
| Field | Type | Default | Description |
|---|---|---|---|
mode |
webhook | label | fleet | none |
none |
How qualified issues are handed off to a coding agent. |
label |
string | - | GitHub label applied when mode = label. |
webhook_ref |
string (ref name) | - | Dashboard webhook name used when mode = webhook. Not required when mode = fleet. |
ask_reporter_first |
boolean | true |
Bot asks the reporter for consent before handing off. |
pr
Pull request review policy.
auto_mergeis on by default. The bot merges once the machine gates pass: CI green, review verdict, and the repo's branch protections. Never blindly. To keep a human in the loop, setauto_merge: falseor list the sensitive branches/paths underhuman_merge. (Free-OSS-tier repos stay opt-in regardless.)
require_reviewis on by default too, and it is what makes "review verdict" above a real gate: a head with no approving external review and no verdict from the built-in reviewer is handed to a human instead of merged. If your repo ships machine-gated on CI alone, setrequire_review: false. Deliberately, rather than by omission.
| Field | Type | Default | Description |
|---|---|---|---|
wait_for_other_bots |
coderabbit | copilot | dependabot | renovate[] |
[coderabbit, copilot, dependabot] |
Bots the maintainer bot waits for before acting on a PR. |
required_checks |
string[] | [] |
CI check names that must pass before the bot considers merging. |
large_threshold_lines |
integer > 0 | 500 |
The large_pr threshold: a PR whose additions plus deletions exceed it is held for a maintainer when large_pr is listed in escalate.ask_before_acting (the default for HUMAN-authored PRs only, owner ruling 2026-10-09: askBeforeActingFor(managed=true, {list, written}) returns written, so an AI-managed PR, this App, Dependabot, Renovate, is held only when the repo wrote the category). Enforced by the merge gate before every merge the bot makes. |
auto_merge |
boolean | true |
On by default. The bot merges once the machine gates pass, CI green, review verdict, and branch protections satisfied. Set false (or use human_merge) to hand the merge back to a human. |
auto_merge_scope |
any | own | own_and_dependency_bots | not_project |
any |
Whose pull requests auto_merge covers. any (the default) merges every author’s. own merges only the pull requests the bot opened; own_and_dependency_bots also merges Dependabot’s and Renovate’s. not_project merges and babysits pull requests by this App, dependency bots, and people without write access to the repo; never a project member’s (write, maintain or admin). It reads the author’s permission from GitHub’s GET /repos/{owner}/{repo}/collaborators/{username}/permission, so the bot’s GitHub App installation must be able to read that repository’s collaborator permission. An answer is remembered for 10 minutes, so a change to someone’s access takes up to that long to apply. When an author’s repo permission cannot be read, the bot does nothing on that pass, says so on the pull request, and reads it again a few minutes later. Any other pull request is left to its author: the bot neither pushes CI fixes to it nor merges it, and pr approve_pending does not release that. A repo connected through a member’s own GitHub token keeps any, because the bot’s pull requests there are authored by that member. |
require_review |
boolean | true |
On by default. The bot never merges a head nobody reviewed, a review being either an approving external review (a human, or CodeRabbit) or any verdict from the built-in reviewer for that head. With no review the PR waits, then goes to a human. Set false to ship machine-gated on CI alone. |
require_up_to_date |
boolean | false |
Off by default. The bot never merges a PR whose green CI ran over an older base when the base has since changed a file the PR also changes: it updates the branch so CI re-runs, then merges. A base that moved elsewhere merges as it is. Set true to update every PR that is behind its base first, for a repo whose checks can break across files (a whole-repo count or ratchet). |
delete_declined_branches |
boolean | false |
Off by default. When the bot declines a pull request, it may also delete that pull request’s head branch, so declined work leaves no stray branch. The tool refuses unless the pull request is closed and unmerged, was closed by the bot itself, and the head branch lives in this repo, is still the declined tip, is not the repo’s default branch, is not protected, is not the platform’s own dz/onboarding branch, and is not the base or head of another open pull request. A repo with 1,000 or more open pull requests cannot be checked for that last condition, so the bot deletes nothing there. Set true to opt in. |
human_merge.branches |
string[] (globs) | [] |
Target-branch globs held for a human even when auto_merge is on (e.g. main, release/*). A human decides the merge: approve the PR in your approvals list (pr approve_pending) and the bot merges that exact head, or merge it yourself. |
human_merge.paths |
string[] (globs) | [] |
Changed-path globs held for a human even when auto_merge is on (e.g. db/migrations/**, **/*.tf). A human decides the merge: approve the PR in your approvals list (pr approve_pending) and the bot merges that exact head, or merge it yourself. |
close_awaiting_author_after_days |
integer > 0 | false |
false |
Off by default: omit it and no pull request is closed on a clock. We recommend 21. How many days a contributor's pull request may wait on its author before it is closed. The clock runs only while the next move is the author's: requested changes or review threads they have not answered, a required check that ran and failed, merge conflicts with the base branch, or a pull request still in draft. A required check that was cancelled, timed out, went stale or is waiting for a maintainer to approve the workflow run is the project's move, never the author's. Only the author stops it: a push, a comment, a review reply or a request for review of theirs restarts the clock, and a comment from anyone else does not. A pull request that is waiting on a review from the project is never on this clock. The bot posts one comment at a third of the window naming what is waiting, a second at two thirds naming the close date, and then closes with a comment that tells the author how to continue: a new pull request from the same branch. A merge conflict has no date of its own, so a pull request whose only problem is a conflict is counted from the bot's first comment about it and gets the whole window after that. A draft gets 7 more days before the close. The branch is never deleted. Pull requests by a project member (write or admin access to the repository, where GitHub reports maintain as write, read from GitHub’s collaborator permission; one whose author’s permission could not be read that day is left alone too), by a bot, with a milestone, locked, held by human_merge, or with work in flight are never touched. false is the default written out. |
release
Release automation. The bot can cut releases after a PR merges based on conventional commit history.
| Field | Type | Default | Description |
|---|---|---|---|
manager |
release-please | none |
release-please |
Release automation driver. |
channels |
github-release[] |
[github-release] |
Where the bot publishes when it cuts a release. github-release is the only supported value, and the registry channels are not a pending feature: npm, crates, pypi and rubygems are rejected by validation because publishing to a registry means holding your publish tokens, which this platform refuses to do by design. Publish to registries from your own CI, on the tag the bot pushes. An empty list (channels: []) is valid and means the bot (with manager: release-please and bump_strategy: bump_pr_first) may still open the bump PR but cuts no tag and no GitHub Release, because the repository's own workflow owns them. |
bump_strategy |
tag_only | bump_pr_first |
tag_only |
How a cut reaches the version your ecosystem publishes. tag_only (the default) cuts the GitHub Release and lets your CI read the version off the tag, writing nothing to the repo. bump_pr_first opens a release: vX.Y.Z pull request that edits your version manifest, and cuts the Release from that merge commit, for a CI that publishes the version read from a file rather than from the tag. Choosing bump_pr_first requires manifest_path. |
manifest_path |
string (repo-relative path) | - | Repo-relative path to the file holding the published version (package.json, Cargo.toml, pyproject.toml, lib/<gem>/version.rb). Required whenever bump_strategy is bump_pr_first, and validation refuses the pair without it: nothing detects a manifest, and guessing would edit the wrong file. Ignored under tag_only. An absolute path, a .. segment or a backslash is refused. |
changelog_path |
string (repo-relative path) | - | Repo-relative path to your Keep a Changelog file (for example CHANGELOG.md). Only valid with bump_strategy: bump_pr_first; validation refuses it under tag_only. When set, the release pull request edits it in the same commit as the manifest: the ## [Unreleased] notes move under a new ## [x.y.z] - YYYY-MM-DD heading (generated from the commits since the last tag when Unreleased is empty) and the [Unreleased] compare link moves to the new tag, and a new [x.y.z]: .../compare/<previous tag>...v<x.y.z> link line is added under it. The file needs a ## [Unreleased] heading and an [Unreleased]: .../compare/<tag>...HEAD link line, or the bump refuses. |
bump_rules |
map: commit type → none | patch | minor | major |
- | Override the default Conventional-Commits to semver mapping. Map a commit type to none, patch, minor or major; entries merge over the built-in defaults, so listing only what you want changed is enough. A window of Dependabot commits alone (chore(deps), build(deps)) cuts a patch by default: set chore(deps): none to stop dependency-only releases. |
cadence |
daily | weekly | monthly |
event-driven | Consider a cut on a schedule instead of only on a merge, for a repo whose releases should not wait for the next one. daily, weekly or monthly. Omit it and releases stay purely event-driven, which is the default. A cadence never forces a release: it asks the same question on a clock, so a window with nothing releasable still cuts nothing. |
escalate
Escalation rules: when the bot emails you immediately, and when it drafts an action and waits for your approval instead of taking it.
| Field | Type | Default | Description |
|---|---|---|---|
always |
string[] | [hostile_tone, direct_mention] |
Categories that immediately email the maintainer, on top of the hardcoded security, license, cla set. Writing this key REPLACES the default, it does not add to it: list every category you want kept, not only the one you are adding. Anything outside the built-in vocabulary below is rejected by validation. |
ask_before_acting |
string[] | [breaking_change, major_dep_bump, first_time_contributor, large_pr] |
Conditions that pause the bot and request explicit approval before continuing. Writing this key REPLACES the default, it does not add to it: one narrow entry silently turns the rest off. Anything outside the built-in vocabulary below is rejected by validation. |
Always escalated, not configurable: security, license, cla. Listing them is harmless
but redundant, they fire whether or not always names them.
Accepted in always: security, license, cla, hostile_tone, direct_mention, paid_customer.
Accepted in ask_before_acting: breaking_change, major_dep_bump, first_time_contributor, large_pr.
Both lists are closed. A category outside them is a validation error on the whole file, and writing either key replaces its default rather than extending it.
digest
Not configurable, and there are no digest: keys. The digest is one message
per account per week: every repo the account installs the bot on is summarised in the
same message, sent weekly, Mondays at 09:00 UTC. It goes to each member whose own
weekly_summary setting is on, or once to the account's verified digest.weekly
route when one is set.
cadence, day, time and timezone used to be published here and were read by
nothing; they were removed rather than left as decoration. A maintainer.yml that still
carries the block is still valid; the keys are ignored, not rejected.
Switch the digest off per person with the weekly_summary preference, and drop the
account-wide route under Email routes in the dashboard (digest.weekly). The
unsubscribe link in the email footer is the per-recipient one-click.
coding
Knobs for the on-VM coding agent (BYOVM): which model tier each class of work gets, and how long this repo's own check may run after each edit batch.
| Field | Type | Default | Description |
|---|---|---|---|
models |
map: class → fast|general|smart|heavy |
- | Per-job-class capability-tier hint. Map a complexity class (coding / debugging_qa / general / quick) to a tier (fast / general / smart / heavy), e.g. coding: smart on a security-flavoured repo. At dispatch the tier ranks your own BYOK keys and the tier tags you set on their models, and the run records that the policy decided it, unless a task pin, a route pin, your own model default or the model ceiling on a donated key decided instead; it never picks a model you did not name, and a key matching no tier stays usable, only ranked lower. An omitted class keeps its built-in default. |
check_timeout_ms |
int (1000-3600000) | 120000 (or the box knob) | Wall clock, in milliseconds, for ONE run of this repo's own check (bin/check, else the detected stack's check) that the coding agent runs after every edit batch. Set it when the gate legitimately takes longer than the default two minutes; the value is a fact about this repo, so it wins over any per-box setting. A check that outruns it is killed and checking degrades to every fourth batch for the rest of the run; the run's own budget still bounds the total. Range 1000 to 3600000. |
disk_per_task_gb |
number | 10, or more if measured | Free workspace disk, in GiB, a box needs before it takes one task on this repo: its checkout, installed dependencies and build output. The platform does not hand this repo's work to a box whose live heartbeat shows less free space than this, or than the box's own disk floor if that is higher. Once the repo has at least 3 recent runs that report how much they grew the workspace, the largest of them raises this figure when it is higher; it never lowers it. Unset, the platform default of 10 GiB applies the same way. Range 0.1 to 2000. |
bin/check and bin/clean
Two scripts are read by CONVENTION rather than configured here. Committing the script is the whole opt-in; neither has a key in this file.
bin/check is this repo's own gate, the one coding.check_timeout_ms above
bounds. bin/clean is its cleanup sibling: an executable bin/clean in the
checkout, run by the box that holds it. It is for the reclaim only your repo knows
about, a cargo clean, a named compose volume, a generated asset tree. Our own
sweep cannot guess those, so the repo says.
bin/clean |
What it gets |
|---|---|
| When | The box's weekly hygiene sweep, and earlier than that when the box is low on disk. The sweep waits for an idle box, so it never runs alongside work on that repo. |
| Where | The working directory is the checkout. |
| How | Executed directly, by path, with no shell. Standard input is ignored. |
| Environment | PATH, plus a HOME pointing at a fresh empty directory, created for the script and removed with it. So $HOME/.cargo, $HOME/.cache and friends resolve to nothing and are not yours to clean: the platform prunes those shared caches itself when the box runs low on disk. |
| Bounds | bin/clean runs as the box's runner user with its working directory set to your checkout. We bound what it can consume: the same memory cap a task's commands get, a five-minute wall, and a kill the moment the box picks up work. We run it with no shell, no stdin, and an empty HOME. We do not sandbox the filesystem: nothing stops a script writing to paths outside your checkout that the runner user can write to, including other repos' checkouts on the same box. That is the same trust boundary your bin/check already has; what bin/clean changes is that it runs unattended. Write it to clean its own tree. |
| Failure | A script that fails, hangs or is stopped never fails your repo, never affects a task, and never becomes a verdict on your code. One repo's script failing does not stop the pass, so the rest still run. |
| Not required | A repo with no bin/clean is skipped silently. Our sweep still reclaims checkouts, worktrees, docker leftovers and caches. |
#!/usr/bin/env bash
# bin/clean - what only this repo knows how to reclaim. Must be executable.
set -euo pipefail
cargo clean
docker volume rm -f myapp_pgdata || true
ci
Fences for the self-hosted CI runners the fleet boxes serve, plus the knob for
what the bot does about a red check. The per-workflow opt-in is the label itself
(runs-on: developerz-*); these repo-wide booleans sit on top of it. All are
defaulted, so a repo that commits a policy but omits ci: still gets the safe
fences.
Two independent halves: enabled decides whether your CI jobs run on our
boxes, self_fix decides whether the bot pushes a commit at a check that
went red. Neither turns off observation, the merge gate always waits for green,
whoever ran the job, so the bot never merges a red pull request.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Whether the fleet places this repo's CI jobs on your own enrolled boxes (BYOVM, never our hardware). The developerz-* runner labels are the opt-in per workflow; set false to stop placing CI jobs on fleet boxes without editing every workflow file. It governs placement only, check ingestion, the wait-for-green merge gate and the self-fix lane all keep running with it off. To stop the bot pushing commits at red checks, use self_fix. |
self_fix |
boolean | true |
Whether the bot may answer a red check by pushing a fix commit to the pull request. On by default: the bot opened the PR, so a failing check on it is its own defect to clear, and CI still has to go green again before anything merges. Set false when your CI runs somewhere else (Blacksmith, your own runners) and a red check is your engineers' to fix. The bot then still waits for those checks and never merges a red PR, it just stops pushing commits at them. Independent of enabled. |
public_repos |
boolean | false |
Non-negotiable public-repo fence. Off by default, a public repo lets any contributor open a PR whose workflow runs on your hardware, so the ci-assigner refuses to place a public-repo job unless a maintainer flips this on. |
mcp_servers
Array of external MCP servers the agent may call. Each entry:
| Field | Type | Default | Description |
|---|---|---|---|
name |
ref name string | - | Unique identifier for this server (used in audit logs). |
url |
URL | - | Base URL of the MCP endpoint. |
transport |
sse | http-streamable | stdio |
- | Transport protocol. |
token_ref |
string (ref name) | - | Dashboard secret name injected as Authorization: Bearer. |
allowed_tools |
string[] | ["*"] |
["*"] allows all tools. Otherwise an explicit allowlist. |
timeout_ms |
integer 1–60 000 | 5000 |
Per-request timeout in milliseconds (max 60 000). |
Ref names (token_ref) resolve to secrets registered in the dashboard.
The value is never stored in the policy file.
Example
mcp_servers:
- name: linear
url: https://mcp.linear.app/sse
transport: sse
token_ref: linear-api-key
allowed_tools: [create_issue, search_issues]
timeout_ms: 10000
integrations
Per-integration use grants, the config-as-code half of the credential gateway. Connecting an integration in the dashboard stores the credential; your yml must ALSO opt in here before the agent gets the gateway tool. The agent uses the connection through a platform-mediated tool, the credential is resolved server-side per call and never enters the model's context.
| Field | Type | Default | Description |
|---|---|---|---|
slack |
boolean | false |
Allow the agent to post to the connected Slack integration's configured default channel (gateway-mediated, the bot token never enters the session, and the agent cannot pick another channel). |
Example
integrations:
slack: true # agent may post via the connected Slack workspace
tool_services
Per-repo config for the tool services the agent reaches as MCP servers. Config,
never a credential: connect the service once in
the dashboard and its key reaches the box as process env only, which the generated
.mcp.json's ${VAR} references resolve against. Nothing here is secret, so the
file stays safe to commit.
Keyed by service slug, an unknown slug is a validation error, not silently ignored.
Only a service with its own config FILE takes a key here (ui-debugger's browser
targets); one configured entirely by its env block needs nothing in your repo. The
body is passed through verbatim to that service's own config file
(.dz/ui-debugger/ui-debugger-mcp.json), so it is the tool's schema, not ours.
Setup mode writes the file establish-or-verify, never clobber: your existing file
survives byte-identical, and a .example companion always ships so you can see the
shape. Declare nothing and no live file is written, a guessed dev-server URL that
404s teaches the agent your app is broken.
Example
tool_services:
ui-debugger:
workspace: ./tmp/ui-debugger-mcp
targets:
dashboard:
adapter: browser
headless: true
url: http://localhost:5173
kb
Repo-steered knowledge, a committed, reviewable way to correct or seed the agent's understanding of your repo. The bot reads it during triage alongside what it has learned on its own, so a short note here grounds answers without any code change.
kb.repo_notes
Short, attributed facts. Combined note length is capped at 10 000 characters.
| Field | Type | Default | Description |
|---|---|---|---|
note |
string 1–1 000 | - | The fact itself (1–1 000 chars). |
by |
string 1–120 | - | Who asserted it, a GitHub login, team, or name. Optional. |
kb.pages
An explicit table-of-contents that overrides the agent's auto-clustering.
Up to 30 pages; titles must be unique and any parent must name another page.
| Field | Type | Default | Description |
|---|---|---|---|
title |
string 1–120 (unique) | - | Page title (1–120 chars, unique across pages). |
purpose |
string 1–500 | - | One-line description of what the page covers (1–500 chars). |
parent |
string (existing title) | - | Title of the parent page for a shallow tree. Must name an existing page. |
notes |
string ≤2 000 | - | Optional longer prose for the page (≤2 000 chars). |
kb.memory
Retention and consolidation settings for the bot's accumulated memories (learnings from
past reviews, issues, and discussions). Distinct from repo_notes and pages (which
are explicit, human-authored steering). Memory entries that grow stale or unused are
pruned or merged automatically by the nightly gardener; these knobs tune that cleanup.
| Field | Type | Default | Description |
|---|---|---|---|
stale_after_days |
integer ≥7 | 90 |
A memory entry untouched this long (days) is eligible for consolidation into a KB page. |
delete_unused_after_days |
integer ≥7 | false |
40 |
A memory entry unused this long (days) is deleted outright. Set to false to disable deletion. |
consolidate |
boolean | true |
Merge/summarize stale-but-still-referenced memory entries instead of leaving them to rot. |
Consolidation. When consolidate: true, the gardener summarizes stale-but-still-
referenced memories into KB pages via the account's BYOK model. When false, they are
left as-is (or deleted if unused). Deletion. Set delete_unused_after_days to
false to keep all memories forever; set a numeric value ≥7 to delete memories that
have never been recalled (or recalled before the deletion cutoff).
kb.background
Consent + cost caps for the BYOK-billed background KB work, the one-time import that
seeds your wiki when the bot is first installed, and the nightly gardener consolidation.
Both spend your own BYOK tokens, so this block governs whether they run at all and how
large a wiki they may grow. Leave enabled unset to take the plan default (on for paid
plans, off for the free OSS tier); set it explicitly to override.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | plan default | Whether the platform may run BYOK-billed background KB work (the initial import at install + the nightly gardener consolidation) for this repo. Leave unset to take the plan default (on for paid plans, off for the free OSS tier) or set true/false to override it. |
max_pages |
integer 1–200 | 40 |
Cap on how many agent-maintained wiki pages the background lanes may seed and keep for this repo. |
Example
kb:
repo_notes:
- note: "Auth lives in packages/auth; ignore the legacy auth-old/ tree."
by: "@maintainer"
- note: "We never accept PRs that touch the vendored/ directory."
pages:
- title: Architecture
purpose: How the worker dispatches jobs.
- title: Dispatch
purpose: Two-phase pull queue + per-entity lock.
parent: Architecture
memory:
stale_after_days: 90
delete_unused_after_days: 40
consolidate: true
schedules
Scheduled checkups: at each listed UTC hour on each listed day, a scout surveys this repo
(plus any repos) from the brief you commit, and files what it finds as asks that triage
sorts like any other report. It runs on your own boxes and your own key. Absent means nothing
is scheduled. Days and hours, never a cron string: a cron typo fires every minute. Up to
10 entries; no field has a default, and a cron: key is refused.
| Field | Type | Default | Description |
|---|---|---|---|
name |
ref name (unique) | - | Unique name of the schedule in this file. It keys each firing, its tasks and its audit rows. |
days |
mon … sun[] |
- | UTC days it fires on, any of mon through sun. At least one. |
hours_utc |
integer 0–23[] | - | UTC hours (0 to 23) it fires at on each listed day. At least one. An hour missed while the platform was down still fires later that same UTC day, never the next. |
brief_file |
repo-relative path | - | Repo-relative path to the committed brief the scout works from, read at the default branch's head each time it fires. A missing file refuses that firing and says so in the audit log. |
repos |
owner/repo[] |
- | Other owner/repos of this account the same checkup covers. Optional. The repo holding this file is always covered and must not be listed, so at most 4 fit. A listed repo that is not enabled is refused, in the audit log. |
Example
schedules:
- name: nightly-checkup
days: [mon, tue, wed, thu, fri]
hours_utc: [2]
brief_file: .dz/schedules/checkup.md
repos: [acme/infrastructure]
review
Native AI code review, a hosted, CodeRabbit-compatible reviewer running on
your BYOK model, included in the seat. The native review: block is a
superset of .coderabbit.yaml (adds anti-noise floor/cap and KB learning
write-back); when the bot reads a .coderabbit.yaml it honors most fields but
defers reviews.tools and reviews.finishing_touches (surfaced as warnings).
Precedence. If a repo has both a
.coderabbit.yamland areview:block, thereview:block wins (maintainer.yml>.coderabbit.yaml). The block is optional, omit it and the bot reads your existing.coderabbit.yaml(if any) or falls back to the built-in review defaults. Migrating off CodeRabbit is a no-op: keep the file, uninstall their app.
Bot etiquette. The reviewer always defers when another review bot (e.g. CodeRabbit, Copilot, Dependabot) is active on the repo, no bot-on-bot loops. To hand review to the native reviewer, uninstall the other bot, or set
review.force: trueto review in parallel while migrating off CodeRabbit.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Master switch for the native reviewer. |
depth |
inline | deep |
inline |
inline (default) (a diff-based review from the BYOK inference path. deep) the reviewer additionally runs your repo’s own build/test/lint on a checkout and folds the results into the review. deep requires an enrolled server holding a checkout of this repo: that pass runs there, and nothing on our side can stand in for it. Without one you still get a review, an inline (diff-only) one that says so on the pull request and names the reason. Never a silent downgrade. |
coding_agents |
copilot | devin[] |
[] |
Coding-agent identities whose bot-authored PRs are your own work item and should be reviewed instead of deferred as a competing bot. Empty by default → every bot author defers ("no bot-on-bot loops"); list copilot to review your GitHub Copilot coding agent’s PRs, or devin for Devin (devin-ai-integration[bot]). Distinct from the CodeRabbit/Dependabot/Renovate defer set, only a real coding agent can be listed. |
force |
boolean | false |
Review even when another review bot (CodeRabbit) is active on the repo. Off by default so two bots never double-review the same PR; turn on while migrating off CodeRabbit. |
profile |
chill | assertive |
chill |
chill posts fewer, higher-signal comments (raises the default severity floor); assertive surfaces more, including minor issues. |
language |
BCP-47 string | en-US |
BCP-47 language tag for review copy (e.g. en-US, de). |
tone_instructions |
string ≤4 000 | "" |
Free-text steering for the reviewer’s voice and emphasis. |
request_changes |
boolean | true |
Emit a request_changes verdict (not just a comment) when a blocking finding exists. ON by default: a blocking finding blocks the merge on GitHub, not just in our own gate. The bot dismisses its own earlier request_changes as soon as a later review of the same pull request stops asking for changes, so it can never leave a block standing on work that has been fixed. Set false to keep blocking findings advisory. |
approval |
boolean | true |
Let the reviewer post a real GitHub approval on a clean pass, the machine merge-gate signal ("gates are machines"). ON by default: a clean review satisfies a review-required branch protection the same way a human approval does. Set false to keep it advisory, so an approving review stays a quiet comment. Takes effect only where review runs (verified-OSS tier and up). The bot never approves a pull request it authored itself. |
high_level_summary |
boolean | true |
Post a single top-level walkthrough/summary comment on the PR. |
learnings |
boolean | true |
Write review learnings back to the repo knowledge base (kb) so later reviews cite them, the CodeRabbit-style moat feature. |
chat_auto_reply |
boolean | true |
Engage review comment threads automatically, reply to a human’s response and resolve the thread when it is settled. Off: the bot posts nothing and resolves nothing on your threads; it still reads them so later reviews honor decisions already made (turn learnings off too to stop that as well). |
verified_tier |
boolean | true |
Judge every finding of severity major or above against the diff with a skeptical, calibrated verifier before posting. Only a finding the verifier explicitly refutes is dropped, and refuted ones are disclosed as a count on the review body. A finding the verifier cannot judge (its file is not in the diff, or the verification call failed) posts unverified, and if the verifier is unavailable every finding posts. Minor findings and nits post unjudged. ON by default: a major holds a merge, so it is checked before it can. Each judged finding spends up to three bounded verification attempts on your own connected provider key (retries fire only on transport or malformed-response failures). Set false to post every finding unjudged. |
auto_review.enabled |
boolean | true |
Review PRs automatically on open/synchronize. |
auto_review.incremental |
boolean | true |
On a re-push, review only the new commits rather than the whole diff again. |
auto_review.drafts |
boolean | false |
Review draft PRs too. |
auto_review.base_branches |
string[] | [] |
Base branches BESIDE the default one to auto-review, as anchored regular expressions (e.g. release/.*, or .* for every branch). Your default branch is always reviewed and needs no entry. Same key and same dialect as CodeRabbit's reviews.auto_review.base_branches. A pattern that cannot be compiled safely is refused and the file fails validation. |
auto_review.ignore_title_keywords |
string[] | [] |
Skip PRs whose title contains any of these (e.g. WIP, DO NOT MERGE). |
anti_noise.severity_floor |
critical | major | minor | nit |
minor |
Drop findings below this severity. Blocking findings always bypass it. Overrides the profile-derived floor. |
anti_noise.max_comments |
integer 1–200 | 10 |
Cap on non-blocking inline comments per review. Blocking findings are always kept. |
anti_noise.dedupe |
boolean | true |
Collapse repeated findings against prior reviews on re-push. |
path_filters |
string[] (globs) | [] |
Glob filters applied before chunking; a leading ! excludes a path. |
path_instructions |
{ path, instructions }[] |
[] |
Per-glob free-text guidance the reviewer applies to matching files. |
tolerated_check_failures |
string[] | [coderabbit:rate limit, coderabbit:limit reached, greptile:rate limit, greptile:limit reached, greptile:quota exceeded] |
Non-blocking check-failure whitelist. Case-insensitive substrings matched against a failing check’s own reason text (its title, the head of its summary, or a status context’s description), so a red that says Rate limit exceeded reads as a quota outage (a check that never ran) rather than as evidence about the diff. Write check:reason to require BOTH (the check’s name must contain check and its reason must contain reason), which is how the built-ins are written, so a genuinely broken build whose log happens to quote a rate limit is never waved through. A bare entry matches on the reason alone. A matched check is omitted from the merge gate’s CI verdict, never re-graded as passing: a pr.required_checks entry still refuses, so tolerance can wave through an advisory check and nothing else. Omit the key for the built-ins; set [] to tolerate nothing. Each half is 3–120 characters, 20 entries max. Anything refused is reported, never silently dropped. |
defer_to |
string[] | detected | Which third-party review bots this repo defers to (coderabbit, greptile, copilot). Omit the key and we defer to whichever ones we can see acting on the repo (detected, not configured), so installing CodeRabbit is enough. Name a list to narrow it; set [] to defer to nobody and always review here. This is not force: force runs our reviewer ALONGSIDE a healthy incumbent, while this says which incumbents exist at all. Where one is named and cannot review a commit (it reports a rate limit, or never arrives) we review it ourselves and the review says which reviewer was unavailable. Detection currently observes CodeRabbit only, so naming greptile or copilot alone means nothing is detected and we review every PR here; the other two are accepted today so a repo can declare its intent before we can see them act. |
mandatory_passes |
correctness | security | tests | api_contract | style_nits[] |
platform set | Which review passes must produce a verdict before the review is allowed to settle. Choose from correctness, security, tests, api_contract and style_nits. Omit the key to take the platform’s own set, which widens as each per-pass reviewer is wired end to end. An explicitly empty list is a decision, not an omission: it means nothing is mandatory. A name outside the list above is reported at validation, never dropped, because a typo would quietly let a pass you meant to require slip out of the gate. |
Anti-noise. Quiet by default (their #1 complaint): findings below
anti_noise.severity_floor are dropped, volume is capped at max_comments, and
repeats are deduped on re-push. Blocking findings (principle/gate violations)
always bypass both the floor and the cap.
Example
review:
profile: chill
request_changes: true
learnings: true # write review learnings back to kb
anti_noise:
severity_floor: minor
max_comments: 10
auto_review:
base_branches: ["release/.*"] # the default branch is always reviewed
ignore_title_keywords: [WIP, "DO NOT MERGE"]
path_filters: ["!**/*.lock", "!dist/**"]
path_instructions:
- path: "packages/**/*.ts"
instructions: "Enforce the repo principles in docs/idea/principles.md."
tolerated_check_failures: ["coderabbit:rate limit", "quota exceeded"]
This page is generated from the .maintainer.yml schema the parser validates against. Run bun run policy:doc to regenerate.