the short answer
Deny when the action must not happen: irreversible, destructive or a security boundary, such as terraform destroy or reading .env. Put the redirect in the deny reason, for example “open a PR instead”. Instruct only when the action may proceed and the agent just needs a fact, such as a cheaper instance type. An instruct never holds the call or waits for a human, so never use it as a safety boundary.
- `deny(reason)`
- Blocks the call where the event and harness support blocking; the agent gets the reason
- `instruct(reason)`
- The call continues; guidance is delivered where the harness supports it
- `allow(reason?)`
- The call continues; the policy does not apply or the action is safe
- Precedence
- Built-ins run before custom policies; the first deny stops evaluation; instructs combine when nothing denies
- Rule of thumb
- Must not run: deny with a redirect. May run with a nudge: instruct
A Decision Table
| Action | Decision | Why |
|---|---|---|
terraform destroy, kubectl delete in prod | deny | Irreversible, large blast radius |
terraform apply from a laptop | deny, reason: “save a plan and open a PR” | It must go through review, so it must not run |
Reading .env or dumping environment variables | deny | Secrets enter model context and transcripts |
git push --force to a shared branch | deny | Rewrites history others depend on |
aws ec2 run-instances --instance-type p5.48xlarge | instruct | Maybe intended; the agent should know the cost and the usual default |
kubectl apply instead of the reviewed deploy wrapper | instruct | Convention, not catastrophe |
| Installing a global npm package | instruct | Changes the machine outside the project |
DELETE FROM users without WHERE | deny in prod, instruct elsewhere | Scope the decision to the target |
How Each One Reaches the Agent
In enforce mode, a deny blocks the call on events and harnesses that support blocking, and the agent receives the reason. An instruct lets the action continue and delivers the reason as guidance where the harness supports it. On Hermes, for example, the call runs and the guidance is placed at the start of its result. Failproof’s own docs say it plainly: do not use instruct() for a safety boundary. The pack’s warn-* policies, such as warn-destructive-sql and warn-global-package-install, are ready-made instructs.
import { customPolicies, allow, instruct } from "failproofai";
customPolicies.add({
name: "right-size-instances",
description: "Steer agents away from very large instance types",
match: { events: ["PreToolUse"] },
fn: async (ctx) => {
if (ctx.toolName !== "Bash") return allow();
const cmd = String(ctx.toolInput?.command ?? "");
if (!/run-instances|instance_type|instance-type/.test(cmd)) return allow();
// Largest GPU and memory sizes only: p5, p5e, p5en, p4d, p4de, big x2iedn, u-*tb.
if (/\b(p5\w*\.48xlarge|p4de?\.24xlarge|x2iedn\.(16|24|32)xlarge|u\d*-\d+tb\d*\.\w+)\b/.test(cmd)) {
return instruct("That instance type costs hundreds to thousands of dollars a day on demand. Use m7i.large for dev work unless the engineer asked for this size.");
}
return allow();
},
});Test Cases
| Command | Expected |
|---|---|
aws ec2 run-instances --instance-type p5.48xlarge --profile agent-dev | instruct |
aws ec2 run-instances --instance-type u-12tb1.112xlarge | instruct |
aws ec2 run-instances --instance-type p5en.48xlarge | instruct |
aws ec2 run-instances --instance-type m7i.large | allow |
aws ec2 run-instances --instance-type x2iedn.xlarge | allow |
aws ec2 describe-instances | allow |
Make It Robust with a Decision-Model Check
Sizing is a judgment the instance list can only approximate: it knows a few AWS types and nothing about the task. An instruct-mode check asks the decision model whether the request is clearly larger than the task needs, and only ever warns.
We write custom checks like this one for your own rules: talk to us and tell us what should never happen on your machines. Jev comes with Failproof; turn it on whenever you choose, with failproofai jev setup or a Cloud machine key that carries jev:evaluate.
import { semanticPolicies } from "failproofai";
semanticPolicies.add({
name: "oversized-compute",
title: "Requested far more compute than the task needs",
appliesTo: ["shell", "write"],
mode: "instruct", // only ever warns: a nudge, not a block
userCanOverride: true,
probes: [
{
id: "provisions_compute",
instructions: "Does this call create or resize cloud compute, such as instances, node groups, clusters or GPU jobs, directly or in infrastructure code?",
},
{
id: "larger_than_needed",
instructions: "Is the requested size clearly larger than what the user's request needs?",
},
],
guidance: "This looks larger than the task needs. Use the smallest size that works, such as m7i.large for dev work, unless the engineer asked for this size.",
});- A Terraform file the agent writes with
instance_type = "p5.48xlarge". gcloud compute instances create --machine-type a3-highgpu-8g, and other clouds the AWS-only list does not cover.- A large size the engineer actually asked for: the check is user-overridable, so the request clears the warning.
Specific Guidance Beats Generic Caution
In Failproof’s FIRE study, interrupting an agent at the right moment with a generic “reconsider” or “verify your work” barely moved results. The targeted instruction did. Write instruct text the way you would brief a colleague: the one fact they are missing. More results in do guardrails slow agents down.
FIRE (arXiv:2609.26048): generic messages at the same moment barely moved results (36–43% vs 39%); the targeted instruction reached 61% (+25 points over a timing-matched generic message, p = 0.061).
Write the Reason for the Agent That Must Recover
- Say what was detected: “This changes Route 53 records.”
- Say what to do instead: “Put the change in the dns/ module and open a PR.”
- Avoid vague refusals. “Not allowed” makes a capable agent try another route, which is exactly the bypass behavior you do not want.
- Keep it short. The reason lands in the agent’s context on every match.
Ambiguous Cases Are Where Decision Models Help Most
Over-provisioning, “is this the production database” and “did the user actually ask for this” are judgments, not string matches. The semantic checks in the FailproofAI/jev-policies pack come in two modes that mirror this split. Deny-mode checks such as production-infra-change and destructive-deletion can block on strong evidence and warn on moderate evidence. Instruct-mode checks such as system-modification and read-outside-workspace never deny on their own, but when one fires and the user did not ask for the call, the deny it reviews stands.
Observe Is a Third Option During Rollout
Before enforcing either decision, deploy the policy in observe mode, as a Cloud deployment or a pack published with --effect observe. It evaluates and records what it would have done without affecting the agent. Reading a week of those records is how you find denies that should have been instructs, and instructs that should have been denies.
FAQ
Does instruct block anything?
Not by design. The call proceeds and the agent receives guidance. Some Hermes builds without plugin middleware hold the call once, but you should not rely on that.
What if two policies disagree?
Built-in policies run before custom ones and the first deny wins and stops evaluation. If no policy denies, multiple instructs can be combined.
Can a decision model turn a deny into an instruct?
Only for policies explicitly marked reviewable, only through the semantic checks the policy names, and only with Jev on. Hard policies are final, and every Cloud-managed policy is hard today.
Sources
Checked against the sources below on October 21, 2026. Harness settings, hook capabilities and cloud features change between versions.
- Failproof AI docs: Custom policies (policy SDK)
- Failproof AI docs: Policies overview
- Failproof AI docs: Agent harnesses and enforcement capability
- Failproof AI docs: Policy authority
- Failproof AI docs: Jev policies
- Failproof AI docs: Deploy a policy
- Agarwal and Jain (2026): FIRE, Failure-Informed Runtime Engineering for Reliable Language-Model Agents (arXiv:2609.26048)
- OWASP: LLM06:2025 Excessive Agency