# "Give concrete detail" made the LLM invent it

> A give-concrete-detail brand-voice line made a content pipeline invent unverifiable specifics. Fix: a context constraint, a [FILL] channel, a banned list.

**Canonical URL**: https://agentcookbooks.com/blog/ai-hallucinated-concrete-detail/

**Published**: 2026-06-03

**Tags**: claude-code, agent-reliability, context-engineering

---

The first batch of an AI-content pipeline drafted local directory pages, and the drafts read well — which was the problem. A brand-voice instruction to "give concrete detail" had quietly become an instruction to *invent* concreteness, because the model had no real source for the venues it was writing about. The output was confident, plausible, and unverifiable: the exact failure mode that poisons E-E-A-T, because the claims sound trustworthy without being true.

## What I ran

This is a cross-project transfer, not a skill activation — an AI-content failure mode worth writing down because it generalizes to anyone running an LLM over a low-source niche (local media, directories, guides). The setup: a pipeline drafts pages into a gitignored folder, the operator edits, and only edited drafts publish. The model's job was framed as producing base text a human editor finishes. The draft step carried a brand-voice instruction to write in a "natural, concrete voice."

Opening the first draft, three sentences stood out:

> *"There are more people at Venue A than at Venue B."*
> *"Located along the boulevard, Venue A is especially good for a morning visit."*
> *"On weekend evenings the district is a popular meeting spot for locals."*

None of it is verifiable. The operator doesn't visit these venues, doesn't read their reviews, isn't in the local groups. The model was satisfying the "give concrete detail" instruction the only way it could with no source — by generating plausible specifics.

## What happened

The model wasn't lying so much as following its instructions. "Give concrete detail" reads, to a model with nothing in context, as "invent concreteness if there is none." Crowd counts, time-of-day claims, social vibe — all of it is the shape of grounded local writing, with none of the grounding. And it doesn't show on first glance. The draft looks finished until you ask one question of each sentence: *how would the model know this?* For these three, the answer is it couldn't.

The fix shipped as one commit with three parts.

**1. A "red flags" list** added to the content-workflow doc — banned expression categories the model and the editor both check against:

```
- Qualitative judgments (busier, calmer, most central, best-known)
- Time claims (open X–Y, weekend evenings, good for morning visits, in summer)
- Crowd claims (more people, crowded, popular)
- UX claims (especially good for, worth seeing, leave time for)
- Social claims (gathering spot, heart of the community)
- Emotional claims (calming, cozy, pleasant)
```

**2. A prompt change** to the pipeline's draft step:

```diff
YOUR TASK: produce base text a human editor finishes for publication.
- Don't make assumptions if you lack information — express uncertainty.
+ Use ONLY facts present in the provided context + general
+ street-name- and geography-level local knowledge.
+ Mark uncertainty explicitly with a [FILL: ...] tag.
```

Alongside the diff: a new *"UNVERIFIED CLAIMS — ABSOLUTE BAN"* section placed *before* the brand-voice instructions, plus good/bad examples in the user prompt.

**3. Manual cleanup of the 5 drafts** — keeping only data facts (addresses, postal codes, venue names, district tags), street-level general knowledge, and `[FILL: ...]` markers where the operator's real observation belongs (busiest hours, fenced status, route suggestions).

The numbers are small but the ratio is the point: 5 drafts, every one carrying invented claims, all caught before publish. The whole intervention added one ~6-character fill-in marker convention to the prompts.

## Where it drifted

The interesting part is *why the old line didn't already cover this.* The pre-fix prompt said `Don't make assumptions if you lack information — express uncertainty.` That sounds like it should have stopped exactly this. It didn't, and the reason is structural: **the model doesn't recognize its own hallucination as an assumption.** From inside the generation, "Venue A is especially good for a morning visit" isn't a guess it's flagging — it's a confident sentence in the brand voice it was told to use. There's no internal signal that says *this is the part you're unsure about*. "Express uncertainty" only fires when the model already feels uncertain, and a fluent invention doesn't feel uncertain.

That's why "don't make assumptions" alone fails, and why it took two separate mechanisms working together:

- An **explicit context constraint** — *use ONLY facts present in the provided context.* This removes the model's license to fill gaps at all. The default behavior of an LLM is to complete the pattern; the constraint replaces "complete it plausibly" with "complete it only from what's here."
- An **explicit uncertainty channel** — the `[FILL: ...]` tag. The constraint tells the model what it can't do; the tag gives it somewhere to *put* the gap instead of papering over it. Without the channel, a constrained model either stays silent (losing the structural slot the editor needs) or leaks the claim back in. With it, "I don't know the busiest hours" becomes a literal, greppable `[FILL: busiest hours]` the editor resolves.

Neither move works alone. The constraint without the channel just makes the model omit things the page needs; the channel without the constraint just gives invented claims a tag they never use. Together they reframe the model's role entirely: the LLM supplies structure and the data it was given; the human supplies local observation, reviews, and community knowledge. That division is the actual fix — the prompt edits are just how you enforce it.

This is squarely a [context-engineering](/skills/context-engineering/) problem, and it's an agent-reliability hazard before it's a writing one: "use a natural, concrete voice" is an E-E-A-T trap any time the facts aren't supplied. The grounded-writing patterns the [humanizer](/skills/humanizer/) skill chases assume real source material underneath; without it, "concrete voice" is just confident fabrication. (The line between editing AI tells and supplying missing grounding is its own topic — see [copy-editing vs humanizer](/blog/copy-editing-vs-humanizer/).)

## What I would change

Two concrete moves, both portable to any low-source content pipeline.

**Treat every brand-voice instruction as a potential fabrication license, and pair it with a sourcing constraint in the same prompt.** "Write concretely / specifically / vividly" is fine *only* when the facts are in context. When they aren't, that instruction is the bug. The constraint (`use ONLY facts present in the provided context`) has to live right next to the voice instruction so the model reads them as one rule, not two competing ones — and the ban on unverified claims should come *before* the voice section, so it frames everything after it.

**Give the model a marker to declare what it doesn't know, and verify the drafts on that marker, not on how good they read.** The `[FILL: ...]` convention turns "is this draft trustworthy?" from a judgment call into a check: a draft with zero fill markers over a niche the model has no source for is a red flag, not a win. The editor's pass — and any [verification step before publishing](/skills/verification-before-completion/) — should grep for the markers and audit the claims that *don't* have one. The same instinct that drives an editorial-bar [content-judge hook](/blog/claude-code-content-judge-hook/) applies here: don't trust a draft because it's fluent; trust it because every claim in it traces to a source or a marker.

5 drafts, all carrying invented claims, caught before publish. One ~6-character fill-in marker convention added to the prompts.