A GitHub review bot that fans a pull request out across independently-gated check runs.
Warning
Work in progress. The CLI surface is declared in full, but most subcommands are still returning "not implemented yet". See ROADMAP.md for what has landed.
The incumbents produce one giant review from one giant model call. Everything arrives as a suggestion, nothing is separable, and there is no way to require only the parts you trust — so teams either take the noise or turn the whole thing off.
tinysweeper splits the review into lanes. Each lane is a separate agent with a narrow job, a narrow slice of evidence, and its own GitHub check run:
| Check run | What it looks at |
|---|---|
tinysweeper/critique |
Correctness of the diff, with the surrounding code pulled in as context |
tinysweeper/security |
Dependency changes, new network/exec sites, workflow permission widening |
tinysweeper/tests |
Whether changed behaviour is actually covered, and whether the assertions mean anything |
tinysweeper/commits |
Secrets, large blobs, vendored junk and other dirt committed into the history |
tinysweeper/description |
Whether the PR body matches what the diff actually does |
tinysweeper/gate |
The deterministic aggregate — the one check to require in branch protection |
Because they are separate check runs, branch protection can require exactly the lanes you trust, and a noisy lane can be switched off without losing the rest.
Deterministic scanners run before any model call, so a committed private key fails for free and the model is only asked to adjudicate what a scanner already flagged.
Alongside the lanes, a pull request gets a change map: one comment, edited in place, with a diagram of the components the change touches and the untouched ones it reaches through an import or a call. It is drawn from the diff and the repository graph — no model call, so it is reproducible and free — and it says which parts it could not see rather than drawing an empty picture. See docs/modules/overview/README.md.
- The model never holds a write token. Lanes take a
ForgeReadand only the apply path takes aForgeWrite, so a lane structurally cannot mutate a pull request. The installation token used for writing is minted after every model call has returned. - Contributor code is never executed. tinysweeper reads the diff and the tree. It does not build, install dependencies, or run the target repo's scripts.
- Automation never parses prose. Review and apply communicate through hidden HTML markers carrying a verdict, a head SHA and a confidence score.
- An empty review is a valid review. Padding a finding list with style preferences is treated as a defect.
- Offline by default. The default build links no HTTP client, and the test suite never touches the network.
tinysweeper is a GitHub App: one server, installed on as many repositories as you like, receiving webhooks. There is no workflow file to add to a repository and no Action to pin — installing the App is the whole installation, and improvements reach every repository at once because there is only one place running the code.
There used to be a second path — a reusable workflow and a composite action. It has been removed. Two distribution paths meant two trigger models, two credential models and two things to keep honest, and the workflow half could not carry the one thing that justified the server: a contributor whitelist is a fact about a person over time, and a stateless job has nowhere to keep one.
git clone --recurse-submodules https://github.com/tinyhumansai/tinysweeper
cd tinysweeper
cargo build --release --features all
cp .env.example .env # fill it in
./target/release/tinysweeper serveThe App needs checks:write, contents:read, issues:write,
pull_requests:write and metadata:read, and a webhook pointed at
/webhook. deploy/github-app-manifest.json and scripts/create-github-app.sh
create one with those settings. A Docker image is published from Dockerfile
by CI.
.github/workflows/deploy.yml is an operator button in the Actions tab: it asks
the cluster to restart the workload so it re-pulls the published image. It never
fires on a push or a green CI run — merging publishes an image, and a human
decides when production takes it. Both settings below can be overridden per run
from the dispatch form. It needs:
| Name | Kind | Meaning |
|---|---|---|
KUBE_CONFIG |
secret | Base64 kubeconfig (base64 -w0 < kubeconfig.yaml) for a service account that may patch deployments in the one namespace. |
K8S_NAMESPACE |
variable | Namespace. Defaults to tinysweeper. |
K8S_DEPLOYMENTS |
variable | Comma-separated deployment names. Defaults to tinysweeper. |
The restart only deploys new code if the pod template tracks a moving tag
(ghcr.io/tinyhumansai/tinysweeper:latest) with imagePullPolicy: Always. For
digest-pinned manifests, set the image to the commit SHA instead — CI already
tags every image with type=sha,format=long.
The server refuses to start without TINYSWEEPER_WEBHOOK_SECRET: an unsigned
delivery endpoint is a way for anyone to make the bot review anything.
The vendored TinyAgents LangGraph-style runtime exports each model run to
Langfuse when all three variables below are set. The exporter is best effort:
an unavailable Langfuse service is logged but does not fail a review. Prompt
and model-response payloads are included in these traces, so set the variables
only in the server's secret environment, never in a repository or committed
.env file.
LANGFUSE_BASE_URL=https://fuse.tinyhumans.ai
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...LANGFUSE_ENVIRONMENT is optional and labels the resulting traces. Leaving
the Langfuse variables unset keeps the existing behaviour.
Configure per-repository behaviour with a .tinysweeper.toml at the repository
root, and validate it with tinysweeper check. See
docs/triggers.md for what fires when — including the things
GitHub emits no event for at all.
Operator endpoints live under /admin, guarded by a bearer token in
TINYSWEEPER_ADMIN_TOKEN and compared in constant time. When that variable is
unset the admin router is not mounted at all, so a misconfigured deployment
loses the API rather than exposing it.
curl -H "Authorization: Bearer $TINYSWEEPER_ADMIN_TOKEN" \
https://tinysweeper.example/admin/contributors/octocat
curl -X PUT -H "Authorization: Bearer $TINYSWEEPER_ADMIN_TOKEN" \
-H 'content-type: application/json' \
-d '{"trust":"blocked","note":"spam pull requests"}' \
https://tinysweeper.example/admin/contributors/octocat/trustIndex status (/admin/index/…) and knowledge documents
(/admin/knowledge/…) are declared and return 501 until their stores land.
local-review runs every lane over a local git range, with no GitHub item and
no tokens — useful before you push, and the way prompt changes get iterated. It
needs a model key and the harness feature; it needs nothing else.
# The working tree against origin/main, uncommitted and untracked files included.
tinysweeper local-review --base origin/main
# A committed branch, with the description you are about to open the PR with.
tinysweeper local-review --base main --head HEAD \
--title 'feat: add the council' --body 'Why it exists.'The range is base...head, taken from the merge base the way GitHub shows a
pull request, so commits that landed on the base branch meanwhile are not
reviewed as your work. Nothing is written anywhere — there is no ForgeWrite on
this path at all. See docs/modules/app/README.md
for the rest of its behaviour.
eval scores the review against the labelled corpus in evals/. Running costs
money; scoring and reporting are free and offline, because a matching rule gets
rewritten many times before it is right.
tinysweeper eval run --record # live, writes cassettes and proposals
tinysweeper eval score # free, re-reads the proposals on disk
tinysweeper eval report --baseline evals/baselines/current.jsoncargo test replays the committed cassettes on every run, so a prompt change
that nobody re-recorded fails offline and for free. See
evals/README.md for the labelling contract — including what
the corpus does not measure yet.
TinyAgents, a recursive
language-model harness for Rust, vendored at vendor/tinyagents. Models are
reached through an OpenAI-compatible gateway, so OpenRouter, Moonshot and
MiniMax are all the same code path.
- ROADMAP.md — what has landed and what is next
- CONTRIBUTING.md — local checks and pull request expectations
- AGENTS.md — conventions for humans and agents working in this repo
- docs/triggers.md — what wakes tinysweeper up, and what emits no event at all
- docs/modules/server/README.md — the server, its security boundary, and the admin API
- docs/modules/overview/README.md — the change map: what gets drawn, and why nothing in it comes from a model
docs/— module documentation and design notes
GPL-3.0-only. See LICENSE.