<?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: Kernflux</title>
    <description>The latest articles on DEV Community by Kernflux (@kernflux).</description>
    <link>https://dev.to/kernflux</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%2F4215464%2Fe5cc6c29-7da8-4ebb-b235-ef2e5dea2c22.png</url>
      <title>DEV Community: Kernflux</title>
      <link>https://dev.to/kernflux</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kernflux"/>
    <language>en</language>
    <item>
      <title>OpenDsec: self-hosted sandboxes for RL rollouts and code-running agents</title>
      <dc:creator>Kernflux</dc:creator>
      <pubDate>Sat, 31 Oct 2026 04:39:47 +0000</pubDate>
      <link>https://dev.to/kernflux/opendsec-self-hosted-sandboxes-for-rl-rollouts-and-code-running-agents-g</link>
      <guid>https://dev.to/kernflux/opendsec-self-hosted-sandboxes-for-rl-rollouts-and-code-running-agents-g</guid>
      <description>&lt;p&gt;If you train a model with agentic reinforcement learning, every rollout needs somewhere to run code: install a package, run the tests, hit the network, write files. Thousands of those run at once. When the training job is preempted they all have to stop together and come back later. And when you want to explore one state several ways, you want copies of the environment as it is right now, memory included, not fresh ones from the image.&lt;/p&gt;

&lt;p&gt;Hosted sandbox services cover part of this. We wanted all of it on our own machines, so we built &lt;a href="https://github.com/Kernflux/opendsec" rel="noopener noreferrer"&gt;OpenDsec&lt;/a&gt; and published it under the AGPL.&lt;/p&gt;

&lt;p&gt;This is the first post in a series about it. Here: what it does, what it looks like to run, and where its limits are.&lt;/p&gt;

&lt;h2&gt;
  
  
  One run, start to finish
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpmjcqzfvf5mffp2ddf78.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpmjcqzfvf5mffp2ddf78.gif" alt="deploy/quickstart.sh, then a microVM sandbox prints 42 and forks into four copies" width="799" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a real recording on one bare-metal server: the install script, then a short Python program that creates a Firecracker microVM, runs &lt;code&gt;print(6*7)&lt;/code&gt; in it and forks it into four copies. The six minutes of building and installing are cut; everything after runs at real speed. The 0.16 s is the whole API call (placement, boot, the in-VM agent ready), measured from the client.&lt;/p&gt;

&lt;p&gt;The program is the whole client side:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opendsec&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DSecClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DSecRunArgs&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DSecClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sandbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DSecRunArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;microvm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root/py:3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ready in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s (whole API call: placement + boot)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3 -c &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;print(6*7)&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;forks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# copies with memory
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;forked 4 in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where the design comes from
&lt;/h2&gt;

&lt;p&gt;DeepSeek described the sandbox platform behind their agentic RL training in &lt;a href="https://arxiv.org/abs/2609.22978" rel="noopener noreferrer"&gt;&lt;em&gt;DeepSeek Elastic Compute (DSec)&lt;/em&gt;&lt;/a&gt;. OpenDsec follows that paper: a stateless apiserver, IAM with nested projects and quotas, a watcher that aggregates scheduling state, power-of-k placement where each node has the final say on admission, layered images loaded on demand, a default-deny network policy, and pausing a whole task at once.&lt;/p&gt;

&lt;p&gt;Where the paper leaves things open, or where we chose differently, the decisions are ours: nftables and a DNS proxy instead of eBPF for the network policy, any shared file system (NFS, CephFS, JuiceFS) instead of 3FS for images, and Firecracker with memory snapshots as the main backend. The &lt;a href="https://github.com/Kernflux/opendsec/blob/main/docs/SPEC.md" rel="noopener noreferrer"&gt;design document&lt;/a&gt; marks every point as either from the paper or our own.&lt;/p&gt;

