<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Sarah Deaton - hooks</title>
      <link>https://sdeaton.com</link>
      <description>On AI, developer tools, technical writing, and whatever else catches my interest</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://sdeaton.com/tags/hooks/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Prompts are not guardrails</title>
          <pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://sdeaton.com/blog/prompts-are-not-guardrails/</link>
          <guid>https://sdeaton.com/blog/prompts-are-not-guardrails/</guid>
          <description xml:base="https://sdeaton.com/blog/prompts-are-not-guardrails/">&lt;p&gt;My husband likes to say “prompts aren’t guardrails,” and it’s been ringing in my head for months, so I figured I’d finally write down my take on it.&lt;&#x2F;p&gt;
&lt;p&gt;I regularly see people who are frustrated, or kind of confused, because they told an LLM very emphatically (in ALL CAPS! Repeatedly! Very seriously!) to never do something… and then it did the thing anyway. This shows up in AGENTS.md lines like:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ALWAYS FOLLOW THE STYLE GUIDE!&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Never make an unsigned commit.&lt;&#x2F;li&gt;
&lt;li&gt;Do NOT push to main. NEVER PUSH TO MAIN.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I’ve also winced my way through stories of LLMs deleting full production databases or wiping someone’s home directory. The frustration or anger when an LLM does the wrong thing makes sense – we told the computer what not to do, gave it examples of the right thing, and clearly stated how bad it would be if it disobeyed.&lt;&#x2F;p&gt;
&lt;p&gt;But, at the end of the day, the model is a probabilistic token generator with a ton of other stuff in its context window. If something really needs to happen or to not happen, more words and rules aren’t the solution. The rule has to live somewhere that can actually stop the model, which is what “prompts aren’t guardrails” means to me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;imagining-myself-doing-the-llm-s-task&quot;&gt;Imagining myself doing the LLM’s task&lt;&#x2F;h2&gt;
&lt;p&gt;I don’t want to anthropomorphize models, but sometimes imagining myself in a similar situation to the LLM, or trying to do the same task, helps me see a little why something might or might not work. (It’s how I realized that &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sdeaton.com&#x2F;blog&#x2F;onboarding-human-made-ai-smarter&#x2F;&quot;&gt;onboarding a human made my AI smarter&lt;&#x2F;a&gt;; if nobody gave me onboarding materials, I wouldn’t understand how to do the job, and I shouldn’t expect differently of an LLM.)&lt;&#x2F;p&gt;
&lt;p&gt;Here’s what I’m imagining in the “prompts aren’t guardrails” scenarios:&lt;&#x2F;p&gt;
&lt;p&gt;Say you asked me to edit a doc, and then handed me a 20-page style guide plus the Chicago Manual of Style and told me to follow all the rules exactly. I can guarantee I would &lt;strong&gt;not&lt;&#x2F;strong&gt; follow them exactly and would miss a ton of rules. I’d read it and I’d try my best, and I’d still miss things.&lt;&#x2F;p&gt;
&lt;p&gt;If you gave me a list of the most important rules that you cared about, I’d probably get closer to respecting those, but might still drop a few items.&lt;&#x2F;p&gt;
&lt;p&gt;If you instead gave me a tool that tells me every time I’ve violated the style guide, and asked me to get that count to zero, I could do that. The tool does the remembering, and my job becomes fixing whatever it flags.&lt;&#x2F;p&gt;
&lt;p&gt;And if you set things up so that I couldn’t even merge a doc until the style guide violations count was at zero, then it would definitely work out, because the doc has no other way to ship.&lt;&#x2F;p&gt;
&lt;p&gt;The first setup depends on me remembering; the second helps me at least prioritize what to remember; the third tells me when I forgot; and the fourth makes it impossible to ship what I forgot. Only that last one is what I’d call a guardrail, and it’s what’s missing when the model doesn’t obey what you told it to do.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;if-only-words-stop-it-there-isn-t-a-guardrail&quot;&gt;If only words stop it, there isn’t a guardrail&lt;&#x2F;h2&gt;
&lt;p&gt;The same thing applies for actions the LLM (or I) should or shouldn’t take. If something should never happen, the system needs to actually prevent it from happening.&lt;&#x2F;p&gt;
&lt;p&gt;Say you don’t want me to SSH into production, but it’s technically possible; I have the keys and nothing is stopping me except the fact that the CTO said everyone needs to seriously stop yeehawing into prod (this may or may not be a real example).&lt;&#x2F;p&gt;
&lt;p&gt;Most days, I’d respect that. However, in the one case where something is broken on prod and SSHing in to fix it would be the fastest path, the rule is something I weigh against the outage and might decide to ignore. The rule and its words, however strongly emphasized, are not the guardrail. If the CTO really wanted me to not do something, the system should have enforced that rather than relying on a memo.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-was-that-even-allowed&quot;&gt;Why was that even allowed?&lt;&#x2F;h3&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sre.google&#x2F;sre-book&#x2F;postmortem-culture&#x2F;&quot;&gt;blameless postmortem cultures&lt;&#x2F;a&gt;, the goal is to look for the system failure rather than blaming an individual person for their choices. If something broke or something bad happened, why was that thing allowed to happen at all?&lt;&#x2F;p&gt;
&lt;p&gt;If someone on the team ran &lt;code&gt;terraform apply&lt;&#x2F;code&gt; and accidentally took down our whole EC2 cluster (another possibly real life example), the useful question is why anyone was able to do that in the first place, and not why that individual wasn’t more careful with reading the terraform output in this one instance.&lt;&#x2F;p&gt;
&lt;p&gt;This is something that could apply to LLM failures as well. Rather than asking the LLM why it didn’t do something you’d asked it to, it might be better to fix it at the root. This matters even more for a model than for a person. After an incident, a person usually remembers what happened and does better next time, but the model comes in fresh every session. Anything it “learns” from being corrected only sticks if it gets written down somewhere, which puts it right back in words.&lt;&#x2F;p&gt;
&lt;p&gt;If it’s something small that’s more of an annoyance (ex: why didn’t you remind me when the job finished running?), it might be worth rewording your skill or prompt a bit and trying again. But everything you actually care about needs to move out of words and into a deterministic check or a system that doesn’t let it happen at all.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;less-convenient-but-safer&quot;&gt;Less convenient, but safer&lt;&#x2F;h3&gt;
&lt;p&gt;Guardrails can make things less convenient. Not being able to SSH into prod is annoying, especially when something is broken and you know exactly what to fix. Branch protection means even a one-character typo fix goes through a pull request. Network restrictions mean the model won’t always have everything I might want in its context, but it also isn’t going to send internal data to a random site.&lt;&#x2F;p&gt;
&lt;p&gt;The small annoyances of guardrails add up to a lot fewer incidents. I sometimes hate the extra work I have to do because of them, but ultimately respect and appreciate that we have systems that prevent the “definitely don’t do this” actions. And often the guardrails are built from accidents of the past.&lt;&#x2F;p&gt;
&lt;p&gt;There are ways to make that tradeoff less painful, too. Grant only what the task needs, like a read-only database user for a model that only has to query data, and you keep most of the convenience without the risk of it dropping a table.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-guardrails-can-live&quot;&gt;Where guardrails can live&lt;&#x2F;h2&gt;
&lt;p&gt;If you’re regularly running into issues with a model ignoring what you’re saying, there are lots of places where you could start looking to address root failures outside of the model.&lt;&#x2F;p&gt;
&lt;p&gt;Most of the ones I rely on in my own work are pretty standard CI. Branch and merge protection keep anything from landing on main without a review and passing checks. &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;vale.sh&#x2F;&quot;&gt;Vale&lt;&#x2F;a&gt; checks the style guide rules that can be pattern-matched, &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lychee.cli.rs&#x2F;&quot;&gt;lychee&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.mintlify.com&#x2F;docs&#x2F;cli&#x2F;commands#mint-broken-links&quot;&gt;&lt;code&gt;mint broken-links&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; catch broken links, and another check looks for anything private that shouldn’t end up public. Here’s where those, and the examples from the top of this post, could live.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;If you want…&lt;&#x2F;th&gt;&lt;th&gt;Put the guardrail in…&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;No pushes to main&lt;&#x2F;td&gt;&lt;td&gt;Branch protection with required reviews and passing checks, plus a Claude Code &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;code.claude.com&#x2F;docs&#x2F;en&#x2F;permissions&quot;&gt;permission deny rule&lt;&#x2F;a&gt; or &lt;code&gt;PreToolUse&lt;&#x2F;code&gt; hook that blocks the push before it happens&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;The style guide followed&lt;&#x2F;td&gt;&lt;td&gt;Vale in CI as a required check, plus a Claude Code &lt;code&gt;PostToolUse&lt;&#x2F;code&gt; &lt;a rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;code.claude.com&#x2F;docs&#x2F;en&#x2F;hooks-guide&quot;&gt;hook&lt;&#x2F;a&gt; that runs it after every edit so problems show up before CI does&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;No broken links&lt;&#x2F;td&gt;&lt;td&gt;lychee or a similar tool in CI as a required check&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Nothing private made public&lt;&#x2F;td&gt;&lt;td&gt;A required CI check that scans for secrets and internal info before merge, plus push protection&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Only signed commits&lt;&#x2F;td&gt;&lt;td&gt;Branch protection that requires signed commits&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;A task that runs every week&lt;&#x2F;td&gt;&lt;td&gt;A scheduled job, like a cron job, a GitHub Actions schedule, or a Claude Code routine&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;For anything running unattended, a sandbox or container bounds what can go wrong to whatever that environment can reach. Network restrictions go a step further by keeping it off networks it has no business touching, like sensitive internal systems or sites you don’t trust.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;side-benefit-more-room-in-the-context-window&quot;&gt;Side benefit: more room in the context window&lt;&#x2F;h3&gt;
&lt;p&gt;Doing some of these can also have the benefit of removing context for the LLM. Instead of spending the context window describing what not to do, just don’t let the bad things happen, and save that space for the things you actually want to tell the LLM.&lt;&#x2F;p&gt;
&lt;p&gt;I used to have our whole style guide in skills and CLAUDE.md, including tons of lines describing deterministic, mechanical rules with examples of right and wrong usage. After adding Vale, I was able to remove all of those. The rules still exist in the Vale config, where they run on every change, and the model only hears about the specific ones it broke. Now the model can focus its attention on the work that takes judgment instead of trying to remember capitalization rules.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;words-still-matter&quot;&gt;Words still matter&lt;&#x2F;h2&gt;
&lt;p&gt;None of this means you should stop telling the model what you want. Written instructions are still how it knows what it’s supposed to do, learns your preferences, understands team conventions, etc. But anything you put into words is something the model considers, the same way I’d consider the CTO’s memo about yeehawing into prod when things were on fire. If something truly can’t happen, all the ALL CAPS in the world won’t do what a branch protection rule does.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
