System overview
DevBench is three phases sharing one filesystem: a human-driven authoring phase that produces a spec and a backlog, an autonomous orchestration phase that executes one work unit at a time under judged review, and a gitops phase that commits, opens the PR, watches CI and merges. Each phase ships as its own artifact.
Authoring plugin
devbench-authoring v0.2.0: 4 skills, zero agents, zero hooks, zero scripts. Its manifest states the reason: "No PreToolUse hooks; safe to enable in any workspace where backlog authoring happens."
Produces: spec/<project>.md, BACKLOG.md plus one markdown file per work unit, backlog/config/devbench.yaml, and cloned + toolchain-validated target repos.
Orchestrate plugin
devbench-orchestrate v0.4.0: 1 skill, 10 agents, 1 hooks manifest, 12 shell scripts. Roughly 1,880 lines of agent prompt and 1,550 lines of guard shell.
The two plugins were split by issue #224 precisely because the work-unit write guard "correctly blocked the executor and incorrectly blocked the authoring skill as collateral damage."
docs/migration-0.4.0.md:1-22GitOps
Branch, commit, push, PR, CI watch, merge: all behind the devbench git-ops CLI command, not an agent. Four mutually exclusive modes; illegal combinations are rejected at config load.
The pipeline terminates at merge. There is no deployment, release or post-merge promotion stage anywhere in the codebase.
src/devbench/cli.py:9465 · src/devbench/github/git_ops.py:530-559The orchestrator is not a daemon
devbench start makes exactly one claude_agent_sdk.query() call, with the prompt "Run the devbench-orchestrate:orchestrate skill to process the backlog until complete" and ClaudeAgentOptions(plugins=[{"type": "local", "path": <plugin path>}], permission_mode="bypassPermissions"). The loop is the SKILL.md prompt, executed inside that single session: not a scheduler, not a queue consumer, not a service.
--daemon does not change that. It detaches the same session to the background and writes a PID file; instances, tail, stop-instance and restart then manage the detached process.
What keeps a prompt-driven loop alive is the Stop hook (section 3): it blocks the session from ending while a task is in progress and injects the next step. The circuit breaker is what stops it looping forever.
State lives on disk, in markdown
Markdown work-unit files are the source of truth. BACKLOG.md is a derived index; validate-backlog reports drift between them as an error rather than auto-correcting, and the parser logs a warning and trusts the file. Work-unit files carry no YAML frontmatter: status, repo, branch, dependencies and acceptance criteria are parsed out of markdown by regex.
There is no message broker, no database server and no external issue tracker in the data path. SQLite is present, but only as a rebuildable report cache (section 9). Because state is on disk and every agent action appends a timestamped audit comment, the loop resumes from any point after a restart.
docs/backlog-contract.md:234 · src/devbench/backlog/parser.py:206-217 · src/devbench/constants.py:39-52Per-work-unit pipeline
validate-backlog → next → claim → ensure-branch → executor (TDD)
→ [manifest-amender, conditional] → review-supervisor (4 judges in parallel)
→ security-reviewer → git-ops (commit / push / PR / CI / merge) → mark-done → loop
plugin/devbench-orchestrate/skills/orchestrate/SKILL.md:16-115 · docs/architecture.md:52
↑ Back to contents
The 10 agents
Ten agent prompt files ship in the orchestrate plugin: six at the top level and four in agents/review_team/. Each takes a single argument (a work-unit ID) and each opens with an ## Evidence block whose commands are executed and inlined before the prompt is evaluated, so agents reason over CLI output rather than over free filesystem access.
| Agent | Model | Tools | disallowedTools | Role | Invoked by |
|---|---|---|---|---|---|
executor | sonnet | Bash, Read, Write, Edit, Glob, Grep | none declared | Implements one work unit under TDD (RED/GREEN/REFACTOR); stages files only | orchestrate skill step 4 SKILL.md:56, step 6a on REVIEW_FAIL, and git-ops rc 2 / rc 3 |
review-supervisor | sonnet | Bash, Agent(code-reviewer, test-reviewer, doc-reviewer, changes-manifest) | none declared | Discovers the review team, fans out in parallel, parses their JSON envelopes, writes the canonical verdicts | orchestrate skill step 5 SKILL.md:83; re-invoked after each executor retry |
code-reviewer | opus | Bash | Write, Edit, Read, Glob, Grep |
52-point rubric: acceptance criteria, SOLID, DRY, fail-fast, 12-factor, security, idiomatic code, infra completeness | review-supervisor (parallel fan-out) |
test-reviewer | opus | Bash | Write, Edit, Read, Glob, Grep |
49-point rubric: real tests only, TDD discipline, coverage, git completeness. The only judge that runs the test suite (devbench run-tests is injected as evidence) |
review-supervisor (parallel fan-out) |
doc-reviewer | opus | Bash | Write, Edit, Read, Glob, Grep |
29-point rubric: documentation synchronisation, API/config/architecture docs, evidence-based content | review-supervisor (parallel fan-out) |
changes-manifest | opus | Bash | Write, Edit, Read, Glob, Grep |
27-point rubric: staged diff vs declared Changes Manifest, plus an independent commit-attribution check over origin/main..HEAD |
review-supervisor (parallel fan-out) |
security-reviewer | opus | Bash | Write, Edit, Read, Glob, Grep |
57-point rubric across 10 groups (SOC 2, PCI DSS, FINRA, SEC, GDPR, CCPA, SOX). FAIL if any critical or high finding exists | orchestrate skill step 7 SKILL.md:95: only after all four review judges pass. Never retried |
manifest-amender | opus | Bash | Write, Edit, Read, Glob, Grep |
Layer-2 semantic judge of a pending Changes-Manifest amendment: approach authorization, scope minimality, justification coherence, pre-conflict check | step 4b, gated on test -f .devbench/amendments/<id>.json |
blocker-resolver | opus | Bash | Write, Edit, Read, Glob, Grep |
21-question rubric; classifies a blocker and emits a structured proposal JSON for new work units | step 4c, only when task_factory.enabled: true and the amender just rejected |
task-factory | opus | Bash | Write, Edit, Read, Glob, Grep |
Runs exactly one command (devbench materialise-proposal) and surfaces any non-zero exit code. Promotion stays the operator's decision |
step 4a.d (validation-gate path) and step 4c.c (amendment-reject path) |
8 of 10 are Bash-only
Eight agents declare tools: Bash with Read, Write, Edit, Glob, Grep explicitly in disallowedTools. They obtain all evidence through pre-injected CLI output and act only through uv run devbench … commands.
Only the executor writes
executor is the single agent with filesystem write access. Even it is barred from backlog/**/*.md by a PreToolUse guard, and from branching, committing and pushing by its own prompt.
Only the supervisor spawns
review-supervisor is the only agent holding the Agent tool, and its declaration names exactly four subagents. A guard enforces the same allowlist at runtime.
The "Model Per Role" table in docs/plugin-architecture.md is stale in every row. It claims executor runs on opus, the four review judges on haiku, and security-reviewer on sonnet. The actual frontmatter is: executor and review-supervisor on sonnet, the other eight agents on opus. The configure-devbench skill's own table matches reality; the architecture doc does not.
The stale table also recommends haiku, which config load rejects for every per-agent field, because under load the Agent tool was observed silently dropping off haiku sessions (issue #198). Models are per-agent overridable via the agents: block, which materialises a workspace-local shadow plugin and rewrites the frontmatter line; so any public claim should say "declared model", not "the model that runs".
The pipeline
code_review
test-reviewer → test_review
doc-reviewer → doc_review
changes-manifest → changes_manifest
Amendment side-branch (step 4b). If .devbench/amendments/<id>.json exists: manifest-amender → apply-amendment (continue to review) or reject-amendment (revert the files, archive the request, block the task).
Recovery side-branch (step 4c). Only when task_factory.enabled: true and the amender rejected: blocker-resolver → write-proposal → task-factory → materialise-proposal → back to step 2.
Both branches are gated on file existence, never on an agent's words. The skill is explicit: "The check is on the FILE, not on the agent's verdict word. The verdict word … is NEVER a control point."
Three hard invariants the skill states
- "Never bypass the done-gate -- review-supervisor must pass before git-ops."
- "Security review runs exactly once per work unit -- after review-supervisor passes."
- "The retry loop (step 6) re-runs only review-supervisor, never security-reviewer."
Verdict names, rubrics and rejection vocabularies
Verdicts must be logged under underscored canonical names, not the agents' hyphenated frontmatter names; the done-gate parses only the underscored forms, so a hyphenated verdict means mark-done can never succeed. Five names satisfy the gate; four more are accepted by log-verdict as audit-only.
| Canonical judge | Counts toward done-gate? | Rubric size | Controlled rejection vocabulary |
|---|---|---|---|
code_review | Yes | 52 items | MAKE_VALIDATE_FAILURE, HARDCODED_URL, MISSING_AC_EVIDENCE, SOLID_VIOLATION, SECURITY_BYPASS_ANNOTATION, SCOPE_VIOLATION, MANIFEST_TODO_UNFILLED, AGENT_LOG_CONTRADICTS_DIFF |
test_review | Yes | 49 items | GIT_COMPLETENESS, STUB_TEST, COVERAGE_REGRESSION, TDD_CYCLE_MISSING, DRY_VIOLATION |
doc_review | Yes | 29 items | README_SYNC, CHANGELOG_SYNC, API_DOCS_STALE, EVIDENCE_BASED_CLAIM, CONFIG_DOCS |
changes_manifest | Yes | 27 items | SCOPE_GAP, MANIFEST_MISMATCH, STAGING_GAP, OUT_OF_SCOPE_FILES |
security_review | Yes | 57 items | SECRET_LEAK, UNAUTHORIZED_DEP, SCOPE_VIOLATION |
manifest_amender | Audit only | 4 questions | SCOPE, APPROACH_AUTH, JUSTIFICATION_COHERENCE, PRE_FILTER, OTHER |
blocker_resolver | Audit only | 21 questions | verdicts proposed / resolved / escalated / blocked |
task_factory | Audit only | n/a | surfaces the CLI exit code |
executor | Audit only | n/a | blocked by a guard from writing any of the five canonical verdicts |
Read these as rubrics, not automated checks. The 52 / 49 / 29 / 27 / 57 figures are counts of numbered prompt line items an LLM judge is instructed to evaluate. They are not independently executed deterministic assertions; the deterministic layer is the hook guards in section 3 and the CLI gates in section 5.
Evidence injection
Every agent opens with an ## Evidence section whose backtick-bang lines run before the prompt is evaluated. That is how Bash-only agents see code at all:
- read-unit
- work-unit content,
repo_pathandwork_unit_pathas JSON (injected for all ten agents). The four review-team judges (plusmanifest-amenderandtask-factory) pass--strip-commentsso prior verdicts do not bias them;security-reviewer,review-supervisor,blocker-resolverandexecutorread the unit with its comment history intact for their injected evidence;blocker-resolveradditionally issues a stripped read inside its recovery recipe blocker-resolver.md:87 - get-diff
- the authoritative scope source for every judge, mode-aware per ADR-12, so judges never compute scope with a raw
git diff origin/main - run-tests
- injected only for
test-reviewer - proposal / amendment JSON
cat-ed directly fortask-factoryandmanifest-amender
Hook system: the deterministic floor
Agent prompts are instructions; hooks are enforcement. One file (hooks/hooks.json) registers 10 event types, 14 matcher groups and 21 command registrations, all pointing at ${CLAUDE_PLUGIN_ROOT}/scripts/*.sh. The contract is blunt: exit 0 allows the tool call, exit 2 blocks it and the guard's stderr becomes Claude's feedback. The Stop hook is the exception: it emits a JSON decision envelope instead.
Complete registration map
| Event | Matcher | Scripts, in order |
|---|---|---|
PreToolUse | Bash | hook-logger.sh, guard-bash.sh, guard-verdict-format.sh, guard-comment-format.sh, guard-git-stage.sh, guard-destructive-git.sh, guard-review-supervisor-scope.sh |
PreToolUse | Write | guard-work-unit-write.sh |
PreToolUse | Edit | guard-work-unit-write.sh |
PreToolUse | .* | hook-logger.sh |
PostToolUse | Bash | hook-logger.sh, assert-tests-pass.sh |
PostToolUse | .* | hook-logger.sh |
PostToolUseFailure | .* | hook-logger.sh |
UserPromptSubmit | none | hook-logger.sh |
Stop | none | continue-orchestration.sh |
SubagentStart | none | hook-logger.sh |
SubagentStop | none | hook-logger.sh |
PreCompact | none | hook-logger.sh |
PermissionRequest | none | hook-logger.sh |
Notification | none | hook-logger.sh |
These ten event names are registered handlers. Whether a given Claude Code build emits all ten was not verified; the safe phrasing is "the plugin registers handlers for ten hook event types".
What each guard enforces
| Guard | Scope | Blocks | Operator override |
|---|---|---|---|
guard-bash.sh | all Bash callers | 10 literal destructive substrings: rm -rf, rm -fr, git push --force/-f, git reset --hard, git checkout --, git clean -f/-fd/-fdx, and backgrounded output suppression | none |
guard-destructive-git.sh | all Bash callers | 13 regex-anchored git patterns, each with a prescribed alternative: git rm --cached, reset --hard, checkout --/checkout ., clean -f, force push, branch -D, filter-branch, update-ref -d, rebase -i, commit --amend, and --no-verify / --no-gpg-sign on any git command | DEVBENCH_ALLOW_DESTRUCTIVE_GIT=1 |
guard-git-stage.sh | all Bash callers | Rule 1: git commit with an empty index. Rule 2: git add <path> where the path is not a row in the active work unit's ## Changes Manifest; the block message prints both the offending paths and the manifest | none |
guard-verdict-format.sh | log-verdict calls | Malformed verdicts (fewer than 3 positional args, unknown judge name, verdict that is not pass/fail, fail with empty feedback). Plus a scoped rule: when agent_type == devbench-orchestrate:executor, all five canonical reviewer verdicts are blocked: the executor cannot self-attest a review | none |
guard-comment-format.sh | log-comment calls | 11 forbidden control-language phrases in a comment body: halt orchestration, halting orchestration, halt the loop, halt loop, stop the loop, stop orchestration, abort orchestration, operator action required, resume orchestration once, emergency halt, do not continue. The block message supplies rewrites | none |
guard-review-supervisor-scope.sh | scoped to agent_type == devbench-orchestrate:review-supervisor (a no-op for every other caller) | 19 mutation command patterns (rm, rmdir, mv, cp, chmod, chown, chgrp, touch, mkdir, truncate, dd, tee, sed -i, awk -i, git mutation verbs, git branch -d/-D, git config writes, find -exec/-delete, xargs into destructive commands), plus > and >> redirection, plus any Agent spawn outside a four-name allowlist | DEVBENCH_ALLOW_REVIEW_SUPERVISOR_MUTATIONS=1 |
guard-work-unit-write.sh | Write and Edit on backlog/**/*.md | Rule 10: em-dash (U+2014) in work-unit content. Rule 11: Changes-Manifest or Source: paths carrying a checkout_directory prefix. Role gate: writes are refused unless DEVBENCH_AGENT_ROLE=orchestrator; a missing or unrecognized role defaults to BLOCK | DEVBENCH_AGENT_ROLE=orchestrator |
assert-tests-pass.sh | PostToolUse on Bash | A matched test command (pytest …, make test/test-unit/test-functional/validate) that exited non-zero, so the loop cannot walk past a red suite | none |
continue-orchestration.sh | the Stop event | The session ending while a task is in progress (see below) | stop_hook.* config |
hook-logger.sh | 11 of the 21 registrations, covering 9 of the 10 event types (everything except Stop) | Nothing. It appends one JSON line per event to hook-logs.jsonl and always exits 0 | n/a |
The Agent-spawn allowlist
guard-review-supervisor-scope.sh permits exactly four subagent_type values (the four review-team judges) and blocks anything else. Its header explains the loophole it closed: the previous Bash-only guard "let the supervisor escalate by spawning subagents (executor / git-ops) via the Agent tool", collapsing the documented pipeline into one mega-step.
Two orphans by design and by accident
Of the 12 script files, _hook_lib.sh is a shared library sourced by nine scripts (correctly unregistered), and guard-backlog.sh is dead code: present, executable, documented as active in docs/plugin-architecture.md, but referenced nowhere in hooks.json. Exactly 10 distinct scripts are wired.
The Stop hook: what keeps a prompt-driven loop running
continue-orchestration.sh is the only Stop handler and the only script that emits a JSON decision envelope: {"decision": "block", "reason": "…", "hookSpecificOutput": {…}}. It always exits 0: the block lives in the JSON, not the exit code. Its purpose, per its own header, is to "prevent the orchestrator from stopping mid-loop" and to inject a continuation instruction "so Claude re-enters the loop without human intervention".
Configuration: env var > YAML > hard default
| Setting | Env | YAML | Default |
|---|---|---|---|
| Blocks before allowing stop | DEVBENCH_STOP_MAX_BLOCKS | max_blocks | 5 |
| Circuit-breaker window | DEVBENCH_STOP_WINDOW_SECONDS | window_seconds | 180 s |
| Stale-task threshold | DEVBENCH_STOP_STALE_MINUTES | stale_task_minutes | 120 min |
Last action → injected next step
| Detected last comment | Injected instruction |
|---|---|
| executor completed | Invoke review-supervisor; run the four review agents |
| review pass | Check whether all four passed; if so invoke security-reviewer |
| review fail | Re-run executor with prior feedback, then re-run review-supervisor |
| security pass | Run git-ops <id> then mark-done <id> |
| git-ops completed | Run mark-done, then loop to validate-backlog && next |
| task done | Loop back: validate-backlog && next |
| unknown | read-unit <id> to reload context, then continue |
Circuit breaker and forensics
The counter resets when the window expires. At max_blocks the breaker allows the stop, writes [CIRCUIT_BREAKER] Allowed stop after N blocks in Ns. Human intervention may be needed. as an audit comment and clears its state file (/tmp/devbench-stop-hook-state-<session>.json, per-session so concurrent orchestrators keep independent counters). Every invocation also writes a forensic JSON file to <workspace>/.devbench/stop-hook-diag/<timestamp>-<task-id>.json capturing the task, last action, block count, window, elapsed seconds, stale warning, next step and the exact stdout emitted. The stated purpose: "so a future hang can be post-mortemed from evidence rather than speculation."
Why the guards are written in jq and pure bash
Three separate real-bug fixes are encoded in the scripts and worth citing when people ask why hook code looks defensive: python3 resolution "fails silently under asdf-shim PATHs", which made guards bow out and let dangerous calls through, so jq is tried first everywhere; local -n namerefs broke every PreToolUse hook on stock macOS bash 3.2 and were replaced with indirect reads; and a shlex.split tokeniser was silently returning zero tokens, causing legitimate verdicts to be blocked as "missing arguments".
Skills: five, across two plugins
A skill is a SKILL.md prompt Claude Code loads by name. DevBench ships five: four authoring skills that a human drives interactively, and one execution skill that is the orchestration loop. Amendment handling, block resolution, reporting and tailing are not skills: they are agents and CLI commands.
| Skill | Plugin | Model | Tools | Lines | Produces |
|---|---|---|---|---|---|
create-spec | authoring | opus | Read, Write, Edit, Bash | 236 | spec/<project>.md: 18 canonical sections under 16 top-level numbers (0 through 15, plus 3.5 and 3.6), target size "1000+ lines for non-trivial programs" |
spec-to-backlog | authoring | opus | Read, Write, Edit, Bash | 449 | BACKLOG.md + one markdown file per leaf task, in a 4-level Epic → Feature → Story → Task hierarchy, each task carrying 15 canonical sections |
configure-devbench | authoring | sonnet | Read, Write, Edit, Bash | 496 | backlog/config/devbench.yaml plus a devbench-commands.txt launcher file (a 16-step walkthrough, each section round-tripped through the real config loader before the file is written) |
bootstrap-environment | authoring | sonnet | Bash, Read, Edit (no Write) | 218 | Cloned repos, installed asdf toolchains, and a make validate baseline per repo, with a self-verify retry loop and operator-gated escalation |
orchestrate | orchestrate | not pinned | not pinned | 166 | The execution loop itself. Declares only name and description; the orchestrator process model comes from DEVBENCH_CLAUDE_MODEL, and each work agent loads its own model from frontmatter |
The chain
The chain crosses a plugin boundary between step 4 and step 5. The migration guide is explicit that these should be sequential in one workspace, never concurrent: "First install authoring at project scope, materialise the backlog, then uninstall authoring at project scope and install orchestrate at project scope."
Correct invocation names post-0.4.0: devbench-authoring:create-spec, devbench-authoring:spec-to-backlog, devbench-authoring:configure-devbench, devbench-authoring:bootstrap-environment, devbench-orchestrate:orchestrate. Only the last one is automatic: devbench start issues its prompt for you.
What the spec must contain
Eighteen canonical sections under sixteen top-level numbers (0 through 15, with 3.5 and 3.6 as separate sections), every one present or explicitly marked "N/A -- reason" (the skill's own heading says "these 16 top-level sections" and then enumerates 18 bullets): behavior-change items · context · goals · existing primitives to reuse · standards audit · trust model · new command surface · data format · version and interoperability semantics · error handling, logging and configuration · documentation updates · parallel or multi-repo scope · testing requirements · completions/integrations matrix · out of scope · resolved decisions · CLI --help reference · future work.
Authoring is an interview (17 numbered questions in 7 blocks), then section-by-section drafting, then a bounded self-critique loop against an 8-item rubric, then operator sign-off before the file is written. The skill emits a [QUALITY_REFERENCE] audit line naming the exemplar it used, or the literal token <embedded-canonical-sections> when none was configured, so the audit trail proves no external exemplar was consulted.
What the backlog must contain
Fifteen canonical sections per task file, in required order: title · ## Status: · Target Repository · Description · Definition of Ready (5 task-tailored items) · Depends On This · Approach (task-specific RED/GREEN/REFACTOR steps) · Code Standards (6 subsections) · Related Specifications · Dependencies · Acceptance Criteria · Changes Manifest (exactly 2 columns) · Definition of Done (~9 items) · TDD Cycle Log (header only) · Comments (header only).
Regex-parsed markdown, no frontmatter
Status, repo, branch, dependencies and ACs are extracted by named regexes in constants.py. The Changes Manifest is a strict 2-column table; a row with any other column count raises ManifestParseError. Globs are rejected outright; five named sentinels cover execution-determined file lists.
Deterministic post-processing, then validation
The skill calls an 8-pass deterministic post-processor (manifest column normalization, pipe sanitisation, row dedupe, code-standards drift check, index regeneration, dep-ID normalization, N/A suffixing, orphan-path (ref) suffixing) and then validate-backlog, which enforces 20 rules. Exit requires all three: validate-backlog rc=0, every leaf task passing its 12-item rubric, and the Status Summary total matching the index row count. (The rubric enumerates 12 items; the skill's own exit condition still says "all 10 items", an internal drift in the skill text.)
Bounded loops, resumable runs
Both authoring skills run an iterate-until-perfect loop bounded by two constants: SKILL_MAX_ITERATIONS = 5 (exhaustion emits [SKILL_MAX_ITERATIONS_REACHED] and exits non-zero) and SKILL_QUALITY_THRESHOLD = 0 (reaching it emits [SKILL_QUALITY_THRESHOLD_REACHED] and exits success). Checkpoints live under .devbench/skill-state/; spec-to-backlog keeps a second per-task checkpoint so a crash resumes mid-backlog instead of regenerating every file. Above skills.fan_out_threshold (default 10) leaf tasks, it spawns one sub-agent per Feature to author that Feature's tasks in parallel.
The orchestrate loop's own discipline
Three CRITICAL directives sit at the top of the execution skill, and all three exist because of observed failure modes: never stop to ask the user (the only valid exits are ALL_DONE or NO_ACTIONABLE); after every Agent tool result the next tool use must be another Agent call or a devbench Bash call ("ending your turn before emitting that next tool call is a loop-exit bug"); and ending a turn with recap prose is forbidden, because Claude Code reads a turn-end-without-tool-call as "agent done" and fires Stop. That last rule is regression-pinned by a test.
Correspondingly, the loop halts on exactly two signals: devbench next returning ALL_DONE/NO_ACTIONABLE, and the stop-hook circuit breaker. Everything a subagent says is diagnostic: "Subagent text is diagnostic, not control flow." That prompt rule is backed by the deterministic guard-comment-format.sh floor.
Work-unit lifecycle
Nine statuses, two of them terminal, two of them claimable. Four exist specifically as human-decision parking states.
| Status | Meaning | Claimable | Terminal for rollup | Written by |
|---|---|---|---|---|
draft | Authored but not approved for autonomous claim. Valid only on Task work units | No | No | authoring skill; backlog.default_status_for_new_work_units |
in-queue | Ready to be picked up | Yes | No | promote, set-status, auto-requeue cascades |
in-progress | An agent is implementing it | Yes (resumed first) | No | claim |
in-review | PR open, awaiting human review or merge | No | No | git-ops under pause_before_merge |
done | Merged and closed | No | Yes | mark-done (done-gate enforced); auto-rollup for parents |
blocked | Retry budget exhausted, dependency unmet, amendment rejected, security fail, or git-ops hard failure | No | No | mark_blocked, sync-blocked, set-status |
proposed | Auto-emitted draft awaiting operator promote or reject | No | No | proposal materialisation (legacy / hand-authored path) |
declined | Will never be done: a final operator decision | No | Yes | decline <id> --reason |
hold | Deferred / under debate. Explicitly non-terminal: a held child keeps its parent open | No | No | hold / unhold, operator only |
Happy path and side branches
draft → in-queue → in-progress → in-review → done
in-queue | in-progress | in-review → blocked (non-terminal)
in-queue | in-progress → hold (non-terminal)
in-queue | in-progress | blocked → declined (terminal)
There is no transition matrix in code. _set_status validates only that the target value is a member of VALID_STATUSES; legality is enforced by which function you call, and force_status bypasses even the done-gate (the cascades use it). Present the table above as "transitions the system performs", not "transitions the system enforces".
Claimability and the scheduling truth
Only in-queue and in-progress Task work units are actionable; proposed, draft, hold, blocked, declined and in-review are all inert to the scan. But "the next item" is not simply the earliest queued one:
BacklogParser.get_parallel_candidates filters to (status is in-queue or in-progress, type is TASK, all dependencies satisfied) and then sorts by (status_priority, topological_depth, id) with IN_PROGRESS = 0 and IN_QUEUE = 1.
So: interrupted work is resumed before new work is started; then queued work is ordered by topological depth (tasks with no declared dependencies first); with the lexicographic ID as a stable tiebreaker. Depth is computed across the full backlog with memoised recursion and cycle protection: self-loops and unresolvable IDs collapse to depth 0.
src/devbench/backlog/parser.py:328-414, :412-413A dependency is satisfied when its status is done or declined. A dep on an Epic/Feature/Story is satisfied when every descendant task is terminal; a parent with no task descendants is vacuously satisfied. An unknown dep ID is treated as satisfied so a typo cannot deadlock the loop; validate-backlog reports it as an integrity error instead.
Claim is race-safe
devbench claim <id> refuses a work unit whose Changes Manifest still contains a TBD placeholder row (the same rule as validator 19, enforced a second time at claim time), acquires an exclusive flock(BACKLOG.lock), re-reads the on-disk status under the lock, and raises ClaimRaceError without writing if the status changed. Every transition into in-progress appends a [WU_CLAIMED] audit comment stamped with the session name.
The done-gate
mark-done refuses unless the most recent review round carries a [REVIEW_PASS] from all five canonical judges: code_review, test_review, doc_review, changes_manifest and security_review. The round boundary is the most recent [REVIEW_REJECTED] line: the reader walks the Comments section in reverse and stops there. In practice it never stops early, because no code path writes that token. log-verdict emits only REVIEW_PASS or REVIEW_FAIL cli.py:3607, and [REVIEW_REJECTED] appears in src/ only as a read pattern manager.py:1092 · proposal.py:247, so the window degenerates to the whole comment history. Verdicts from executor, blocker_resolver, manifest_amender and task_factory are accepted by log-verdict but do not count.
On success, _set_status writes the status line in the work-unit file, updates the BACKLOG.md row, rewrites the Status Summary table, ticks every checkbox in Acceptance Criteria and Definition of Done, writes the per-task window-stats aggregate, fires the auto-requeue cascades for anything blocked on this unit, and rolls the parent up. Rollup cascades Task → Story → Feature → Epic and deliberately bypasses the done-gate: parent units are structurally done when their children are, and need no judge review.
Retry budgets
Global budget max_executor_retries (default 10, env DEVBENCH_MAX_RETRIES), optionally overridden per judge via max_executor_retries_per_judge; the block trips the moment any single judge's budget is exhausted. CI-failure retries (git-ops rc=2) and PR-bot-feedback retries (rc=3) share the same budget, so total per-task work stays bounded. Security review is never retried.
Block resolution: seven paths
Every work unit whose status is blocked is classified into exactly one of seven states by a single pure function, classify_blocked_task, evaluated first-match-wins in priority order. The classification decides what the operator sees, which notification fires, and whether anything automatic will move the task.
| # · State | Trigger | Auto or human | Bounded by |
|---|---|---|---|
0 · RUNTIME_DEGRADATION |
The SDK silently dropped the Agent tool from the review-supervisor session, leaving only Bash. Caught by the supervisor's own Step-0 self-check, which logs [BLOCKED] agent-tool-unavailable and exits FAIL rather than treating an empty reviewer list as "all passed" |
Automatic orchestrator restart (exit code 42) | 24 h detection window, a last-restart marker, and DEVBENCH_MAX_AUTO_RESTARTS (default 3). Then a human takes over |
1 · HELD |
The unit's own status is hold, a deliberate operator pause |
Human at both ends | Nothing automatic exists. devbench unhold releases it |
2 · BLOCKED_ON_HELD |
Carries a [BLOCKED_PENDING_PROPOSAL] marker whose target is on hold. Because hold is non-terminal, the cascade can never fire |
Human must unhold the target | Converts to path 3 the moment the target is released |
3 · AUTO_CLEARING_VIA_PROPOSAL |
The recovery cascade is in flight: markers point at promoted proposal tasks that are not yet terminal. This is the terminus of the executor → amender → blocker-resolver → task-factory chain | Automatic once every marker target is terminal | orchestrate.max_cascade_depth (default 2); becomes a human approval gate when task_factory.auto_accept_proposals: false |
4 · AWAITING_DEPENDENCY |
No marker, but a row in the ## Dependencies table points at a task that is not yet terminal |
Automatic: two independent mechanisms | Event-driven cascade when the dep completes, plus the sync-blocked sweep. No timer, no escalation |
5 · AWAITING_AMENDMENT_RECOVERY |
A recovery artifact is on disk (a pending proposal JSON, a rejected-amendment archive, or a recent [BLOCKED] comment from a recovery agent), proving the recovery loop is mid-flight |
Automatic on the next sweep | 30-minute window (debug.blocked_recovery_window_seconds, default 1800). On expiry it reclassifies to path 6, the one automatic path-to-path transition in the system |
6 · OPERATOR_ACTION_REQUIRED |
The catch-all: retry budget exhausted, security FAIL, git-ops hard failure (rc=1), PR closed without merge, amendment rejected with task-factory disabled, cascade-depth cap reached, or a recovery window that expired | Human, always | No automation whatsoever. It also suppresses the path-0 auto-restart: one operator block and the orchestrator will not self-restart |
The seven states are mirrored consistently across the operator surfaces: seven sub-buckets in devbench status, seven panels in devbench report (each with a resolution hint), and seven of the sixteen notification events map to them 1:1. The report's panel router raises RuntimeError on an unhandled enum member rather than silently defaulting; the codebase forbids silent fallbacks.
Full step-by-step flows for all seven paths (every agent, CLI call, marker, cascade and config knob, with the human checkpoints marked) are drawn in the interactive diagram. Open the architecture diagram →
Configuration surface
One file (backlog/config/devbench.yaml), validated against a draft-07 JSON Schema with additionalProperties: false at the root and on every nested object except report.models, which is an intentionally open map of model id → rate table so operators can price new models without a code change. Any other unknown key is a hard failure at load. 24 top-level keys, 112 leaf keys (109 live plus 3 retired-but-schema-accepted so the runtime can fail fast with a message naming the replacement).
Discovery
--config <path> → DEVBENCH_CONFIG_PATH → <workspace>/backlog/config/devbench.yaml. The flag is stripped out of argv and turned into the env var before the config module is imported, because that module loads YAML at import time.
Precedence
environment variable > YAML value > code default, implemented by typed _resolve_* helpers. Booleans accept 1/0/true/false/yes/no/on/off case-insensitively and raise on anything else; an empty env string counts as unset.
Validation
Missing file, YAML syntax error, non-mapping root, schema violation and cross-field violation each raise with an actionable message. Schema errors include the dotted field path.
Two mandatory environment variables
Both are checked at devbench.config import time and exit 2 with a one-line diagnostic if unset:
- DEVBENCH_WORKSPACE_ROOT
- Absolute path to the workspace root: the parent directory holding
BACKLOG.md,backlog/and the target repo checkouts as siblings. It is not the backlog repo itself. - DEVBENCH_CLAUDE_MODEL
- Model identifier for the SDK caller. It governs the orchestrate skill's coordination calls only; it does not route the work agents, each of which loads its model from plugin frontmatter or an
agents:override.
In total, 75 environment variables are read by the Python source, the two above included (64 DEVBENCH_*, 6 legacy JUDGE_*, 5 third-party: AWS_REGION, GH_TOKEN, NO_COLOR, USER, USERNAME), plus 16 read only by shell: the Makefile, the plugin hooks and the EC2 provisioning targets.
The git_ops mode matrix
Four mutually exclusive modes decide PR granularity and who merges. This is the primary human-checkpoint surface in the whole system.
| Mode | YAML | PR granularity | Who merges | Where the human sits |
|---|---|---|---|---|
| Multi-PR (default) | no mode flags | One branch + one PR per work unit (backlog/<id>) | DevBench, on green CI | Reviews merged PRs after the fact; can gate with pr_review_resolution |
| Pause-before-merge | pause_before_merge: true | One PR per work unit | Human | PR is pushed, CI watched to green, unit moves to in-review and stops. check-merge reconciles later |
| Single-branch + defer-PR | single_branch: <name> + defer_pr: true | One batch PR per repo | Human by default | Nothing is pushed until git-ops-finalize. Opt in to auto_finalize (opens the PR) and then auto_merge (merges it) to hand each step over |
| Local-only | single_branch: <name> + defer_pr: true + local_only: true | No PR at all | n/a | No remote, no push, no CI: for operational workflows such as teardowns, audits and evidence capture |
Eight named mutual-exclusion errors, enforced at config load
| Rule | Why |
|---|---|
defer_pr requires single_branch | There is no deferred branch to finalize otherwise |
pause_before_merge cannot combine with defer_pr | "defer_pr defers PR creation; pause_before_merge pauses after PR creation. They are mutually exclusive." |
pause_before_merge cannot combine with single_branch | "there is no per-unit branch to create a PR from" |
local_only requires defer_pr | "Local-only repos have no remote to push to" |
local_only requires an explicit default_branch on every repo | "There is no origin to fall back to in local-only mode" |
auto_finalize requires defer_pr | "without defer_pr there is no deferred branch to finalize" |
auto_finalize cannot combine with local_only | "Local-only repos have no remote to push to" |
auto_merge requires auto_finalize | "without auto_finalize there is no PR to merge" |
The human-gate knobs
These are the settings that decide how much rope the system gets. Defaults are the code defaults from config_loader.py. They are worth stating precisely, because two of them are documented backwards in the repo (see section 10).
| Knob | Default | What turning it changes |
|---|---|---|
git_ops.pause_before_merge | false | Every PR waits for a human merge; the unit parks at in-review and the loop moves on |
git_ops.auto_merge | false | Off means a human merges by default in batch mode. On requires auto_finalize + defer_pr |
git_ops.pr_review_resolution.enabled | false | Turns on PR review-comment polling; the whole phase is a no-op while false |
git_ops.pr_review_resolution.decision_blocks | true | A human CHANGES_REQUESTED review hard-blocks the merge regardless of the bot allowlist; this is the human-reviewer merge gate |
git_ops.pr_review_resolution.agents | [] | GitHub logins (e.g. review bots) whose unresolved comments block the merge; polled for settle_seconds (60) at poll_interval (5) |
git_ops.ci_failure_retry | true | CI failure returns rc=2 and the executor retries with the trimmed failing-job log as feedback. false blocks on first failure |
backlog.default_status_for_new_work_units | in-queue | Set to draft to make every new work unit invisible to the orchestrator until a human promotes it |
backlog.bulk_update_confirm_threshold | 10 | Above this many units, set-status with selectors prompts for confirmation (0 = always prompt; --yes skips; --dry-run previews) |
task_factory.enabled | false | The whole blocker-resolver → task-factory recovery chain is opt-in and off by default. Requires manifest_amendment.enabled: true |
task_factory.auto_accept_proposals | true | True means generated drafts are auto-promoted at sweep time: no human approval gate. Set false to make every proposal wait for promote-proposal / reject-proposal |
manifest_amendment.enabled | true | On by default. When off, an executor that needs an undeclared file must escalate to a human instead of amending |
manifest_amendment.max_requests_per_execution | 1 | Caps amendments per executor run, preventing amendment loops |
orchestrate.max_cascade_depth | 2 | Cap on recovery-of-a-recovery depth; exceeding it escalates the source task to the operator instead of materialising another layer |
stop_hook.max_blocks / window_seconds | 5 / 180 | How long the Stop hook fights to keep the loop alive before conceding and letting the session end |
validate.check_orphan_path_tokens | true | Validator rule 20: backtick-quoted path tokens in ACs/DoD that are absent from the Changes Manifest become errors. Rules 1–19 always run |
DEVBENCH_SAFE_PERMISSIONS=1 | unset | The coarsest gate of all: launches the interactive session without --dangerously-skip-permissions, so a human approves every tool call |
Scope: authorizing a slice of the backlog
Scope is a printer-pages selector persisted to scope.json. E2 matches epic E2 and every descendant; E1-E3 expands inclusively across epics; E5-F1-S1-T2-T5 expands across tasks; comma-separated tokens are unioned. Reverse ranges and malformed tokens raise immediately; out-of-range tokens warn without aborting. Written either by devbench start --include … or by devbench scope set; the skill honours the file, not the command that wrote it. Before registering a session, start refuses a scope that overlaps an active session unless --allow-overlap is passed.
Per-agent model overrides
Every field in the agents: block defaults to null, meaning "use the plugin frontmatter". Setting one materialises a workspace-local shadow plugin tree and rewrites that agent's model: line. Values containing haiku (in any form, short name, Anthropic ID or Bedrock ARN) raise at config load with no override path.
The use_bedrock / bedrock_region keys are described in the schema as routing LLM calls via AWS Bedrock. Research found their only consumers in src/devbench to be the model-string format validators: no Bedrock client construction, and no reference to SDK routing variables. Treat these keys as selecting which model-ID format the agents: block accepts; do not claim DevBench routes traffic through Bedrock without further verification.
CLI: 56 commands
A single registry (_COMMANDS: dict[str, tuple[Callable, int, str]]) maps each name to its handler, minimum positional arity and description. 28 of the 56 are "variadic": they own their flag parsing and receive the raw trailing argv; the rest get a fixed-arity slice and warn on extras. Across all of them there are 52 distinct DevBench long flags.
The CLI is deliberately the only way agents touch state. Agents cannot open files or run git freely; they call devbench, and the guards police those calls.
Orchestrator lifecycle & sessions (10)
Start, detach, observe and stop runs. drain is the graceful path: it asks the loop to finish the current unit and exit rather than killing it mid-task.
startstopstop-instancerestartinstancessessionstaildrainwatchdogscope
Backlog state & lifecycle (14)
Everything that reads or writes work-unit status. next and claim are the loop's entry points; hold, unhold, decline and set-status are the operator's.
statusnextclaimset-statusmark-donepromotedeclineholdunholdnew-taskvalidate-backlogsync-blockedreconcile-cascadeadd-dep
Agent bridge: evidence & audit (8)
The read side that gets injected into agent prompts, and the write side that produces the audit trail. Three of these are guarded for format and content.
read-unitget-diffrun-testsloglog-commentlog-verdictlog-tddlog-rejection-feedback
Amendments & proposals (9)
The mid-execution scope-change workflow and the recovery chain that turns a legitimate rejection into new, reviewable work units.
request-amendmentapply-amendmentreject-amendmentwrite-proposalmaterialise-proposalsweep-proposalslist-proposalspromote-proposalreject-proposal
GitOps (6)
check is the pre-flight (symlinks, origin remotes, default_branch parity, no conflicting open PRs). git-ops is the whole commit → PR → CI → merge path behind one exit-code contract.
checkensure-branchgit-opsgit-ops-finalizecheck-mergecleanup-tracked-orphans
Observability & cost (8)
Three live surfaces plus the cache-maintenance and cost-calibration commands. See section 9.
reportwatchhook-tailwrite-snapshotrebuild-window-statsarchive-sessioncost-calibratenotify-test
Plus one runtime-support command: prepare-plugin-shadow, which materialises the per-agent model-override shadow plugin and prints its path. 10 + 14 + 8 + 9 + 6 + 8 + 1 = 56.
The git-ops exit-code contract
One command, four outcomes, and the orchestrate skill branches on the number, never on prose.
| rc | Meaning | Orchestrator action |
|---|---|---|
| 0 | PR merged, or the commit landed locally in deferred mode | Continue to mark-done |
| 1 | Hard failure requiring operator attention | Block the task, log [BLOCKED] with the exact failure surface, return to step 2 |
| 2 | CI failed, retry budget not exhausted | Work unit gains [CI_FAIL] naming .devbench/ci-failures/<id>-<n>.log; re-invoke the executor with a CI-fail payload, then re-run git-ops |
| 3 | PR has unresolved review feedback | Work unit gains [PR_BOT_FAIL] naming .devbench/pr-bot-feedback/<id>-<n>.json; re-invoke the executor with a PR-bot payload |
Deterministic merge rails
Two assertions apply to every commit path and involve no LLM: assert_on_branch rejects orphan-branch commits, and assert_staged_matches_manifest rejects any staged path that is not declared in the Changes Manifest. On top of those, a merge can be stopped by the orphan-pattern gate, by CI (check-registration retry exhaustion refuses the merge, an explicit no-fallback decision), by unresolved PR review feedback, by pause_before_merge, or by batch mode leaving the PR open for a human.
Observability
Three read-only surfaces, each answering a different question, plus an append-only audit trail that lives in the work-unit files themselves.
| Command | Question it answers | Shape |
|---|---|---|
devbench report | "How far along, how fast, how much has it cost?" | Windowed metric tables; streams by default on a TTY |
devbench watch | "What is the orchestrator doing right now?" | One-screen snapshot across 8 panels |
devbench hook-tail | "What happened as it happened?" | Append-only colorized event stream |
report
Renders 19 metric rows per window across two windows by default: all-time, and the current run (bounded by a session gap). Four rows are window-agnostic and span the table: recent pace, estimated time to complete, estimated completion date, and estimated total cost at completion. The first line is always a liveness banner ([ORCHESTRATOR ALIVE] / [STOPPED] / [STARTING]), derived from log recency against stop_hook.window_seconds.
Beyond the metrics: in-progress rows carry a humanised attempt duration, plus a Declined panel, a pending-proposals panel, and seven blocked panels, one per BlockedTaskState, each with a resolution hint written for the operator:
| Panel | Hint shown to the operator |
|---|---|
| auto-clearing via proposal | "Resolves when marker targets reach terminal; no action." |
| awaiting amendment recovery | "Recovery agent in flight; orchestrator's next sweep advances these." |
| awaiting dependency | "Resolves when the dependency completes; no action." |
| held | "On hold by operator; unhold to release." |
| blocked-on-held | "Waiting on a held unit; unhold the target or redirect this task." |
| runtime-degradation | "SDK lost Agent-tool access mid-session; task remains blocked until the orchestrator restarts (auto on NO_ACTIONABLE exit; otherwise manual make start)." |
| operator action required | "No automation path; operator must inspect and resolve manually." |
hook-tail: the JSON firehose, made readable
hook-tail pretty-tails $DEVBENCH_WORKSPACE_ROOT/hook-logs.jsonl, the raw stream every hook registration writes. Columns: timestamp (HH:MM:SS), a two-character event glyph, agent (12 chars), tool (8), description (120), and a stdout preview (80, PostToolUse rows only). All four widths are operator-tunable via hook_tail.*. Timezone resolves --tz > display_timezone > OS local; the log itself always stores UTC. Rotation is detected by inode change. There are deliberately no filter flags: pipe to grep or jq.
-> PreToolUse
<- PostToolUse
!! PostToolUseFailure
U> UserPromptSubmit
|| Stop
+s SubagentStart
-s SubagentStop
Cp PreCompact
P? PermissionRequest
No Notification
Because the catch-all PreToolUse/PostToolUse matchers are .*, every tool call by every agent is recorded, plus SubagentStart/SubagentStop at each agent boundary and PreCompact before every context compaction. Each record carries an orchestrator_session field so hook-tail can scope its output to the orchestrator's own session: a mid-run investigation in a side pane no longer pollutes the audit stream.
Notifications: 16 events, all off by default
Slack is the only transport. A POST requires three independent switches to all be true: notifications.enabled, notifications.slack.enabled, and the specific notifications.events.<name> toggle. All default to false, and the entire block is commented out in the sample config. Webhook URLs are validated at config load (string or null, https:// only) and are expected to arrive via DEVBENCH_NOTIFICATIONS_SLACK_WEBHOOK_URL, never in tracked YAML.
Seven blocked events, mapped 1:1
work_unit_blocked_operator, _runtime_degradation, _held, _on_held, _auto_clearing, _awaiting_dependency, _amendment_recovery (one per BlockedTaskState, via _EVENT_BY_CLASSIFICATION).
Nine lifecycle events
work_unit_done, work_unit_materialised, work_unit_promoted, pr_opened, pr_merged, ci_failure, ci_pass, orchestrator_stop, orchestrator_auto_restart.
Firing is transition-gated, not level-gated: a ping fires when the classification actually changes (initial entry counts), and repeated observations are no-ops. The state cache at .devbench/notification-state.json is written on every call regardless of the toggle, so enabling a toggle later does not retro-fire a backlog of alerts. Notifications are dispatched only from write sites, never from the renderers, so re-rendering a report cannot spam a channel. devbench notify-test --event <name> smoke-tests one event.
What is written where
| Artifact | Content | Authoritative? |
|---|---|---|
backlog/**/*.md | Status, dependencies, Changes Manifest, acceptance criteria, and the append-only ## Comments / ## TDD Cycle Log audit trail: every judge verdict, every agent comment, every status marker, every TDD phase | Yes (source of truth) |
BACKLOG.md | The index: ID, Title, Type, Status, Dependencies, Repo, File Path, plus a Status Summary table | Derived index; drift from the files is a validation error |
hook-logs.jsonl | One JSON line per Claude Code hook event, with the full payload and the orchestrator session id | Append-only audit stream |
logs/orchestrator.log | The Python-side structured log. The Set <id> to '<status>' line is what the reporter and the Stop hook grep for | Append-only |
.devbench/stop-hook-diag/*.json | One forensic file per Stop-hook block: counters, detected last action, injected next step, exact emitted stdout | Forensic |
.devbench/report-cache/events.sqlite | mtime + size + offset-keyed incremental parse cache and indexed event store (ADR-16, ADR-19) | No (derived, rebuildable) |
.devbench/window-stats/<task>.json | Per-task aggregates written on every status transition, so the reader is O(task count) rather than O(log size) (ADR-17) | No (rebuild with rebuild-window-stats) |
.devbench/report-snapshot.json | Pre-rendered report written after each mark-done (ADR-20); deleting it is always safe | No (regenerated next loop) |
logs/legacy/<session>.parquet | Optional columnar cold archive of ended session logs (ADR-21, opt-in extra) | No |
SQLite is a cache, not a database of record. It ships with CPython, is not a declared dependency, and exists so report does not re-parse the whole orchestrator log on every run. There is no message broker, no database server, no external issue tracker and no queue technology in the data path: the queue is markdown files and an index.
Docs gaps this site closes
This page was generated by reading the repository (source files, plugin prompts, JSON schema, shell guards and the CLI registry) rather than by summarizing the repo's own documentation. That turned up a set of places where the docs lag the code. Every row below was verified against both sides. None of them are defects in the running system; all of them are reasons to trust the code over a prose page.
| What a doc says | What the code does | Why it matters |
|---|---|---|
docs/plugin-architecture.md:115-119 has a "Model Per Role" table: executor on opus, four review judges on haiku, security-reviewer on sonnet |
Frontmatter says executor and review-supervisor on sonnet, the other eight agents on opus. Every row of the doc table is wrong | It also recommends haiku, which config load rejects outright for every per-agent field (issue #198). Following the doc produces a config that will not load |
docs/block-types.md:5-16 says "exactly one of six mutually exclusive states" |
The BlockedTaskState enum has seven members. RUNTIME_DEGRADATION is absent from the entire document |
An operator reading the doc has no name for the SDK-degradation case, the one blocked state that self-heals via restart. The doc-consistency test only checks a regex snippet, so the staleness is untested |
docs/architecture.md:91 and ADR-11, on task_factory.auto_accept_proposals: "default is false so the 'human reviews every proposal' posture is preserved" |
In config_loader.py:419, the default is True. The flip is recorded in the CHANGELOG as a breaking change |
The single most consequential doc error found. It tells operators the human-review gate is on when it is off. (The chain is still opt-in overall, because task_factory.enabled defaults to false) |
In docs/architecture.md:61, manifest_amendment is "opt-in via enabled: true" |
In config_loader.py:439, enabled: bool = True. docs/manifest-amendments.md states it correctly |
Two live docs give opposite answers about whether an executor may amend its own scope |
docs/architecture.md:251,411 and docs/backlog-contract.md:471 say the done-gate checks "only" the four review judges |
manager.py:1097 returns passed >= ALL_REQUIRED_JUDGE_NAMES, which is the four review judges plus security_review, making five. Pinned by an explicit TestSecurityGate (tests/test_backlog/test_manager.py:525) |
The README's own "Five judges must pass before a work unit merges" is the correct statement |
docs/block-types.md uses devbench list-blocked (11 times) and devbench show (13 times); SKILL.md:91 calls devbench config-resolve |
None of the three commands exist. They are absent from the _COMMANDS registry and no handler exists in src/. The real equivalents are status, report and read-unit |
An operator following the block-types runbook hits "unknown command" on the first step. This page lists only commands verified present in the registry |
Seventeen sites (the Makefile, the README, ten docs and one shipped example) invoke --plugin-dir plugin/devbench, and the Makefile installs the plugin name devbench |
plugin/devbench does not exist. The constant is DEFAULT_PLUGIN_SUBPATH = "plugin/devbench-orchestrate", and the installable names are devbench-orchestrate@devbench and devbench-authoring@devbench-authoring |
The README gives the correct install command 220 lines after documenting the Makefile target that uses the retired one |
Both plugin manifests declare "license": "MIT" |
The project is Apache-2.0: LICENSE, NOTICE, pyproject.toml and the README all agree, and the relicense commit is HEAD |
Those two strings are the only "MIT" in the repository; the relicense missed them. This site states Apache-2.0 |
docs/architecture.md:544-559 lists four scripts on the PreToolUse/Bash chain and puts hook-logger.sh on Stop |
hooks.json registers seven scripts on that chain (adding the comment-format, destructive-git and review-supervisor-scope guards) and registers only continue-orchestration.sh on Stop |
Three of the strongest guards in the system are invisible in the architecture doc's hook table |
docs/plugin-architecture.md:49 documents guard-backlog.sh as an active guard blocking writes to backlog tracking files |
The script exists and is executable but appears nowhere in hooks.json. Exactly 10 of the 12 script files are wired |
Either dead code or an accidental de-registration; nothing in the CHANGELOG, ADRs or history explains it. Flagged for the maintainers, not characterised here. The registered guard-work-unit-write.sh covers the same surface more narrowly |
Four doc sites still instruct operators to set DEVBENCH_DISABLE_INLINE_ORPHAN_CLEANUP=1 |
The variable was renamed to DEVBENCH_INLINE_ORPHAN_CLEANUP (truthy/falsy); the asymmetric "DISABLE" form was removed and is no longer read |
Setting the documented variable silently does nothing |
docs/cli-reference.md is presented as the full per-command reference |
It documents 50 of 56 commands. Missing: reconcile-cascade, instances, stop-instance, tail, restart, notify-test, five of which are user-facing lifecycle commands |
Section 8 of this page lists all 56, grouped |
The four docs/skills/ quickstarts use the pre-0.4.0 prefix (devbench:create-spec) and name a hardcoded exemplar path on a specific machine as "the canonical quality reference" |
The correct prefixes are devbench-authoring: and devbench-orchestrate:; the skills are application-agnostic and say "Do NOT default to any hardcoded path" |
No test asserts the post-0.4.0 prefix: the quickstart doc test only requires any one of four invocation patterns (test_skill_doc_has_invocation_example is a four-way or), so the stale form is accepted and CI will not catch it. This page uses the migration guide's forms |
| README's example-backlog row: "207 work units across 13 epics + 14 features" | File enumeration: 13 epics, 37 features, 37 stories, 120 tasks = 207 | The 207 and the 13 are right; the feature count is off by 23 |
docs/execution-modes.md:139,165 says "a blocker-resolver agent file exists in the plugin but the orchestrate skill does not currently invoke it" |
SKILL.md:77-81 invokes devbench-orchestrate:blocker-resolver explicitly at step 4c, and docs/architecture.md:619 lists that invocation as resolved |
One doc describes a system without automated block recovery; the skill implements it |
In spec-to-backlog/SKILL.md:16, every generated task file opens ## Status: draft; configure-devbench/SKILL.md:326-328 says the default is in-queue |
Both are true for their own path. The RuntimeConfig default is in-queue, and the shipped new-task template also opens in-queue, but the skill reads the raw YAML itself and writes draft when the key is absent |
The two paths can disagree for the same workspace, so there is no single global default to quote. This page states the config default in section 7 and the skill behavior in section 5, separately |
"devbench next returns the earliest in-queue item"; docs/architecture.md:623 lists "no topological sort for parallel candidates" as a remaining gap |
Candidates are sorted by (status_priority, topological_depth, id): in-progress work resumes first, then depth order (issue #121) |
The scheduling model is materially different from the folk description, and the "gaps" list is stale |
Two things this site deliberately does not claim
Which identifier form Claude Code passes as subagent_type. The review-team agents' frontmatter names are hyphenated (code-reviewer), while guard-review-supervisor-scope.sh allowlists the underscored, plugin-qualified forms (devbench-orchestrate:code_review) and the unit test exercises only the underscored form. Research could not determine the runtime string from static files. The guard's allowlist is described here as written in the script, not as verified-working.
The BACKLOG.md index column set. The parser and the backlog contract require | ID | Title | Type | Status | Dependencies | Repo | File Path |, declared "required exactly". The spec-to-backlog skill's step-6 sample shows a different seven-column layout with no Type column, which parse_index would reject. It is plausible the deterministic index-regeneration pass writes the correct format and the sample is drift; that was not verified. It is the highest-risk internal contradiction found, and it is flagged rather than resolved.
How to read all of this. Every number, name and default on this page came from enumerating the repository at commit c42fc64: file counts, JSON Schema walks, the _COMMANDS registry, agent frontmatter, hooks.json, guard pattern arrays and the constants module. Nothing was estimated. Where the repository disagrees with itself, both sides are shown above and the code is treated as authoritative.
Nothing on this page was produced by running the orchestrator. Behavioral statements describe what the code path does on a matching input, not an observed production run.