DEV Community

Cover image for Every Rule I Added Made It Worse: How Prompt Bloat Killed My Voice
Chidozie Uzoegwu for AWS Community Builders

Posted on • Originally published at builder.aws.com

Every Rule I Added Made It Worse: How Prompt Bloat Killed My Voice

I measured the system prompt from my old setup last week. It came to 224,833 characters, roughly 56,000 tokens, on every single call.

Ninety percent of that was one block of accumulated rules about how the output should sound.

I did not set out to build that. Nobody does. It grew one reasonable decision at a time, and by the end it was actively making the output worse, which I spent months blaming on the model.

(That prompt-engineering path is no longer what I run in production. It survives in my codebase as a rollback target, which is exactly why I could still assemble it and measure it.)

How a prompt gets to 56,000 tokens

The system generates short-form written content in a specific voice. I started the way most people do: a good frontier model, a carefully written prompt, and iteration.

The loop that got me here is one every prompt engineer will recognise:

  1. Output comes back with a cliché I hate.
  2. Add a line to the prompt banning that cliché.
  3. Output improves. Genuinely, for a while.
  4. New failure appears. Add another rule.
  5. Repeat for six months.

Each individual addition was justified. Each one was a real observed failure with an obvious textual fix. The categories that accumulated:

  • Banned phrases, specific clichés that kept reappearing
  • Banned structures, sentence shapes that read as machine-written
  • Opener bans, a list of ways it was not allowed to start
  • Register rules, tone and length and formality per content type
  • Anti-repetition context, the openers and closers used in recent output, injected so it wouldn't repeat itself
  • Worked examples, retrieved samples showing the target voice
  • Meta-rules, instructions about which of the above took priority when they conflicted

That last category is the tell. When your prompt needs rules about how to resolve conflicts between its own rules, the prompt has become a program, and nobody is testing it.

Then came the validators

Rules in a prompt are requests, not guarantees. So when a rule failed to hold, I did the obvious thing and enforced it in code afterwards.

That grew into a stack of post-generation checks: regex to catch a banned sentence shape, a stripper for a punctuation habit, a rejection pass for output that restated its input, a vocabulary blocklist, a fallback for when everything got rejected. At one point I shipped seven of these as a single bundle.

Each was a patch for something the prompt couldn't reliably enforce. And here's the part worth sitting with: I was compounding validators to compensate for a weakness in how the model was being steered. Every new validator was evidence the prompt wasn't working, and my response was another validator.

The symptom I misread

Quality got worse as the rules accumulated.

Not dramatically. Gradually. Output got flatter and more cautious, and it started sounding like something written by a committee avoiding mistakes rather than something with a point of view. Which, functionally, is exactly what it was.

My diagnosis at the time was that the model wasn't good enough. I planned a move to a more expensive tier and put a cost estimate together.

That would not have fixed anything. A stronger model given 56,000 tokens of conflicting constraints produces more expensive committee output.

The actual diagnosis

The rules were competing, with each other and with the task.

A language model attends across the whole context. A prompt is not a config file where each line executes independently. It is context the model weighs all at once. Two hundred lines of prohibitions against a two-line description of the actual job means the overwhelming signal is avoid things. The model optimises for whatever you have given it the most evidence you care about, and I had spent six months providing evidence that what I cared about was not breaking rules.

That produces output which breaks no rules and says nothing.

Worse, it doesn't converge. Adding a rule to fix flatness makes the ratio worse. The tool I was using to fix the problem was the thing causing it.

Why prompting couldn't solve this one

Here's the part I had underweighted for months.

You can instruct a model toward a style. You cannot make a style native to it.

Prompting is instruction. It operates on top of what the model already is. Ask for a voice and you get an impression of that voice, held in place by the instruction, degrading the moment the instruction competes with anything else. The rules were load-bearing scaffolding. Remove them and the voice collapsed. Keep them and they crowded out the task.

That is the signature of using the wrong technique. If a behaviour has to be re-specified in full on every single call, it does not belong in the prompt. It belongs in the weights.

What replaced it

I fine-tuned a model on the voice instead. Later articles in this series cover the how, the cost and the evaluation discipline, but the number relevant here is what happened to the prompt.

Approach Assembled system prompt
Prompt-engineering 224,833 chars, roughly 56,000 tokens
Fine-tuned model 6,805 chars, roughly 1,700 tokens
Difference About 33x smaller

You can measure your own the same way. Assemble the prompt exactly as your production path does and count it, rather than eyeballing the template file:

prompt = build_system_prompt(sample_input)   # your real assembly path
print(len(prompt), "chars |", len(prompt) // 4, "approx tokens")
Enter fullscreen mode Exit fullscreen mode

Break it down by section too. Mine was 90% one block, which is what told me the problem was concentrated rather than spread evenly:

import re
sections = [s.strip() for s in re.split(r"={40,}", prompt) if s.strip()]
for n, head in sorted(((len(s), s.split("\n")[0][:50]) for s in sections), reverse=True)[:10]:
    print(f"{n:7d}  {head}")
Enter fullscreen mode Exit fullscreen mode

The voice rules didn't move into a shorter prompt. They moved into the model. What is left in the prompt is what a prompt is actually good at: what to write about right now, how long, which mode. Instruction, not identity.

The lesson I'd give my past self

Strip before you add.

When output is wrong, the reflex is to append a rule, because appending is easy and feels like progress. But every rule is a permanent tax, paid on every call, forever, competing with every other rule, and never reviewed. I never once removed a rule. I only ever added.

Three questions I now ask before adding anything to a prompt:

  1. Is this instruction, or identity? Instruction belongs in the prompt. Identity, meaning a voice or a consistent style or a way of reasoning that must hold every time, belongs in the weights.
  2. Is this knowledge? Facts the model needs but cannot be expected to hold belong in retrieval, not permanently in the prompt.
  3. What am I removing to make room? If the answer is nothing, you are not editing. You are accreting.

The rough division that came out of it, and that I would now design around from day one:

Fine-tuning owns identity. Prompting owns instruction. RAG owns knowledge.

Put a job in the wrong one and you will spend months adding rules that make it worse, which is a fairly expensive way to learn where the boundaries are.

Top comments (0)