§1 A review, end to end
Every review starts from a plain pull_request event on a
GitHub-hosted runner and ends in three artifacts: a sticky comment, a
check run, and (optionally) proposed patches. In between, cora picks one
of two modes.
pull_request event
│
TriggerPolicy ──── deny ──▶ skip · check run fails closed
│ allow
context: diff + PR metadata + retrieved docs
│
┌─────────────┴─────────────┐
quick deep
one LLM call, multi-turn agent loop:
no tools grep_repo · git_show · MCP,
token + tool-call budgets
└─────────────┬─────────────┘
wall-clock hit?
│──── yes ──▶ escalate a tier, continue
│
verdict: 🟢 · 🟡 · 🔴
│
┌───────────────┼───────────────┐
sticky comment check run «cora» propose_patch
edited in place, required by your hybrid patch
<!-- cora:v1 --> branch protection dispatch
quick- A single LLM call over the assembled context. Cheap, fast, no tools - the mode untrusted PRs are forced down.
deep- A multi-turn agent that greps the checkout, reads blame and history, and calls your MCP tools - under explicit token and tool-call budgets. When a deep review hits its wall-clock limit, an escalation ladder can hand the transcript to a bigger model and keep going instead of giving up. A single-model deployment is simply a one-rung ladder.
Review prompts are conventions, not magic: cora is told to verify before it flags, to reserve its strongest severity for breakage it has personally confirmed, and to say nothing about style your linters already enforce. A false-positive blocker on a teammate's PR is treated as the worst failure mode the system has.
§2 The verdict contract
The whole point of an automated reviewer is a signal you can gate on. cora's output is a fixed, parseable vocabulary:
| verdict | meaning | check run |
|---|---|---|
| 🟢 looks good | nothing worth holding the merge for | success |
| 🟡 minor | suggestions worth reading, none blocking | success |
| 🔴 needs changes | at least one verified blocker | failure |
| - no verdict | review skipped, errored, or denied | fails closed |
Findings inside the comment use two severities:
🚨 Blocker - only for things the agent
has verified will break, with file:line and what to fix - and
⚠️ Concern for suggestion-grade issues.
The verdict lands as a check run named cora; make it a
required status check and the verdict is your merge gate. A
review-override label is the deliberate, audit-visible escape
hatch when a human decides the robot is wrong.
<!-- cora:v1 -->
cora review - 🟡 minor
- ⚠️ Concern:
charts/ingress/values.yaml:41- the new timeout is set on the Service but the upstream annotation attemplates/ingress.yaml:23still pins 30s; the lower one wins. - ⚠️ Concern:
scripts/rotate.sh:12-set -ewithoutpipefail; thecurl | jqon line 19 can fail silently.
deep review · 2 tiers · 41 tool calls · edited in place on the next push
Re-reviews are CI-failure-aware: when your pipeline fails after a verdict, cora re-reads the PR with the failure in hand. And because the engine targets self-hosted reasoning models, it strips chain-of-thought leaks from output before anything reaches GitHub.
§3 Zero infrastructure by default
Everything cluster-shaped sits behind a provider seam with a working
default, so pip install plus an OpenAI-compatible endpoint is
a complete deployment - and each seam upgrades independently when you have
the infrastructure to back it.
| seam | default | upgrades to |
|---|---|---|
RetrievalProvider |
none - review runs on the diff and your conventions file | BM25 over your local docs (Glob, zero infra), or hybrid
dense+sparse Qdrant with TEI reranking |
GitProvider |
the local checkout - grep_repo, git show |
your own SCM backend |
Reporter |
GitHub comment + check run | NullReporter for dry-runs and evals - a review returns a
plain ReviewResult you can inspect |
| LLM | any OpenAI-compatible endpoint - built for self-hosted models, with an escalation ladder across however many tiers you run | |
CI for cora itself runs on GitHub-hosted runners with no private infrastructure - the same constraint an adopter starts from.
§4 Reviewing hostile input
A PR reviewer executes a tool-using agent against attacker-controlled text. cora's posture is diff-as-data: PR content is evidence to evaluate, never instructions to follow - and an injection attempt in a diff is itself flagged as a 🚨 Blocker.
Above the prompt sits TriggerPolicy, a default-deny gate on
who may fire a review and at what capability: author trust rules
(association, allowlist, or an approval label), a comment-only ceiling for
untrusted and fork PRs - forced quick mode, no patch dispatch - and rate
caps per author and per repo. Trust lookups fail closed. The shipped
workflow template runs on plain pull_request, never
pull_request_target.
The full threat model, policy reference, and canonical workflow wiring live in SECURITY.md. Read it before pointing cora at a repo where strangers can open PRs.
§5 One config object
A review is driven by a single dataclass, ReviewerConfig.
Every engine tunable is a field on it; from_env() maps the
same fields onto workflow environment variables, so a deployment is just
env vars in a job. No YAML dialect, no second config system.
from cora import ReviewerConfig, run_review
cfg = ReviewerConfig.from_env() # or construct it directly
result = run_review(cfg) # ReviewResult: verdict, findings,
# tiers run, budgets spent
Default review prompts ship in the package; point a config field at your own markdown to replace them. Telemetry (OpenTelemetry traces of every tier, tool call, and budget) is an opt-in extra, never a hard dependency.