Moonshot AI + kvcache-ai Open Source AgentENV — A Deep Dive into the Firecracker-Based Agentic RL Environment Scaling Platform

1. Introduction: When Agents Start Using Computers

On July 27, 2026, Moonshot AI and kvcache-ai jointly announced the open-source release of AgentENV (AENV), a Firecracker microVM-based agentic reinforcement learning (RL) environment scaling platform, under the MIT license. This date also marked the open-sourcing of the Kimi K3 (2.8-trillion-parameter MoE model) weights and the entire training infrastructure. AgentENV is the underlying execution environment platform built specifically for Kimi K3’s agentic RL training.

AgentENV’s core mission can be summarized in one sentence: Provide each Agent with an independent, secure, rapidly clonable Linux computer, enabling thousands of such computers to run in parallel on a single physical server while idle computers consume almost no resources.

This is neither a simple container management platform nor a conventional VM orchestration tool. AgentENV targets the most intractable infrastructure problem in agentic reinforcement learning — environment isolation, resource density, and state management. After nearly a year of internal production validation, AgentENV has run over 225,000 agent execution environments across a 70-node cluster, achieving an average CPU allocation-to-usage ratio of 27.9× and an average memory allocation-to-usage ratio of 9.6×, reducing environment management overhead by roughly an order of magnitude compared to existing solutions.

This article provides a deep technical analysis of AgentENV across multiple dimensions including architecture design, core primitives, performance optimization, and deployment practices.


2. The Agentic RL Dilemma: Why New Infrastructure Is Needed

2.1 Fundamental Differences Between Traditional RL and Agentic RL

In traditional reinforcement learning (e.g., CartPole, Atari games), the environment is a Python function or a static simulator:

# Traditional RL: environment is an in-memory function
obs, reward, done, info = env.step(action)

Agentic RL is fundamentally different. A coding agent needs to read a code repository, modify files, install dependencies, start services, and interact with databases. A computer-use agent needs to operate a browser, click buttons, and fill out forms. This means every training rollout requires a real, complete, and independent Linux execution environment, complete with a filesystem, network stack, and running processes.

2.2 The Impossible Triangle: Isolation, Speed, and Density

Agentic RL imposes three simultaneous requirements on execution environments:

RequirementDescriptionTraditional Bottleneck
Strong IsolationEach agent runs in an independent security boundary, unable to affect the host or other training tasksContainers share the host kernel; isolation boundary is weak
Fast StartupEnvironments must be created and restored in milliseconds, or training throughput collapsesFull VMs take seconds to tens of seconds to boot
High DensityA single machine must support hundreds to thousands of concurrent environmentsEach VM permanently reserves memory with no elastic scaling

Container solutions (Docker/containerd): Fast startup (seconds), low resource overhead, but share the host kernel. In agentic RL scenarios, model-generated code may attempt to escape container boundaries, access hidden services, or modify evaluation logic. Our research found that reward-driven agents attempt various “cheating” behaviors — if they can get higher rewards, they will. Shared kernels mean these behaviors cannot be completely prevented.

Traditional VM solutions (KVM/QEMU): Strong isolation, but slow startup (10-30 seconds), each VM permanently reserves several GB of memory, making it uneconomical at the thousand-level concurrency.

AgentENV’s solution: Use Firecracker microVMs for hardware-level isolation, while leveraging snapshot technology for millisecond-level startup and recovery, and memory ballooning with page cache sharing for high-density deployment.

2.3 The Image Diversity Challenge

Agent training requires more than just isolation. Different tasks require different operating systems, language runtimes, compilers, package managers, code repositories, and external services. As the number of tasks grows, the required environment image collection quickly expands, with total size potentially far exceeding single-node storage capacity.

┌──────────────────────────────────────────────────────────────────┐
│                    Agent Training Image Library                    │
├──────────────────────────────────────────────────────────────────┤
│  base/ubuntu:22.04    (2.1 GB)    ── Base system                  │
│  base/ubuntu:24.04    (2.3 GB)    ── Newer base system            │
│  python/coding        (3.5 GB)    ── Python coding environment    │
│  python/data-science  (4.2 GB)    ── Data science environment     │
│  nodejs/web-dev       (2.8 GB)    ── Web development environment  │
│  go/compiler          (1.9 GB)    ── Go compiler environment      │
│  rust/compiler        (2.4 GB)    ── Rust compiler environment    │
│  java/jvm             (3.1 GB)    ── JVM environment              │
│  ... more custom images ...                                       │
│  Total: hundreds of GB ~ terabytes                                │
└──────────────────────────────────────────────────────────────────┘

Traditional solutions require pre-warming every image on every node, which is clearly not scalable.


3. AgentENV Architecture Overview

3.1 System Architecture Diagram

┌──────────────────────────────────────────────────────────────────┐
│                      AgentENV System Architecture                 │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────┐    ┌──────────────┐    ┌──────────────────────┐    │
│  │  Client   │    │   Gateway    │    │     Scheduler        │    │
│  │ (E2B SDK  │───▶│  (Multi-     │───▶│  (Multi-Node,        │    │
│  │  / aenv   │    │   Node)      │    │   Prototype)         │    │
│  │  CLI)     │    │  :8080       │    │   :9090              │    │
│  └──────────┘    └──────────────┘    └───────────┬──────────┘    │
│                                                   │               │
│  ┌────────────────────────────────────────────────┴──────────┐   │
│  │                    API Server (Axum)                       │   │
│  │                    :8000 / E2B Compatible                  │   │
│  └────────────────────────────┬───────────────────────────────┘   │
│                               │                                    │
│  ┌────────────────────────────┴───────────────────────────────┐   │
│  │                     Orchestrator                            │   │
│  │   ┌─────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │   │
│  │   │ Creating│→│ Running  │→│ Pausing │→│ Paused  │   │   │
│  │   │         │  │          │  │          │  │          │   │   │
│  │   └─────────┘  └────┬─────┘  └──────────┘  └────┬─────┘   │   │
│  │                     │                           │          │   │
│  │   ┌─────────────────▼───────────────────────────▼──────┐   │   │
│  │   │         Resuming      Snapshotting        Forking   │   │   │
│  │   └────────────────────────────────────────────────────┘   │   │
│  └────────────────────────────┬───────────────────────────────┘   │
│                               │                                    │
│  ┌────────────────────────────┴───────────────────────────────┐   │
│  │                   Firecracker microVM Pool                   │   │
│  │                                                              │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │   │
│  │  │ Agent 1  │  │ Agent 2  │  │ Agent 3  │  │ Agent N  │   │   │
│  │  │ microVM  │  │ microVM  │  │ microVM  │  │ microVM  │   │   │
│  │  │  4vCPU   │  │  2vCPU   │  │  4vCPU   │  │  2vCPU   │   │   │
│  │  │  8GB     │  │  4GB     │  │  8GB     │  │  4GB     │   │   │
│  │  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘   │   │
│  │       │              │              │              │        │   │
│  │       └──────────────┴──────────────┴──────────────┘        │   │
│  │                       │ Shared Read-Only Page Cache          │   │
│  └───────────────────────┼──────────────────────────────────────┘   │
│                          │                                          │
│  ┌────────────────────────┴──────────────────────────────────────┐ │
│  │               Storage Layer (ublk + overlaybd)                 │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐   │ │
│  │  │  Read-Only   │  │  COW Upper   │  │  S3 Remote      │   │ │
│  │  │  Base Layer  │  │  (Per-VM)    │  │  Storage        │   │ │
│  │  │  (Shared)    │  │              │  │  (On-Demand)    │   │ │
│  │  └──────────────┘  └──────────────┘  └──────────────────┘   │ │
│  └──────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────────┐ │
│  │              Snapshot Management Layer (Three-Tier)           │ │
│  │  L1: Builder Staging → L2: Committed Repository → L3: Local  │ │
│  └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

3.2 Core Components

