Skip to main content
A verifier agent answers one question about every pull request: does the change actually do what it claims? It answers by running the software — bringing up your stack, driving the changed behavior with real inputs, and observing what happens — then posts exactly one verdict as a PR comment, backed by reproducible evidence. When new commits land, the same agent abandons the stale verification, re-verifies the new head, and updates its comment in place. When the PR closes, it is torn down. By the end of this guide you have:
  • A verifier agent persona in your tenant catalog that encodes the verification discipline.
  • A GitHub Actions workflow that runs one persistent verifier per PR — spawned on open, re-driven on every push, killed on close — using the composite actions at prassoai/murmur-actions.

Why a verifier, not another test job

A test suite asserts intent; a verifier confirms reality. The persona’s core rules:
  • Exercise, don’t inspect. Reading the diff and concluding “looks right” is not verification. The verifier brings up the real system and drives the actual behavior a user or caller would see.
  • One verdict. Every run ends in exactly one of three verdicts — CONFIRMED CORRECT, CONFIRMED INCORRECT, or NOT LOCALLY TESTABLE — never “probably works.”
  • Evidence or it didn’t happen. A verdict carries the commands, inputs, and outputs that prove it, so anyone can re-run the steps and see the same thing.
  • One comment per PR. The verifier upserts a single marker-tagged comment instead of stacking a new comment on every push.

How it works

The workflow maps PR lifecycle events onto one long-lived agent with a stable slug, verify-<repo-id>-pr-<number>, built from the repository ID and the PR number: Cancelling a superseded verification lives in the agent (via preempt), not in CI job concurrency. The CI jobs stay thin calls that return in seconds; the long-running work is the agent’s. Because the agent persists, it keeps its context across commits and updates one verdict comment in place.

Prerequisites

  • A workspace whose repos include the repository you want verified — the agent checks out the PR head inside its VM. See the admin quickstart.
  • Permission to create a service profile and API key in your organization, and admin access to the GitHub repository (to add a secret and the workflow file).

Step 1: Author the verifier persona

The persona is the standing discipline — verdict rules, evidence rules, boundaries — plus the project-specific knowledge of how to bring your system up. Create it with murmur set agent-persona, piping Markdown with YAML frontmatter:
The persona has two parts. Everything outside “Your local environment” is the verification discipline — it is project-independent, and you can keep it as is. The “Your local environment” section is the one part that is yours: replace it wholesale with your project’s exact bring-up, seed, and teardown commands, the ports things listen on, and any harnesses the agent should prefer before declaring something untestable. The more concrete that section is, the fewer NOT LOCALLY TESTABLE verdicts you get.
The persona holds the reusable discipline; the per-PR facts (PR number, head SHA, which comment to upsert) arrive in the spawn prompt from the workflow in step 3. Editing the persona affects future spawns only — see agent persona lifecycle.
Confirm the persona with murmur get agent-persona verifier.

Step 2: Set up credentials

The workflow authenticates to Murmur with a service profile API key, so verifier runs belong to a bot identity rather than a person.
  1. Create a service profile (e.g. pr-verifier) — see Create a service profile.
  2. Create an API key against that profile — see API keys. The key is shown once, in the form mur_….
  3. Store the key as a repository (or organization) secret named MURMUR_API_KEY:

Step 3: Add the workflow

Create .github/workflows/verify-pr.yml in the repository. Replace my-workspace with your workspace name, and adjust the numbered protocol in the prompt to your project:
The slug uses github.event.repository.id because it works for any repository: the ID is unique and stable across repositories, needs no per-repository editing — the same file drops into every repository unchanged — and, because it is numeric, always satisfies the slug grammar ^[a-z][a-z0-9-]{0,62}$ (start with a lowercase letter; lowercase letters, digits, and hyphens only; max 63 characters). If you prefer the more readable github.event.repository.name, it works only when the repository’s name already fits that grammar — names like socket.io, my_project, React, or 2048 produce an invalid slug and the spawn fails. If your name doesn’t fit, sanitize it or keep the ID.
The composite actions run the published Murmur CLI image on the runner — the same murmur spawn, murmur queue add --preempt, and murmur kill you can run by hand. Their full inputs and outputs are documented in the murmur-actions repository. Six details in the workflow are worth understanding before you adapt it:
  • The slug carries a repository discriminator. A slug identifies the agent within the workspace, not within the repository, and PR numbers repeat across repositories — with a bare verify-pr-42, two repositories in one workspace would resolve their PR #42 events to the same agent. The repository ID keeps the slugs distinct however many repositories share the workspace, and the purpose field keeps the human-readable repository name visible on the dashboard.
  • The same-repo gate. Every job requires github.event.pull_request.head.repo.full_name == github.repository. Fork PRs receive no repository secrets, so on a fork run MURMUR_API_KEY is empty and every call fails — the gate keeps fork events from running the jobs at all. Because a fork PR never spawns an agent, it also has nothing to re-verify or tear down.
  • needs: reverify with always(). The spawn job waits on reverify so the synchronize recovery path can read its delivered output. always() keeps spawn running on open/reopen, where the synchronize-only reverify job is skipped.
  • The concurrency group drops stale pending runs — safely. GitHub queues at most one pending run per concurrency group: with cancel-in-progress: false a running job always finishes, and when a newer event arrives while another run is already pending, GitHub cancels the older pending run and the newer one takes the pending slot. The newest event always survives, and the jobs are shaped so a dropped older event never matters: a re-verify is latest-wins by design, a pending spawn displaced by a re-verify is recovered through the deliver-or-spawn path, a pending teardown displaced by a reopen is covered by force-new: "true", and the kill tolerates an agent that is already gone.
  • The prompt is the single source of per-PR truth. PR-context values flow through the action’s with: inputs — never a runner shell step — so a PR-author-controlled value like a branch name cannot inject script into the CI job. The prompt’s gh snippet runs later, inside the agent, not on the runner.
  • The verdict comment carries <!-- MURMUR_IGNORE -->. Murmur delivers PR comments to subscribed agents as events; a comment whose body contains this marker wakes no agent. Stamping it on the verdict keeps the verifier’s own machine-generated comment from arriving as a pr_comment follow-up to an agent working the PR. It is separate from <!-- pr-verifier -->, which the upsert step searches for.

Step 4: Pin the actions to a commit SHA

The spawn, reverify, and teardown jobs all carry MURMUR_API_KEY, so follow the standard practice for secret-bearing jobs: pin uses: to a full commit SHA rather than a moving tag or branch, so the action code your jobs run only changes when you change it. Look at the murmur-actions repository and pin its head commit, keeping a trailing comment naming the ref you resolved (# murmur-actions main) so the human-readable version stays visible. Resolve the head commit:
Use that SHA in all three uses: lines, and bump it deliberately when you want to roll the actions forward.

Open a pull request

Push the workflow, then open a PR against the repository. On open, the spawn job returns within seconds and the agent appears on your dashboard as verify-<repo-id>-pr-<number>. When it finishes, the PR carries one comment:
Push another commit and the comment updates in place with a verdict for the new head. Close the PR and the agent disappears from murmur ls.
Between commits the agent sleeps rather than terminating. You can talk to it like any other agent — murmur queue add to ask it to re-check something, murmur status to see what it’s doing.