Skip to content

feat(computer)!: an app's shm_mb sizes its computer's /dev/shm - #573

Merged
earakely-scale merged 3 commits into
mainfrom
edgararakelyan/fd-5052-m850-an-apps-shared-memory-shm_mb-on
Nov 9, 2026
Merged

earakely-scale merged 3 commits into
mainfrom
edgararakelyan/fd-5052-m850-an-apps-shared-memory-shm_mb-on

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

What

A Linux app can now declare the shared memory it needs: @linux(..., shm_mb=N), in MiB. A computer gives its /dev/shm the most any of its apps asks for. agentenv's chromium asks for 1024, so Chrome keeps its shared memory in /dev/shm instead of in files (--disable-dev-shm-usage), which it used to fall back to on Docker's 64 MiB.

This is M8.50 (FD-5052).

  • At put: shm_mb is checked like ports: 1 to 65536 MiB, Linux installs only. A macOS or Windows install that names it is refused.
  • Recorded: the computer's size goes in the composed computer's metadata["computer"]["shm_mb"] and in the deploy record's metadata["computer"]["shm_mb"]. Nothing is recorded when no app asks.
  • At boot, create_container and reset_container take shm_mb:
    • the base VM path's docker run (modal_vm and every VM backend) and local Docker pass --shm-size {N}m;
    • a reset passes it again, so the size survives a reset_env;
    • a probe boots a build with it too, as a deploy with no env_params does;
    • the disk form's init mounts /dev/shm at AGENTENV_SHM_MB MiB, which the deploy puts in the seed (512 MiB without it, as before);
    • a deploy, its probe and a reset pass shm_mb only to a provider whose SHARED_MEMORY is on (below). Any other provider, such as CoreWeave, a container-only backend or a plugin that overrides create_container, boots with its own /dev/shm, and the deploy logs <Provider> can't size /dev/shm: the container keeps its default, not the N MiB asked for.
  • SandboxProvider.SHARED_MEMORY, beside WEBSOCKETS and PRIVATE_NETWORK, says whether the container create_container or reset_container runs has the /dev/shm that shm_mb asks for, or more. Local Docker and sail_vm declare it. The default VM path (modal_vm, smol_vm, any VM plugin that keeps the base create_container) gets it derived. Modal declares it because its /dev/shm is a host-sized tmpfs larger than any app can ask. It is off for a container-only backend, and for a class that overrides create_container or reset_container without declaring it.
  • Out of scope: M8.13 sets compose's shm_size for the browser it adds to compose.

Decisions (FD-5052)

  • 1a. The disk init sizes /dev/shm from AGENTENV_SHM_MB, defaulting to 512 MiB.
  • 2a. A backend that can't size it deploys anyway, records shm_mb and logs.
  • 3a. With no app asking, nothing is passed, so every backend keeps its own default.

Rollout

  • Existing declarations are unchanged. Linux.shm_mb defaults to None and the protocol dumps with exclude_none, so an app that doesn't name it stores and hashes as before.
  • The disk recipe's digest changes, because boot/init changed, as it did in feat(computer): a versioned disk boot seed, and a reset an older launcher can't misread #476. New composes and the next release's published base disks get new keys. A disk composed before this PR keeps its old init, which ignores AGENTENV_SHM_MB: it boots at 512 MiB, and chromium on it falls back to --disable-dev-shm-usage as it does today.
  • No plugin breaks at runtime; the ! is for the plugin-api check. SandboxProvider.create_container and reset_container gain shm_mb, which the check counts as a break to the surface because it reads signatures. But a deploy passes shm_mb only where SHARED_MEMORY is on, and a class that overrides either method is off unless it declares it. So a plugin with the old signature never receives it: it boots with its own /dev/shm, and the deploy logs. A unit test boots such a plugin.