&lt;p&gt;OpenDsec is an independent project. It is not affiliated with DeepSeek and contains none of its code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three kinds of sandbox&lt;/strong&gt; behind one API: Firecracker microVMs, containers, and full QEMU VMs. Exec, files, processes, PTY and HTTP into the sandbox work the same on all of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast microVMs.&lt;/strong&gt; About 0.1 s for the VM to boot, about 55 ms to create one from a memory snapshot, about 50 ms to resume one from a pause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork with memory.&lt;/strong&gt; &lt;code&gt;fork(count=N)&lt;/code&gt; pauses the source for about 10 ms and gives you N copies with the same processes, imports and open ports. On one node the copies share memory pages; on another node they pull the snapshot from the source node directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale.&lt;/strong&gt; One 384-thread node took a burst of 5120 microVMs in 37 s. A two-node cluster took 8192 containers in one burst with no failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task-level pause.&lt;/strong&gt; Pause every sandbox of a training task in one call, push their memory to swap, resume them later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2B compatible.&lt;/strong&gt; Code written for E2B's SDKs runs against your own machines after changing three environment variables (&lt;code&gt;E2B_API_URL&lt;/code&gt;, &lt;code&gt;E2B_SANDBOX_URL&lt;/code&gt;, &lt;code&gt;E2B_API_KEY&lt;/code&gt;). E2B's own Python and JS SDK checks pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operable.&lt;/strong&gt; mTLS between all components, a Raft-replicated IAM, Prometheus metrics and alerts, staged rollouts with automatic rollback, offline installs. Restarting or upgrading any component leaves running sandboxes alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All numbers are our own measurements on our bare-metal machines; the design document says how each was measured. Your hardware will give different ones. For example, the fork time depends on the file system under the sandbox state: 0.1 s on XFS, which copies memory files by reflink, and about 1 s on ext4, which has to copy them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on one machine
&lt;/h2&gt;

&lt;p&gt;You need a Linux x86_64 machine with systemd and cgroup v2 (we test on Ubuntu 24.04). MicroVMs also need &lt;code&gt;/dev/kvm&lt;/code&gt;, so use bare metal or a cloud VM with nested virtualization; without it you get containers only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;
apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; make git python3-venv
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://go.dev/dl/go1.27.3.linux-amd64.tar.gz | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local &lt;span class="nt"&gt;-xz&lt;/span&gt;
git clone https://github.com/kernflux/opendsec &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;opendsec
deploy/quickstart.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script checks the machine, builds everything, downloads Firecracker and the guest kernel (checked against pinned hashes), installs the components as systemd services with their own CA, imports &lt;code&gt;python:3.12&lt;/code&gt; from Docker Hub, and runs one container and one microVM as a check. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; .venv/bin/pip &lt;span class="nb"&gt;install&lt;/span&gt; ./sdk/python
&lt;span class="nb"&gt;.&lt;/span&gt; /root/opendsec-ops/demo.env
.venv/bin/python sdk/python/examples/quickstart.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;deploy/quickstart.sh --uninstall&lt;/code&gt; removes all of it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is not (yet)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It is 0.x. Interfaces may change before 1.0.&lt;/li&gt;
&lt;li&gt;We have only verified Ubuntu 24.04 hosts. Other distributions, real GPUs and multi-node Kubernetes are untested.&lt;/li&gt;
&lt;li&gt;It is infrastructure you run. There is no hosted version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;The servers are AGPL-3.0. The Python SDK, the Go client and the API definitions are Apache-2.0, so programs that only call OpenDsec are not bound by the AGPL. A commercial license is available for closed modifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next in this series
&lt;/h2&gt;