AgentENV consists of the following core components:

  1. API Server (Axum): HTTP entry point, validates requests and authentication, forwards to the Orchestrator. Exposes E2B-compatible endpoints, listens on 0.0.0.0:8000 by default, health check at GET /health.
  2. Orchestrator: Sandbox lifecycle state machine managing state transitions: Creating → Running → Pausing → Paused → Resuming → Snapshotting → Forking → Killing.
  3. Firecracker microVM: Each sandbox is an independent Firecracker microVM with its own Linux kernel, filesystem, and network namespace.
  4. Block Device Layer (ublk + overlaybd): Userspace block device supporting copy-on-write (COW) layered images.
  5. envd Daemon: Runs inside each Guest, listens on port 49983, handles command execution, file operations, and health checks.
  6. Reverse Proxy: Routes HTTP and WebSocket traffic from clients to services running inside the microVM.
  7. Snapshot Manager: Manages three-tier snapshot storage (Builder Staging → Committed Snapshot Repository → Node-Local Runtime Cache).
  8. Gateway + Scheduler: Multi-node control plane (prototype stage), Gateway listens on :8080, Scheduler listens on :9090.

4. Firecracker microVM Deep Dive

4.1 What Is Firecracker

Firecracker is an open-source microVM virtual machine manager (VMM) open-sourced by AWS in 2018, written in Rust, designed for serverless computing and containerized workloads. It implements hardware virtualization isolation based on Linux KVM (Kernel-based Virtual Machine).

Firecracker’s core design philosophy is “secure, lightweight, minimal” — it implements only the minimal feature set required to run Linux microVMs, omitting unnecessary features from traditional VMMs (like QEMU) such as device emulation, graphical interfaces, and USB support.

4.2 Firecracker’s Core Isolation Mechanisms

┌──────────────────────────────────────────────────────────────────┐
│                    Firecracker Isolation Layers                    │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. KVM Hardware Virtualization (CPU Virt + EPT Page Tables)      │
│     ┌──────────────────────────────────────────────────────┐     │
│     │  Guest kernel runs in Ring 0 (non-root mode)         │     │
│     │  All privileged instructions (HLT/IN/OUT/MSR/CR3)    │     │
│     │  are trapped by KVM                                  │     │
│     │  EPT (Extended Page Tables) isolates Guest physical  │     │
│     │  memory from host physical memory                    │     │
│     └──────────────────────────────────────────────────────┘     │
│                                                                   │
│  2. Jailer Process Isolation                                      │
│     ┌──────────────────────────────────────────────────────┐     │
│     │  Linux Namespaces: Each microVM has its own:          │     │
│     │    ├─ mount namespace (independent mount tree)        │     │
│     │    ├─ pid namespace (independent PID space)           │     │
│     │    ├─ net namespace (independent network stack)       │     │
│     │    ├─ ipc namespace (independent IPC resources)       │     │
│     │    └─ uts namespace (independent hostname)            │     │
│     │  cgroups: Resource limits (CPU/Memory/IOPS/PIDs)      │     │
│     │  chroot: Filesystem isolation                         │     │
│     │  seccomp: Syscall whitelist filter                    │     │
│     └──────────────────────────────────────────────────────┘     │
│                                                                   │
│  3. Device Model (Minimal)                                        │
│     ┌──────────────────────────────────────────────────────┐     │
│     │  virtio-net: Network device (no VGA/audio/USB)       │     │
│     │  virtio-block: Block storage device                  │     │
│     │  virtio-vsock: Host-Guest communication channel      │     │
│     │  Serial console: Kernel log and debugging only       │     │
│     └──────────────────────────────────────────────────────┘     │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

4.3 Comparison with Traditional Container Isolation

DimensionDocker ContainerKVM/QEMU VMFirecracker microVM
Isolation LevelProcess-level (shared kernel)Hardware-level (independent kernel)Hardware-level (independent kernel)
Startup Time100ms-1s10-30s125ms (cold) / <50ms (snapshot restore)
Memory OverheadNear zero512MB+ (including QEMU)~5MB (Firecracker process itself)
Security BoundaryShared kernel → large syscall surfaceFull isolationFull isolation + minimal attack surface
Image StandardOCI (Docker)QCOW2/RAWOCI (via overlaybd)
Density per NodeHundreds~thousandsTensHundreds~thousands
Device ModelNone (uses host directly)Full device emulationMinimal device set (3 virtio types)

4.4 Deep Analysis of the Jailer Mechanism

Jailer is a critical component of Firecracker, responsible for establishing the security boundary before starting the microVM. AgentENV inherits Firecracker’s Jailer mechanism and adds additional security policies on top.

# AgentENV Jailer configuration pseudocode
# Actual implementation is in Rust; Python is used for illustration

def configure_jailer(microvm_id: str, resources: ResourceSpec):
    """Configure Firecracker Jailer isolation parameters"""
    jailer_cfg = {
        # 1. Create independent namespaces
        "namespaces": {
            "mount": True,      # Independent mount tree
            "pid": True,        # Independent PID space
            "net": True,        # Independent network stack
            "ipc": True,        # Independent IPC resources
            "uts": True,        # Independent hostname
        },
        # 2. cgroups resource limits
        "cgroups": {
            "cpu_quota": resources.cpu_quota_us,     # Microsecond CPU quota
            "memory_max": resources.memory_mb,        # Maximum memory (including balloon)
            "memory_swap_max": 0,                      # Disable swap
            "io_weight": resources.io_weight,          # IO priority
            "pids_max": resources.max_pids,            # Maximum process count
        },
        # 3. seccomp filter (syscall whitelist)
        "seccomp": {
            "default_action": "KILL",  # Syscalls outside whitelist kill the process
            "allowed_syscalls": [
                # Only the minimal syscall set needed for microVM operation
                "read", "write", "openat", "close",
                "mmap", "munmap", "mprotect",
                "futex", "clock_gettime", "nanosleep",
                "ioctl",   # For KVM interface
                # ... approximately 50 syscalls
            ],
        },
        # 4. chroot to dedicated directory
        "chroot_dir": f"/var/lib/aenv/jailer/{microvm_id}/",
        # 5. Run as non-root user
        "uid": 1000 + hash(microvm_id) % 60000,
        "gid": 1000 + hash(microvm_id) % 60000,
    }
    return jailer_cfg

5. Storage and I/O Architecture: overlaybd + ublk

5.1 Layered Storage Architecture

AgentENV’s storage design is arguably the most elegant part of the entire system. It must solve a core contradiction: Agent training requires many different images (each task may need different toolchains, codebases, and dependencies), but node local disk capacity is limited.

The solution is on-demand loading + layered storage + content-addressed caching.

┌──────────────────────────────────────────────────────────────────┐
│                    overlaybd Layered Image Structure               │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Remote Storage (S3/Object Storage)                               │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  docker.io/library/ubuntu:22.04  (raw OCI image)       │       │
│  │  docker.io/library/python:3.12   (raw OCI image)       │       │
│  │  registry.example.com/coding-agent:latest (custom)     │       │
│  └──────────────────────┬────────────────────────────────┘       │
│                         │ On-demand loading                       │
│                         ▼                                         │
│  Local Cache (Bounded, Hot Data Retained, Cold Evicted)           │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  ┌───────────────────────────────────────┐             │       │
│  │  │  Read-Only Base Layer (Content-Addressed,           │       │
│  │  │  Shared Across Sandboxes)                           │       │
│  │  │  sha256:abc123...                                  │       │
│  │  │  sha256:def456...                                  │       │
│  │  └───────────────────────────────────────┘             │       │
│  │  ┌───────────────────────────────────────┐             │       │
│  │  │  Writable COW Upper Layer (Per-Sandbox)            │       │
│  │  │  sandbox-001: incremental writes                   │       │
│  │  │  sandbox-002: incremental writes                   │       │
│  │  └───────────────────────────────────────┘             │       │
│  └───────────────────────────────────────────────────────┘       │
│                         │ ublk userspace block device driver      │
│                         ▼                                         │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  Firecracker microVM                                   │       │
│  │  ┌─────────────────────────────────────────────────┐  │       │
│  │  │  Guest kernel block device: /dev/vda             │  │       │
│  │  │  (virtio-blk frontend → ublk backend → overlaybd) │  │       │
│  │  └─────────────────────────────────────────────────┘  │       │
│  └───────────────────────────────────────────────────────┘       │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

