The problem
Human review attention is the scarcest resource in most teams. The failure mode of naive automation is noise: a fresh bot review per push, restated diffs, unranked nitpicks, and no way to tell whether the latest verdict applies to the latest head. This example is built around three constraints:- One current verdict per PR. A new push supersedes the in-flight review rather than stacking a second one.
- Read-only by construction. The mount grants
contents: read, and the GitHub tool surface is narrowed to reading PRs and posting one comment — no approve, no merge, no pushes, regardless of what the prompt says. - A check, not just a comment. The trigger declares a managed check (
pr-review) that the platform creates asqueuedthe moment the session spawns and that concludesfailureon its own if the agent never reports — so a dead reviewer can’t leave a PR permanently blocked in limbo.
How it works
Three triggers drive the agent:
The
bind + onUnmatched: spawn combination is what makes the session own the PR: when no session is bound to the pull request yet, the router spawns one and binds it to the PR in the same creation step, so every later matching event resolves to it. See triggers and events for the routing model.
Install from the consolidated agent package
The packaged PR Review role is published in@auto/agents. The thin install is one file — the import carries the prompts, triggers, tools, runtime, and identity:
.auto/agents/pr-review.yaml
repoFullName and githubConnection to your repository and GitHub connection. When GitHub Sync applies this file from the bound repository, both variables also have context defaults derived from the Sync binding, and values you declare win. Override any inherited field by declaring it in this file (triggers merge by their name: key), and drop inherited entries with remove: { triggers: [...], tools: [...] }. See managed templates and imports and fragments.
Merge the file to your production branch and GitHub Sync applies it.
The full configuration
The directory below is the handwritten equivalent of the template, pinned to a concrete repository so every name lines up. Copy it if you want to own the whole definition instead of tracking the template..auto/fragments/environments/agent-runtime.yaml
.auto/agents/pr-review.yaml
- The mount is the PR head itself.
ref: refs/pull/{{payload.github.pullRequest.number}}/headwithdepth: 1checks out exactly what is under review. Mountrefis the one template surface where thepayload.prefix is correct — see mounts. contents: readmakes the reviewer physically unable to push, whilepullRequests: writeandissues: writeallow the review comment. GitHub credentials are minted per call by the platform and never enter the sandbox — see the GitHub tool.- The
githubtool is narrowed to two tools (pull_request_read,add_issue_comment). Naming tools replaces the curated default allowlist entirely. - Slack is optional wiring. Both the chat tool and the mention trigger carry
optional: true: apply skips them while noslackconnection exists and activates them on the next apply once one does. The GitHub review flow works either way. $.github.auto.authored: falseand$.github.auto.externalBot: falsekeep the conversation trigger from reacting to the agent’s own comments or to third-party bots.
Walkthrough
1
A pull request opens
GitHub delivers
pull_request with action opened; ingress verifies the webhook signature, normalizes it to github.pull_request.opened, and records it once (duplicate deliveries never re-route). The pr-events trigger matches its where filter on the repository.2
Spawn, bound to the PR
The
bind route looks for a session bound to this github.pull_request target. There is none, so onUnmatched: spawn creates one — and binds it to the PR in the same creation step. From now on, every matching event for this PR resolves to this session.3
The check appears immediately
Because the trigger declares
checks:, the platform creates the Auto PR review check run on the head SHA in queued state as part of routing — before the sandbox even boots — and arms the beginTimeout watchdog. If the agent never calls checks.begin within 1200 seconds, the check concludes failure on its own.4
The review runs
The sandbox boots the
node24 environment with the PR head mounted at /workspace/repo. The agent calls checks.begin (check goes in_progress), reads the convention docs and the diff, optionally runs targeted tests, posts exactly one review comment with add_issue_comment, and concludes with checks.success or checks.failure carrying the reviewed SHA and gating findings. If Slack is connected, it threads a one-line verdict under the PR’s message in #pr-review.5
A push folds into the same session
The author pushes; GitHub sends
synchronize. The bind route now resolves the owning session, so instead of spawning a second reviewer, the trigger message is delivered into it — interrupting the current turn if one is running. On the platform side the delivery supersedes the old check cycle: the old head’s check run is concluded (as skipped when it never finished) and a fresh queued run is created on the new head. The agent re-begins the check and re-reviews the current head. One PR, one session, one current verdict.6
Conversation routes back; nothing leaks after archive
Reviewer comments and review threads deliver into the same session through
pr-conversation. Its onUnmatched: drop means that once the session is gone, stray conversation events are dropped rather than spawning a reviewer with no review context — while a fresh push (via pr-events, onUnmatched: spawn) starts a new owning session.displayName is what GitHub shows.
Variations
- Cheaper reviews. Swap
model.idtoclaude-sonnet-5(any id from the curated Anthropic list works with theclaude-codeharness). Keep Opus for repositories where a missed P0 is expensive. - Several repositories. Install one thin template file per repository with a distinct agent
name(for examplepr-review-widgets,pr-review-api) and per-filevariables. Two agents cannot share a name. - Tune the posture, keep the machinery. Override
systemPromptin your importing file to encode your team’s real review bar — the trigger/check/binding machinery is inherited unchanged. Use{ append: ... }onsystemPromptto add house rules below the template’s text instead of replacing it. - CI watchdog. If required GitHub Actions workflows guard the repo, configure GitHub Sync’s
ciWatchdog(via theauto.sync.enabletool or sync settings) for workflows that supportworkflow_dispatch. This is deliberately separate from the review trigger:github.check_run.completedtriggers are passive routing and may reference checks auto cannot safely re-dispatch. - React to CI results. Add a trigger on
github.check_run.completedwithrouting: { kind: bind, target: github.pull_request }and awherefilter like$.github.checkRun.conclusion: failureto have the reviewer weigh red CI into its verdict. Check-completion deliveries defer until the session is idle instead of interrupting mid-review. See GitHub events.