Measured

  • modal_vm: a container asking for 1024 MiB gets ShmSize=1073741824 and a 1 GiB /dev/shm, a reset asking again keeps it, and a reset asking for none gets Docker's 64 MiB.
  • Modal: a deploy passes it the size, which needs nothing there, and a container asking for 1024 MiB boots with no log about /dev/shm. Its /dev/shm is a tmpfs of about 377 GiB (size=395120960k, the host's), more than any app can ask for, and Modal's SDK has no option to size it. The sandbox's memory limit is what bounds it.

Tests

  • Unit:
    • the protocol and the put check: the most an app asks for wins, out-of-range sizes are refused, macOS and Windows installs refuse shm_mb;
    • deploys: the boot gets shm_mb, the record keeps it, a reset passes it again, and nothing is passed without it;
    • the disk form: the seed carries AGENTENV_SHM_MB at boot and reset, and create_vm gets no shm_mb;
    • providers: local Docker and the base VM path add --shm-size 1024m at create and reset. SHARED_MEMORY is derived on for the VM path and off where a class overrides create or reset without declaring it. A boot passes shm_mb to a provider that gives it, and for a plugin with the old signature only logs while the plugin boots;
    • chromium declares 1024 MiB.
  • Integration: a new chromium_app_test.py test deploys chromium's desktop, reads /dev/shm with statvfs as the browser base does, checks Chrome's argv, reset_envs, and reads both again. It runs as a container and, on a machine with /dev/kvm, as a disk. The existing argv test no longer expects --disable-dev-shm-usage. website_browser_test.py's fixture browser declares nothing, so it still expects the flag: that is the control.
  • VM e2e on agent-environments-admin. amd64 is a c8i.4xlarge with nested virtualization for /dev/kvm; arm64 is an m7g.4xlarge. All results are at fc84d49 unless marked.
    • The new shm test passes as a container on both arches. Chromium gets a 1 GiB /dev/shm (statvfs), runs without --disable-dev-shm-usage, and keeps both after reset_env.
    • The disk form passes under KVM at ec19637, on a third amd64 VM whose Docker uses the classic image store, as CI's runners do: chromium's disk boots with a 1 GiB /dev/shm and keeps it across a reset_env (a reboot). disk_test.py and disk_boot_test.py pass there too (4/4), so the desktop disk still boots at 512 MiB when nothing asks.
    • Negative control: on that VM, with main's boot/init put back, the same disk test fails with [536870912, 536870912] (512 MiB at boot and after the reset). So the test does measure the init.
    • A fresh Docker 29 can't boot local disks at all, before and after this PR. On the first amd64 VM, Docker 29.1.3 uses the containerd image store, and its --mount type=image fails on the disk's long image reference ("file name too long") before the VM starts. The same mount works under a short tag. That's FD-5416. CI's runners use the classic store, so CI only tests the copy path.
    • Regression suites (chromium_app_test, website_browser_test, browser_computer_test, computer_app_test, computer_env_test, multi_env_computers_test, desktop_example_test, recording_chaos_test, multi_env_child_envs_local_test, test_verify_sandbox_local, disk_test):
      • amd64: browsers 32/33, the one failure being the disk case above (FD-5416); computers 35/35.
      • arm64: browsers 29/32; computers 35/35; disks 3/3, its KVM test skipped.
      • The three arm64 browser failures are the CDP-agent tests. Their echo-agent fixture builds for amd64 only, and the VM can't run amd64 builds (exec format error). They pass on amd64.
      • Skips: the desktop example's model test (no model endpoint), the CDP-client tests (no Node), and arm64's KVM tests.
    • Unit tier at ec19637 on amd64: CI's two unit shards pass. On the VM, every failure comes from the VM: its ssh sessions get ulimit -n 1024, which the tier's local-sandbox and gateway files exhaust. They fail identically at main (0ec4652): 45 failed and 22 errors on both trees. With ulimit -n 65536 those files pass (597) bar the 3 known environment-only tests (no httpx in the system Python, no .git in the synced tree).
    • After option c (487bfa8): chromium's shm test passes again as a container and as a disk under KVM, and the modal_vm (4/4) and Modal (3/3) checks pass again.
    • Every VM is torn down.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 4/5

The inherited old-plugin reset failure still needs fixing before merge.

Fix All in CursorFindings

  1. P1 Older plugin subclasses cannot reset ▶
Fix with agent prompt
### Issue 1
src/agent_env/providers/sandbox_providers/sandbox_provider.py:266-270
If a VM plugin overrides `reset_container` with the old signature, a subclass that inherits it can wrongly regain `SHARED_MEMORY`. The override is absent from the child's `__dict__`, while its inherited `create_container` matches the base method, so this check turns the flag back on.

For an app that requests shared memory, `reset_env` then passes `shm_mb` to the old reset method and fails with an unexpected keyword argument. Check inherited reset methods too, and preserve the parent's capability decision when neither method changes.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Apps can request Linux shared memory through shm_mb. Computers use the largest request and keep it across resets.

  • Linux apps can request shared memory for their computer.
  • Computer boots carry the requested shared-memory size across provider and disk paths.
  • Chromium asks its computer for 1 GiB of shared memory.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Apps declare shm_mb"] --> B["Computer takes largest request"]
    B --> C["Probe or deploy"]
    C --> D{"Computer form"}
    D -->|Container| E{"Provider SHARED_MEMORY"}
    E -->|On| F["Pass shm_mb"]
    E -->|Off| G["Keep default and log"]
    D -->|Linux disk| H["Seed AGENTENV_SHM_MB"]
    B --> I["Save size in deploy record"]
    I --> J["Reset repeats the same path"]
Loading

Reviews (6) · Last reviewed commit: "feat(providers): a provider declares SHA..." · Reviewed by Greptile

A Linux app declares the shared memory it needs with @linux(shm_mb=N), checked at put. A computer takes the most its
apps ask for, records it with its composed computer and its deploy, and boots with it: the VM path's docker run and
local Docker pass --shm-size, a reset keeps it, the disk form's init mounts /dev/shm at that size (512 MiB without),
and a container-only backend (Modal, CoreWeave) boots anyway and logs that it can't size it. agentenv's chromium asks
for 1024 MiB, so Chrome keeps its shared memory in /dev/shm rather than in files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 9, 2026 20:48
@earakely-scale earakely-scale changed the title feat(computer): an app's shm_mb sizes its computer's /dev/shm feat(computer)!: an app's shm_mb sizes its computer's /dev/shm Nov 9, 2026
earakely-scale and others added 2 commits November 9, 2026 12:55
…ask for

A probe boots a build as a deploy with no env_params does, so it now passes the computer's shm_mb too: an app that
needs its size to start no longer fails at the probe and leaves its build undeployable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es shm_mb only to one that does

A sandbox provider says whether the container create_container or reset_container runs has the /dev/shm their shm_mb
asks for, or more. It's derived when undeclared: on for the default VM path, off for a container-only backend and for a
class that overrides either method. A deploy, its probe and a reset pass shm_mb only to a provider that declares it,
and log once otherwise, so a plugin whose create_container doesn't take shm_mb boots as before. Local Docker, sail_vm
and Modal declare it (Modal's /dev/shm is a host-sized tmpfs); CoreWeave no longer takes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 40b2008 into main Nov 9, 2026
36 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-5052-m850-an-apps-shared-memory-shm_mb-on branch November 9, 2026 23:31
Comment on lines +266 to +270
if "SHARED_MEMORY" not in cls.__dict__:
if {"create_container", "reset_container"} & cls.__dict__.keys():
cls.SHARED_MEMORY = False
elif cls.create_container is SandboxProvider.create_container:
cls.SHARED_MEMORY = SANDBOX_MODE_VM in cls.SUPPORTED_SANDBOX_MODES

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Older plugin subclasses cannot reset

If a VM plugin overrides reset_container with the old signature, a subclass that inherits it can wrongly regain SHARED_MEMORY. The override is absent from the child's __dict__, while its inherited create_container matches the base method, so this check turns the flag back on.

For an app that requests shared memory, reset_env then passes shm_mb to the old reset method and fails with an unexpected keyword argument. Check inherited reset methods too, and preserve the parent's capability decision when neither method changes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/sandbox_provider.py
Line: 266-270

Comment:
**Older plugin subclasses cannot reset**

If a VM plugin overrides `reset_container` with the old signature, a subclass that inherits it can wrongly regain `SHARED_MEMORY`. The override is absent from the child's `__dict__`, while its inherited `create_container` matches the base method, so this check turns the flag back on.

For an app that requests shared memory, `reset_env` then passes `shm_mb` to the old reset method and fails with an unexpected keyword argument. Check inherited reset methods too, and preserve the parent's capability decision when neither method changes.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant