ADR-025: An agent task lane — proposing, on demand, never merging¶
Status: Accepted Date: 2026-08-11 Deciders: San Lee
Context¶
ADR-016 gave this repo an agent that can review a
PR — advisory, comment-only, on-demand. There was no lane where an agent can propose one:
implement a scoped task and put the result up as a PR for the same human + deterministic-gate
review every other PR gets. The pieces exist elsewhere: the review lane already proved the
action, the auth, the model pin and the cost knobs; and agent-ops
conventions/agent-in-ci.md
defines the assembly rule any agentic pipeline step must satisfy — (1) sandboxed, scoped
credentials; (2) proposal-only output, never a push to a protected branch; (3) a
deterministic verifier after the agent; (4) the redline guards travel with the job.
The question this ADR settles is the same one ADR-016 settled for review: what authority does the proposing agent get, and how is each of the four assembly rules made pointable in the YAML rather than intended.
Decision¶
Add .github/workflows/agent-task.yml: a workflow_dispatch-only lane where the agent
implements the dispatched task input, pushes a claude/agent-task-* branch, and opens a
draft PR. It never merges and cannot push main.
| Choice | Value | Why |
|---|---|---|
| Trigger | workflow_dispatch only, required task input |
The owner launches deliberately; the lane costs nothing idle — the same economics ADR-016 Amendment 1 arrived at, adopted here from day one. No comment phrase, no schedule, no issue trigger. |
| Actor guard | github.actor == 'sanlee-ys' at the job if |
Dispatch is already owner-gated by repo permissions, but a check that could not run is not a pass (agent-trigger-authorization.md); the assertion keeps holding if a collaborator or dispatching integration ever appears. |
| Permissions | contents: write, pull-requests: write, id-token: write, nothing else |
rule 1. contents: write pushes the proposal branch; branch protection on main is what makes it proposal-only — the token can push claude/agent-task-*, it cannot push main. pull-requests: write opens the draft. id-token: write is the action's OIDC exchange, same as the review lane. |
| Output shape | Draft PR from claude/agent-task-<run_id> (or -<branch_suffix>) |
rule 2. The PR body carries the task verbatim, the gate output, and an explicit "Not done" section — a proposal a human and the gates judge. |
| Verifier | Not in this workflow. tests.yml + evals.yml + CodeQL run on the proposal PR like any PR |
rule 3, satisfied one level up. Agent green is not build green; the PR's own checks are the gate. |
| Guards | Fetched from agent-ops canon (security/credential-guard.py, hooks/git-staging-guard.py, hooks/published-history-guard.py) into $HOME/.claude/hooks/ and wired as PreToolUse hooks before the action step; a fetch failure fails the job |
rule 4. Running unguarded is a posture change relative to every provisioned machine, approved by nobody — so there is no "run anyway" fallback. |
| Model / cost | claude-sonnet-5 pinned (SYS-002), --max-turns 50 |
Implementation needs more turns than review (four gates, push, PR), still bounded. |
| Auth | anthropic_api_key: secrets.ANTHROPIC_API_KEY |
Mirrors claude-review.yml; one key, one revocation point. |
pull_request_target |
Not used, prohibition restated in the header | Moot for dispatch, retained so a future auto-triggered variant does not reintroduce it. |
Consequences¶
- A green run means "a draft PR may exist," nothing more. An agentic step exits 0 whether the work landed, was denied, or was never attempted (the review lane measured exactly this). The check to trust is the PR and its checks, not the workflow tick.
contents: writeenters the repo's workflow set for the first time, and its safety is delegated to branch protection onmain. If that protection is ever loosened, this scope silently becomes push-to-main and this lane must be re-decided — recorded in the workflow header so the coupling is visible where it bites.- Guard fetches pin
main, not a SHA. The guards are maintained canon and provisioned machines deploy frommain; a SHA pin would be the stale-copy drift agent-opssecurity/posture.mdlimit 6 records. The trade is that an agent-opsmainoutage or a moved file fails this job — loudly, which is the designed failure. - The prompt is a maintained surface (gate commands, branch naming, CLAUDE.md references), same as the review lane's.
- Spend is per-dispatch and human-initiated, so it scales with intent rather than events. No idle cost.
Downstream surfaces¶
| Surface | State |
|---|---|
.github/workflows/agent-task.yml |
New. The lane itself. Like the review lane, the action refuses to run when the workflow file differs from main's copy — changes here get human review and post-merge dispatch verification. |
ANTHROPIC_API_KEY repo secret |
Reused (set 2026-07-11 for evals.yml; also used by ADR-016's lane). Not re-provisioned. |
Branch protection on main |
Load-bearing for this ADR. It is the mechanism that makes contents: write proposal-only. |
tests.yml, evals.yml, CodeQL |
Unchanged; they are this lane's deterministic verifier by running on the proposal PR. |
.github/workflows/claude-review.yml / ADR-016 |
Unchanged. Separate lane, separate (smaller) permission envelope; the review lane keeps contents: read. |
agent-ops conventions/agent-in-ci.md |
The assembly rule this lane implements; the four pointable lines are the permissions block, the branch/PR instructions in the prompt, the PR's own CI, and the guard-install step. |
README.md CI section |
Not updated — this lane changes nothing about what CI enforces. |
Alternatives Considered¶
| Option | Reason Not Chosen |
|---|---|
| Auto-trigger on labeled issues or issue comments | Recurring cost the review lane already walked back (ADR-016 Amendment 1), and a wider trigger-authorization surface: issue text is external input reaching a capable agent, and every check in agent-trigger-authorization.md would need re-deriving for it. Dispatch keeps the trigger a deliberate human act. |
| A deterministic verifier step inside this workflow | Duplicates what tests.yml/evals.yml/CodeQL already run on the proposal PR — a second copy that drifts from the first. The PR is the artifact; the PR's checks are the gate. |
| Extend the review lane's workflow instead of a new one | Different permission envelope. The review lane is comment-triggered and must stay contents: read; folding proposing into it would leak contents: write into a lane a comment event fires — precisely the widening ADR-016's "no contents: write" note exists to prevent. |
| Let the agent merge its own PR when gates pass | The verifier, not the agent, holds the merge (agent-in-ci.md rule 2 / delegation policy). Gates being green does not make the change wanted. |
| No lane — implement tasks only in interactive sessions | The status quo, and still the default for most work. The lane exists for scoped, well-specified tasks where the review-by-PR shape is sufficient oversight and a session is not otherwise open. |