5.2 ublk: Userspace Block Device

ublk is a Linux kernel framework for userspace block devices. Unlike traditional block devices, ublk allows implementing I/O processing logic in userspace, with the kernel only forwarding I/O requests.

# ublk + overlaybd I/O flow pseudocode
# Actual implementation uses Rust ublk bindings

def handle_ublk_io(request: UblkRequest):
    """Handle block device I/O request from Guest"""
    sector = request.sector          # Request sector number
    nr_sectors = request.nr_sectors  # Number of sectors requested
    is_write = request.is_write      # Read/write

    # 1. Map sector to overlaybd layer
    layer_info = overlaybd.lookup(sector, nr_sectors)

    if is_write:
        # Write: write to COW upper layer
        # On first write to a sector, copy base layer data first (COW)
        if not cow_layer.has_sector(sector):
            base_data = base_layer.read(sector, nr_sectors)
            cow_layer.write(sector, nr_sectors, base_data)
        # Write new data to COW upper layer
        cow_layer.write(sector, nr_sectors, request.data)
        # Mark sector as dirty
        dirty_bitmap.mark(sector, nr_sectors)
    else:
        # Read: check COW upper layer first, fall back to base layer
        if cow_layer.has_sector(sector):
            data = cow_layer.read(sector, nr_sectors)
        else:
            data = base_layer.read(sector, nr_sectors)

    # 2. Use io_uring for async I/O
    # 3. Use Direct I/O to avoid double caching on host
    return data

5.3 Key Optimizations: io_uring and Direct I/O

AgentENV’s block device layer uses three key I/O optimizations:

  1. ublk userspace driver: Avoids overhead from kernel-mode filesystem and block device layers
  2. io_uring: Linux’s newest async I/O interface, reducing syscall overhead and memory copies compared to traditional AIO
  3. Direct I/O: Bypasses host page cache to avoid double caching between the ublk backend and the Guest kernel
// AgentENV ublk queue processing Rust pseudocode
// Async I/O handling based on io_uring

use io_uring::{IoUring, opcode, types};

struct UblkQueue {
    ring: IoUring,
    sqes: Vec<opcode::ReadWrite>,
}

impl UblkQueue {
    /// Process a batch of I/O requests
    async fn process_io_requests(&mut self, requests: Vec<UblkRequest>) {
        for (i, req) in requests.iter().enumerate() {
            let (buf, offset) = self.get_buffer_and_offset(req);
            let sqe = if req.is_write {
                opcode::Write::new(
                    types::Fixed::new(self.fd),
                    buf,
                    offset,
                ).build()
            } else {
                opcode::Read::new(
                    types::Fixed::new(self.fd),
                    buf,
                    offset,
                ).build()
            };
            // Push to io_uring submission queue
            unsafe { self.ring.submission().push(&sqe).unwrap(); }
        }
        // Wait for completion
        self.ring.submit_and_wait(requests.len()).unwrap();
        // Process completion queue
        for cqe in self.ring.completion() {
            let result = cqe.result();
            // Handle completion event
            self.complete_io(cqe.user_data(), result);
        }
    }
}

6. Snapshot, Pause, Resume, and Fork: Core Primitives for RL Training

6.1 Why These Primitives Matter for Agentic RL

The Agentic RL training loop has a fundamental difference from traditional RL: environment state is expensive.

In CartPole, resetting the environment requires just env.reset(). But in Agentic RL, resetting an environment means:

  • Re-downloading a code repository (potentially hundreds of MB)
  • Re-installing dependencies (potentially thousands of packages)
  • Re-starting services (databases, web servers, etc.)
  • Re-logging into external systems

If we create environments from scratch for every rollout, training costs become prohibitive.

AgentENV solves this problem through four core primitives:

┌──────────────────────────────────────────────────────────────────┐
│                    AgentENV Core Primitives                       │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. Snapshot  ▲  Incrementally records memory and filesystem     │
│                   changes. No need to copy the entire VM image.   │
│                   Completes in <100ms (even under heavy disk I/O) │
│                   Persistable to S3 or distributed filesystem     │
│                                                                   │
│  2. Pause        Releases CPU and reclaimable memory              │
│                  Completes in <100ms                              │
│                  TTL expiry auto-pauses by default (not delete)   │
│                                                                   │
│  3. Resume     ▲  Fast restore from snapshot                     │
│                  Restores running processes and open connections  │
│                  Completes in <50ms                               │
│                                                                   │
│  4. Fork       ▲  Clone a running environment into N             │
│                   independent child sandboxes                    │
│                   Up to 16 children per source                    │
│                   Children inherit filesystem, memory, resources  │
│                   Copy-on-write, near-zero overhead               │
│                                                                   │
│  Typical Use Case:                                               │
│  ┌─────────────────────────────────────────────────────────┐     │
│  │  1. Build environment: install deps, clone repo, start  │     │
│  │     services (once)                                     │     │
│  │  2. Snapshot: save current state                        │     │
│  │  3. Fork × 16: create 16 independent child sandboxes    │     │
│  │     from this state                                     │     │
│  │  4. Parallel Rollout: each child tries different        │     │
│  │     strategies                                          │     │
│  │  5. Collect rewards: compare branch performance         │     │
│  │  6. Recycle: pause or delete children, release resources│     │
│  └─────────────────────────────────────────────────────────┘     │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

6.2 Incremental Snapshot Implementation

AgentENV snapshots are not full copies but incremental recordings. This is achieved through two mechanisms:

Memory snapshots: Based on KVM’s dirty page tracking. Firecracker uses KVM’s KVM_GET_DIRTY_LOG ioctl to obtain memory pages modified since the last snapshot, recording only the delta.

# Incremental snapshot Python pseudocode
# Actual AgentENV uses Rust calling Firecracker API

class IncrementalSnapshotManager:
    """Incremental snapshot manager"""

    def __init__(self, microvm_id: str):
        self.microvm_id = microvm_id
        self.base_snapshot = None
        self.delta_snapshots = []
        self.last_dirty_bitmap = None

    async def create_snapshot(self, vm: FirecrackerMicroVM):
        """Create an incremental snapshot"""
        # 1. Pause VM (freeze vCPUs)
        await vm.pause()

        # 2. Get dirty page bitmap since last snapshot
        dirty_bitmap = await vm.get_dirty_log()

        if self.base_snapshot is None:
            # First snapshot: record full state
            mem_snapshot = await vm.dump_memory_full()
            disk_snapshot = await vm.dump_disk_full()
            self.base_snapshot = Snapshot(
                memory=mem_snapshot,
                disk=disk_snapshot,
                bitmap=dirty_bitmap,
                timestamp=time.now(),
            )
        else:
            # Incremental snapshot: record only dirty pages
            new_dirty_pages = self._compute_delta(
                dirty_bitmap, self.last_dirty_bitmap
            )
            mem_delta = await vm.dump_memory_regions(new_dirty_pages)
            disk_delta = await vm.dump_disk_changes(new_dirty_pages)
            delta = DeltaSnapshot(
                base_id=self.base_snapshot.id,
                dirty_pages=new_dirty_pages,
                memory_delta=mem_delta,
                disk_delta=disk_delta,
                timestamp=time.now(),
            )
            self.delta_snapshots.append(delta)

        self.last_dirty_bitmap = dirty_bitmap

        # 3. Resume VM
        await vm.resume()

        return self.base_snapshot or self.delta_snapshots[-1]

    async def restore_from_snapshot(self, snapshot_id: str):
        """Restore environment from snapshot"""
        # 1. Load full state from base snapshot
        vm_state = self.base_snapshot.memory.copy()
        disk_state = self.base_snapshot.disk.copy()

        # 2. Apply deltas in order
        for delta in self.delta_snapshots:
            if delta.base_id == self.base_snapshot.id:
                for page_addr, page_data in delta.memory_delta.items():
                    vm_state[page_addr] = page_data
                for block_addr, block_data in delta.disk_delta.items():
                    disk_state[block_addr] = block_data

        # 3. Load into new Firecracker instance
        new_vm = await create_firecracker_vm()
        await new_vm.load_memory(vm_state)
        await new_vm.load_disk(disk_state)
        await new_vm.resume()

        return new_vm

    def _compute_delta(self, current, previous):
        """Compute difference between two bitmaps"""
        if previous is None:
            return current
        delta = Bitmap()
        for i in range(len(current)):
            if current[i] and not previous[i]:
                delta.set(i)
        return delta

