GitHub tracks code and diffs. It does not track who should review what, how overloaded reviewers are, or what happens when a PR sits for three days blocking a release. PRFlow fills that gap with an event-driven orchestration engine that models developer expertise, routes reviewers deterministically, balances workloads, and escalates stalled PRs before anyone notices.
This is not a bot that posts comments. It is a state machine that owns the review lifecycle from webhook ingestion to merge queue insertion.
Architecture: Modular Monolith with Four Processing Engines
PRFlow runs as a Java 21 Spring Boot monolith with a TypeScript ingress gateway. The ingress layer handles GitHub webhooks at high throughput using Bun and Express. It validates payloads, enriches events, and forwards them to the core orchestration layer.
The core orchestration layer runs four processing engines:
- Routing engine: Calculates familiarity scores using decay curves, then assigns reviewers deterministically based on file ownership and current workload.
- Workload balancer: Tracks open review requests per developer and redistributes assignments when capacity thresholds are breached.
- SLA enforcement engine: Monitors review age and triggers escalation workflows when PRs exceed configured time limits.
- Merge queue coordinator: Decides when to insert PRs into GitHub’s merge queue based on approval state, CI status, and branch protection rules.
State lives in PostgreSQL with Flyway migrations. Valkey (Redis-compatible) caches familiarity scores and workload counters. The system uses optimistic locking on PR state transitions to handle concurrent events from multiple agents and humans.
State Management Across Review Stages
PRFlow models each PR as a finite state machine with six states: PENDING_REVIEW, IN_REVIEW, CHANGES_REQUESTED, APPROVED, MERGE_QUEUED, and MERGED. Transitions are triggered by GitHub webhook events (review submitted, CI status changed, force push detected) and internal timer events (SLA breach, escalation timeout).
When a force push invalidates an agent review, the orchestration engine:
- Receives a
pushwebhook with the new commit SHA. - Queries the database for all reviews associated with the old SHA.
- Marks those reviews as
STALEand transitions the PR back toPENDING_REVIEW. - Recalculates familiarity scores for the new diff and re-routes reviewers.
If CI fails mid-approval, the merge queue coordinator blocks insertion and transitions the PR to CHANGES_REQUESTED. The SLA timer resets only when the author pushes a fix and CI passes again.
Deterministic Reviewer Routing with Decay Curves
The routing engine assigns reviewers based on familiarity scores, not round-robin or random selection. Familiarity is calculated per file using a decay curve:
familiarity(file, dev, t) = commits(file, dev) * e^(-λ * days_since_last_commit)
Lambda (λ) is configurable per repository. A typical value is 0.05, which halves familiarity every 14 days. This ensures that developers who touched a file recently get higher scores than those who last edited it six months ago.
The routing engine:
- Parses the PR diff to extract changed files.
- Queries the database for commit history per file.
- Calculates familiarity scores for all developers.
- Filters out developers already at workload capacity.
- Assigns the top N reviewers (N is configurable, default is 2).
If no developer has sufficient familiarity, the engine falls back to team-level routing based on CODEOWNERS files.
Merge Queue Coordination Without Race Conditions
GitHub’s merge queue API requires PRs to meet branch protection rules before insertion. PRFlow’s merge queue coordinator polls PR state every 30 seconds and checks:
- All required reviews are approved.
- CI checks are passing.
- No merge conflicts exist.
- No SLA escalation is active.
When all conditions are met, the coordinator calls GitHub’s /repos/{owner}/{repo}/pulls/{number}/merge endpoint with the merge_method set to merge or squash based on repository configuration.
Race conditions are handled using optimistic locking. Each PR record has a version column. When the coordinator attempts to transition a PR to MERGE_QUEUED, it includes the current version in the SQL UPDATE statement:
UPDATE pull_requests
SET state = 'MERGE_QUEUED', version = version + 1
WHERE id = ? AND version = ?
If another process (human or agent) modified the PR state concurrently, the WHERE clause fails and the transaction rolls back. The coordinator retries on the next poll cycle.
SLA Enforcement and Self-Healing Escalation
The SLA enforcement engine runs a scheduled job every 5 minutes. It queries for PRs in PENDING_REVIEW or IN_REVIEW states where created_at or last_review_at exceeds the configured SLA threshold (default is 24 hours).
For each stale PR, the engine:
- Checks if an escalation workflow is already active.
- If not, creates an escalation record and assigns a fallback reviewer (typically a tech lead or manager).
- Posts a comment on the PR notifying the original reviewers and the fallback.
- Resets the SLA timer.
If the fallback reviewer also fails to respond within the escalation SLA (default is 12 hours), the engine escalates again to a higher tier (VP of Engineering or equivalent). This continues until someone responds or the PR is closed.
Failure Modes and Observability
PRFlow exposes Prometheus metrics for:
- Webhook ingestion rate and latency.
- State transition counts per PR state.
- Familiarity score calculation duration.
- Merge queue insertion success/failure rate.
- SLA breach count per repository.
Logs are structured JSON with trace IDs for each PR. The system uses Spring Boot Actuator for health checks and readiness probes.
Common failure modes:
| Failure Mode | Detection | Recovery |
|---|---|---|
| GitHub API rate limit | 403 response with X-RateLimit-Remaining: 0 | Exponential backoff, fallback to secondary token pool |
| Webhook delivery failure | Missing event in ingress logs | GitHub webhook redelivery (automatic for 24 hours) |
| Database deadlock | PostgreSQL error code 40P01 | Automatic retry with jitter, max 3 attempts |
| Stale familiarity cache | Cache miss on routing | Recalculate from database, repopulate cache |
| Merge conflict after approval | GitHub API returns 409 | Transition PR to CHANGES_REQUESTED, notify author |
Deployment Shape
PRFlow runs as two containers: the ingress gateway (TypeScript/Bun) and the orchestration monolith (Java 21). The ingress gateway is stateless and scales horizontally behind a load balancer. The orchestration monolith runs as a single instance with leader election (using PostgreSQL advisory locks) to prevent duplicate processing of scheduled jobs.
Database migrations run automatically on startup using Flyway. Valkey is deployed as a single instance with persistence enabled (AOF mode). In production, Valkey should run in sentinel mode for high availability.
The system requires:
- PostgreSQL 16+ with at least 2 CPU cores and 4 GB RAM.
- Valkey 8.0+ with 1 GB RAM.
- Orchestration monolith: 4 CPU cores, 8 GB RAM (JVM heap set to 4 GB).
- Ingress gateway: 2 CPU cores, 2 GB RAM.
Technical Verdict
Use PRFlow when you need deterministic reviewer assignment based on code familiarity and automated escalation for stale PRs. It is particularly useful for teams with more than 20 developers where manual review assignment becomes a bottleneck.
Avoid PRFlow if your team is small (fewer than 10 developers) or if you already have a working review process with low SLA breach rates. The operational overhead of running PostgreSQL, Valkey, and two services is not justified for teams that can coordinate reviews manually.
Also avoid PRFlow if your repository has frequent force pushes or rebases. The familiarity score recalculation on every force push adds latency and database load. In that case, simpler round-robin assignment may be more efficient.
The merge queue coordinator assumes you are using GitHub’s native merge queue. If you use a third-party CI/CD system with custom merge logic, you will need to modify the coordinator to call your system’s API instead of GitHub’s.