&lt;p&gt;Firecracker, gVisor and containers compared for running AI-generated code: isolation, start time, memory and compatibility, with measurements.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/Kernflux/opendsec" rel="noopener noreferrer"&gt;github.com/Kernflux/opendsec&lt;/a&gt;, the docs at &lt;a href="https://kernflux.github.io/en/docs/" rel="noopener noreferrer"&gt;kernflux.github.io&lt;/a&gt;. Issues and questions are welcome there or in the comments.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>Forking a Running AI Agent Sandbox Without Losing Its Memory</title>
      <dc:creator>Kernflux</dc:creator>
      <pubDate>Thu, 29 Oct 2026 13:57:59 +0000</pubDate>
      <link>https://dev.to/kernflux/forking-a-running-ai-agent-sandbox-without-losing-its-memory-54p3</link>
      <guid>https://dev.to/kernflux/forking-a-running-ai-agent-sandbox-without-losing-its-memory-54p3</guid>
      <description>&lt;p&gt;When an AI agent executes code, the useful state is often not just a directory on disk.&lt;/p&gt;

&lt;p&gt;It may also include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an interpreter with modules already imported;&lt;/li&gt;
&lt;li&gt;a shell with environment variables and working-directory state;&lt;/li&gt;
&lt;li&gt;processes listening on local ports;&lt;/li&gt;
&lt;li&gt;caches held in memory;&lt;/li&gt;
&lt;li&gt;an application paused halfway through a task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Starting another container from the same image does not reproduce that state. Copying the filesystem does not reproduce it either.&lt;/p&gt;

&lt;p&gt;For reinforcement-learning rollouts, this distinction matters. If several trajectories must branch from the same point, rebuilding the environment for every branch wastes time and can change the state being evaluated. What we want is closer to &lt;code&gt;fork()&lt;/code&gt; at the level of an isolated machine: create several sandboxes from one running environment, including memory.&lt;/p&gt;

&lt;p&gt;This post explains how we approached that problem in &lt;a href="https://github.com/Kernflux/opendsec" rel="noopener noreferrer"&gt;OpenDsec&lt;/a&gt;, an open-source, self-hosted sandbox platform for code-executing agents and RL workloads.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Project context:&lt;/strong&gt; OpenDsec is an independent implementation inspired by the architecture described in the &lt;em&gt;DeepSeek Elastic Compute (DSec)&lt;/em&gt; paper. It is not DeepSeek code, not an official release, and is not affiliated with or endorsed by the paper's authors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A sandbox has more than one kind of state
&lt;/h2&gt;

&lt;p&gt;A useful mental model separates sandbox state into four parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Immutable image state
&lt;/h3&gt;

&lt;p&gt;This is the base OCI image: the operating system, language runtime, libraries, and preinstalled tools.&lt;/p&gt;

&lt;p&gt;It is easy to reproduce, but it only describes the starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Mutable filesystem state
&lt;/h3&gt;

&lt;p&gt;The agent may create files, install packages, clone repositories, or modify configuration. Overlay filesystems make this layer relatively cheap to capture and reuse.&lt;/p&gt;

&lt;p&gt;OpenDsec can also package filesystem changes into a new image through &lt;code&gt;pack_diff&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Memory and device state
&lt;/h3&gt;

&lt;p&gt;This contains running processes, page contents, CPU state, virtual device state, open sockets, and in-memory caches. A filesystem snapshot does not preserve any of it.&lt;/p&gt;

&lt;p&gt;For a microVM, this is the state that must be captured by the virtual machine monitor.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Control-plane state
&lt;/h3&gt;

&lt;p&gt;The platform still needs to know who owns the sandbox, which compute node holds it, what resources it consumes, and how requests reach it.&lt;/p&gt;

&lt;p&gt;A fast VM snapshot is not enough if restoring it requires rebuilding a large routing database or coordinating every control-plane component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lifecycle we need
&lt;/h2&gt;

&lt;p&gt;OpenDsec exposes four related operations for Firecracker microVMs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pause&lt;/strong&gt; stops execution while preserving the sandbox state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot&lt;/strong&gt; records memory and virtual machine state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore&lt;/strong&gt; creates a runnable microVM from that snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork&lt;/strong&gt; produces one or more new sandboxes from a running source.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important property is that a fork includes memory. If a Python process has already imported a large dependency, the child starts with that dependency present. If a service is listening inside the sandbox, the child contains the corresponding process state.&lt;/p&gt;