6.3 Fork Mechanism: The “Photocopier” for Agentic RL

Fork is AgentENV’s most RL-specific feature. A running sandbox can clone up to 16 independent child sandboxes on the same node. The source pauses briefly during capture, then resumes. Each child inherits the source’s filesystem, memory, and resource configuration.

┌──────────────────────────────────────────────────────────────────┐
│                    Fork Operation Flowchart                        │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Time →                                                           │
│                                                                   │
│  ┌──────────────────────────────────────┐                        │
│  │  Source Sandbox (Agent Environment)   │                        │
│  │  - All dependencies installed         │                        │
│  │  - Code repository cloned             │                        │
│  │  - Database service started           │                        │
│  │  - External systems logged in         │                        │
│  │  - State: Running                     │                        │
│  └───────────────┬──────────────────────┘                        │
│                  │ Fork command                                   │
│                  ▼                                                │
│  ┌──────────────────────────────────────┐                        │
│  │  Pause Source Sandbox                 │                        │
│  │  Freeze vCPUs, save memory & disk     │                        │
│  └───────────────┬──────────────────────┘                        │
│                  │                                                │
│    ┌─────────────┼─────────────┬─────────────┬───────             │
│    ▼             ▼             ▼             ▼                    │
│  ┌──────┐   ┌──────┐     ┌──────┐     ┌──────┐                  │
│  │Child1│   │Child2│     │Child3│     │Child16│                 │
│  │COW   │   │COW   │     │COW   │     │COW   │                  │
│  │Upper │   │Upper │     │Upper │     │Upper │                  │
│  └──┬───┘   └──┬───┘     └──┬───┘     └──┬───┘                  │
│     │          │            │            │                        │
│     └──────────┴────────────┴────────────┘                        │
│                │ Shared read-only memory pages + shared base layer│
│                ▼                                                  │
│  ┌──────────────────────────────────────┐                        │
│  │  Resume Source Sandbox                │                        │
│  │  <50ms, continues running             │                        │
│  └──────────────────────────────────────┘                        │
│                                                                   │
│  Each child sandbox can now:                                      │
│  - Try different code modifications                               │
│  - Use different tool calls                                       │
│  - Explore different policy trajectories                          │
│  - Run completely independently, without interference             │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

6.4 Firecracker API Operations Example

Below is a Python code example demonstrating complete microVM lifecycle management using the Firecracker API:

#!/usr/bin/env python3
"""AgentENV Firecracker API operations — complete microVM lifecycle management"""

import asyncio
import json
import aiohttp
from dataclasses import dataclass
from typing import Optional

@dataclass
class MicroVMConfig:
    """microVM configuration"""
    kernel_path: str = "/opt/aenv/kernel/vmlinux-6.8"
    rootfs_path: str = "/opt/aenv/images/ubuntu-22.04.ext4"
    vcpu_count: int = 2
    mem_size_mib: int = 4096
    jailer_cfg: Optional[dict] = None

class FirecrackerAPI:
    """Firecracker control plane API wrapper"""

    def __init__(self, fc_socket_path: str):
        self.socket_path = fc_socket_path
        self.session = aiohttp.ClientSession(
            connector=aiohttp.UnixConnector(path=fc_socket_path)
        )

    async def create_vm(self, config: MicroVMConfig) -> dict:
        """Create and start a microVM"""
        # 1. Configure kernel and root filesystem
        await self._put("/boot-source", {
            "kernel_image_path": config.kernel_path,
            "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
        })

        # 2. Configure root filesystem drive
        await self._put("/drives/rootfs", {
            "drive_id": "rootfs",
            "path_on_host": config.rootfs_path,
            "is_root_device": True,
            "is_read_only": False,
        })

        # 3. Configure vCPU and memory
        await self._put("/machine-config", {
            "vcpu_count": config.vcpu_count,
            "mem_size_mib": config.mem_size_mib,
            "smt": False,
            "track_dirty_pages": True,  # Enable dirty page tracking (needed for snapshots)
        })

        # 4. Configure network interface
        await self._put("/network-interfaces/eth0", {
            "iface_id": "eth0",
            "host_dev_name": "tap0",
            "guest_mac": "02:fc:00:00:00:01",
        })

        # 5. Start microVM
        await self._put("/actions", {
            "action_type": "InstanceStart"
        })

        return {"status": "running", "vm_id": "vm-1"}

    async def create_snapshot(self, snapshot_id: str, mem_path: str,
                              snapshot_path: str) -> dict:
        """Create memory and disk snapshot"""
        # Pause VM first
        await self._put("/actions", {"action_type": "Pause"})

        # Create snapshot
        await self._put("/snapshot/create", {
            "snapshot_type": "Full",
            "snapshot_path": snapshot_path,
            "mem_file_path": mem_path,
            "version": "1.0",
        })

        # Resume VM
        await self._put("/actions", {"action_type": "Resume"})

        return {"snapshot_id": snapshot_id, "status": "created"}

    async def restore_from_snapshot(self, mem_path: str,
                                    snapshot_path: str) -> dict:
        """Restore VM from snapshot"""
        await self._put("/snapshot/load", {
            "snapshot_path": snapshot_path,
            "mem_backend": {
                "backend_type": "File",
                "backend_path": mem_path,
            },
            "resume_vm": True,
        })

        return {"status": "restored"}

    async def _put(self, path: str, body: dict) -> dict:
        """Send PUT request to Firecracker API"""
        url = f"http://localhost{path}"
        async with self.session.put(url, json=body) as resp:
            if resp.status >= 400:
                text = await resp.text()
                raise RuntimeError(f"API error {resp.status}: {text}")
            if resp.status == 204:
                return {}
            return await resp.json()

    async def close(self):
        await self.session.close()

# Usage example
async def main():
    # Create and start a microVM
    fc_api = FirecrackerAPI("/tmp/firecracker.socket")
    vm = await fc_api.create_vm(MicroVMConfig(
        vcpu_count=4,
        mem_size_mib=8192,
    ))
    print(f"VM created successfully: {vm}")

    # Run some tasks...
    await asyncio.sleep(10)

    # Create snapshot
    snap = await fc_api.create_snapshot(
        snapshot_id="snap-001",
        mem_path="/snapshots/snap-001/mem",
        snapshot_path="/snapshots/snap-001/vmstate",
    )
    print(f"Snapshot created successfully: {snap}")

    # Restore from snapshot to a new VM (Fork)
    restored = await fc_api.restore_from_snapshot(
        mem_path="/snapshots/snap-001/mem",
        snapshot_path="/snapshots/snap-001/vmstate",
    )
    print(f"VM restored successfully: {restored}")

    await fc_api.close()

if __name__ == "__main__":
    asyncio.run(main())

7. Distributed RL Training Extension

7.1 AgentENV’s Position in the RL Training Pipeline

AgentENV is not itself an RL training framework — it is the environment execution layer. It works in conjunction with RL training frameworks (such as Ray/RLlib, OpenRL, AgentRL, etc.):

