ReviewLoop
A production-minded Rust CLI/daemon for
paperreview.aisubmission and review retrieval.
Most paper review automation breaks in boring ways: duplicate submissions, lost tokens, noisy polling, and zero traceability.
ReviewLoop gives you a durable loop with guardrails:
- Queue reviews from Git tags or PDF hash changes
- Persist every transition in SQLite
- Pull tokens from Gmail OAuth or IMAP
- Write reproducible artifacts (
review.json,review.md,meta.json) - Recover from failures with explicit retries and fallback submission
Why This Project Exists
Reviewing pipelines are usually a pile of scripts plus cron plus hope. ReviewLoop is built for the opposite:
- predictable state transitions
- low default provider pressure
- human approval gates where it matters
- clear local evidence of what happened and why
If you want reliable, low-drama automation for paperreview.ai, this is the tool.
1-Minute Quick Start
# 1) install (choose one)
# after public release
&&
# OR
# 2) register paper (global config file is auto-created on first run)
# 3) optional: add a custom git-tag trigger for this paper
# 4) submit and run daemon
# `paper add` will prompt whether to submit immediately
Installation
Homebrew (recommended on macOS)
# after public release
Upgrade:
# or force Homebrew path
Cargo
# after public release
Upgrade:
# or force Cargo path
Build From Source
Command Surface
Global usage:
Core commands:
|
self-update only replaces the executable. It does not delete:
- global config (
~/.config/reviewloop/reviewloop.toml) - global data directory (database, artifacts, logs)
- project-local configs
Runtime Model
Daemon tick interval: every 30 seconds.
Each tick performs:
- Trigger scan (
git tags, PDF hash changes) - Optional Gmail OAuth + IMAP token ingestion
- Timeout marking
- Submission processing (
QUEUED -> SUBMITTED/PROCESSING) - Poll processing (
PROCESSING -> COMPLETED/FAILED/...)
Manual immediate poll:
reviewloop check --job-id <id>forces one check now for that processing job (ignoresnext_poll_at)reviewloop check --paper-id <paper-id>checks the latest processing job for that paperreviewloop check --all-processingchecks all current processing jobs
Output artifacts per completed job:
<state_dir>/artifacts/<job-id>/review.json<state_dir>/artifacts/<job-id>/review.md<state_dir>/artifacts/<job-id>/meta.json
What Makes It Reliable
- State machine, not ad-hoc scripts: jobs move through explicit statuses (
PENDING_APPROVAL,QUEUED,PROCESSING,COMPLETED, etc.) - Duplicate guard: prevents repeated submissions for the same
backend + pdf_hash - Load-aware polling: default schedule starts at 10 minutes with jitter/cooldown behavior
- Recovery built in: every transition is evented, retries are explicit
- Fallback path: optional Node + Playwright submit path when provider API flow fails
Triggering Modes
Git tag trigger
Supported patterns:
review-<backend>/<paper-id>/<anything>review-<backend>/<anything>(uses the first configured paper of that backend)- optional per-paper custom pattern via
paper add --tag-trigger "<pattern>"(supports*)
Example:
review-stanford/main/v1
PDF change trigger
- Computes SHA256 for configured PDFs
- New hash enqueues job
- Default status is
PENDING_APPROVAL(manualapproverequired)
Email Token Ingestion
ReviewLoop can attach review tokens from email to open jobs.
IMAP mode (built in)
Default token pattern includes Stanford:
[]
= "https?://paperreview\\.ai/review\\?token=([A-Za-z0-9_-]+)"
Recommended defaults:
imap.header_first = trueto scan headers firstimap.max_lookback_hours = 72imap.max_messages_per_poll = 50
Gmail OAuth mode
Configure:
[]
= true
= "your-google-oauth-client-id"
= "your-google-oauth-client-secret"
= "~/.review_loop/oauth/google_token.json" # optional
= 300
= true
= 72
= 50
= true
[]
= "(?is)(from:\\s*.*mail\\.paperreview\\.ai|subject:\\s*.*paper review is ready)"
[]
= "https?://paperreview\\.ai/review\\?token=([A-Za-z0-9_-]+)"
You can also provide credentials via environment variables:
REVIEWLOOP_GMAIL_CLIENT_IDREVIEWLOOP_GMAIL_CLIENT_SECRET
For official CI-built binaries, these same variable names can be injected at compile time
via GitHub Actions secrets.*; runtime env/config can still override them.
Then login:
email login will try to open your default browser automatically and wait in CLI for OAuth completion.
ReviewLoop runs Gmail API polling first when available, then IMAP fallback.
Configuration Highlights (reviewloop.toml)
Global config is auto-generated at:
$XDG_CONFIG_HOME/reviewloop/reviewloop.toml- or
~/.config/reviewloop/reviewloop.toml
Config precedence (low to high):
$XDG_CONFIG_HOME/reviewloop/reviewloop.toml(or~/.config/reviewloop/reviewloop.toml)--config /path/to/file.toml
Paper registration:
- start with an empty
papers[] - add papers through
reviewloop paper add ... - remove papers through
reviewloop paper remove --paper-id ...- add
--purge-historyto also delete DB jobs/events/reviews and local artifacts for that paper
- add
- control PDF watcher per paper with
reviewloop paper watch ...
Safe defaults:
core.max_concurrency = 2core.max_submissions_per_tick = 1core.state_dir = "~/.review_loop"(orREVIEWLOOP_STATE_DIRwhen set)core.db_path = "~/.review_loop/reviewloop.db"(or<REVIEWLOOP_STATE_DIR>/reviewloop.db)core.review_timeout_hours = 48- for
stanford, timeout is linearly scaled by PDF page count up to 20 pages
- for
polling.schedule_minutes = [10, 20, 40, 60]polling.jitter_percent = 10retention.enabled = trueretention.prune_every_ticks = 20(10 minutes with 30s tick)retention.email_tokens_days = 30retention.seen_tags_days = 90retention.events_days = 30retention.terminal_jobs_days = 0(disabled by default)trigger.pdf.auto_submit_on_change = falsetrigger.pdf.max_scan_papers = 10trigger.git.tag_pattern = "review-<backend>/<paper-id>/*"trigger.git.auto_create_tags_on_pdf_change = falsetrigger.git.auto_delete_processed_tags = false
providers.stanford defaults:
base_url = "https://paperreview.ai"fallback_mode = "node_playwright"fallback_script = "tools/paperreview_fallback.mjs"emailoptional (falls back to active email account)venue = "ICLR"(can be overridden by user config)
Logging:
logging.output = "stdout" | "stderr" | "file"- file mode default path:
<state_dir>/reviewloop.log
CI/CD and Release Flow
This repository ships with GitHub Actions for both quality gates and release automation.
CI (.github/workflows/ci.yml)
On pull requests and pushes to main/master:
cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo test --all-targets --locked
Runs on both Ubuntu and macOS.
Release (.github/workflows/release.yml)
On tag push like v0.1.0:
- Verify tag version matches
Cargo.toml - Run quality gates again
- Publish crate to crates.io
- Update Homebrew tap formula in
Acture/homebrew-ac - Create GitHub Release with generated notes
Required secrets:
CARGO_REGISTRY_TOKENHOMEBREW_TAP_GITHUB_TOKEN
Optional secrets (compile-time OAuth defaults for release/CI builds):
REVIEWLOOP_GMAIL_CLIENT_IDREVIEWLOOP_GMAIL_CLIENT_SECRET
Optional repo variables:
HOMEBREW_TAP_REPO(default:Acture/homebrew-ac)HOMEBREW_FORMULA_PATH(default:Formula/reviewloop.rb)
Fallback Requirements
When API submit fails and fallback is enabled:
- Node.js must be available
- Playwright runtime dependencies must be installed
- script path defaults to
tools/paperreview_fallback.mjs
Responsible Use
ReviewLoop is intentionally conservative.
Please keep it that way:
- use it only for authorized submissions/retrieval
- keep concurrency and submit rate low unless provider approves otherwise
- do not aggressively shorten poll cadence
- respect provider Terms of Service and fair-use boundaries
Current Scope
- Supported backend:
stanford(paperreview.ai) - Database: SQLite (global state path by default, supports
:memory:) - Interface: CLI + daemon