Stage 03 · BuilderModule 13 of 26~5h

Content Creation

Draft long-form content with a consistent voice.

← All modules in this stage

The mistake everyone makes with LLM writing: ask for a 1,500-word blog post in one shot, then complain it sounds bland. The fix is the same craft any human editor uses — work in stages, hold a clear voice, and let critique do real work.

By the end of this module you'll have

Time: about 1 hour for the basics, ~5 hours with all three notebooks.

Prerequisites: Modules 3 (prompt basics), 6 (advanced prompting), 10 (conversations).


Why one-shot generation is bad

A single "write a 1,500-word post about X" produces:

Asking for the outline first solves all three: you (or the model) can fix the structure cheaply, set the voice deliberately, and decide where to dig in for specifics.


A working three-stage pipeline

Save as write_post.py:

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()
client = Anthropic()

VOICE = """\
VOICE GUIDE
- Plain English, second person ("you"), British spelling.
- Short sentences. One idea per paragraph.
- No marketing words: "leverage", "robust", "seamless", "in today's fast-paced world".
- Use a concrete example wherever a generality could go.
"""

def call(messages, *, max_tokens=900):
    return client.messages.create(
        model="claude-sonnet-4-6", max_tokens=max_tokens, system=VOICE, messages=messages,
    ).content[0].text

def outline(topic: str, audience: str) -> str:
    return call([{
        "role": "user",
        "content": (
            f"Topic: {topic}\nAudience: {audience}\n\n"
            "Produce a tight outline: a working title, a one-sentence promise to the reader, "
            "and 4–6 H2 sections with one bullet each. No fluff. No 'introduction' or 'conclusion' headings."
        ),
    }])

def draft(topic: str, audience: str, outline_text: str) -> str:
    return call([{
        "role": "user",
        "content": (
            f"Topic: {topic}\nAudience: {audience}\n\nOUTLINE:\n{outline_text}\n\n"
            "Write the full post following the outline. ~1000 words. Concrete examples preferred over abstract claims. "
            "If you'd normally hedge with 'often' or 'in many cases', either commit or rewrite the sentence."
        ),
    }], max_tokens=1800)

def critique(post: str) -> str:
    return call([{
        "role": "user",
        "content": (
            "You are a strict editor. Read the draft. Output up to 8 issues, each as: "
            "ISSUE (one line), WHY (one line), FIX (a rewritten sentence). "
            "Only flag: clichés, hedging, vague claims, marketing-speak, contradictions. "
            "Skip typos.\n\nDRAFT:\n" + post
        ),
    }])

if __name__ == "__main__":
    topic    = "How to onboard a junior engineer in their first week"
    audience = "Engineering managers at small companies"
    o = outline(topic, audience);  print("=== OUTLINE ===\n", o, "\n")
    d = draft(topic, audience, o); print("=== DRAFT ===\n", d, "\n")
    c = critique(d);               print("=== CRITIQUE ===\n", c)

Read the outputs in order. The outline should already feel sharper than a generic blog post. The draft should follow it. The critique should mostly flag things you also noticed — that's calibration.


What just happened?

Three pieces are doing the work:

  1. The voice guide is a system prompt. It applies to every call, so the outline, draft, and critique all share the same voice rules. Change VOICE once; the whole pipeline shifts.
  2. The outline is the cheap stage. Mistakes here cost ~30 cents and 5 seconds. Mistakes in the draft cost ~10× more. Iterate on the outline.
  3. The critic is a different role, not a different model. The persona ("strict editor") is what changes the output, not the model id. You can mix and match — Sonnet to write, Haiku to critique, Opus for the final polish.

Holding voice across long pieces

The longer the piece, the more voice drifts. Three things that help:


Things to watch for

Failure mode Fix
Bland, hedge-y prose Add "Commit or rewrite" rule. Forbid "often", "many", "various".
Specifics that look real but aren't Tell the model: "If a number, name, or quote is needed, write [TODO: verify]."
Voice drifts mid-piece Anchor with example paragraphs in system, draft in chunks.
Repeats the same point Critique pass: "list any duplicate ideas across sections."
Sounds the same on every topic Give topic-specific framing in the user message: who, what, why now.

Try changing one thing


Going deeper: open the notebooks


Module checklist


Next module

Module 14 · Production Patterns — taking everything you've built and making it survive real users.