demoteSystemMessages rewrites every system-role message in the packed history to a user message. buildMessages always prepends the embedded system prompt as wire element 0, so any system message Pack returns is a SECOND, non-leading system message, which strict OpenAI-compat backends reject outright
(kept []Message)
| 225 | |
| 226 | // demoteSystemMessages rewrites every system-role message in the packed history |
| 227 | // to a user message. buildMessages always prepends the embedded system prompt as |
| 228 | // wire element 0, so any system message Pack returns is a SECOND, non-leading |
| 229 | // system message, which strict OpenAI-compat backends reject outright ("System |
| 230 | // message must be at the beginning"; observed on strict backends like Ollama |
| 231 | // and llama.cpp), the same class of wire-shape 400 the dangling/orphan/userless passes |
| 232 | // guard against. The only system content reaching history is a soft-nudge note |
| 233 | // (the embedded prompt is never stored there), so demoting to user keeps that note, |
| 234 | // automated-check prefix and all, in front of the model while keeping the wire |
| 235 | // legal everywhere. Must run AFTER anchorUserMessage: a demoted nudge would |
| 236 | // otherwise masquerade as a surviving user message and suppress the original-task |
| 237 | // anchor. Mutates only the copied kept slice, never history. |