Prompts as code Required
Keep user text out of the instructions
Put user input and retrieved documents in their own message or a clearly marked block. Never paste them into the system instructions.
How to do it
Send fixed instructions as the system or developer message. Send the user's words as the user message. Wrap retrieved text and tool results in named tags, such as <document>, and say in the instructions that text inside them is data. Remove those tag names from user input before you wrap it, so a user cannot close the block early. This does not stop prompt injection on its own. The limits in indirect prompt injection still apply.
Example
const stripTags = (text: string) => text.replace(/<\/?(document|policy)\b[^>]*>/gi, '');
const messages = [
{ role: 'system', content: renderPrompt(prompt.template, { policy: returnPolicy }) },
{ role: 'user', content: `<document>${stripTags(customerEmail)}</document>\n\nQuestion: ${stripTags(question)}` },
];
TAGS = re.compile(r"</?(document|policy)\b[^>]*>", re.I)
def strip_tags(text: str) -> str:
return TAGS.sub("", text)
messages = [
{"role": "system", "content": render_prompt("return-assistant", policy=return_policy)},
{"role": "user", "content": f"<document>{strip_tags(customer_email)}</document>\n\nQuestion: {strip_tags(question)}"},
]
How to check
A unit test renders each prompt with user input containing </document> and "ignore the instructions above". The system message is unchanged, and the input appears only inside its own block.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers ai features playbook: https://agentwranglers.se/playbook/ai-features/prompts-as-code/user-text-apart.