&lt;p&gt;The source sandbox should also experience only a short interruption while a consistent point-in-time state is captured.&lt;/p&gt;

&lt;p&gt;A simplified Python example looks like this:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opendsec&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DSecClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DSecRunArgs&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DSecClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sandbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;DSecRunArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;microvm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root/py:3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cpu_cores_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;memory_limit_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3 -c &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;import numpy; print(numpy.__version__)&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3 -c &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;print(6 * 7)&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Same-node and cross-node forks are different problems
&lt;/h2&gt;

&lt;p&gt;A cluster cannot assume that every child will fit on the source node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Same-node fork
&lt;/h3&gt;

&lt;p&gt;When source and child stay on one compute node, memory pages can be shared copy-on-write. The initial child does not need a complete physical copy of every page. Pages are copied only after one side modifies them.&lt;/p&gt;

&lt;p&gt;This improves both fork latency and memory density, especially when many rollout environments branch from the same prepared state.&lt;/p&gt;

&lt;p&gt;The trade-off is locality: the children initially depend on memory managed by that node, and capacity on one machine is finite.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-node fork
&lt;/h3&gt;

&lt;p&gt;When placement selects another node, the destination cannot use the source node's local memory pages directly. It must obtain the snapshot, prepare the filesystem view, and restore the microVM locally.&lt;/p&gt;

&lt;p&gt;In OpenDsec, the destination pulls the snapshot from the source edge. The control plane does not proxy the snapshot bytes. This keeps bulk transfer out of the stateless API layer and avoids turning the control plane into a data-plane bottleneck.&lt;/p&gt;

&lt;p&gt;Cross-node fork is naturally slower than a same-node copy-on-write fork, but it allows a prepared environment to fan out across the cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Placement cannot trust stale global state
&lt;/h2&gt;

&lt;p&gt;Cluster schedulers work with observations that are already slightly old.&lt;/p&gt;

&lt;p&gt;OpenDsec uses a stateless API server for authentication, quota checks, and initial placement. A watcher aggregates node health and available capacity. However, the selected node's &lt;code&gt;edge&lt;/code&gt; daemon makes the final admission decision using its actual local resources.&lt;/p&gt;

&lt;p&gt;This avoids a common race:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the scheduler observes enough free memory;&lt;/li&gt;
&lt;li&gt;several requests choose the same node;&lt;/li&gt;
&lt;li&gt;the node's real capacity changes before all requests arrive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The edge can reject an admission, allowing the caller to retry placement rather than overcommitting the node based on stale information.&lt;/p&gt;

&lt;p&gt;Each sandbox ID also encodes its owning edge. Requests can therefore be routed without maintaining a central per-sandbox routing table. Restarting the API server does not interrupt microVMs already running on compute nodes.&lt;/p&gt;

&lt;p&gt;Components communicate over gRPC with mutual TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime access must survive the lifecycle
&lt;/h2&gt;

&lt;p&gt;Restoring a VM is only useful if the platform can still execute commands and transfer files inside it.&lt;/p&gt;

&lt;p&gt;OpenDsec runs a small component called &lt;strong&gt;Aether&lt;/strong&gt; inside each sandbox for the runtime channel. &lt;strong&gt;Chronus&lt;/strong&gt; manages persistent shell sessions. Exec, files, HTTP, processes, and PTY access use the same runtime API across Firecracker microVMs, containers, and full QEMU VMs.&lt;/p&gt;

&lt;p&gt;The backend changes, but the client-facing interaction model does not.&lt;/p&gt;

&lt;p&gt;This also matters for compatibility. OpenDsec includes an E2B-compatible gateway, so existing E2B SDK programs can be pointed at a self-hosted deployment with environment variables. It is not yet complete: directory watching, signed file URLs, unauthenticated browser access to sandbox ports, and template building are currently unsupported.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we measured
&lt;/h2&gt;

