← All posts
SOOT · BLOG

A valid signature is not permission

A signed mandate and a settled payment prove the request was not changed in flight. They do not prove a person allowed it. In an agent that spends money, the order of the checks and the place you put the gate are the product.

Suppose a payment goes out twice this morning and every line in the log says the same thing: verified.

The request was signed. The mandate checked out. The charge settled. No step in the pipeline errored, so nothing paged anyone. Then the operator opens the run and asks the only question they actually care about — was this agent allowed to spend this amount, to this payee, today — and the tooling answers with a list of everything that could be wrong. Not a yes. Not a no.

The signature was never the question. It is the thing that made us stop asking the question.

Verified answers a narrower question than it feels like

A valid signature means the request arrived unmodified and came from whoever holds the key. That is worth having, and in an agent stack it is easy to produce. It says nothing about whether that holder was allowed to make this exact request, on this account, for this amount, at this time.

The distinction is old in payments and it keeps getting flattened in agent pipelines, where one green check stands in for three different facts: the request was not tampered with; the mandate covers it; a person approved it. Only the third one is a control you can point at a week later when someone asks who decided. It is worth reading the argument stated plainly: a signature can be valid and still not be authorized (Signature valid is not authorized).

Verified is a statement about transport and keys. Allowed is a statement about authority. Systems that log the first and mean the second are not lying; they are answering a question nobody asked while the real one stays open.

The order of the checks is what you build

Once you accept that a signature is an input and not a verdict, the design question changes. It is no longer "can we verify the mandate" — you can — but "in what order do the checks run, and what do I get when one of them says no". That ordering is the product, not a detail of it, which is the point of The order of the checks is the product.

A spending agent has five questions in front of it, and each one has its own answer:

  • Who is asking. Which agent, which run, which operator it belongs to.
  • What may it do. The tool and the verb — read a balance is not the same tool as send a payment, and the check belongs on the second one.
  • To whom. The payee or destination, compared against something that was written down before the run started, not inferred from the request.
  • How much. The amount, and the amount already spent in the same window, because three payments that are each inside the cap are not inside the cap together.
  • When. Expiry, the window, the day. A mandate that was right last week is not evidence about this morning.

Order matters because each check narrows the next one, and because where a check sits decides whether it gates anything. An amount check placed after the settlement call is a receipt. A payee check that only runs when the agent asks for confirmation is a suggestion. The cheap, satisfying check — signature valid — must not be the one that short-circuits the rest. Put it first if you like; just do not let it return early.

The other half of ordering is what the operator sees. A checker that dumps every possible warning and leaves the operator to assemble a decision has moved the work, not done it. What you want at the end of the chain is a yes or a no with the reason attached, in a sentence a tired person can read at 07:40.

A label is not a count

The same mistake shows up when nothing is being signed at all. An integration gets described as read-only, the description lives in a config file, and everyone downstream treats it as a property of the system. Then someone actually counts the capabilities and the number disagrees with the label — as in the case of a medical MCP server where the read-only claim and the capability count did not line up (5 capabilities counted, 0 write).

Read that carefully, because the easy lesson is the wrong one. The lesson is not "the count was zero, so it is fine". It is that the zero had to be produced by a measurement rather than assumed from a description. If your gate reads the label, the label is your entire security model, and nobody has ever audited a label.

In practice: derive permissions from what the tool can actually do, re-derive when the tool changes, and keep the derived answer where the gate can read it. A read-only claim in a doc and a read-only grant in the runner are two different objects. Only one of them refuses a write.

What a failed call leaves behind

There is a third shape of the same bug, and it is the one that survives the incident review. A call fails — times out, errors, gets killed mid-run — and the operator concludes that nothing happened. Often something did: a record was created, a job was claimed, an entry exists that somebody has to reconcile. A failed call is not an empty one, which is exactly what What a failed call leaves behind is about: two agents, their records, and the separate question of who can count them.

For a spending agent that means the log has to distinguish attempts from effects. "The call failed" and "nothing was written" are different claims, and only one of them is usually true. If you cannot answer "how many payments did this agent attempt today, and how many landed" without grepping three services, you do not have the count, and if you do not have the count you cannot have the cap.

Where the gate goes in a box you host

The gate belongs at the tool boundary, before the side effect, where all five facts above are still in hand: the mission, the granted tool, the arguments, the caller, the clock. Past that line you are writing history, not policy.

That gives a short, unglamorous checklist for a self-hosted agent:

  • The payee list is read from the same place the grant is read from, so there is one answer, not two.
  • The amount cap carries a window, and the window's running total is counted from effects, not attempts.
  • The tool that moves money is a separate tool from the tool that reads a balance, with a separate grant.
  • The approval step blocks the call instead of racing it. A call that waits is reviewable; a call that warns and continues is already done.
  • Every refusal records which check refused and what value it saw, so the answer is reproducible next week.

And one habit that matters more than any of the above: the reason shown to the person approving has to name the payee, the amount, and where the money goes. "Allow once" in a dialog trains people to click. A line that says *new payee, added yesterday, not on the list* gets read.

What Soot does in this lane

Soot is in preview, and it does one narrow thing in this argument: a Soot receives a mission and a set of granted tools, and a tool can be declared as needing operator approval. When it is, the call waits in Nest until a person decides; it does not run and report afterwards. soot check --show reads the grant that Soot currently has, so the answer to "what is this agent allowed to touch" is a command, not an interview.

# read what this Soot is actually granted right now
soot check --show

Soot Forge is a catalog of packs — skills and tools you can install — and installing a pack does not grant the tool it contains. That separation is the same idea as everything above, at a different level: the catalog describes what exists, the grant decides what this agent may do, and the operator's approval decides what happens today.

No pricing is published and there is no hosted service; running a Soot costs what your provider and your own machine or VM cost. What Soot is not, today, is a system that decides for you whether a mandate is good. It is the place where the decision can be made and read back.

The question worth designing for

Not "was this signed". Not "did it settle". The question is *who decided this was allowed, on what grounds, and can you show me that tomorrow*. Everything in this post — the order of the checks, the count behind the label, the difference between a failed call and an empty one, the gate before the side effect — is a way of making that question answerable with one sentence instead of a list.

Start with the tool that can move money, and write down the five answers for it before you grant it to anything.