A pull request opens on GitHub. The built-in Github.PullRequestOpened event fires, and an agent reviews the diff and posts inline comments on the PR. No sensor code, no event declaration.
1 step in workflows/review/. The trigger is the built-in event, named directly in on:.
---
on: Github.PullRequestOpened
agent: Reviewer
output: { verdict: str, comments_posted: int }
---
A checkout of {{ event.repo }} is already in your workspace, with a
GitHub token wired into git and the `gh` CLI.
Review PR #{{ event.number }} ("{{ event.title }}"), which merges
{{ event.branch }} into {{ event.base }}:
1. Fetch the PR branch and diff it against {{ event.base }}.
2. Flag correctness bugs, security issues, and clear simplifications.
Keep to high-confidence findings; don't nitpick style.
3. Post each finding as an inline review comment on {{ event.url }}
via `gh`. If the diff is clean, leave one approving summary comment.
Return your verdict and the number of comments you posted.No sensor and no events: entry. Github.PullRequestOpened is a built-in event: the step names it in on:, and the compiler registers the event contract and synthesizes a sensor on /hooks/github for you. GitHub posts every delivery to that one URL; the runner verifies the X-Hub-Signature-256 signature once at the edge, fans the delivery out to the built-in sensors, and only the matching one emits (a PR opened, not closed).
The full catalog: Github.PullRequestOpened, Github.PullRequestMerged, Github.IssueOpened, Github.IssueCommentCreated, and Github.Push. Trigger on any of them the same way.
loopy auth github configures a GitHub App. At runtime the backend mints a short-lived, repo-scoped installation token per step and injects it into the sandbox, so the agent's git and gh calls authenticate without a static PAT ever crossing the boundary.
There are no events: to declare. The inbound Github.PullRequestOpened contract is registered automatically when a workflow triggers on it, and this workflow returns its output without emitting an event of its own. The registry only names the agent and its sandbox.
defaults:
agent: { sandbox: BaseSandbox, model: claude-sonnet-4-6, harness: claude-code }
sandboxes:
BaseSandbox:
provider: daytona
image: { debian_slim: "3.12", apt: [git, gh], workdir: /home/loopy, user: loopy }
repos: [octocat/Hello-World] # cloned into the workspace, auth injected
agents:
Reviewer: {} # reviews an opened PR; inherits the defaults above