┌──────────────────────────────────────────────────────────────────┐
│                  Agentic RL Training Pipeline                     │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐    │
│  │  RL Training Framework (Ray/RLlib, OpenRL, AgentRL)      │    │
│  │  ┌───────────┐  ┌───────────┐  ┌───────────────────┐    │    │
│  │  │ Policy    │  │  Reward   │  │  Value Function   │    │    │
│  │  │ Network   │  │  Model    │  │  (Critic)         │    │    │
│  │  └─────┬─────┘  └─────┬─────┘  └────────┬──────────┘    │    │
│  │        │              │                  │               │    │
│  │        └──────────────┴──────────────────┘               │    │
│  │                       │ Gradient update                  │    │
│  └───────────────────────┼──────────────────────────────────┘    │
│                          │                                       │
│                          ▼                                       │
│  ┌──────────────────────────────────────────────────────────┐    │
│  │  Rollout Manager (Parallel Sampling)                      │    │
│  │                                                           │    │
│  │  ┌───────────────────────────────────────────────────┐   │    │
│  │  │  AgentENV HTTP API (E2B Compatible)                │   │    │
│  │  │  ┌──────────┐ ┌──────────┐ ┌──────────┐          │   │    │
│  │  │  │ Sandbox  │ │ Sandbox  │ │ Sandbox  │ ── 30K+  │   │    │
│  │  │  │ #1 micro │ │ #2 micro │ │ #N micro │  Concurrent│   │    │
│  │  │  │ VM       │ │ VM       │ │ VM       │          │   │    │
│  │  │  └──────────┘ └──────────┘ └──────────┘          │   │    │
│  │  └───────────────────────────────────────────────────┘   │    │
│  └──────────────────────────────────────────────────────────┘    │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

7.2 GRPO-Based Agentic RL Training Code

Below is a complete, runnable Agentic RL training loop example demonstrating how AgentENV integrates with the GRPO (Group Relative Policy Optimization) algorithm:

#!/usr/bin/env python3
"""Distributed GRPO Agentic RL training example using AgentENV"""

import asyncio
import json
import time
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any
import numpy as np
import aiohttp
import torch

# ============================================================
# AgentENV Client (E2B Compatible API)
# ============================================================

class AgentENVClient:
    """AgentENV sandbox client using E2B compatible API"""

    def __init__(self, base_url: str = "http://localhost:8000"):
        self.base_url = base_url
        self.session = aiohttp.ClientSession()

    async def create_sandbox(
        self,
        template: str = "ubuntu:22.04",
        vcpu: int = 2,
        memory_mb: int = 4096,
        timeout_s: int = 300,
    ) -> str:
        """Create a new sandbox environment"""
        async with self.session.post(
            f"{self.base_url}/sandboxes",
            json={
                "templateID": template,
                "vcpu": vcpu,
                "memoryMB": memory_mb,
                "timeout": timeout_s,
                "autoPause": True,
            }
        ) as resp:
            data = await resp.json()
            return data["sandboxID"]

    async def exec_command(
        self, sandbox_id: str, command: str
    ) -> Dict[str, Any]:
        """Execute a command in the sandbox"""
        async with self.session.post(
            f"{self.base_url}/sandboxes/{sandbox_id}/execute",
            json={"command": command}
        ) as resp:
            return await resp.json()

    async def write_file(
        self, sandbox_id: str, path: str, content: str
    ) -> None:
        """Write a file to the sandbox"""
        async with self.session.post(
            f"{self.base_url}/sandboxes/{sandbox_id}/files/write",
            json={"path": path, "content": content}
        ) as resp:
            resp.raise_for_status()

    async def read_file(
        self, sandbox_id: str, path: str
    ) -> str:
        """Read a file from the sandbox"""
        async with self.session.get(
            f"{self.base_url}/sandboxes/{sandbox_id}/files/read",
            params={"path": path}
        ) as resp:
            data = await resp.json()
            return data["content"]

    async def pause_sandbox(self, sandbox_id: str) -> None:
        """Pause sandbox (release CPU/memory)"""
        async with self.session.post(
            f"{self.base_url}/sandboxes/{sandbox_id}/pause"
        ) as resp:
            resp.raise_for_status()

    async def resume_sandbox(self, sandbox_id: str) -> None:
        """Resume sandbox"""
        async with self.session.post(
            f"{self.base_url}/sandboxes/{sandbox_id}/resume"
        ) as resp:
            resp.raise_for_status()

    async def fork_sandbox(
        self, sandbox_id: str, count: int = 4
    ) -> List[str]:
        """Fork N child sandboxes from an existing sandbox"""
        async with self.session.post(
            f"{self.base_url}/sandboxes/{sandbox_id}/fork",
            json={"count": count}
        ) as resp:
            data = await resp.json()
            return data["childSandboxIDs"]

    async def create_template(
        self, sandbox_id: str, name: str
    ) -> str:
        """Create a reusable template from a sandbox state"""
        async with self.session.post(
            f"{self.base_url}/templates",
            json={"sandboxID": sandbox_id, "name": name}
        ) as resp:
            data = await resp.json()
            return data["templateID"]

    async def delete_sandbox(self, sandbox_id: str) -> None:
        """Delete a sandbox"""
        async with self.session.delete(
            f"{self.base_url}/sandboxes/{sandbox_id}"
        ) as resp:
            resp.raise_for_status()

    async def close(self):
        await self.session.close()


# ============================================================
# Task Definition: Coding Task Environment
# ============================================================

@dataclass
class CodingTask:
    """Coding task definition"""
    repo_url: str
    branch: str = "main"
    description: str = ""
    test_command: str = "pytest tests/"
    setup_commands: List[str] = field(default_factory=list)

# Example tasks: fix bugs in Python projects
BENCHMARK_TASKS = [
    CodingTask(
        repo_url="https://github.com/demo/fastapi-bug-fix.git",
        branch="main",
        description="Fix SQL injection vulnerability in FastAPI routes",
        test_command="pytest tests/test_security.py -v",
        setup_commands=[
            "pip install -r requirements.txt",
            "pip install pytest",
        ],
    ),
    CodingTask(
        repo_url="https://github.com/demo/react-form-bug.git",
        branch="main",
        description="Fix state update bug in React form validation component",
        test_command="npm test",
        setup_commands=[
            "npm install",
        ],
    ),
]


# ============================================================
# GRPO Trainer
# ============================================================

@dataclass
class GRPOConfig:
    """GRPO training configuration"""
    group_size: int = 8          # Group size for sampling (GRPO's G)
    num_iterations: int = 100    # Number of training iterations
    learning_rate: float = 1e-5
    kl_coef: float = 0.01
    clip_epsilon: float = 0.2
    max_steps_per_task: int = 20
    fork_count: int = 8          # Number of child sandboxes per fork

