Skip to content

Docs / maintainer.yml reference

maintainer.yml reference

Complete field reference for the maintainer.yml policy file.

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.json as 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_ref is required and the webhook must be registered in the dashboard. mode = fleet does not require webhook_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_merge is 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, set auto_merge: false or list the sensitive branches/paths under human_merge. (Free-OSS-tier repos stay opt-in regardless.)

require_review is 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, set require_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.yaml and a review: block, the review: 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: true to 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.