DEV Community

Sho Naka
Sho Naka

Posted on

I Stopped Asking AI Agents to Behave: 3 Permission Levels Instead

An AI agent merged work that I expected to review myself.

The tests passed. An existing auto-merge path ran. The deployment started. A stale authentication setting made that last step fail, so production did not change.

That was luck, not a control.

TL;DR: Do not decide an AI agent's permissions by how much you trust it. Decide them by reversibility. I now use three levels: delegate reversible work, require confirmation for recoverable work, and block high-impact work at execution time.

Quick answer: what are the three permission levels?

Level Examples What makes it belong there Control
Delegate freely Research, code generation, drafts, pull requests I can discard or redo the result without another party being affected Normal machine checks
Require confirmation A merge after checks, an external draft, an internal announcement, bulk test-data updates It can be undone, but a person must notice and do work to undo it A record plus explicit confirmation
Block at execution Production deployment, an external charge, public release The action creates a fact outside the workflow that cannot be fully recalled A tool-level block and a human-owned path

The point is not that every merge is always irreversible. The point is to inspect the whole chain. A merge that triggers a deployment is not the same operation as a merge that stays in an isolated branch.

What actually happened?

I had written a rule equivalent to: "Do not automatically merge into production. Only do that with explicit user instruction."

The agent did not ignore the words. It took a reasonable path through them. In its view, waiting for checks and then using the existing auto-merge mechanism was a cautious continuation of the task. In my view, the merge was the moment I would make the final call.

Those two views never met in an enforceable place.

The dangerous part was that no individual step looked absurd:

  1. Fix the test and the configuration.
  2. Run checks.
  3. Let the configured merge path continue.
  4. Start the deployment chain.

The failure was not a malicious agent. It was an unclassified chain of operations with no stop point for a human decision.

Why not use a simple safe/unsafe split?

I first tried a binary policy. That made anything slightly hard to undo look unsafe. Soon the agent could do almost nothing without approval, and I was back to reviewing every small action.

The middle tier is what made the policy useful.

Some operations are not harmless, but they are recoverable: a draft posted to an external service, a message sent to an internal channel, or a bulk update to test data. I want an audit trail and a clear "is this right?" point, not a permanent ban.

The highest tier is different. A public release can be read before it is deleted. A production deployment can trigger follow-on effects. A charge can move money even if it is later refunded. These are not places where a better prompt is a sufficient control.

Trust is about the agent. Permission is about the consequence of an action. Keep those decisions separate.

Put the block below the prompt

The useful guard has to run where the command is about to execute, not only in the wrapper script the agent is supposed to choose.

Here is a deliberately minimal example. It reads a proposed command from standard input, so it can sit behind a tool hook or a wrapper. The integration point will differ by agent platform, but the policy is executable:

#!/usr/bin/env python3
"""Reject commands that cross a human-owned boundary."""
import sys

command = sys.stdin.read().lower()
blocked_patterns = (
    "merge production",
    "deploy production",
    "create charge",
)

if any(pattern in command for pattern in blocked_patterns):
    print(
        "BLOCKED: this action is high impact. "
        "Use the human-owned release path instead.",
        file=sys.stderr,
    )
    raise SystemExit(2)
Enter fullscreen mode Exit fullscreen mode

You can try the behavior without connecting it to an agent:

printf '%s' 'deploy production' | python3 guard_irreversible.py
Enter fullscreen mode Exit fullscreen mode
BLOCKED: this action is high impact. Use the human-owned release path instead.
Enter fullscreen mode Exit fullscreen mode

This is intentionally not presented as an adversarial security boundary. A command-string check can miss aliases, wrappers, or a determined attempt to evade it. The stronger boundary is to remove the underlying credential or capability from what the agent can reach.

But it is still a meaningful operational control. It turns the straightforward, accidental command path into a visible routing error before the command runs. The message tells the agent and the operator what happened and where to go next.

Classify chains, not just commands

The classification becomes more useful when you look beyond the command you can see.

For example, changing a file is usually freely delegated. Merging that change might be recoverable. But if the merge triggers a production deployment, the meaningful boundary is not the file edit or the merge alone. It is the chain that ends in an externally visible change.

I now ask three questions whenever a new class of action appears:

  1. What can this action reach on its own?
  2. Can the consequence disappear by itself, be recovered by a known procedure, or remain as an external fact?
  3. Which control belongs immediately before that consequence: none, confirmation, or block?

The classification is not permanent. An action can move outward after we learn it is easier to recover than expected. It can move inward after a near-miss reveals a downstream effect we did not model.

Changing the classification itself is high impact in a team. Treat a request to relax a boundary as something that needs the same kind of review as the boundary protects.

Start with one guard, not a perfect policy

Trying to enumerate every dangerous command before shipping any guard is a reliable way to ship nothing.

My practical loop is smaller:

  1. List the recurring actions an agent takes.
  2. Put each one in one of the three tiers.
  3. Add one hard block for the specific high-impact action that produced a near-miss.
  4. Make the block explain both the reason and the allowed human-owned route.
  5. Add or revise a rule only after a new class of near-miss teaches you something.

That makes the policy a growing record of actual operational lessons, not a giant prompt full of exceptions.

FAQ

Does this mean every agent action needs approval?

No. That is exactly what the three-tier split avoids. Reversible work should stay fast. The confirmation tier preserves a human choice where recovery costs attention. Only the highest-impact actions are blocked.

Is a merge always in the same tier?

No. Classify the real chain. A merge that cannot reach production is different from one that automatically starts a deployment or changes a customer-facing system.

Is the Python guard enough?

No. It is a useful operational guard against the ordinary path, not a complete security boundary. For irreversible capabilities, reduce the permissions and credentials the agent can access as well.

What should I do in an emergency?

Do not add an agent-accessible "emergency" bypass to the block. Give an authorized human a separate, documented path. Otherwise the exception becomes the normal route over time.

Copy-paste checklist: add your first stop point

Pick one operation an AI agent performs today that would be costly to reverse: a deployment, a public post, an external message, a billing call, or a customer-data overwrite.

Classify the whole chain. Then put one stop point immediately before the external fact is created. You do not need a perfect wall on day one. You need the first floorboard before the place where luck was protecting you.


Sho Naka (nomurasan). I build and operate AI-assisted workflows. This piece was adapted from a Japanese essay with AI assistance for the cross-language rewrite; the incident, reasoning, and conclusions are mine.

Top comments (1)

Collapse
 
topstar_ai profile image
Luis

Great perspective. Moving from “telling agents how to behave” to defining clear permission boundaries is a much more scalable approach. As AI agents become more autonomous, trust should come from architecture and controls rather than just better prompts.

The three-level permission model is a practical way to think about agent safety: limiting access, reducing unintended actions, and making agent behavior easier to audit. This is similar to how we design secure systems with least privilege and controlled capabilities.

The future of agentic systems will likely depend less on making agents “smarter” and more on giving them the right tools, context, and constraints. Great reminder that good AI engineering is also good systems engineering.