Blog

Runtime guardrails decide allow, ask, or deny before the tool runs

I leave coding agents on a machine I am not watching. The failure I care about is a tool call that hits the shell, a file, or the network before anyone sees it.

Agents generate actions. rv authorizes them before they run.

If you want the install path first, it is on the docs. This post is how the check works, and where it stops.

What a runtime guardrail is

A coding agent plans in text. Then it emits an action: run a command, write a file, open a socket, call an MCP tool. The host is supposed to fire a hook before that action lands. The guardrail reads the action and returns a verdict. The host is supposed to honor the verdict.

rv is that guardrail. You launch the agent through it. The hook is the only moment the action is visible. I wrote the miss in If a hook never fires, the action already ran. Read that if you only take one other page.

The unit of work is the tool call: the name, the arguments, on a surface the host actually showed. rv does not score the model's personality. It does not rewrite the prompt.

I keep a small picture of the path in my head:

agent emits action
        |
        v
 host fires tool-call hook  -->  if no hook, the action already ran
        |
        v
 rv reads tool, args, surface
        |
        v
 allow | ask | deny | observe
        |
        v
 host runs it, or does not

That is the path. Everything else is setup.

Here is the boring case. The agent writes cleanup.sh with a delete inside it. Then it runs bash cleanup.sh. The first tool call is a file write. The second is a shell run. Each one is a separate chance to allow or deny. If I only read the chat, I see "I'll clean up the temp files." If I read the hook, I see the path and the command.

Why the prompt is the wrong place

Prompt text vs the hook was the first confusion I hit. I tried to solve this with system text. "Do not delete the disk." "Do not touch .env." The model can agree and still emit rm -rf / on the next tool call.

The prompt is a suggestion to a generator. The hook is a gate on an action. I want the gate.

A prompt filter also misses that cleanup script. The prompt never contained the delete. The tool call did. The hook sees the command. The prompt never will.

Packs live next to the installed binaries. That file is not a prompt. It is the rule the hook evaluates when an action arrives. Policy only matters if the hook fired. A clean policy file with a silent host is a comfort object.

When I leave the room, nobody is there to read a paragraph of model reasoning. Somebody has to allow or deny the tool call before it executes. If I cannot name the verdict I expect, I am hoping the model behaves.

Allow, ask, deny, observe

When the hook fires, rv returns one of four verdicts.

allow means the action may run. The host continues.

ask means a human is supposed to decide. On a coding host, leftover unused ask is allow. That includes Grok and OpenClaw. There is no ask UI on those hosts. If you are unattended or in CI, leftover ask hardens to deny. I treat ask as a draft verdict, not a fence, until I know which mode I am in. This is the line that bites people who copy a laptop policy onto a box they do not watch.

deny means the action does not run. You should see that in the session. I use rm -rf / as the hard fence when I check the wiring.

I recorded it once on OpenCode.

OpenCode deny GIF

If you do not see the deny, stop. The hook never fired.

observe means the action may run and rv records the decision. I use it when I want evidence without blocking. It is a recorder. It will not save you overnight.

I want those four names in the session. If I cannot point at one of them, I do not have a runtime guardrail. I have a note I wrote after the fact.

You can see a decision without running the agent:

rv test "rm -rf /"
rv explain "rm -rf /"

That is the same evaluator. It does not prove the host will fire the hook. It proves the policy knows what it would say. I run test when I change local packs. I run the OpenCode deny when I change the host path. Those are different checks.

What gets checked

The hook hands rv an action. What gets checked is the tool, the args, and the surface.

Tool: the name of the call. A shell run, a file write, a network request, an MCP tool. MCP authorization only happens if that call arrived through the hook. An MCP server you added last week is not special. If the host does not emit the call, rv does not see it.

Args: the concrete string. rm -rf / is different from rm -rf ./tmp. Policy matches what the host emitted, not what the model claimed it would do. A plan can say "safe cleanup" while the tool call says the root path. The args are the truth.

Surface: command, file, secrets, network, MCP. Those are the surfaces rv is built to see. A thought that never became a tool call is not a surface. A retry the host runs without asking again is not a surface unless the host hooks it. A binary the agent invokes by absolute path can sit outside a given surface. So can traffic that never hits the proxy.

On macOS, rv can attach Seatbelt. On Linux, Landlock. Windows is wrapper and hook only. rv doctor reports what the platform can do. It does not prove a child session is sandboxed. I run doctor after install. I do not treat a green doctor line as a deny I watched.

Evidence stays on disk. I want to replay a deny later. I do not want to argue with a chat transcript. The transcript is the plan. The evidence is the verdict.

The hook is the fence

You launch an agent you already use. The guardrail sees the actions the host shows. I use the hook when I want a verdict on the tool call in front of me.

A prompt filter is a different layer. If you only change system text, you still have a generator with a shell. I still keep the system text. I do not trust it as the fence.

I record decisions locally so I can see them. That is evidence on disk. It is not a compliance badge and I will not put one on the page.

Absolute-path binaries and non-proxy traffic can sit outside a given surface.

I start from rykanv.com and the docs. The sentence I actually bookmark is still this: if the hook never fires, the action already ran.