Introducing Semantic Overlays.
Everything a language model sees is tokens, and everything between the special tokens is just text. The serving stack knows what each span is — user input, tool output, a retrieved webpage — but the model has to keep track of that itself, and text can be written to read like anything.
Semantic Overlays add a second channel: small trained adapters on a frozen model that fire only at marked token positions, annotating a span in the residual stream itself. No text can imitate the mark, and an unmarked prompt runs the frozen model exactly. Everything below runs live.
An NX bit for LLMs: prompt-injection protection
The NX (no-execute) bit is a hardware feature that lets an operating system mark a region of memory as data: the CPU will read it, but refuses to run it as code. This section is the same idea for language models. Imagine an application that fetches web data. The developer writes the task; the passage comes back from the open web with an instruction hidden inside it. Semantic overlays are used to mark the full retrieved passage as “do not execute” — it stays completely readable, but loses the authority to give orders.
Invisible highlighters: marks & overlapped marks
The text the model sees is just the text that you see below. All the data about the marks comes through the overlays. There are no hidden control characters or tokens.
Python-colored gels
The overlay changes how the model understands what language the snippet is in. Arm a language and click a snippet to stamp it; with nothing armed, click any snippet (or the question) to edit it. Questions can name snippets by number.
var langs = ['foo', 'bar', 'baz']; // real list of langs goes here
var end_tag = '</'+'lang>';
var line;
while (line = readline()) {
line = line.replace(new RegExp('</code>', 'gi'), end_tag);
for (var i = 0; i < langs.length; i++)
line = line.replace(new RegExp('<(?:code )?(' + langs[i] + ')>', 'gi'), '<lang $1>')
.replace(new RegExp('</' + langs[i] + '>', 'gi'), end_tag);
print(line);
}Rubymy @nums = prompt("Please type 11 space-separated numbers: ").words
until @nums == 11;
for @nums.reverse -> $n {
my $r = $n.abs.sqrt + 5 * $n ** 3;
say "$n\t{ $r > 400 ?? 'Urk!' !! $r }";
}with javascript_semantics
function selection_sort(sequence s)
for i=1 to length(s) do
integer m = i
object si = s[i],
sm = s[m]
for j=i+1 to length(s) do
object sj = s[j]
if sj<sm then
{sm,m} = {sj,j}
end if
end for
if sm<si then -- (or equivalently m!=i)
s[i] = sm
s[m] = si
end if
end for
return s
end function
?selection_sort(shuffle(tagset(10)))CSecret instructions
Several requests, one prompt. Stamp a single request with an instruction no token states — only that answer transforms; the others are answered normally. Arm an instruction, then click a request to stamp it.