Skip to content

ADR 0002: Approval-gated tasks never auto-promote to dispatch

Status

Accepted

Context

Not every task on the board can be graded by a deterministic checker. Some carry verify_path = 'approval' because the acceptance criterion is a human judgment call (irreversible action, aesthetic call, a decision only Aaron can make) rather than something CI can assert. internal/hub/task_lint.go already names the two closing paths explicitly: a leaf needs either a pr evidence row or an approval evidence row before it can move to done, or it "will silently stall -- it can never reach verified" (task_lint.go:3-4).

The overnight build runner (ARGUS-228) polls a next-task endpoint and builds whatever it returns, unattended, with no human in the loop mid-run (overnight-build skill: "you are HEADLESS: there is no human to grant anything mid-run"). If an approval-gated task were ever eligible for that endpoint, an agent would either fabricate the human decision to unblock itself or stall the run waiting on an answer that structurally cannot arrive -- both outcomes observed and logged as failures (ARGUS-602: runs that ask for permission mid-run and wait are recorded failed/no_progress).

Decision

The task-selection query the hub-agent next-task endpoint runs excludes verify_path = 'approval' tasks unconditionally, alongside backlog-lifecycle and blocked tasks (internal/hub/next_task_test.go:16,61-70 pins this: "human-only (verify=approval) ... tasks are excluded"). Approval-gated work is never returned to an autonomous dispatch loop, full stop -- there is no override, no "only if idle," no confidence threshold. The exclusion is structural (a query predicate), not a judgment call the agent makes at pick time, so it cannot be reasoned around by an agent convinced this particular approval is obviously fine.

Consequences

  • A human must move approval-gated tasks themselves (or record the approval evidence row) before they can close; they simply never appear as overnight-build candidates.
  • The overnight loop can run genuinely unattended without a "wait for permission" failure mode, because the class of task that would trigger it is filtered out before an agent ever sees it.
  • Mis-tagging a task's verify_path as pr when it actually needs a human call is now the only way this gate can be bypassed -- that risk is owned by whoever files the task, not by the dispatch loop.