local verification boundaryno model calls

Your AI agent says it’s done.

Nothing checked.

plumb reads a repository, finds the verification commands it declares, and runs one before anything reaches CI. It makes no model calls and sends nothing anywhere. The same repository gives the same answer, whichever agent wrote the change.

Then it tells you what that established — and what it did not. plumb has five verdicts and three of them are refusals: a check could not run here, running it settled nothing, or your repository declares no check at all.

And when it cannot establish something it says whose limitation that is — your repository’s, this machine’s, or plumb’s own.

curl -fsSL https://plumbing.sh/install | sh

macOS arm64 · Linux x86-64 · Linux arm64 · Windows through WSL

Move what does not require model judgment out of model judgment.

No model calls. No telemetry. Verify it yourself — run plumb with the interface down, or under tcpdump.

real failure proof

plumb ran on this website and rejected it.

Not a staged example. This site declares one check in its package.json. plumb found it, ran it, and it failed — naming five locations.

declared
npm run lint
runner
plumb executed the repository-declared command locally
verdict
FAIL
evidence
five source locations named by the real check output
plumb check — plumbing.sh, before
PLUMB  ~/Desktop/plumbing-site
           26 files · 4 declared targets · 21ms · discovery via git-ls-files
 
FOUND  1 verification command
  ✓ npm run lint                                 package.json
 
RESULTS
  FAIL  npm run lint                             1.0s
        → src/app/deck.tsx:17
        → src/app/layout.tsx:54
        → src/app/pricing/page.tsx:6
        → src/app/privacy/page.tsx:6
        → src/app/security/page.tsx:6
 
VERDICT  FAIL — 1 executed; a repository-declared check rejects this tree.
exit 1 — a repository-declared check rejected the tree

Four of those were <a href="/"> where Next.js requires <Link>, and one was an unused constant. Real defects, shipped for weeks. Fixed, then run again:

result
PASS
where
locally, before a commit
claim
the declared runnable check accepted the tree

Two seconds, locally, before a commit. The same failure would have taken a CI round trip to find — after the agent run was already paid for.

plumb check — plumbing.sh, after
PLUMB  ~/Desktop/plumbing-site
           26 files · 4 declared targets · 37ms · discovery via git-ls-files
 
FOUND  1 verification command
  ✓ npm run lint                                 package.json
 
RESULTS
  PASS  npm run lint                             0.8s
 
VERDICT  PASS — all 1 runnable check passed.
exit 0 — nothing rejected the tree
before and after the agent

Context first. Work second. Verification last.

before

plumb context

emit the checks, source locations, and repository obligations before the agent edits

during

agent writes

keep the agent you use today; plumb is not another model and does not write code

after

plumb check

run a declared verification command and report what the result establishes

what the model gets before it edits

plumb context emits what a repository declares — every check, where it is declared, whether this machine can run it, and the obligations its documents state. Here it is on spf13/cobra, a repository plumb has never been tuned on.

Every line carries a file and a line number, so an agent can check the claim instead of trusting it. BLOCKED means this machine lacks the toolchain — a fact about the machine, not the repository.

188tokens · spf13/cobra
459tokens · 1,800-file monorepo
0model tokens required to produce it
plumb context — spf13/cobra
[PLUMB]
CANONICAL: make test [Makefile:22] BLOCKED: binary:go (called by make test)
CANONICAL: make lint [Makefile:18] BLOCKED: binary:golangci-lint (called by make lint)
CANONICAL: go vet ./... [go.mod] BLOCKED: binary:go
CANONICAL: go test ./... [go.mod] BLOCKED: binary:go
MUTATOR: make fmt [Makefile:14] BLOCKED: binary:gofmt (called by make fmt)
VARIANT: make all [Makefile:12] BLOCKED: binary:go (called by make all);…
REQUIRED: Run `make all`. [CONTRIBUTING.md:36]
REQUIRED: Create your feature branch (`git checkout -b my-new-feature`) [CONTRIBUTING.md:42]
REQUIRED: Add them to staging (`git add .`) [CONTRIBUTING.md:44]
why this exists

Agents can write code. Finishing is a different problem.

Context is a guess

An agent may or may not find the CONTRIBUTING file, the Makefile target, or the CI workflow that decides whether the change is acceptable.

