<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Carroll Guertin</title>
    <description>The latest articles on DEV Community by Carroll Guertin (@carroll_guertin_f06b4f83a).</description>
    <link>https://dev.to/carroll_guertin_f06b4f83a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166199%2F8fc27378-3651-4d31-8ade-18a08498ea6c.png</url>
      <title>DEV Community: Carroll Guertin</title>
      <link>https://dev.to/carroll_guertin_f06b4f83a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carroll_guertin_f06b4f83a"/>
    <language>en</language>
    <item>
      <title>Give your Trigger.dev agent a Python sandbox with plimsoll</title>
      <dc:creator>Carroll Guertin</dc:creator>
      <pubDate>Tue, 06 Oct 2026 11:02:33 +0000</pubDate>
      <link>https://dev.to/carroll_guertin_f06b4f83a/give-your-triggerdev-agent-a-python-sandbox-with-plimsoll-4gf</link>
      <guid>https://dev.to/carroll_guertin_f06b4f83a/give-your-triggerdev-agent-a-python-sandbox-with-plimsoll-4gf</guid>
      <description>&lt;p&gt;An AI agent analysing data often needs to run code: calculate a total, inspect a file, or test an idea. That generated code needs a sandbox.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;plimsoll&lt;/strong&gt;, an Apache-2.0 code execution sandbox, with an integration for Trigger.dev. It runs Python and JavaScript separately from your task, and supports sessions that keep variables and files available between calls.&lt;/p&gt;

&lt;p&gt;Plimsoll is pre-1.0. There is a working starter you can deploy, including a fixed Python task that checks the connection without calling an AI model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runs where?
&lt;/h2&gt;

&lt;p&gt;Your Trigger.dev task sends code to plimsoll. Plimsoll executes it in a sandbox and returns the output, along with the isolation tier used.&lt;/p&gt;

&lt;p&gt;For self-hosted Trigger.dev, a Docker Compose recipe runs the sandbox service beside your worker on your infrastructure. Task containers reach it over a private Docker network.&lt;/p&gt;

&lt;p&gt;The self-hosted recipe was tested on one host with Trigger.dev v4.7.2. Its README describes the setup and limitations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/plimsollmark/plimsoll-trigger-starter/tree/main/self-hosted" rel="noopener noreferrer"&gt;https://github.com/plimsollmark/plimsoll-trigger-starter/tree/main/self-hosted&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the working example
&lt;/h2&gt;

&lt;p&gt;Clone the starter and install its dependencies using Node.js 22.18 or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/plimsollmark/plimsoll-trigger-starter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;plimsoll-trigger-starter
npm ci
npm run typecheck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before deploying, follow the starter’s instructions to configure a plimsoll service your worker can reach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/plimsollmark/plimsoll-trigger-starter#prepare-plimsolld" rel="noopener noreferrer"&gt;https://github.com/plimsollmark/plimsoll-trigger-starter#prepare-plimsolld&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For self-hosted workers, use the Compose instructions linked above instead.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;PLIMSOLL_URL&lt;/code&gt; and the secret &lt;code&gt;PLIMSOLL_TOKEN&lt;/code&gt; in your Trigger.dev project’s production environment. Set &lt;code&gt;TRIGGER_PROJECT_REF&lt;/code&gt; for the deployment CLI.&lt;/p&gt;

&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run deploy &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your project should list two tasks: &lt;code&gt;code-chat&lt;/code&gt; and &lt;code&gt;deployed-cell-trial&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check that Python remembers its variables
&lt;/h2&gt;

&lt;p&gt;The verification task sends two separate Python calls to one sandbox session.&lt;/p&gt;

&lt;p&gt;The first creates a list and returns its length:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second uses the same list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected results are &lt;code&gt;3&lt;/code&gt; and &lt;code&gt;10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second call depends on a variable created by the first. Like two cells in a notebook, they share a running Python interpreter.&lt;/p&gt;

&lt;p&gt;Supply your Trigger.dev production API key as &lt;code&gt;TRIGGER_SECRET_KEY&lt;/code&gt; through your shell or secret manager, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run trial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For self-hosted Trigger.dev, also set &lt;code&gt;TRIGGER_API_URL&lt;/code&gt; to your own Trigger.dev address before running the command.&lt;/p&gt;

&lt;p&gt;The task checks the outputs, interpreter reuse, and minimum isolation level. It closes the sandbox when finished, including when a check fails.&lt;/p&gt;

&lt;p&gt;A successful run reports &lt;code&gt;interpreterReused: true&lt;/code&gt; and, with the default isolation requirement, &lt;code&gt;kernel&lt;/code&gt; for both calls. A broken session or insufficient isolation makes the task fail.&lt;/p&gt;

&lt;p&gt;This trial makes no AI call. Infrastructure charges can still apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the agent a sandbox tool
&lt;/h2&gt;

&lt;p&gt;The included chat agent exposes an &lt;code&gt;executeCode&lt;/code&gt; tool and follows Trigger.dev’s documented sandbox lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Warm the sandbox in &lt;code&gt;onTurnStart&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reuse it for tool calls while the run remains active.&lt;/li&gt;
&lt;li&gt;Close it in &lt;code&gt;onChatSuspend&lt;/code&gt; or &lt;code&gt;onComplete&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Variables and files remain available while the session exists. Once it closes, a later session starts fresh.&lt;/p&gt;

&lt;p&gt;This lets an agent load data, inspect it, and calculate results across several calls without rebuilding its workspace every time.&lt;/p&gt;

&lt;p&gt;Running the chat agent requires its model credentials and incurs model usage charges. The fixed verification task does not.&lt;/p&gt;

&lt;p&gt;Trigger.dev’s sandbox pattern is documented here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://trigger.dev/docs/ai-chat/patterns/code-sandbox" rel="noopener noreferrer"&gt;https://trigger.dev/docs/ai-chat/patterns/code-sandbox&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Know what the sandbox protects
&lt;/h2&gt;

&lt;p&gt;The self-hosted Compose setup selects gVisor when available and runc otherwise.&lt;/p&gt;

&lt;p&gt;gVisor adds a separate kernel boundary for sandboxed code. Ordinary runc containers share the host kernel and provide a weaker boundary.&lt;/p&gt;

&lt;p&gt;The starter tasks require the &lt;code&gt;kernel&lt;/code&gt; isolation tier by default. If the service only provides the &lt;code&gt;container&lt;/code&gt; tier, the tasks refuse to execute unless you explicitly lower that requirement. Keep the stronger requirement for hostile code.&lt;/p&gt;

&lt;p&gt;Plimsoll’s daemon is trusted infrastructure: it holds the Docker socket, which gives it powerful access to the host. The self-hosted README also documents credential logging observed with Trigger.dev v4.7.2 and other deployment limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The starter includes the chat integration, a deployed verification task, and the self-hosted Compose recipe:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/plimsollmark/plimsoll-trigger-starter" rel="noopener noreferrer"&gt;https://github.com/plimsollmark/plimsoll-trigger-starter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I maintain plimsoll, and I’d like feedback from people using it with Trigger.dev. If you try it, tell me what broke, especially during setup or when connecting it to an existing worker.&lt;/p&gt;

</description>
      <category>triggerdev</category>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
