# How Sandbox Resource Right-Sizing Works for Actors in Agent Substrate

> Agent Substrate right-sizes sandbox resources by converting actor CPU and memory declarations into SandboxSize struct, translating millicores to vCPUs, and embedding constraints into OCI spec for gVisor or micro-VM runtimes.

- Repository: [Agent Substrate/substrate](https://github.com/agent-substrate/substrate)
- Tags: internals
- Published: 2026-08-22

---

**Agent Substrate performs sandbox resource right-sizing by converting actor CPU and memory declarations into a `SandboxSize` struct, translating millicores to vCPUs, and embedding those constraints into the OCI specification before launching gVisor or micro-VM runtimes.**

The agent-substrate/substrate repository provides a secure isolation layer for actors using either gVisor or micro-VM sandboxes. When actors declare resource requirements in their `ActorTemplate`, the system must translate those abstract limits into concrete cgroup constraints and hardware allocations. This examination of the `internal/sizing` package reveals how sandbox resource right-sizing unifies resource management across disparate runtime implementations.

## Core Right-Sizing Logic in internal/sizing

The [`internal/sizing/sizing.go`](https://github.com/agent-substrate/substrate/blob/main/internal/sizing/sizing.go) file defines the `SandboxSize` type and three critical methods that transform actor requirements into runtime constraints. When the **RunWorkload** or **RestoreWorkload** RPCs initiate an actor, the *ateom* process invokes these functions to prepare the container specification.

### Converting Actor Limits with FromLimits

The `FromLimits` function builds a `SandboxSize` from the millicore and byte values received via RPC. It sanitizes input by clamping negative values to zero, treating them as unset rather than invalid.

```go
// internal/sizing/sizing.go
func FromLimits(milliCPU, memoryBytes int64) SandboxSize { … }

```

When an actor template specifies 500 millicores and 256MB of memory, this function captures those values while ensuring no negative constraints propagate to the runtime.

### Calculating Virtual CPU Requirements

The `VCPUs` method converts millicore limits into whole virtual CPUs required by micro-VM runtimes. It always rounds up fractional cores and guarantees a minimum allocation of one vCPU whenever any limit is set.

```go
// internal/sizing/sizing.go
func (s SandboxSize) VCPUs() int { … }

```

This ensures that an actor requesting 1500 millicores receives exactly 2 vCPUs, while an actor requesting 100 millicores receives 1 vCPU rather than being rounded down to zero.

### Writing Constraints to OCI Specifications

The `ApplyToOCISpec` method mutates the `spec.Linux.Resources` section of the OCI runtime specification. It writes CPU quota/period and memory limit values while leaving unset fields untouched, preserving existing defaults like Kata's device allowlist or CPU shares.

```go
// internal/sizing/sizing.go
func (s SandboxSize) ApplyToOCISpec(spec *specs.Spec) { … }

```

This approach allows both runtimes to consume the same standardized resource representation without runtime-specific adaptations in the sizing logic.

## Runtime Integration Paths

Both gVisor and micro-VM implementations rely on the shared `internal/sizing` package. They invoke `ApplyToOCISpec` at different stages of container initialization, ensuring the OCI specification contains the correct resource constraints before the sandbox runtime assumes control.

### gVisor (runsc) Implementation

In [`cmd/ateom-gvisor/runsc.go`](https://github.com/agent-substrate/substrate/blob/main/cmd/ateom-gvisor/runsc.go), the `ensureContainerCgroupsPath` function reads the bundle's [`config.json`](https://github.com/agent-substrate/substrate/blob/main/config.json), invokes `ApplyToOCISpec`, and writes the modified specification back to disk. The runsc binary then creates the per-container cgroup leaf using these values.

The `--cpu-num-from-quota` flag ensures the gVisor sandbox inherits the correct vCPU count derived from the OCI CPU quota.

```go
// cmd/ateom-gvisor/runsc.go
r.size.ApplyToOCISpec(&spec)            // ← right‑size cgroup

```

### Micro-VM (Kata) Implementation

For micro-VMs, [`cmd/ateom-microvm/spec.go`](https://github.com/agent-substrate/substrate/blob/main/cmd/ateom-microvm/spec.go) implements `ensureKataCompatibleSpec`. This function performs the same `ApplyToOCISpec` call before the kata-agent starts the VM. The VM's guest cgroup is then sized according to the limits, with the vCPU count derived from `size.VCPUs()`.

```go
// cmd/ateom-microvm/spec.go
size.ApplyToOCISpec(&spec)            // ← right‑size guest cgroup

```

## Summary

- **Sandbox resource right-sizing** begins with the `FromLimits` function in [`internal/sizing/sizing.go`](https://github.com/agent-substrate/substrate/blob/main/internal/sizing/sizing.go), which sanitizes actor resource declarations into a `SandboxSize` struct.
- The `VCPUs` method translates millicore requirements into whole virtual CPUs, rounding up and enforcing a minimum of one vCPU when limits are specified.
- `ApplyToOCISpec` writes CPU and memory constraints into the standard OCI `spec.Linux.Resources` section, leaving unset values untouched to preserve runtime defaults.
- Both **gVisor** ([`cmd/ateom-gvisor/runsc.go`](https://github.com/agent-substrate/substrate/blob/main/cmd/ateom-gvisor/runsc.go)) and **micro-VM** ([`cmd/ateom-microvm/spec.go`](https://github.com/agent-substrate/substrate/blob/main/cmd/ateom-microvm/spec.go)) runtimes call `ApplyToOCISpec` before initialization, ensuring consistent resource enforcement across sandbox technologies.

## Frequently Asked Questions

### How are negative resource limits handled in Agent Substrate?

The `FromLimits` function treats negative millicore or memory values as "unset" by clamping them to zero. This prevents invalid constraints from reaching the OCI specification while allowing actors to specify only one resource type (CPU or memory) if desired.

### What is the minimum vCPU allocation for an actor?

When any CPU limit is specified, the `VCPUs` method guarantees a minimum allocation of one virtual CPU. This prevents micro-VMs from launching with zero capacity, ensuring the actor has sufficient compute resources to function even with minimal millicore requests.

### Do gVisor and micro-VM runtimes use different resource sizing logic?

No. Both runtimes consume the same `SandboxSize` abstraction and call the identical `ApplyToOCISpec` method from `internal/sizing`. The difference lies in how each runtime consumes the OCI specification: gVisor configures host cgroups directly, while the micro-VM uses the limits to size the guest VM hardware and internal cgroups.

### Where are resource limits stored during the actor lifecycle?

Resource limits originate in the actor's `ActorTemplate`, pass through the **RunWorkload** or **RestoreWorkload** RPCs to the *ateom* process, and are ultimately persisted in the OCI bundle's [`config.json`](https://github.com/agent-substrate/substrate/blob/main/config.json) via `ApplyToOCISpec`. Both runtimes read these values from the OCI specification when creating container cgroups or configuring VM hardware.