Self-review is not review

Asking the model that wrote the change whether the change is done produces an opinion, not evidence.

CI is the wrong place to learn

By the time CI rejects it you have paid for the agent run, the review, and the round trip.

what it will and will not say

Five verdicts, and three of them are refusals.

PASS
established

a declared check ran and accepted the tree

FAIL
established

a declared check ran and rejected it, with the failing locations

BLOCKED
refusal

the check exists; this machine cannot run it

INCONCLUSIVE
refusal

it ran, and the result does not settle the question

NOT-RUN
refusal

this repository declares no command that verifies a change

The last three exist because a verifier that cannot say “I don’t know” will eventually say something false instead. A missing Go toolchain is a fact about your laptop; plumb will not turn it into a claim about your code.

try it on your own repository

What it will tell you in twenty seconds.

Reading costs nothing and needs no license. Point it at a repository you already have and it answers three things you probably cannot answer from memory:

plumb
Which checks does this repository actually declare — and can this machine run them?

Every command, where it is declared, and which ones are blocked because a toolchain is missing here.

plumb rules
What does it require of me that nobody told me?

The obligations its CONTRIBUTING and AGENTS files state, quoted with a file and a line.

plumb coverage
Which of its checks does the documentation never mention?

A contributor following the docs would never learn these exist.

If the answer is “this repository declares nothing”, plumb says that too, and says it is a statement about the repository rather than a failure of the tool.

what has been tested, and what it does not establish →
architecture

It does not need a service to work.

repository analysisLOCAL
verificationLOCAL
license verificationLOCAL
model / API callsNONE
telemetryNONE
repository uploadNONE
update checkASKS FIRST

One exception, named rather than buried: plumb update fetches a signed manifest to see whether a newer release exists. It asks before it ever does, declining buys silence, and nothing else in plumb has a reason to reach anywhere.

A license is a signed file, verified on your machine against a key compiled into the binary. There is no license server, so there is nothing to be unreachable. A license can be carried onto an air-gapped machine on a USB stick and it verifies there.

You do not have to take that on trust. Run plumb with Wi-Fi off, or watch the active interface with tcpdump. Source code would be weaker evidence for a runtime claim than the test you can run yourself.

where it sits

Keep your agent.

plumb is not another model and does not write code. It hands an agent a deterministic map of the repository, and afterwards runs a check the repository itself declares. Registered with plumb init, it runs when the agent says it has finished — and blocks only on FAIL, so a missing toolchain reports BLOCKED and lets the turn end.

No flags, and no agent to name. It merges into configuration files you already own rather than replacing them, and plumb init --undo removes exactly what it added.

plumb init — one command, both agents
  wrote    .claude/settings.json      new file
  wrote    .codex/hooks.json          new file
 
plumb now runs when an agent says it is done, in tmp.rqxqjIA9bn.
It blocks only on FAIL -- a missing toolchain reports BLOCKED and lets the turn end.
One check, up to two minutes. Raise it with PLUMB_HOOK_TIMEOUT if your suite needs longer.
the obvious questions

What you are probably thinking.

Can’t I just tell my agent to run the tests?
Yes — if you already know which commands matter, and you are willing to repeat that for every repository you touch. plumb is for the step before that: finding what this repository declares, where the requirement came from, whether it can run here, and what the result actually establishes. On a project whose whole story is one test script, you do not need it.
Isn’t this just a hook?
A hook decides when something runs. It has no opinion about what your repository requires — you still have to tell it, and keep telling it as the repository changes. plumb answers that question, and installs into the hook you already have.
What if my repository declares nothing?
Then plumb says so, plainly, and runs nothing. That is a real outcome and it is common — five of the author’s own seven projects return exactly that. You will find out in about twenty seconds, and finding out is free.
It is a closed-source binary. Why would I trust it?
Do not. Test it. Run plumb with Wi-Fi off, or watch the active interface with tcpdump while it works. The central claim — that it makes no model call and no network request — is the rare one that is stronger tested than read. Source code would be weaker evidence for a runtime claim than the ten seconds it takes you to check.
price

Reading is free. Running costs money.

plumb, rules, coverage, explain, context and init execute nothing and are free permanently. plumb check executes a command, and that is the paid one.

curl -fsSL https://plumbing.sh/install | sh