neti counts what a tool call will touch before it runs. If the count is over a ceiling you set, the call does not happen.
remove_group_members(group:
)
Your permission system sees a group name on an allowlist, and the name is allowed. neti sees 412 people losing access to 9 applications.
The agent gets a number back, not a refusal. That is what makes it narrow the target and try again instead of giving up or working around it. Nothing about the gate reaches the prompt.
remove_group_members(group: "engineering-all")Every image on this page is generated from output the test suite pins byte for byte. If the product stops printing this, the build fails until the picture is regenerated.
No threshold to tune. No model in the decision path. Nothing calibrated on a corpus.
A resolver turns the argument into a count. The count is compared against bands you committed to a file. Where a call touches several things, the verdicts join and the most restrictive one wins.
It fits. Nothing is said, so your existing permission rules keep working.
It runs, and the record marks it as notable.
Somebody other than the operator should decide this one.
It does not run, and the reason carries the number.
Two rules make a verdict sound. No resolver may return 0 for something it could not reach, because an unreachable target and an empty one are opposite situations with the same number. And anything capped or estimated reports a lower bound, which can block safely but can never allow. The targets too large to count are the ones that cannot slip through quietly.
Before any traffic, before any ceilings, and without waiting a week to find out.
This is not a measurement of one call. It is a bound on what the agent could do in a single call, which is the number nobody has. On day one it usually reads: this agent holds a credential that can reach 52,400 people and 214 applications, and nothing today would stop it.
It ships in observe mode with every band left blank on purpose.
A ceiling nobody chose is a ceiling nobody will defend the first time it fires. So the numbers arrive a week later, out of what you actually did. It shows its working, including where the statistics disagree with themselves. You edit the numbers and commit them. Nothing it learns ever reaches the decision path.
A seam without a resolver is a place to write "allow", so this list is the real measure of coverage. Adding one is about 80 lines, and it is the contribution that matters most.
| resolver | unit | one call resolves | cost |
|---|---|---|---|
| fs.paths | objects | a path, directory or glob | local walk, capped |
| db.rows | rows | DELETE or UPDATE, via select count(*) | one scan |
| storage.objects | objects | s3://bucket/prefix | paginated, capped |
| github.repos | repositories | an owner, and every repo under it | one request |
| github.files | objects | owner/repo, files on the default branch | one request |
| entra.principals | principals | a group, everyone in it, nested included | one $count |
| entra.apps | apps | a group, applications assigned to it | one $count |
| entra.guests | principals | a group, the external members only | one $count |
| terraform.destroy | resources | a plan, what it would destroy | local read |
Every runtime has one place where a request becomes an execution. neti sits at that place, so a blocked call comes back to the model as a tool result with the number in it, never an exception that kills the run.
The same call through every door reaches the same verdict, the same magnitude and the same sentence, byte for byte. That is a test, not a claim. A verdict that depends on which door a call arrived through is a bug in the product.
The only seam that can see a harness's built-in tools: Read, Edit, Glob, Grep, Write. No proxy can, because they never travel over a protocol. It is also where the gate is most effective, covering 6 of 10 built-ins against 25 of 160 MCP tools.
Whatever command launched the server becomes an argument to neti gate. The client is not modified, not configured, and not aware, which is why this list can be long without any of it being work.
Four of these have a before-tool callback the gate attaches to. The rest wrap the one method that executes and copy name, description and schema across verbatim. An agent must not be able to tell a gated tool from an ungated one by looking at it.
Those two lists are not equally strong, and the scorecard keeps them apart rather than adding them up. An SDK adapter was driven by that comparison. An MCP client above was never run. What is tested is that neti gates a real MCP server, and that Cursor speaks MCP is a fact about Cursor.
neti answers how big, not whether this is a good idea. The gaps are numbered so tests and write-ups can cite them, and the numbers get published whether or not they flatter.
rm -rf $X deletes is a
grammar, not a value, and a gate that guesses is worse than no gate.neti suggest asks a model about the rest, with your key or with a
model running on your own machine, and writes a commented-out fragment a person has to
uncomment. Nothing a model says reaches a decision.All of it is written down in SCOPE.md before you install anything, next to the sentences this project does not say.
On one machine there is nobody to ask, so the gate stops the call. That is correct, and a free install will keep doing it forever.
neti cloud is the somewhere that question can go.
The console, on the synthetic tenant. The rows carry their own "synthetic" tag and the demo badge because nothing here was edited.
The narrowest thing it can. A grant that means more than the person thought is the failure that matters here. Each of these is a test in the public repository.
One policy version across every machine, signed and pinned by digest, instead of a file per laptop that drifts. Session budgets that survive a restart. Per-machine record chains anchored centrally, so a deleted local file becomes detectable rather than simply gone.
Your records stay yours. The local file is the
source of truth. A control plane anchors it and is not an authority over it, and
neti verify works with the network unplugged.
That is the whole rule and it decides every case. Enforcement runs on one machine, so blocking is free. Forever, with nothing withheld and nothing time limited. A second person approving a call does not run on one machine, so it is not.
Every item on the second row is a gap SCOPE.md already documents, or work that only exists because more than one person did it. We are not inventing limitations to sell past.
Nothing is blocked until you write a number. The worst case of installing it is one extra hop.
$ pip install "neti[all]" $ neti demo --hereNothing is blocked by either command. The install itself is checked: just
e2e walks this whole page against the published wheel and asserts every number.