The best guardrail is the one outside your agent
Instruction files tell an agent how you want work to flow. They are the wrong place to put a rule that must never be broken.
Open almost any CLAUDE.md or AGENTS.md file today and you will find a section like this:
Do not commit directly to main.
Never delete the main branch.
Every change goes in a new branch.
Ask before running destructive commands.
These files are genuinely useful. They give an agent local context, name the conventions of the repo, and describe the workflow you want followed. Most of the time, the agent follows them.
Most of the time is the problem.
A rule written in an instruction file is a rule interpreted by a probabilistic system. It competes for attention with everything else in the context window. It can be misread, deprioritised under a long task, or technically followed in a way you did not intend. For a preference, that is fine. For something that must never happen, it is the wrong place to put the rule.
The stronger version of that rule is not a better-worded instruction. It is a setting in GitHub.
Move the rule to the system that owns the consequence
Protect the main branch and the shape of the problem changes.
Block direct pushes. Block force pushes. Disallow deletion. Require a pull request, with status checks and review before merge. GitHub’s documentation is blunt about the effect: by default, you cannot delete a protected branch. GitLab reaches the same outcome by setting Allowed to push and merge to No one, which routes every change through a merge request.
Now trace what happens when the agent gets it wrong.
It tries to push to main. The push is rejected. Not because the model remembered the instruction, but because the operation is not available to it. The agent reads the error, creates a branch, and opens a pull request. The workflow you described in prose is now the only workflow that exists.
The instruction did not become useless. It became an optimisation. It tells the agent the right path on the first attempt instead of the second. But it is no longer the thing standing between you and a rewritten history.
That is the distinction worth internalising:
Instructions describe what an agent should do. Permissions determine what it can do.
One is guidance. The other is enforcement. Problems start when a team writes guidance and believes it has built enforcement.
The same move applies everywhere an agent touches a real system
Git is the clearest example because the controls are mature and free. The pattern generalises.
Databases. Connect the agent with a role that is not the owner, and grant only the operations it actually needs. PostgreSQL lets you grant SELECT, INSERT, UPDATE, DELETE, and TRUNCATE separately. An agent doing read-heavy analysis has no reason to hold DELETE. Ownership carries destructive powers like DROP and schema modification, so keep it away from the agent’s role entirely and route schema changes through a migration identity or a reviewed deployment.
Cloud identity. AWS permissions boundaries set the maximum permissions an entity can have. Effective permissions become the intersection of the identity policy and its boundary. This matters for agents specifically, because it survives the case where something later attaches a broader policy. The ceiling holds.
APIs and MCP tools. Issue a scoped credential rather than a general one. Expose only the operations the task requires. A tool wrapper that never implements delete is a stronger control than a tool description asking the model not to call it. For consequential operations, put an approval step in the path rather than a warning in the prompt.
OWASP’s Securing Agentic Applications Guide frames the input layer the same way, describing deterministic controls on inputs and access at the API gateway. Deterministic is the operative word. The control does not evaluate intent. It evaluates whether the operation is permitted.
Where this argument stops
Three honest limits, because the pattern gets oversold.
A guardrail is only as strong as its bypass path. This is the one that quietly breaks most setups. Protected branches do nothing if the agent is authenticated with an admin token that carries bypass permission. A restricted database role does nothing if the connection string in .env is the owner. An MCP allowlist does nothing if the agent also has unrestricted shell access and can reach the same system another way. Before trusting a boundary, check what credential the agent is actually holding and whether that credential can turn the boundary off. A rule your agent can disable is a preference.
Enforcement does not create a recovery path. A rejected push does not create a branch or open a pull request. It produces an error. Whether the agent recovers gracefully or thrashes against the wall depends on the instructions and orchestration around it. This is exactly why instruction files stay valuable. They own the recovery behaviour that permissions cannot express. The two layers are complementary, not competing.
Not every risk is a permission. Prompt injection, data leakage, and suspicious multi-step behaviour are contextual. They do not reduce to an allowlist, because the individual operations may each be legitimate. Those need controls inside the application or agent runtime, where there is enough context to evaluate a sequence rather than a single call. OWASP makes this point too: application-level guardrails often have better context for detecting multi-turn attacks.
So the claim is not that every guardrail belongs outside the agent. It is narrower and more useful than that:
Every hard invariant should be enforced outside the model, as close as possible to the system that owns the consequence.
A practical starting point
Take the instruction file you already have and read it with one question in mind: which of these rules would cause real damage if ignored once?
Those are your invariants. For each one, find the place in the real system where it can be enforced. A branch rule, a role grant, a token scope, a tool definition. Move it there. Leave the prose behind as workflow guidance, because it still earns its place.
Then check the credential. If the agent holds something that can bypass what you just configured, you have documentation, not a boundary.
The goal is not an agent that always behaves correctly. That is not a property you can guarantee of a probabilistic system. The goal is a system where incorrect behaviour cannot reach anything that matters.
Give the agent the capability to do its job. Not the capability to do damage, plus a note asking it not to.
References
- About protected branches, GitHub Docs
- Available rules for rulesets, GitHub Docs
- Protected branches, GitLab Docs
- Permissions boundaries for IAM entities, AWS IAM
- Privileges, PostgreSQL Docs
- Securing Agentic Applications Guide 1.0, OWASP