DEV Community

Multigrid
Multigrid

Posted on • Originally published at multigrid.ai

A Style Guide a Model Can Follow

A house style guide is written for a person who can ask a colleague. Pasting it into a system prompt fails for two reasons: most of it is not phrased as a constraint, and the parts that are get lost among the parts that are not. Sort the rules into three piles, enforce one pile with a script, and put only the second pile in the prompt.

Why pasting your style guide does not work

Three failures, all predictable. First, a long instruction list is followed unevenly — the rules near the beginning and end of a block are honoured more reliably than the ones in the middle, which is the same positional effect as anything else buried in a long context. A forty-rule guide is not forty rules to a model; it is a general impression of formality.

Second, most style rules are not checkable as written. “Write plainly” and “avoid jargon” do not have a decision procedure, so compliance is unmeasurable and non-compliance is invisible until a human reads the output.

Third, rules stated as prohibitions describe the thing they prohibit, and any instruction is a weak lever compared with the pull of the training distribution. “Never use the word delve” is a much weaker mechanism than a script that greps for it and fails the build, and prohibitions in general are the weakest kind of instruction.

Three kinds of rule

Kind Description
Mechanical Decidable by a regular expression or a lookup: spelling variant, serial comma, date format, product name capitalisation, banned words, heading case, number formatting. Enforce with a script. Do not put these in the prompt at all.
Structural Decidable by counting: sentence length distribution, paragraph length, headings per thousand words, passive-voice density, link count. A script can measure and warn; a threshold is a judgement call, so it warns rather than fails.
Judgement Not decidable without knowing what the piece is for: whether a metaphor earns its place, whether a caveat is necessary, whether the tone suits the news. Neither script nor prompt. This is what the human gate is for.

The sorting itself is the valuable part of this exercise, and it is usually uncomfortable. Most house guides turn out to be about sixty per cent mechanical, twenty per cent structural, and twenty per cent judgement dressed as rules — a sentence like “be authoritative but approachable” is not a rule anybody has ever followed or broken.

Rewriting rules into constraints

A constraint names what is checked, what passes, and what happens on failure. Here is the same set of house rules before and after.

House rule Description
Use plain English Split into: no sentence over 40 words (warn above 30); no paragraph over 5 sentences; banned-phrase list of 40 entries; anything remaining is judgement and goes to the editor.
Be consistent about our product name Exact-match rule: the string appears only as written in the lexicon file. Any case variant, hyphenation or spacing variant fails.
Avoid hype Banned-adjective list (revolutionary, seamless, powerful, cutting-edge, game-changing) plus a warning at more than one superlative per 500 words.
Prefer active voice Warn when passive constructions exceed 15% of sentences. Never fail: some passives are correct and the detector is approximate.
British spelling Word list of the 60 variants that actually occur in your subject area, not a general dictionary. Fail on a hit.
Cite sources Not mechanical. Every external claim needs a link or a named source, checked at the human gate, and the check is recorded.

Note what happened to “use plain English”. It became three checkable rules plus an honest admission that the remainder is not automatable. That admission is worth more than a fourth rule, because it tells an editor where to spend their attention.

The checker

Rules in a JSON file, checker in one script, so a writer can add a banned phrase without touching code. Standard library only.

# style.json
{
  "banned": [
    {"pattern": "\\bdelve\\b", "message": "banned word: delve"},
    {"pattern": "\\bseamless(ly)?\\b", "message": "hype: seamless"},
    {"pattern": "\\bgame[- ]?chang", "message": "hype: game-changing"},
    {"pattern": "\\bnot just\\b.{0,40}\\bbut\\b", "message": "construction: not just X but Y"},
    {"pattern": ", (ensuring|allowing|making|enabling|providing) ",
     "message": "participial tail — promote to its own sentence"},
    {"pattern": "\\b(may|might|could) potentially\\b", "message": "stacked hedge"},
    {"pattern": "(?i)\\bin today's\\b", "message": "banned opener"},
    {"pattern": "\\borganiz", "message": "use -ise spelling"}
  ],
  "lexicon": {"multigrid": "Multigrid", "open router": "OpenRouter"},
  "max_sentence_words": 40,
  "warn_sentence_words": 30,
  "max_paragraph_sentences": 5,
  "max_em_dashes_per_paragraph": 1
}
Enter fullscreen mode Exit fullscreen mode
# stylecheck.py — Python 3.9+, standard library only.
# Usage: python stylecheck.py style.json draft.md
# Exit 1 on any error. Warnings do not fail.

import json, re, sys

rules = json.load(open(sys.argv[1], encoding="utf-8"))
text  = open(sys.argv[2], encoding="utf-8").read()

errors, warnings = [], []

def line_of(pos):
    return text.count("\n", 0, pos) + 1

# 1. Banned patterns.
for rule in rules["banned"]:
    for m in re.finditer(rule["pattern"], text):
        errors.append((line_of(m.start()), rule["message"], m.group(0)))

