le0c

Prompt rules via tagging

The rules I want an agent to follow change depending on the context. A reply going out to a customer, the same information written up for someone integrating against our API, and a post in my own voice are three different sets of rules over one piece of content. Code is the same. A feature touching authentication wants instructions about trust boundaries and failing closed; the same afternoon I might want something written as defensively as possible because a bug in it would be expensive to find later.

I have most of those rules written down somewhere already. I keep a styleguide for how my writing should read, and a skill that checks files against it. If I'm firing off a reply to a customer email I don't want to invoke a whole skill for it, and I don't want to type out the path to a styleguide either. What I'd like is to put a tag on the end of the prompt and have the rules turn up:

Draft a reply to the customer email #style

It turned out to be qutie simple!

TLDR

The hook

Claude Code has a hook event called UserPromptSubmit. It fires on every prompt, hands the script the prompt text as JSON on stdin, and injects whatever the script prints back into that turn before the model sees the message.

Registering it is one block in settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3",
            "args": ["/Users/me/.claude/hooks/rulesets.py"]
          }
        ]
      }
    ]
  }
}

The script pulls #tags out of the prompt, looks for a file with that name, and prints the contents back with a short header saying these are directives rather than something to talk about. Tags that don't match a file get ignored, so a #1072 in a PR reference or a markdown # heading are both harmless.

I did check that # wasn't already taken before building this. It used to be the shortcut for adding a memory, and that's been removed from current builds.

Rulesets as files

A ruleset is a markdown file, & the filename is the tag - security.md answers to #security. The body is written as instructions to the agent, in the same register I'd use in a CLAUDE.md.

The field I use most is include:, which points the tag at a file that already exists:

---
tags: [voice, prose]
include: ~/writing/STYLE.md
---

So #style & my writing-check skill read the same file. I'd rather have one copy of those rules than two that drift, & pointing at the original was less work than any syncing I'd have had to bake in. There's also extends: for stacking one ruleset on another, and tags: for aliases - the Simplified Technical English one lives in a file called asd.md but I type #ste, because that's what I remember it as.

When a tag matches, the terminal prints a confirmation:

rulesets loaded: #style

If I typo a tag there's no confirmation line, which is how I notice.

Writing contexts

The case I built it for is one piece of information going to different audiences. Say a set of changes has just gone in and three people want to hear about it:

prompt 1: Summarise the changes in this merge request for the customer #customer_comms
prompt 2: Summarise the changes in this merge request for the integration guide #developer_docs
prompt 3: Write up what I did this week from these commits #style

The rules behind each of those are things I'd otherwise have to say in the prompt every time, and would say slightly differently every time. A customer ruleset would tell it to lead with what changes for them and to leave internal names out. A developer-docs one would set the standard the docs are written to. #style points at my own writing rules, which include a long list of AI writing tells to avoid, so a one-off reply gets checked against the same list an article does.

I've made two so far: #style is the writing styleguide. #ste is ASD-STE100 Simplified Technical English - a controlled English used for aircraft maintenance manuals, with an approved word list sat in a folder next to the ruleset. I use it for specs, and for instructions I'm writing for another agent to read. Watching an agent rewrite its own reply into short sentences and approved vocabulary is quite satisfying.

Coding contexts

The original idea that sparked this work was this:

using SPEC.md implement the feature described in point 12.1 #security #space_shuttle

Two different things are being asked for there. #security is about what kind of code this is, and #space_shuttle is about how I want it written. I wrote both while trying the mechanism out. The security one holds the things I'd otherwise have to specify each time

The space-shuttle one is a coding style for code where a latent bug is expensive to find later:

Stacking the two on one prompt is what I wanted, because most coding prompts want a couple of these at once. It works well; /space_shuttle /security implement the feature in 12.1 expands both in order, and /space_shuttle <request> #security mixes a command with a tag, because the hook reads the raw prompt text before any command expansion happens.

Triggers

Nothing completes #style for me. I have to remember it, which is fine for two rulesets and less fine for more.

Claude Code has five trigger characters in the prompt box - /, !, @, : & ? - and the set is fixed. / completes against installed commands, which is a short list to search, so the fix was to generate one command file per ruleset:

python3 ~/.claude/hooks/rulesets.py --sync-commands

Each generated command pulls its body from the same source file, so the rules still live in one place. It regenerates on demand, deletes commands whose ruleset was renamed or removed, and leaves alone anything it didn't write.

Selector Autocomplete When I use it
/style <request> good, short list Reaching for a ruleset by feel
#style none I know the tag & want it mid-sentence

The slash commands are a workaround, but it also pollutes the list of skills with things that aren't really skills. Each expansion repeats my request text and the framing header, so two stacked rulesets carry that twice, and there's a whole generated directory of command files to keep in step with the rulesets directory.

Before that I tried hanging the tags off @, since that's the trigger with a fuzzy file picker behind it. Put the rulesets somewhere the picker indexes, type @c_su, pick one off the list. When I tried it I typed @c_sui, but this actually matched a long list of things and not my rule. My workspace has about 100,000 files in it, and even the somewhat unique tag c_sui is a subsequence of roughly 9,500 of those paths, so a ruleset was never going to rank into a dropdown showing six rows. I pulled the @ support back out afterwards.

What I'd like from Claude Code is # as a sixth trigger character, completing against ruleset files the way / completes against commands. I went looking for a way to add one, first in the settings schema and then in the binary, and there isn't one. A hook that could register a trigger character & supply its completions would cover it, and would presumably be useful for a lot of things that aren't rulesets. The mechanism works fine without it - I'd just rather delete the command-generating half of my script.

Sharing it

You can find the example repo here.

I've deliberately made this example repo quite small, as I am sure you will have different rules and preferences to me. So there is one example rule you can work from + the installer

Most people already have hooks configured which I don't want to overwrite, so the installer:

What I plan to use it for

I put this together today, so most of what follows is intention rather than habit. #style goes on the one-off writing - a customer reply, a note on a PR, the covering message for a release. #ste goes on specs, and on the instructions I write for another agent to read, where a controlled vocabulary is doing something useful. #security I want on the prompt any time I'm near an auth flow or a file upload, because those are the changes where I'd otherwise be relying on remembering to ask.

Something I quite like is the idea of using include:. Most of the rules an agent should follow are written down somewhere already - a styleguide, or a section of a CLAUDE.md that's grown into a procedure. Pointing a tag at that file gives me a switch for rules I already maintain.

The rulesets I've written are shaped around my own work, so if you end up trying this, I'd like to hear what you point it at and how it holds up!