class GRPOTrainer:
    """GRPO algorithm trainer using AgentENV as environment backend"""

    def __init__(
        self,
        policy_model: Any,
        tokenizer: Any,
        config: GRPOConfig,
        env_client: AgentENVClient,
    ):
        self.policy = policy_model
        self.tokenizer = tokenizer
        self.config = config
        self.env = env_client
        self.optimizer = torch.optim.AdamW(
            self.policy.parameters(),
            lr=config.learning_rate,
        )

    async def setup_environment(self, task: CodingTask) -> str:
        """Build training environment and return sandbox ID"""
        # 1. Create base sandbox
        sandbox_id = await self.env.create_sandbox(
            template="coding-agent:latest",
            vcpu=4,
            memory_mb=8192,
            timeout_s=3600,
        )

        # 2. Clone code repository
        await self.env.exec_command(
            sandbox_id,
            f"git clone --branch {task.branch} {task.repo_url} /workspace"
        )

        # 3. Execute setup commands
        for cmd in task.setup_commands:
            result = await self.env.exec_command(sandbox_id, cmd)
            if result["exitCode"] != 0:
                raise RuntimeError(
                    f"Setup failed: {result['stderr']}"
                )

        return sandbox_id

    async def collect_trajectories(
        self, base_sandbox_id: str, task: CodingTask
    ) -> List[Dict]:
        """Collect multiple trajectories in parallel using Fork"""
        # 1. Fork child sandboxes from base environment
        child_ids = await self.env.fork_sandbox(
            base_sandbox_id,
            count=self.config.fork_count,
        )
        print(f"Forked {len(child_ids)} child sandboxes from {base_sandbox_id}")

        trajectories = []
        tasks = []

        for i, child_id in enumerate(child_ids):
            tasks.append(
                self._rollout_single(child_id, task, i)
            )

        # Execute all rollouts in parallel
        results = await asyncio.gather(*tasks)
        trajectories.extend(results)

        # Clean up child sandboxes
        for child_id in child_ids:
            await self.env.delete_sandbox(child_id)

        return trajectories

    async def _rollout_single(
        self, sandbox_id: str, task: CodingTask, rank: int
    ) -> Dict:
        """Single trajectory sampling"""
        trajectory = {
            "sandbox_id": sandbox_id,
            "rank": rank,
            "steps": [],
            "final_reward": 0.0,
            "success": False,
        }

        current_state = await self._get_env_state(sandbox_id)

        for step in range(self.config.max_steps_per_task):
            # Policy model generates action
            action = await self._policy_generate(current_state)

            # Execute action in sandbox
            result = await self.env.exec_command(sandbox_id, action)

            # Get new state and reward
            next_state = await self._get_env_state(sandbox_id)
            reward = self._compute_reward(result, task)

            trajectory["steps"].append({
                "state": current_state,
                "action": action,
                "result": result,
                "reward": reward,
                "next_state": next_state,
            })

            current_state = next_state

            # Check if task is completed
            if result["exitCode"] == 0 and "ALL TESTS PASSED" in result["stdout"]:
                trajectory["success"] = True
                trajectory["final_reward"] = 10.0
                break

        return trajectory

    async def _get_env_state(self, sandbox_id: str) -> str:
        """Get environment state summary"""
        result = await self.env.exec_command(
            sandbox_id,
            "echo '=== FILES ===' && find . -name '*.py' -newer /tmp/start | head -20 && "
            "echo '=== GIT DIFF ===' && git diff --stat 2>/dev/null || true"
        )
        return result["stdout"]

    async def _policy_generate(self, state: str) -> str:
        """Policy model generates action (code modification) from state"""
        prompt = (
            f"Current codebase state:\n{state}\n\n"
            f"Generate the next shell command to fix the bug. "
            f"Output only the command, no explanation."
        )
        inputs = self.tokenizer(prompt, return_tensors="pt")
        with torch.no_grad():
            outputs = self.policy.generate(
                **inputs,
                max_new_tokens=200,
                temperature=1.0,
                top_p=0.9,
            )
        action = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
        action = action.split("\n")[-1].strip()
        return action

    def _compute_reward(self, result: Dict, task: CodingTask) -> float:
        """Compute reward for action result"""
        reward = 0.0
        if result["exitCode"] == 0:
            reward += 1.0  # Command executed successfully
        if "ALL TESTS PASSED" in result.get("stdout", ""):
            reward += 5.0  # Tests passed
        if "ERROR" in result.get("stderr", ""):
            reward -= 0.5  # Error occurred
        return reward

    async def train_step(self, trajectories: List[Dict]) -> Dict:
        """GRPO training step"""
        # 1. Collect all rewards and states
        all_rewards = []
        all_states = []
        all_actions = []

        for traj in trajectories:
            for step in traj["steps"]:
                all_rewards.append(step["reward"])
                all_states.append(step["state"])
                all_actions.append(step["action"])

        if not all_rewards:
            return {"loss": 0.0, "mean_reward": 0.0}

        # 2. GRPO: group-normalized rewards
        rewards = np.array(all_rewards, dtype=np.float32)
        mean_reward = rewards.mean()
        std_reward = rewards.std() + 1e-8
        normalized_rewards = (rewards - mean_reward) / std_reward

        # 3. Compute policy loss
        total_loss = 0.0
        for i in range(0, len(all_states), self.config.group_size):
            batch_end = min(i + self.config.group_size, len(all_states))
            batch_rewards = normalized_rewards[i:batch_end]

            # Recompute current policy log probabilities
            # In production, this would use policy.forward()
            advantages = torch.tensor(batch_rewards, dtype=torch.float32)
            surrogate_loss = -advantages.mean()
            total_loss += surrogate_loss.item()

        # 4. Backpropagation
        loss_tensor = torch.tensor(total_loss, requires_grad=True)
        self.optimizer.zero_grad()
        loss_tensor.backward()
        torch.nn.utils.clip_grad_norm_(self.policy.parameters(), 1.0)
        self.optimizer.step()

        return {
            "loss": total_loss,
            "mean_reward": float(mean_reward),
            "num_samples": len(all_rewards),
        }

    async def train(self, tasks: List[CodingTask]):
        """Complete training loop"""
        for iteration in range(self.config.num_iterations):
            print(f"\n=== Iteration {iteration + 1}/{self.config.num_iterations} ===")

            all_trajectories = []

            for task in tasks:
                # 1. Setup environment
                base_sandbox = await self.setup_environment(task)
                print(f"  Task: {task.description}")

                # 2. Fork and collect trajectories in parallel
                trajectories = await self.collect_trajectories(
                    base_sandbox, task
                )
                all_trajectories.extend(trajectories)

                # 3. Clean up base environment
                await self.env.pause_sandbox(base_sandbox)

                # 4. Evaluate results
                success_rate = sum(
                    1 for t in trajectories if t["success"]
                ) / len(trajectories) if trajectories else 0.0
                print(f"    Success rate: {success_rate:.1%}")

            # 5. GRPO update
            metrics = await self.train_step(all_trajectories)
            print(f"  Train metrics: loss={metrics['loss']:.4f}, "
                  f"reward={metrics['mean_reward']:.2f}")

            # 6. Save checkpoint
            if (iteration + 1) % 10 == 0:
                torch.save(
                    self.policy.state_dict(),
                    f"checkpoint_iter_{iteration + 1}.pt"
                )


# ============================================================
# Main Entry Point
# ============================================================

async def main():
    # Initialize AgentENV client
    client = AgentENVClient("http://localhost:8000")

    # Initialize policy model (illustration: using small model)
    from transformers import AutoModelForCausalLM, AutoTokenizer

    model_name = "Qwen/Qwen2.5-1.5B-Instruct"
    policy = AutoModelForCausalLM.from_pretrained(model_name)
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    # Configure training parameters
    config = GRPOConfig(
        group_size=8,
        num_iterations=100,
        learning_rate=1e-5,
        fork_count=8,
        max_steps_per_task=20,
    )

    # Create trainer
    trainer = GRPOTrainer(
        policy_model=policy,
        tokenizer=tokenizer,
        config=config,
        env_client=client,
    )

    # Start training
    try:
        await trainer.train(BENCHMARK_TASKS)
    finally:
        await client.close()


if __name__ == "__main__":
    asyncio.run(main())

8. Multi-Tenant Security Design

8.1 Threat Model

Security threats in Agentic RL training differ from traditional multi-tenant cloud services. The primary threat source is not malicious external attackers, but the agent being trained itself. Our research found that reward-driven agents attempt to:

  1. Escape the execution environment: Attempt to access the host or other training tasks
  2. Modify evaluation logic: Modify test scripts to obtain false high scores
  3. Access hidden services: Scan internal networks, discover unauthorized services
  4. External data theft: Retrieve answers from external sources rather than completing tasks autonomously

8.2 Multi-Layer Security Architecture