# 2. Lexicon: wrong casing or spacing of a known term.
for wrong, right in rules["lexicon"].items():
    for m in re.finditer(re.escape(wrong), text, re.IGNORECASE):
        if m.group(0) != right:
            errors.append((line_of(m.start()), f"write it as {right}", m.group(0)))

# 3. Per-paragraph structure.
paragraphs = [p for p in re.split(r"\n\s*\n", text) if p.strip()]
fence  = chr(96) * 3            # a code fence, without typing one here
offset = 0
for para in paragraphs:
    start = text.index(para, offset)
    offset = start + len(para)
    if para.lstrip().startswith(("#", "|", "-", "*", fence)):
        continue
    dashes = para.count("")
    if dashes > rules["max_em_dashes_per_paragraph"]:
        warnings.append((line_of(start), f"{dashes} em dashes in one paragraph", ""))
    sentences = [s for s in re.split(r"(?<=[.!?])\s+", para.strip()) if s]
    if len(sentences) > rules["max_paragraph_sentences"]:
        warnings.append((line_of(start), f"{len(sentences)}-sentence paragraph", ""))
    for s in sentences:
        n = len(s.split())
        if n > rules["max_sentence_words"]:
            errors.append((line_of(start), f"{n}-word sentence", s[:60]))
        elif n > rules["warn_sentence_words"]:
            warnings.append((line_of(start), f"{n}-word sentence", s[:60]))

for line, msg, hit in sorted(warnings):
    print(f"draft.md:{line}: warning: {msg} {hit}".rstrip())
for line, msg, hit in sorted(errors):
    print(f"draft.md:{line}: error: {msg} {hit}".rstrip())

print(f"\n{len(errors)} errors, {len(warnings)} warnings")
sys.exit(1 if errors else 0)
Enter fullscreen mode Exit fullscreen mode

Three design decisions are worth defending. Errors fail and warnings do not, because a checker that fails on a 32-word sentence gets disabled within a week. Code blocks and headings are skipped for structural rules, since a table row is not a sentence. And the sentence splitter is a naive regular expression that will mis-handle “Dr.” and “e.g.” — acceptable, because the cost of an occasional wrong warning is far lower than the cost of a dependency, and this must run in a pre-commit hook without a virtual environment.

Run it on your last twenty published pieces before you run it on a draft. Any rule that fires on work you were happy with is a wrong rule, and calibrating on your own archive is what stops the guide becoming somebody else’s taste.

What goes in the prompt instead

Once the mechanical rules are enforced downstream, the prompt only needs the rules a script cannot decide. That is typically five to eight lines, and short lists are followed far better than long ones.

House style, applied to everything you draft:

- Every claim states the condition under which it holds. Replace "it
  depends" with what it depends on.
- Prefer a specific number, version or command to an adjective.
- Name the actor. "We changed X" rather than "X was changed".
- One idea per sentence. Split rather than subordinate.
- If you do not know something the piece needs, write [CHECK: ...] and
  continue. Never fill the gap with a plausible value.
- End on the last piece of information. No summary, no exhortation.

Spelling, banned words and formatting are checked automatically after
you. Do not spend instructions on them.
Enter fullscreen mode Exit fullscreen mode

The [CHECK: ...] convention is the highest-value line in that block. It gives the model a legitimate destination for the case it cannot handle, which is the same reason an explicit abstention path works elsewhere, and it produces a grep-able list of everything a human must verify before publication.

Examples beat instructions

For anything remaining after the script and the six lines, show rather than tell. Three before-and-after pairs drawn from your own edits will move output further than a paragraph of description, because a pair demonstrates a transformation while a description has to be interpreted.

Examples of edits we make to drafts. Apply the same transformations.

BEFORE: The new caching layer significantly improves performance for
        most workloads.
AFTER:  The cache cuts time to first token by roughly half on prompts
        that share a 2,000-token system message, and does nothing below
        about 1,000 tokens of shared prefix.

BEFORE: It is important to note that rate limits may vary by plan.
AFTER:  Rate limits are per plan: 60 requests a minute on Starter,
        600 on Team.

BEFORE: This approach offers several key benefits, including improved
        reliability and reduced operational overhead.
AFTER:  Two things get better: retries stop paging the on-call
        engineer, and the runbook loses a page.
Enter fullscreen mode Exit fullscreen mode

Selecting those pairs from your own published archive rather than writing them fresh is what makes them a house style rather than a general preference — the voice corpus page covers how to pull them out at scale.

The rules a checker cannot hold

  • Whether a claim is true. Nothing above touches accuracy. A draft can pass every rule and be wrong in every paragraph.
  • Whether the piece is worth publishing. Style compliance and value are unrelated, and a checker will happily green a well-formed piece with nothing in it.
  • Tone against context. The right register for a product announcement is the wrong one for an incident report, and the script cannot tell which it is reading.
  • Whether a rule should exist. Rules accumulate. Review the JSON file twice a year and delete anything that has not fired, or has only ever fired on prose you liked.

If you run the same drafting prompt against more than one model to see which follows the six house rules more closely, Multigrid keeps that behind one API and one key, which makes the comparison a change of model name rather than a change of client — and the checker script gives you an objective score for each.

Related

Top comments (0)