[Bug] Model defaults to soft prompting despite explicit in-context instructions for strict schemas
Bug Description
Title: Strong ingrained bias toward "soft prompting" (range + natural-language guidance) that overrides explicit project instructions to use strict schemas
Summary
When building LLM-generation pipelines, Claude (Opus 4.8 in Claude Code) repeatedly defaults to "soft prompting" — encoding structural constraints as natural-language wishes and as ranges that hand the final choice to the model — even when:
(a) explicit, repeated project memory and CLAUDE.md instructions say the opposite ("strict JSON schemas, exact values, zero choice to the LLM, never soft prompt"), AND
(b) Claude itself had just articulated the correct principle in the same conversation.
The bias resurfaces turn after turn and has to be corrected by the user multiple times. It is not a one-off slip; it is a strong prior that survives direct, recent, in-context correction.
Concrete pattern observed (writing-exercise generator)
The correct design (per the project's standing rules) is: enforce an exercise's per-difficulty error count with the tool-call JSON schema as an EXACT value per level — foundation/intermediate/advanced = 3/4/5 via minItems == maxItems == level_count, building the tool per difficulty. No prose, no model choice.
What Claude repeatedly produced instead, despite the rule:
- First "fix" to an over-generation bug was to ADD prose: "Plant EXACTLY N data errors… never more than 5." (Pure soft prompting; the user flagged it.)
- Next iteration used a schema RANGE (minItems:3, maxItems:5) plus natural-language per-level guidance ("at foundation: 3 errors, at intermediate: 4, at advanced: 5"). This is half-structural: the range is enforced, but the exact per-level number is left to the model nudged by prose — i.e., still soft, and still the banned "all-levels block."
- Claude described this residual softness as merely "guidance," only recognizing it as soft prompting after the user pushed again.
Why this matters
- It directly contradicts documented, in-context user instructions — an instruction-adherence failure, not a knowledge gap (the model knows the right pattern and can explain it).
- "At level X… at level Y… at level Z" prose in a prompt is a reliable tell that a per-level VARIABLE was needed; the model emits the all-levels block anyway.
- Real costs: token waste (shipping all states every call), generation drift (model picks inconsistent values), and a worse product (non-deterministic structure).
- The user had to repeatedly intervene ("you keep soft prompting while EVERYTHING in your memory tells you not to"), which erodes trust and wastes the user's time and tokens.
What "good" looks like (so the contrast is clear)
- Bad: description="plant 3-5 errors; for foundation use 3" + schema minItems:3,maxItems:5
- Good: schema minItems:3,maxItems:3 for a foundation exercise (tool built per-difficulty); the number lives ONLY in the schema; guidance fields carry zero counts.
- General rule the model should internalize: if a constraint can be expressed structurally (enum, minItems/maxItems, required, structured array vs free text), it MUST be — never as prose, never as a range when an exact value is known.
Suggested improvements
- Strengthen the model's prior so that, in generation/codegen contexts, "structural constraint expressed as prose or as a permissive range" is treated as a smell to avoid by default — not a convenience to reach for.
- Weight explicit, recent, in-context user/system instructions (and project memory) more heavily than the general training prior when they conflict — especially after the user has corrected the same behavior once already in the conversation.
- Recognize the "at level X… at level Y…" enumeration as a self-check trigger for "use a variable / strict schema instead."
Environment: Claude Code v2.1.159, Opus 4.8.
Environment Info
- Platform: darwin
- Terminal: Apple_Terminal
- Version: 2.1.160
- Feedback ID: ecdd7b6f-afdf-4392-ad8a-2219a09dea0c
Errors
[]This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