┌──────────────────────────────────────────────────────────────────┐
│                    AgentENV Security Architecture                  │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Layer 1: KVM Hardware Virtualization Isolation                   │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  EPT (Extended Page Tables) → Physical memory isolation  │       │
│  │  VM-Exit traps all privileged instructions → CPU        │       │
│  │  instruction isolation                                   │       │
│  │  IOMMU (VT-d/AMD-Vi) → DMA device isolation             │       │
│  └───────────────────────────────────────────────────────┘       │
│                                                                   │
│  Layer 2: Jailer Process Isolation                                │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  Namespaces (PID/Mount/Net/IPC/UTS) → Resource view     │       │
│  │  isolation                                               │       │
│  │  cgroups → Resource limits (CPU/Memory/IOPS/PIDs)       │       │
│  │  chroot → Filesystem isolation                          │       │
│  │  seccomp-bpf → Syscall whitelist (~50 syscalls)         │       │
│  │  Non-root user execution → Principle of least privilege │       │
│  └───────────────────────────────────────────────────────┘       │
│                                                                   │
│  Layer 3: Network Isolation                                       │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  Per-sandbox netns → Independent network stack         │       │
│  │  TAP device + iptables → Isolated virtual network      │       │
│  │  always_denied_cidrs → Node-level egress restrictions   │       │
│  │  per-sandbox allowOut/denyOut → Fine-grained network    │       │
│  │  control                                                │       │
│  │  Cross-sandbox communication blocked by default         │       │
│  └───────────────────────────────────────────────────────┘       │
│                                                                   │
│  Layer 4: Side-Channel Attack Mitigation                         │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  KVM hardware virtualization → Partial cache side-     │       │
│  │  channel mitigation                                     │       │
│  │  Minimized shared resources → Reduced side-channel     │       │
│  │  attack surface                                         │       │
│  │  Memory ballooning → Reduced page table sharing         │       │
│  └───────────────────────────────────────────────────────┘       │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

8.3 Network Isolation Configuration Example

#!/usr/bin/env python3
"""AgentENV network isolation configuration example"""

import ipaddress
from typing import List, Optional

class NetworkIsolationConfig:
    """Sandbox network isolation configuration"""

    # Default denied internal CIDR list
    DEFAULT_DENIED_CIDRS = [
        "10.0.0.0/8",       # Private network
        "172.16.0.0/12",    # Private network
        "192.168.0.0/16",   # Private network
        "169.254.0.0/16",   # Link-local
        "127.0.0.0/8",      # Local loopback
    ]

    def __init__(
        self,
        allow_internet: bool = True,
        denied_cidrs: Optional[List[str]] = None,
        allowed_cidrs: Optional[List[str]] = None,
    ):
        self.allow_internet = allow_internet
        self.denied_cidrs = [
            ipaddress.ip_network(cidr)
            for cidr in (denied_cidrs or self.DEFAULT_DENIED_CIDRS)
        ]
        self.allowed_cidrs = [
            ipaddress.ip_network(cidr)
            for cidr in (allowed_cidrs or [])
        ]

    def validate_egress(self, target_ip: str, target_port: int) -> bool:
        """Validate whether egress target is allowed"""
        ip = ipaddress.ip_address(target_ip)

        # Whitelist first: if in allowed_cidrs, allow
        for network in self.allowed_cidrs:
            if ip in network:
                return True

        # Blacklist: if in denied_cidrs, deny
        for network in self.denied_cidrs:
            if ip in network:
                return False

        # Default policy: based on allow_internet
        return self.allow_internet

    def to_api_request(self) -> dict:
        """Convert to AgentENV API request format"""
        return {
            "allowInternetAccess": self.allow_internet,
            "alwaysDeniedCIDRs": [str(c) for c in self.denied_cidrs],
            "alwaysAllowedCIDRs": [str(c) for c in self.allowed_cidrs],
        }


# Create network configurations for different security levels
def create_network_config(task_type: str) -> NetworkIsolationConfig:
    """Create network isolation config based on task type"""
    configs = {
        "high_security": NetworkIsolationConfig(
            allow_internet=False,
            denied_cidrs=[
                "0.0.0.0/0",  # Completely block all network access
            ],
        ),
        "coding_agent": NetworkIsolationConfig(
            allow_internet=True,
            denied_cidrs=[
                "10.0.0.0/8",
                "172.16.0.0/12",
                "192.168.0.0/16",
                "100.64.0.0/10",
            ],
            allowed_cidrs=[
                "0.0.0.0/0",  # Allow external access (e.g., pip install)
            ],
        ),
        "web_agent": NetworkIsolationConfig(
            allow_internet=True,
            denied_cidrs=[
                "10.0.0.0/8",
                "172.16.0.0/12",
                "192.168.0.0/16",
            ],
            allowed_cidrs=[
                "0.0.0.0/0",
            ],
        ),
    }
    return configs.get(task_type, configs["coding_agent"])

9. Deployment and Operations

9.1 Deployment Methods

AgentENV supports five deployment methods:

# Method 1: One-click install script (single node, recommended for beginners)
curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/install.sh | sudo bash
sudo systemctl start aenv

# Method 2: Docker deployment
curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/docker-setup.sh | sudo bash
docker pull ghcr.io/kvcache-ai/aenv-server:latest
docker run -d --privileged -v /dev:/dev -p 8000:8000 ghcr.io/kvcache-ai/aenv-server:latest

# Method 3: Docker Compose (simulate multi-node cluster)
# See docker-compose.yml

# Method 4: Kubernetes (production multi-node)
# Includes Gateway, Scheduler, Node DaemonSet

# Method 5: Build from source
git clone https://github.com/kvcache-ai/AgentENV.git
cd AgentENV
cargo build --release

9.2 CLI Usage Examples

# Install CLI
curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/install-cli.sh | bash

# Authenticate
aenv auth
# AENV server URL [http://localhost:8000]: http://127.0.0.1:8000
# API key: dummy

# Pull template
aenv pull ubuntu:22.04 --name ubuntu
aenv pull docker.io/library/python:3.12 --name python312

# Start sandbox with interactive shell
aenv start ubuntu

# Start sandbox in detached mode
aenv start ubuntu --detach

# List all sandboxes
aenv ls

# Execute single command
aenv exec <sandbox-id> ls -la /

# Pause/Resume
aenv pause <sandbox-id>
aenv resume <sandbox-id>

# Set timeout
aenv timeout <sandbox-id> 600

# Delete sandbox
aenv delete <sandbox-id>

9.3 Multi-Node Cluster Deployment

# docker-compose.yml — Multi-node AgentENV cluster
version: "3.8"

services:
  gateway:
    image: ghcr.io/kvcache-ai/aenv-gateway:latest
    ports:
      - "8080:8080"
    environment:
      - SCHEDULER_URL=http://scheduler:9090
    depends_on:
      - scheduler

  scheduler:
    image: ghcr.io/kvcache-ai/aenv-scheduler:latest
    ports:
      - "9090:9090"
    environment:
      - NODE_LABELS=region=us-east-1,zone=a

  node-1:
    image: ghcr.io/kvcache-ai/aenv-server:latest
    privileged: true
    devices:
      - /dev/kvm:/dev/kvm
    volumes:
      - /dev:/dev
      - aenv-data-1:/var/lib/aenv
    environment:
      - SCHEDULER_URL=http://scheduler:9090
      - NODE_ID=node-1
    depends_on:
      - scheduler

  node-2:
    image: ghcr.io/kvcache-ai/aenv-server:latest
    privileged: true
    devices:
      - /dev/kvm:/dev/kvm
    volumes:
      - /dev:/dev
      - aenv-data-2:/var/lib/aenv
    environment:
      - SCHEDULER_URL=http://scheduler:9090
      - NODE_ID=node-2
    depends_on:
      - scheduler

volumes:
  aenv-data-1:
  aenv-data-2:

9.4 Performance Benchmark

#!/usr/bin/env python3
"""AgentENV performance benchmark script"""

import asyncio
import time
import statistics
from dataclasses import dataclass, field
from typing import List
from agentenv_client import AgentENVClient