&lt;p&gt;The following are &lt;strong&gt;OpenDsec project measurements, not numbers from the DSec paper&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Our test environment used two AMD EPYC 9Y24 processors (384 hardware threads), 1.5 TB of memory, NVMe RAID0, and bare-metal Ubuntu 24.04.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Observed value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Firecracker microVM cold start&lt;/td&gt;
&lt;td&gt;about 0.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore from a memory snapshot&lt;/td&gt;
&lt;td&gt;about 55 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume after pause&lt;/td&gt;
&lt;td&gt;about 50 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source VM interruption during fork&lt;/td&gt;
&lt;td&gt;about 10 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-node fork, 8 children&lt;/td&gt;
&lt;td&gt;about 0.58 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent restore, 64 children&lt;/td&gt;
&lt;td&gt;about 0.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burst on one 384-thread node&lt;/td&gt;
&lt;td&gt;5,120 microVMs in 37 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These results describe our hardware and implementation, not a universal performance guarantee. Storage, image contents, memory size, networking, kernel configuration, and concurrent load all affect the outcome. The repository's &lt;a href="https://github.com/Kernflux/opendsec/blob/main/docs/SPEC.md" rel="noopener noreferrer"&gt;specification&lt;/a&gt; documents the architecture and measurement context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolation and networking still matter
&lt;/h2&gt;

&lt;p&gt;Fast cloning must not weaken sandbox boundaries.&lt;/p&gt;

&lt;p&gt;OpenDsec supports Firecracker microVMs, containers, and full QEMU VMs. Firecracker is the primary backend for workloads that need a stronger isolation boundary than a shared-kernel container.&lt;/p&gt;

&lt;p&gt;Outbound networking is denied by default. Policies can allow specific domains, CIDRs, and ports. Our implementation uses nftables and a DNS proxy rather than the eBPF approach described in the paper.&lt;/p&gt;

&lt;p&gt;Images can live on NFS, CephFS, or JuiceFS. This is another deliberate difference from the paper, which discusses 3FS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure boundary is the host
&lt;/h2&gt;

&lt;p&gt;Memory-preserving fork is useful, but it does not make a sandbox durable across every failure.&lt;/p&gt;

&lt;p&gt;Today, if a compute host reboots, the sandboxes on that host are lost, including paused microVMs. The availability of the image store depends on the shared filesystem selected by the operator. QEMU PCI passthrough has also not yet been tested with real devices.&lt;/p&gt;

&lt;p&gt;These limitations are important when deciding whether the platform fits a workload. A rollout environment that can be recreated is different from a long-lived production service whose in-memory state must survive a machine failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  When memory-preserving fork helps
&lt;/h2&gt;

&lt;p&gt;It is most useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many trajectories share an expensive initialization phase;&lt;/li&gt;
&lt;li&gt;state is held in running processes rather than only in files;&lt;/li&gt;
&lt;li&gt;reproducible branching matters;&lt;/li&gt;
&lt;li&gt;the environment must remain on private or offline infrastructure;&lt;/li&gt;
&lt;li&gt;hundreds or thousands of isolated copies must start together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is less useful when a clean process starts instantly, all relevant state is external, or a managed sandbox service is preferable to operating Linux hosts and KVM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;For agent infrastructure, “start another sandbox” and “continue from this exact execution state” are fundamentally different operations.&lt;/p&gt;

&lt;p&gt;Images solve repeatable beginnings. Filesystem snapshots preserve disk changes. Memory-preserving fork creates branches from a running point in time.&lt;/p&gt;

&lt;p&gt;OpenDsec is exploring that third operation as an open-source, self-hosted building block. The code, Quickstart, architecture documents, and known limitations are available in the &lt;a href="https://github.com/Kernflux/opendsec" rel="noopener noreferrer"&gt;OpenDsec repository&lt;/a&gt;. Feedback from people running agent or RL workloads is especially welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>agents</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
