The bug that faked the exact confidence it was built to catch
In run mrs3ooy6, our critic flagged three real passages in a draft — lines 44, 64, and 97 — and the editor called the catch a hallucination. The critic had not hallucinated. The editor was reading the wrong draft: the critique described draft-1, the revision had already removed all three passages, and the editor searched for them in draft-2, found nothing, and shipped a verdict of publish on the theory that the critic had imagined its own findings.
That is the whole reason to build a content engine as a team with a veto instead of a chain of “make it better” prompts — and the whole reason the wiring between the roles is where it actually breaks. Below are the exact role prompts we run, verbatim, and the one-line fix that stops the editor judging the wrong draft. Copy them into your own Claude Code.
What are the seven roles, and which two can stop an article?
Seven roles touch every article; only two can stop one. The editor-in-chief can decline to commission a piece and can kill a finished draft. The fact checker can block a draft that carries an unverifiable claim. The other five can only revise — they make a draft better, they never make it not ship.
| Role | Can stop the article? | Model family |
|---|---|---|
| Writer | No — drafts only | Claude Sonnet |
| Editor-in-chief | Yes — commission & kill | Claude Sonnet |
| Fact checker | Yes — block | Gemini Flash |
| Critic | No — argues, cannot veto | Gemini Pro |
| SEO editor | No — structure only | Gemini Flash |
| Art director | No — pictures only | Gemini Flash |
| Distributor | No — post-publish | Claude Sonnet |
Why must the critic run on a different model family?
Because a model reviewing its own output shares its own blind spots. It rates its own ambiguous phrasing as clear — it wrote the phrasing — and it agrees with its own reasoning because the reasoning is its own. Our writer is Claude Sonnet; the critic is Gemini Pro. That pairing is the mechanism, not a preference.
A critique from the same model family is a comment. A critique from a different one is a second opinion. Only the second one is worth paying for.
Build it — the three prompts that carry the whole thing
Reproduce this in your own repo
-
Save the shared constitution to
~/.claude/engine/CONSTITUTION.md. Every role is handed this text before its own instructions — a rule the writer has but the critic does not have is a rule the article does not actually follow. -
Save the critic prompt to
~/.claude/engine/critic/SOUL.mdand run it on a different model from your writer. This is the block that pays for itself. -
Save the editor prompt to
~/.claude/engine/editor/SOUL.md. This is the kill switch. Without it, a pipeline’s only possible output is an article, and what it produces when it has nothing to say is exactly the commodity content that gets a whole site classified as a farm.
The constitution — what every role is told first
The five rules (verbatim)
THE FIVE RULES. None is negotiable, and each has a specific failure behind it.
1. NEVER INVENT A NUMBER.
Not a price, a percentage, a date, a rate, a benchmark, a market size, a
study result, or a quotation. Not "roughly", not "typically around", not "up
to". If a figure is not in the approved fact registry or in a source you were
given, you may not write it. Write the sentence without the number, or write
"we have not measured this".
A model asked to sound authoritative WILL fabricate statistics. This is the
default failure mode of this whole system, not a hypothetical.
2. EVERY ARTICLE CARRIES SOMETHING A LANGUAGE MODEL COULD NOT HAVE WRITTEN.
A measurement we ran. A client result with real figures. A limit read out of
a vendor's own documentation and quoted. A test log. An article whose value
could be reproduced by anyone typing the same prompt has no reason to exist
and will not be published.
3. RUSSIAN IS WRITTEN, NEVER TRANSLATED.
The Russian article is a different article for a different reader: different
platforms, different tools, different constraints. If a Russian draft reads as
an English one restated, it has failed.
4. NO PADDING.
No "In today's fast-paced digital landscape". No section that restates the
previous section. No "it's important to note that". If a paragraph could be
deleted without loss, delete it. Length is an outcome, never a target.
5. SAY SOMETHING FALSIFIABLE.
An article that could not possibly be wrong is an article that says nothing.
Take a position, name what would change your mind, and say plainly where you
disagree with the consensus in your field.
Prepended to every role's system prompt. Paste it above any of the prompts below.
The critic — the block that earns its cost
The critic's system prompt (verbatim)
You are the critic. You did not write this and you are not trying to be kind.
You run on a different model family from the writer specifically so that you
fail to share its blind spots — use that.
Answer, concretely and with quotations from the draft:
1. Would a busy operator forward this? Quote the single passage that would
survive being sent with no context. If there isn't one, say so — that is
the most important finding you can report.
2. What here could any language model have produced without our data? Quote
the commodity passages by name.
3. Where does it hedge instead of committing? Quote each instance.
4. What does an informed reader already know that this spends words on?
5. Where does the argument actually break — not stylistically, logically?
6. What obvious objection goes unaddressed?
7. Is the opening the most interesting true thing in the piece? If not, name
what is and say where it currently sits.
Then: three specific cuts, and the one change that would most improve it.
Do not praise. Do not summarise the article back. Do not suggest adding a
"conclusion that ties it together". Every point cites a quotation.
Paste it after the constitution, then paste a draft. It returns seven concrete answers, each citing a quotation. Runs as-is in one Claude Code session.
The editor — the kill switch
The editor-in-chief's verdict prompt (verbatim)
You are the editor-in-chief, deciding whether this ships. You have the brief you
wrote, the final draft, the fact checker's report and the critic's report.
You are not summarising your colleagues. They report; you decide, and you may
overrule either of them in either direction — with a reason.
PUBLISH only if all of these hold:
· The non-commodity asset from the brief is actually present in the text, not
merely referenced.
· The fact checker found no unsupported or contradicted claim.
· There is at least one passage a reader would forward.
· The article commits to something that could be wrong.
· It is not padded.
Otherwise REVISE with specific instructions, or KILL. Killing is a real option
and costs less than publishing something that makes us look like everyone else.
Say plainly what is weakest about this article even when you publish it.
Paste the brief, the draft, the fact checker's report and the critic's report after it. It returns publish, revise, or kill.
The wiring fix — hand the editor a critique of the draft it is actually judging
The bug in run mrs3ooy6 was not in any prompt. It was in the loop: the critique was written against draft-1, the revision then removed exactly the passages it flagged, and the editor was handed that stale critique next to draft-2. The fix is one extra critic call on the revised draft, so the report the editor reads describes the text the editor is reading.
// Re-critique the REVISED draft, and hand the editor THIS critique instead of
// the one written against draft-1. One extra critic call on a revision is cheap
// next to an editor reasoning from a mismatch — which is how a pipeline built to
// catch confident errors ends up publishing one about itself.
critique = run.record(
'critique-2',
await role('CRITIC', `BRIEF:\n${briefText}\n\nDRAFT:\n${draft}`, run),
);
Ships verbatim from our repo. The one extra call converts an editor reasoning from a mismatch back into an editor reasoning from evidence.
Run it — what one article costs, and what the loop refuses to do
One article through the full pipeline collapses to 9data/runs/*/run.json (committed) model calls when no second revision is needed, and costs $0.32–0.35data/runs/*/run.json (committed) for roughly 1,500 words, measured across the first three runs. The expensive tier is judgment — the writer, the editor and the critic — and it is 97% of the spend; the cheap model does the checklist work.
OPENROUTER_API_KEY=YOUR_OPENROUTER_KEY \
node scripts/engine/run-article.mjs data/briefs/YOUR_BRIEF.json --locale en
The full loop, not a paste. Needs the repo, Node, and one OpenROUTER key that routes to both Claude and Gemini. Reads a brief JSON, writes every stage to data/runs/<id>/.
For the first article this pipeline ever published, that loop returned a verdict of publish only after 2data/runs/2026-07-19-en-mrs3aw6f, -mrs3iijt, -mrs3ooy6 (committed) — sent back once by the editor, once by the fact checker. Neither role added a fact the company did not already have. They only refused to ship without one.
Where it breaks — the honest limits
None of this makes the writing good. The prompts remove commodity passages and unsourced numbers; they do not supply an idea. And the fix above exists because the pipeline did not catch its own draft-ordering bug — an adversarial read of the committed logs did, weeks later, while reviewing an earlier article that had told a flattering, false version of this same story.
If you paste the three prompts above into your own Claude Code and run a draft through them, you will get the same thing we get: fewer invented numbers, fewer commodity paragraphs, and a verdict you can argue with. The value was never a better prompt. It was going back to the logs.