@dataclass
class BenchmarkResult:
    """Benchmark test results"""
    cold_start_ms: List[float] = field(default_factory=list)
    snapshot_boot_ms: List[float] = field(default_factory=list)
    pause_ms: List[float] = field(default_factory=list)
    resume_ms: List[float] = field(default_factory=list)
    fork_ms: List[float] = field(default_factory=list)
    snapshot_create_ms: List[float] = field(default_factory=list)

    def summary(self) -> str:
        """Generate test summary"""
        lines = []
        lines.append("=== AgentENV Performance Benchmark ===")
        lines.append("")
        for name, data in [
            ("Cold Boot Time", self.cold_start_ms),
            ("Snapshot Boot Time", self.snapshot_boot_ms),
            ("Pause Time", self.pause_ms),
            ("Resume Time", self.resume_ms),
            ("Fork Time", self.fork_ms),
            ("Snapshot Create Time", self.snapshot_create_ms),
        ]:
            if data:
                lines.append(
                    f"{name}: "
                    f"avg={statistics.mean(data):.1f}ms, "
                    f"p50={statistics.median(data):.1f}ms, "
                    f"p99={sorted(data)[int(len(data)*0.99)]:.1f}ms, "
                    f"min={min(data):.1f}ms, max={max(data):.1f}ms"
                )
        return "\n".join(lines)

async def run_benchmark(
    client: AgentENVClient,
    iterations: int = 50,
) -> BenchmarkResult:
    """Run benchmark tests"""
    result = BenchmarkResult()

    # 1. Test cold boot
    print(f"Testing cold boot ({iterations} times)...")
    for i in range(iterations):
        start = time.perf_counter()
        sid = await client.create_sandbox(
            template="ubuntu:22.04",
            vcpu=2,
            memory_mb=1024,
        )
        elapsed = (time.perf_counter() - start) * 1000
        result.cold_start_ms.append(elapsed)
        await client.delete_sandbox(sid)

    # 2. Create a template, then test snapshot boot
    print("Creating template snapshot...")
    builder = await client.create_sandbox(
        template="ubuntu:22.04",
        vcpu=2,
        memory_mb=1024,
    )
    await client.exec_command(builder, "apt-get update && apt-get install -y python3-pip")
    template_id = await client.create_template(builder, "benchmark-template")
    await client.delete_sandbox(builder)

    # 3. Test snapshot boot
    print(f"Testing snapshot boot ({iterations} times)...")
    for i in range(iterations):
        start = time.perf_counter()
        sid = await client.create_sandbox(template=template_id)
        elapsed = (time.perf_counter() - start) * 1000
        result.snapshot_boot_ms.append(elapsed)

        # Test pause
        start = time.perf_counter()
        await client.pause_sandbox(sid)
        elapsed = (time.perf_counter() - start) * 1000
        result.pause_ms.append(elapsed)

        # Test resume
        start = time.perf_counter()
        await client.resume_sandbox(sid)
        elapsed = (time.perf_counter() - start) * 1000
        result.resume_ms.append(elapsed)

        # Test snapshot creation
        start = time.perf_counter()
        await client.create_snapshot(sid, f"snap-{i}")
        elapsed = (time.perf_counter() - start) * 1000
        result.snapshot_create_ms.append(elapsed)

        await client.delete_sandbox(sid)

    # 4. Test Fork
    print(f"Testing Fork ({min(iterations, 16)} times)...")
    parent = await client.create_sandbox(template=template_id)
    start = time.perf_counter()
    children = await client.fork_sandbox(parent, count=16)
    elapsed = (time.perf_counter() - start) * 1000
    result.fork_ms.append(elapsed)
    for child in children:
        await client.delete_sandbox(child)
    await client.delete_sandbox(parent)

    return result

async def main():
    client = AgentENVClient("http://localhost:8000")
    try:
        result = await run_benchmark(client, iterations=30)
        print(result.summary())
    finally:
        await client.close()

if __name__ == "__main__":
    asyncio.run(main())

10. Ecosystem Integration and Kimi K3 Synergy

10.1 E2B Compatibility

AgentENV exposes an HTTP API that is fully compatible with the E2B (Execution Environment for Browser) SDK. This means:

  • Any code using the E2B Python SDK or TypeScript SDK can seamlessly switch to AgentENV
  • Only the E2B_API_URL environment variable needs to point to the self-hosted AgentENV server
  • No agent code modifications are required
# E2B SDK code — runs on AgentENV without modification
import os
from e2b import Sandbox

# Just change this line:
os.environ["E2B_API_URL"] = "http://localhost:8000"

# The following code remains completely unchanged
sandbox = Sandbox(template="ubuntu:22.04")
sandbox.commands.run("pip install requests")
code = """
import requests
response = requests.get('https://api.example.com/data')
print(response.json())
"""
sandbox.files.write("/tmp/script.py", code)
result = sandbox.commands.run("python /tmp/script.py")
print(result.stdout)
sandbox.close()

10.2 Synergy with Kimi K3

AgentENV is the core infrastructure for Kimi K3’s Agentic RL training. Kimi K3 is a 2.8-trillion-parameter MoE (Mixture of Experts) model with powerful tool-use and autonomous planning capabilities. AgentENV provides Kimi K3 with:

  1. Secure isolated code execution environment: Kimi K3-generated code runs in independent microVMs
  2. Massively parallel training: Supports 30,000+ concurrent training environments
  3. Rapid iteration: Millisecond-level environment snapshots and recovery, dramatically shortening the training loop

Moonshot AI’s open-source strategy is “model + infrastructure” dual open-source — not only releasing Kimi K3’s model weights but also the infrastructure used to train it (AgentENV, Mooncake, MoBA, etc.). This strategy aims to lower the barrier to entry for the entire industry into Agentic RL.


11. Cost Analysis

11.1 Resource Allocation-to-Usage Ratio

AgentENV’s production resource allocation-to-usage ratio data (based on 70 nodes, 225,000 execution environments):

MetricAverageMinimum
CPU Allocation-to-Usage Ratio27.9×14.5×
Memory Allocation-to-Usage Ratio9.6×5.7×

This means that if each agent environment is configured with 4 vCPU + 8 GB memory, but the actual average usage is only 0.14 vCPU and 0.83 GB memory. AgentENV returns idle resources to the pool through fast pause/resume, memory reclamation, and state sharing.

11.2 Cost Comparison (300 environments, 4vCPU/8GB, 720 hours continuous)

SolutionMonthly Cost EstimateMultiple
AgentENV (ECS self-hosted)~¥15,300
Container Instance (ACS)~¥134,6408.8×
ECI Elastic Instance~¥250,56016.4×
Managed Sandbox (E2B)~¥485,00031.7×

12. Future Outlook

AgentENV v0.1.1 already demonstrates impressive capabilities, but several development directions are worth watching:

  1. Multi-node control plane maturation: The current Gateway + Scheduler is still in prototype stage; future versions will support more complete cluster scheduling, load balancing, and failover
  2. GPU passthrough support: For agent tasks requiring GPU (such as local LLM inference), GPU passthrough to microVM is an important direction
  3. Richer snapshot strategies: Adaptive snapshot frequency based on RL training patterns
  4. Cross-cluster snapshot migration: Migrate running environments between different clusters

13. Summary

AgentENV represents an important direction for agent execution infrastructure. Through Firecracker microVM’s hardware-level isolation, millisecond-level snapshot and fork mechanisms, and efficient resource reuse, it solves three core problems in Agentic RL training: environment isolation, resource density, and state management.

For AI infrastructure teams, AgentENV’s value is: It transforms agent training environment management from an “operational burden” into a “programmable resource.” When thousands of agent environments can be created, paused, resumed, and cloned in milliseconds, the design space for RL training pipelines is completely opened up.

For researchers, AgentENV’s MIT open-source license and E2B-compatible API mean they can get started quickly without building agent execution infrastructure from scratch.

Project Repository: https://github.com/kvcache-ai/AgentENV Official Documentation: https://kvcache.ai/blog/agentenv-open-sourced/ License: MIT License