Configuration
autoducks is configured via .autoducks/autoducks.json at the root of your repository.
Full schema
Section titled “Full schema”{ "command": "", "providers": { "its": "github", "git": "github", "llm": "claude" }, "defaults": { "model": "claude-sonnet-5", "effort": "high", "max_turns": 50, "base_branch": "main", "integration_branch": "main", "merge_method": "auto" }, "orchestrator": { "mode": "waves" }, "reviewer": { "required_check": false, "check_name": "Autoducks: Reviewer" }, "checks": { "enabled": false, "setup": "", "commands": [], "git_hooks": false, "max_iterations": 3 }, "product": { "enabled": true, "schedule": "0 9 * * *", "priority_backend": "auto", "project_number": null, "priority_field": "Priority", "auto_merge_duplicates": true, "max_closes_per_run": 5, "confidence_threshold": "high", "max_issues_per_run": 100, "provisional_classification": true }, "triggers": { "architect": [], "engineer": [], "execute": [], "fix": [], "revert": [], "close": [], "review": [], "triage": [], "merge": [] }, "context": { "developer": { "parts": ["issue_title", "issue_description", "prior_feedback"] }, "reviewer": { "parts": [ "issue_title", "design.problem_statement", "design.proposed_solution", "design.constraints", "design.out_of_scope", "pr_diff", "pr_meta", "security_guidelines" ] } }, "review": { "security_guidelines": ".autoducks/security-guidelines.md", "auto_rework": true, "max_iterations": 3 }, "security": { "trusted_associations": ["OWNER", "MEMBER", "COLLABORATOR"], "allow": [], "deny": [], "codeowners": false, "per_agent": { "revert": { "trusted_associations": ["OWNER", "MEMBER"] }, "close": { "trusted_associations": ["OWNER", "MEMBER"] }, "product": { "trusted_associations": ["OWNER", "MEMBER", "COLLABORATOR"] }, "merge": { "trusted_associations": ["OWNER", "MEMBER"] } } }}command
Section titled “command”An optional namespace word. Commands are short-form by default — a bare verb right after the slash (/execute, /architect) — and command is the escape hatch for teams that want (or need) a namespaced prefix instead (/quack execute, /quack architect), e.g. to avoid colliding with another bot’s slash commands on the same repository.
Default: "" (empty — short-form commands, no namespace word)
Validation is /-tolerant: ^$ (empty) or ^/?[a-z0-9-]+$ (a lowercase word, with or without a leading slash). "quack" and "/quack" are equivalent — both normalize to the namespace word quack and produce /quack <verb> triggers. Anything else falls back to the default (empty).
{ "command": "" } // short-form (default): /execute, /architect, /review...{ "command": "quack" } // namespaced: /quack execute, /quack architect...{ "command": "/quack" } // identical to above — the leading slash is optionalGitHub Actions evaluates workflow if: guards without reading repository files, so the namespace (like custom trigger aliases) is baked into the workflow YAML. After changing it, regenerate and commit the guards:
bash scripts/update-triggers.shBounded blast radius
Section titled “Bounded blast radius”A bare verb (/execute) is more likely to appear at the start of an unrelated comment than a namespaced one (/quack execute) — that’s the tradeoff for the shorter surface. Two independent safeguards bound the consequences of an off-target fire:
- The Authorization Gate runs before anything else — an accidental match from an untrusted commenter is denied before any LLM call, comment, branch, or PR.
- Agents are safe, state-based no-ops when there’s nothing to do — they recompute from visible ITS/git state rather than blindly acting, so a stray trigger on an issue with no matching state (e.g.
/reverton an issue that was never planned) does nothing destructive.
If your repository’s comment traffic makes even a gated, no-op accidental match undesirable, set command to a namespace word.
providers
Section titled “providers”Selects the implementation for each provider interface.
| Key | Value | Status | Description |
|---|---|---|---|
its | github | ✅ Supported | GitHub Issues via the gh CLI |
git | github | ✅ Supported | Git CLI with GitHub repository operations |
llm | claude | ✅ Supported | Anthropic Claude via the Claude Code action |
defaults
Section titled “defaults”Default runtime parameters for all agents, unless overridden by a directive argument or a per-agent defaults.json.
defaults.model
Section titled “defaults.model”The Claude model ID for LLM agents. Default: claude-sonnet-5
| Model | Use case |
|---|---|
claude-sonnet-5 | Default — good balance of speed and quality |
claude-opus-4-8 | Best quality for design/planning work |
claude-haiku-4-5 | Fastest, lowest cost — simple execution tasks |
defaults.effort
Section titled “defaults.effort”LLM effort level: off, low, medium, high, max. Default: high
Maps to the provider’s native reasoning-effort control, not a prompt instruction — for Claude, the CLI’s --effort flag, forwarded via claude_args (“effort” follows the cross-provider convention — OpenAI reasoning_effort, Anthropic output_config.effort). On adaptive-reasoning models, off degrades to the model’s minimum reasoning effort rather than fully disabling thinking. max meaningfully increases per-run token spend.
defaults.max_turns
Section titled “defaults.max_turns”Maximum agentic turns per run. Optional at every tier; when unset everywhere, the Claude provider action falls back to 50.
defaults.base_branch
Section titled “defaults.base_branch”The cut-point: the branch that pipeline branches are created from. Default: main
When integration_branch is unset, base_branch also serves as the PR target.
defaults.integration_branch
Section titled “defaults.integration_branch”The PR target: where the final pipeline PR is opened. Default: unset (falls back to base_branch).
Exists for fork workflows: cut from the upstream base_branch, land PRs on a fork-local branch. The invariant merge-base(pipeline-branch, integration_branch) == base_branch always holds.
defaults.merge_method
Section titled “defaults.merge_method”Method for auto-merging task/fix PRs into the pipeline branch. Default: auto
| Value | Description |
|---|---|
auto | Probe the repository’s allowed methods, preferring merge → squash → rebase |
merge / squash / rebase | Force one method |
auto keeps autoducks working on squash-only or rebase-only repos without configuration.
orchestrator
Section titled “orchestrator”Controls how the Maestro dispatches tasks from the tactical plan.
orchestrator.mode
Section titled “orchestrator.mode”The dispatch topology: waves or sequential. Default: waves
| Value | Behavior |
|---|---|
waves | Dispatch every unfinished task in the current wave to its own Developer in parallel; advance to the next wave only once the whole wave is done |
sequential | Dispatch one task at a time, in dependency order — wave grouping in the tactical plan is ignored, and only one Developer runs at a time |
This is the lowest-priority tier in the topology resolution order — a mode: directive on the triggering comment or a Mode:* label on the issue both take precedence over it. See Maestro → Topology for the full resolution order.
reviewer
Section titled “reviewer”Controls the Reviewer’s GitHub Check-run and whether it gates merges. See Reviewer → Required check.
reviewer.required_check
Section titled “reviewer.required_check”When true, scripts/setup.sh (run with repo-admin gh credentials) creates a branch ruleset requiring the reviewer’s check on the integration/base branch, so a request-changes verdict blocks the merge. Default: false (the Reviewer still emits the check on final PRs, but nothing is enforced).
reviewer.check_name
Section titled “reviewer.check_name”The Check-run name emitted on final PRs and required by the ruleset — kept in one place so the two never drift. Default: Autoducks: Reviewer.
checks
Section titled “checks”Configures the Developer’s build-layer verification loop: automated checks (lint, build, test, or anything else scriptable) that run against the agent’s own changes before a task PR ever opens. Optional — the whole block, and every field in it, can be omitted; checks are disabled by default. See Developer → Verification loop for the runtime behavior this config drives.
checks.enabled
Section titled “checks.enabled”Turns the loop on. Default: false
Checks only actually run when enabled is true and there’s something to run — at least one entry in commands, or git_hooks: true with a discoverable hook. enabled: true with neither is a no-op.
checks.setup
Section titled “checks.setup”An optional shell command that prepares the toolchain before any check runs (installing dependencies, building, starting a service, etc.). Default: "" (no setup step)
checks.commands
Section titled “checks.commands”An ordered array of { "name": "...", "run": "..." } checks, each run from the repository root via bash -c "<run>". They run in array order; the first non-zero exit stops the loop right there (later commands don’t run). Default: []
{ "checks": { "commands": [ { "name": "lint", "run": "npm run lint" }, { "name": "test", "run": "npm test" } ] }}name is optional (falls back to "check") and is only used to label the failure in the feedback comment; run is required.
checks.git_hooks
Section titled “checks.git_hooks”When true, probes the repository for an existing pre-commit setup and, if one is found, runs it as an implicit first check (before commands, in this order): pre-commit run --all-files (when .pre-commit-config.yaml exists and pre-commit is on PATH), else an executable .githooks/pre-commit, else an executable .git/hooks/pre-commit. Default: false
If none of those are discoverable, git_hooks: true degrades to a no-op (with a workflow warning) rather than failing the run — a missing hook config never wedges every task.
checks.max_iterations
Section titled “checks.max_iterations”Caps how many additional LLM attempts the loop will dispatch against a failing check before giving up. Default: 3. Clamped to 1–10; a non-numeric or out-of-range value falls back to the default.
Loop mechanics
Section titled “Loop mechanics”The loop runs after the Developer’s LLM step has committed its changes, and before the task PR is opened:
setupruns once (if set); a non-zero exit is aninfrafailure — see the caveat above.- The
git_hooks-derived check (if any) runs, thencommandsin order. The first failure stops the loop. - All checks pass → the loop clears any stale feedback comment and the run proceeds to push + open the PR as normal.
- A check fails and iterations remain (
iteration < max_iterations) → the branch is pushed as WIP (resumable, no PR yet), a marker-anchored comment on the task issue is created or updated in place with the failing output, and the Developer re-dispatches itself on the same task with the iteration counter incremented — the same LLM prompt, plus the prior failure appended as context. - A check fails and iterations are exhausted (
iteration >= max_iterations) → the loop gives up: the branch is pushed with its last attempt preserved, the run fails with categorycheck_failed, and/fixon the task resumes from that branch.
A setup (infra) failure never consumes an iteration — only commands/git_hooks failures do.
product
Section titled “product”Configuration for the Product Owner (/triage and /merge) — the only agent that also runs unattended, on a schedule.
product.enabled
Section titled “product.enabled”Whether the schedule sweep fires at all. Default: true
Setting this to false removes the schedule: trigger from the baked workflow entirely — /triage, /merge, and workflow_dispatch keep working, only the timer is gone. Like the namespace and custom trigger aliases, this is baked into the workflow YAML and requires bash scripts/update-triggers.sh after changing it.
product.schedule
Section titled “product.schedule”The cron expression for the backlog sweep. Default: "0 9 * * *" (09:00 UTC daily)
Also baked into the workflow by scripts/update-triggers.sh — GitHub Actions’ on.schedule cron is static YAML and can’t read config at run time.
product.priority_backend
Section titled “product.priority_backend”Which storage /triage uses for priorities: auto, project, labels, or off. Default: auto
| Value | Behavior |
|---|---|
auto | Probes for a reachable Projects v2 priority field; uses project if found, else falls back to labels |
project | Trusts the config — always targets the repo’s Projects v2 board, no probing |
labels | Trusts the config — always uses the named Priority:* labels, no probing |
off | Priority assignment disabled entirely; /triage still runs duplicate detection |
product.project_number
Section titled “product.project_number”Pins the project backend to a specific Projects v2 board by number, when a repository has more than one linked. Default: null (use the first project linked to the repository)
product.priority_field
Section titled “product.priority_field”The Projects v2 single-select field name that represents priority, matched case-insensitively. Default: "Priority"
product.auto_merge_duplicates
Section titled “product.auto_merge_duplicates”Whether /triage proposes and applies duplicate-issue closes at all. Default: true
Only takes effect on a full backlog sweep — a scoped single-issue /triage never has anything to compare against, regardless of this setting.
product.max_closes_per_run
Section titled “product.max_closes_per_run”Caps how many duplicates a single /triage sweep can close, to bound the blast radius of a bad LLM proposal. Default: 5
Groups are kept in the order proposed, greedily, until the next group would exceed the cap — the rest are dropped and reported in the job summary, not silently discarded.
product.confidence_threshold
Section titled “product.confidence_threshold”The minimum confidence (high, medium, low) a proposed duplicate group must carry to be applied. Default: "high"
product.max_issues_per_run
Section titled “product.max_issues_per_run”Caps how many open issues a full backlog sweep pulls into the inbox. Default: 100
When the open backlog exceeds this, the run is bounded and the excess is reported in the job summary rather than silently dropped.
product.provisional_classification
Section titled “product.provisional_classification”Whether /triage classifies un-classified issues by applying the Bug/Feature label. The Architect remains the authoritative classifier and overrides the label if its design concludes differently. false disables triage classification entirely; /triage still assigns priorities and proposes duplicates.
triggers — custom trigger aliases
Section titled “triggers — custom trigger aliases”Per-team alias verbs, keyed by agent (architect, engineer, execute, fix, revert, close, review, triage, merge):
{ "triggers": { "engineer": ["blueprint"], "execute": ["ship"] }}Rules: lowercase [a-z0-9-]+, no collision with built-in verbs/aliases, unique across agents. Aliases resolve to the canonical verb everywhere — routing, #auto: chains, and cascade included.
Like the namespace, aliases must be baked into the workflow guards:
bash scripts/update-triggers.shThe script validates the whole block first (a bad alias is a hard error, never baked), regenerates each guard wholesale (idempotent — byte-identical when nothing changed), and mirrors .github/workflows/ ↔ .autoducks/runtimes/github-actions/.
Per-agent defaults
Section titled “Per-agent defaults”Each agent can have its own .autoducks/agents/<agent>/defaults.json:
{ "model": "claude-opus-4-8", "effort": "high"}Agent directories: architect, engineer, maestro, developer, fix, revert, close, reviewer, product.
Priority (highest to lowest):
- Directive argument in the comment (
model:,effort:,turns:) - Agent-level
defaults.json - Global
.autoducks/autoducks.json - Provider action default — applies only to
max_turns(50)
context
Section titled “context”Controls which pieces of issue/PR/design context each agent is fed, keyed by agent (architect, engineer, developer, reviewer). Optional — omit the whole block, or any agent within it, to use that agent’s built-in default manifest (see Default manifests below).
{ "context": { "developer": { "parts": ["issue_title", "issue_description", "prior_feedback", "issue_metadata"] } }}context.<agent>.parts
Section titled “context.<agent>.parts”An allow-list, not an additive list: when parts is present, it entirely replaces that agent’s default manifest rather than adding to it. An explicit "parts": [] is a deliberate “select nothing” and is honored as-is — that’s distinct from omitting parts altogether, which falls back to the default manifest.
Every requested part is validated against the availability matrix for that agent. An unknown part ID, or one not available to that agent, aborts the run (exit 1) with a fix hint pointing back at this config — never a silent no-op.
Part catalog & availability
Section titled “Part catalog & availability”| Part ID | Source | Architect | Engineer | Developer | Reviewer |
|---|---|---|---|---|---|
issue_title | Issue title | ✅ | ✅ | ✅ | ✅ |
issue_description | Issue body | ✅ | ✅ | ✅ | ✅ |
issue_comments | Last 20 issue comments | ✅ | ✅ | ✅ | ✅ |
issue_metadata | Labels, type, author | ✅ | ✅ | ✅ | ✅ |
design.problem_statement | Design-section marker extract | — | ✅ | ✅ | ✅ |
design.proposed_solution | Design-section marker extract | — | ✅ | ✅ | ✅ |
design.technical_design | Design-section marker extract | — | ✅ | ✅ | ✅ |
design.dependencies | Design-section marker extract | — | ✅ | ✅ | ✅ |
design.constraints | Design-section marker extract | — | ✅ | ✅ | ✅ |
design.out_of_scope | Design-section marker extract | — | ✅ | ✅ | ✅ |
design.full | Whole design zone of the issue body | — | ✅ | ✅ | ✅ |
plan | Tactical zone (plan YAML) of the issue body | — | ✅ | ✅ | ✅ |
task_title | Task issue’s title | — | — | ✅ | — |
task_description | Task issue’s body | — | — | ✅ | — |
prior_feedback | Marker-anchored check-failure comment (only materializes when ITERATION > 1) | — | — | ✅ | — |
task_criteria | Acceptance criteria of every task in the feature’s wave plan | — | — | — | ✅ |
pr_diff | Unified diff of the PR | — | — | — | ✅ |
pr_meta | PR title/base/head/state plus changed-file list | — | — | — | ✅ |
security_guidelines | Repository security guidelines file (see review.security_guidelines) | — | — | — | ✅ |
task_title/task_description are shaped identically to issue_title/issue_description — they’re distinct catalog IDs because the Developer resolves them against the task issue rather than the parent feature/bug issue.
Default manifests
Section titled “Default manifests”The parts injected when context.<agent> (or its parts key) is absent — chosen to reproduce each agent’s pre-existing /tmp context files byte-for-byte:
| Agent | Default parts |
|---|---|
architect | issue_title, issue_description, issue_comments |
engineer | issue_title, issue_description, issue_comments, design.full |
developer | issue_title, issue_description, prior_feedback |
reviewer | issue_title, issue_description, task_criteria, design.full, pr_diff, pr_meta, security_guidelines |
Architect: issue_comments runs every turn
Section titled “Architect: issue_comments runs every turn”Unlike every other part, the Architect’s issue_comments is turn-agnostic: whenever it’s selected, it’s materialized on the first pass and every revision — there’s no gate on iteration count or on whether feedback exists yet. In practice this means a reviewer’s feedback comment is visible to the Architect’s very next turn, not just a later resumed revision.
To opt out, list context.architect.parts explicitly without issue_comments:
{ "context": { "architect": { "parts": ["issue_title", "issue_description"] } }}Reviewer: curated design context
Section titled “Reviewer: curated design context”The default context.reviewer manifest (see Default manifests) hands the Reviewer the entire design zone via design.full, plus task_criteria. Repositories that want the Reviewer judging strictly against the proposed solution and constraints — narrower context, fewer tokens, less surface for the LLM to over-index on unrelated design prose — can select the individual design.<section> parts instead:
{ "context": { "reviewer": { "parts": [ "issue_title", "design.problem_statement", "design.proposed_solution", "design.constraints", "design.out_of_scope", "pr_diff", "pr_meta", "security_guidelines" ] } }}This is the manifest that ships in this repository’s own autoducks.json. Dropping issue_description and task_criteria from the selection means the Reviewer’s Plan conformance section (see Reviewer) judges the diff against the design’s proposed solution and constraints directly, rather than against a separately-tracked list of task acceptance criteria.
Design-section markers & the legacy full-design fallback
Section titled “Design-section markers & the legacy full-design fallback”When the Architect finalizes a design, its issue body is wrapped in wire-format HTML-comment markers around each of the six canonical sections:
<!-- autoducks:design:problem_statement:begin -->...<!-- autoducks:design:problem_statement:end -->(likewise for proposed_solution, technical_design, dependencies, constraints, and out_of_scope)
Downstream agents can select individual design.<section> parts to receive only the relevant slice of the design, or design.full for the entire design zone. Two precedence rules resolve conflicts between them, each logged as an ::notice:: when triggered:
design.fullwins. Selecting bothdesign.fulland one or moredesign.<section>parts drops the individual sections — only the full zone is materialized.- Legacy fallback. If the issue body carries none of the section markers at all (e.g. a design authored before this feature existed), any selected
design.<section>part degrades to the full design zone instead. No agent ever runs with an empty design because of a missing marker.
review
Section titled “review”Settings for the Reviewer (/review). Optional — the whole block, and every field in it, can be omitted.
review.security_guidelines
Section titled “review.security_guidelines”Path to a repository-specific security guidelines file, relative to the repository root. The Reviewer reads this file (via /tmp/security-guidelines.md, staged by pre.sh) and applies it with priority over its built-in baseline security checklist.
Default: .autoducks/security-guidelines.md
The file is entirely optional: if it’s absent at the configured path, the Reviewer degrades gracefully and falls back to the baseline checklist alone — this is not treated as an error. A copyable starter template ships at the default path; see the Reviewer’s security review section for what it covers.
This field is unrelated to the security block below — that block controls who can trigger agents, this one supplies review context for judging what the code does. See Security review vs. the Authorization Gate.
review.auto_rework
Section titled “review.auto_rework”Whether a request-changes verdict automatically dispatches a headless /rework round instead of waiting for a human to run /rework//defer. Default: true.
{ "review": { "auto_rework": false } }review.max_iterations
Section titled “review.max_iterations”Caps how many automatic Review → Rework rounds auto_rework will run for a single PR before handing off to a human. Default: 3. Clamped to 1–10; a non-numeric or out-of-range value falls back to the default. Has no effect when auto_rework is false.
See Reviewer → Bounded auto-rework loop for the round-tracking and hand-off mechanics this drives.
security
Section titled “security”Every trigger passes the Authorization Gate before any agent runs — before any reaction or status comment. The security block controls trusted author associations, allow/deny lists, per-agent overrides (keys: architect, engineer, execute, fix, revert, close, review, product, merge — the Maestro and Developer share the execute policy), and optional CODEOWNERS integration.
See the Security reference for the full threat model and schema.