Review a pull request with nobody at the keyboard¶
An autonomous review is one review of one pull request that runs to the end without you.
You start it from a terminal, from keryx shell, or from a schedule. It opens a review
package, dispatches the reviewers, ingests their findings and closes the package. When it
stops, it leaves a verdict in an exit code, a summary.json for a program and a report.md
for a person.
It uses the review-autonomous profile: keryx shell --print in trust mode under
--review-system review-auto, reading the source only. It has no browser to open and nobody to
answer a question, so every place an interactive review would wait has a fixed rule below.
Run one¶
keryx review run --autonomous --target pr --ref https://github.com/owner/repo/pull/123 \
[--budget-minutes 60] [--budget-tokens 2000000] [--stall-minutes 10] \
[--out <dir>] [--provider p] [--model m] [--no-notify] [--json]
--ref must be a pull request URL. No terminal is needed on any stream, so this is the form
to put in a script, a cron line or a CI job. --target pr is the only target.
| Exit code | Status | Meaning |
|---|---|---|
| 0 | complete |
The package was closed by keryx review complete. |
| 2 | incomplete |
The run ended, or was stopped, before the package was closed. |
| 3 | floor |
The run was stopped at a floor: an action that always needs a human was refused. |
| 4 | budget |
--budget-minutes or --budget-tokens was spent. |
| 5 | stalled |
No new event for --stall-minutes (default 10). |
| 1 | refused | Bad arguments, or another run of the same pull request holds the lock. |
What it does where a person would answer¶
- A menu (
ask_user). A question marked irreversible is refused (the run sees it as cancelled). Any other question takes the recommended option, or the first one when none is recommended. A question with nothing to choose from is refused. Every automatic answer is written toanswers.ndjsonwith its basis (recommended,first,refused-irreversible,refused-no-option), so you can read later what was decided for you. A question the review skill tagged as policy (scope, budget, partial coverage, a smaller slice) goes to the review arbiter instead: it answers from numbered rules, and adenyor anescalateis a refusal, with its basis inanswers.ndjson. - A floor. The calls that always need a human (destructive, credentials, publishing) are refused, not granted. A refused call stays refused: the same call in any spelling is declined without a new question, and the run is told to choose another path. A run that can only go on through a floor stops with exit code 3.
- Time and tokens.
--budget-minutesand--budget-tokensare checked while the run goes. When one is spent, the run is stopped at a safe point, the package is kept as it is, and the exit code is 4. - A hang. The child process is watched through its events file. If nothing new appears for
--stall-minutes, the run is stopped and the exit code is 5. - A browser. A check that needs one is skipped and the report says it was skipped.
- Rounds. At most five review rounds, and six reviewers per wave, and no new round when the last one found no new blocker or major finding.
- You stop it. SIGTERM and SIGINT stop the child and write a summary with status
incomplete; the package stays open and resumes later.
What it leaves¶
Everything is under .metaproject/data/review-autonomous/<owner>-<repo>-pr<N>/ (or --out).
| File | What it is |
|---|---|
summary.json |
One object, schema version 1: status, exit code, reason, last phase, rounds, tokens, finding counts by severity, the package, floor refusals and automatic answers. |
report.md |
The same for a person: the verdict first, then the findings and what was decided for you. |
events.ndjson |
The child's event stream. |
answers.ndjson |
Every automatic answer, with its basis. |
arbiter.log |
One line per policy question the review arbiter was asked: verdict, rule, model and cost. Copied into the review package when the run ends. |
progress.json |
The phase, rounds and tokens now. /autoreview list and the sidebar read this. |
lock.json |
The pid and start time of the run that holds the pull request. |
child.log |
The child's own output, for a failed run. |
Resume and re-run¶
A run on a pull request whose head commit was already reviewed to the end does not start a new
review. It reuses the finished one, prints reused, and exits 0. A run on the same head whose
earlier run did not finish resumes the open package from where it stopped, and counts as the next
attempt. A run on a new head starts a fresh review and keeps the old summary beside it as
summary.<head>.json. A run that finds the first still alive refuses with exit code 1; a lock
whose process has died is taken over.
From keryx shell¶
/autoreview start https://github.com/owner/repo/pull/123 [--budget-minutes N] [--budget-tokens N]
/autoreview list
/autoreview stop 123
/autoreview arbiter [123]
arbiter lists what the run's arbiter was asked and what it answered.
start runs the command above in a detached process and returns at once. stop sends it
SIGTERM, so the run writes its partial summary; with one live run the argument can be left out.
While a run is going, the sidebar shows an autonomous-review section with one line per run: the
pull request, the phase, rounds, tokens, and the last automatic answer. A run that finished stays
for a day. A run whose process died is marked died. The section is hidden when there is none.
/autoreview is in /help and in the command menu.
Telegram¶
When a bot is paired with keryx serve, a run sends one line when it starts and one when it
ends. The end line has the verdict, finding counts, tokens and time:
Autonomous review incomplete (exit 2): owner/repo PR #123, 0 blocker, 2 major, 5 minor, 412000 tokens, 1840 s. stopped by the operator; last phase: ingest; package rev-… kept at ingested
A run that stopped because the arbiter escalated sends one more line naming the question and
the reason; the log is in the package as arbiter.log.
The note is best effort. With no serve, no paired bot or a slow network, the run is neither
held up nor failed. --no-notify turns it off.
Put it on a schedule¶
keryx schedule add --review-pr https://github.com/owner/repo/pull/123 \
--name pr-123 --every "every 4 hours" \
--provider anthropic --model claude-sonnet-5 --rates 3,15 --ceiling 5 \
[--budget-minutes 60] [--budget-tokens 2000000] [--stall-minutes 10] [--max-seconds 3600] [--yes]
You get the usual confirmation card, and nothing is stored until you confirm it at a terminal.
--prompt is not needed. --digest and --review-pr cannot be combined, and the three budget
flags belong to a review only.
A scheduled review is a normal schedule with an OS timer, and it is covered by the same
confirmation as any other: the pull request, the profile and the budgets are part of what you
confirmed, so changing one needs a new add. Each fire runs keryx trigger run --schedule
pr-123, which starts keryx review run --autonomous for that pull request. Then:
- a fire on a head that was already reviewed to the end reuses the finished review;
- a fire that finds the previous run still going refuses, so two runs never share a pull request;
- the run's
report.mdis copied to.metaproject/data/trigger/reports/pr-123/<runId>.md, andkeryx schedule show pr-123and/scheduleslist it with the verdict.
cron and systemd timers¶
keryx schedule add installs the OS timer after you confirm the card. To read exactly what it
would install, or to carry it into a crontab or a unit directory you manage yourself, print it;
nothing is installed by this:
It prints the cron line and the systemd service and timer pair, with a real OnCalendar=
translated from your cadence and a unit name that is unique to the project. Every form changes
into the project root and runs the same thing: keryx trigger run --schedule pr-123.
| Backend | Where it lives | A missed run |
|---|---|---|
systemd --user (Linux) |
~/.config/systemd/user/keryx-<projecthash>-pr-123.{service,timer}, Persistent=true |
made up once at the next boot or wake |
| launchd (macOS) | ~/Library/LaunchAgents/ai.keryx.<projecthash>.pr-123.plist |
made up once on wake |
| cron (elsewhere) | a block marked # keryx-managed <projecthash> pr-123 in your crontab |
not made up |
Choose one with --backend systemd|launchd|cron. For systemd:
systemctl --user list-timers 'keryx-*'
systemctl --user status keryx-<projecthash>-pr-123.service
journalctl --user -u keryx-<projecthash>-pr-123.service
Without linger a user timer does not run while you are logged out. keryx never enables linger
for you; run loginctl enable-linger $USER yourself if you want the review to run then. The
machine must be on at the time of a fire.
Manage the schedule with keryx schedule pause pr-123, resume pr-123 and remove pr-123.
When the pull request is merged, remove it.
Limits worth knowing¶
- It reviews the source only. A change that is visible only in a running app is not checked.
- A run that stops at a floor, a budget or a stall leaves a partial result. The report says which, and what remains. It never reports a verdict it did not reach.
- The profile cannot be loosened from the command line. To let a run do something a floor refuses, do it yourself and run the review again.
See review and schedule in the
CLI reference, Permission modes for trust and review-auto, and
Review with a durable record for the package the run creates.