Skip to content

feat(deploy_sandbox): default an unsized sandbox to 1 vCPU on Modal - #231

Draft
canersoz-scaleai wants to merge 2 commits into
scaleapi:mainfrom
canersoz-scaleai:canersoz/fd-4008-modal-deploy-sandbox-default-1-cpu
Draft

canersoz-scaleai wants to merge 2 commits into
scaleapi:mainfrom
canersoz-scaleai:canersoz/fd-4008-modal-deploy-sandbox-default-1-cpu

Conversation

@canersoz-scaleai

@canersoz-scaleai canersoz-scaleai commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Kept as a draft alternative. The change under review is #229 (1 vCPU on every provider). This Modal-only version leaves other providers at 2, in case reviewers prefer that.

Summary

On Modal, deploy_sandbox steps that set no cpu now reserve 1 vCPU instead of 2. Every other provider keeps 2.

  • SandboxProvider.DEPLOY_SANDBOX_CPU holds the per-provider default: 2.0 on the base class, 1.0 on ModalSandboxProvider and ModalVmSandboxProvider.
  • DeploySandboxTaskStep.cpu now defaults to unset and is resolved from the provider when the step runs.
  • ChainedSandboxProvider reports its providers' largest value, so in a modal_vm,beta_scale chain a beta_scale fallback still gets 2.
  • Steps that set cpu are unchanged. That includes every deploy_sandbox step stored today, because to_dict writes the value out.

Part of FD-4008 (Modal sandbox cost). Modal bills whichever is higher, the request or actual use, and a scalar cpu is a request, not a cap. A lower default therefore cuts idle cost without capping boot bursts. Elsewhere the number means something different:

  • On beta_scale (and smol_vm, e2b, local) it's a hard core count, so 1 would mean slower sandboxes and no Modal saving.
  • Tenki and Tensorlake raise any request to 2 anyway.

The generic version, 1 vCPU on every provider, is #229.

Production data (Snowflake AGENTENV_MONGO, Datadog modal.cpu.*, last 30 days)

  • About 80K deploy_sandbox runs already requested less than 1 vCPU.
  • One project ran the same modal_vm workload at two sizes: 0.125 vCPU (Sep 24–Oct 2, ~50K runs) and 2 vCPU (Oct 3 onward, ~176K runs).
    • Run failures were 0.69% at 0.125 and 1.49% at 2.
    • Deploy and install step failures were 0.58% and 0.78%.
    • Median run time was 21 min at 0.125 and 12 min at 2. These were different batches, so this isn't a clean speed comparison.
  • Modal sandboxes burst well past their request. 2-vCPU env sandboxes reach a p90 of 3.9 cores during boot, then sit around 0.35 cores.
  • The callers that leave cpu unset today are on modal_vm (for example the hub's artifact-generation builder). Every non-Modal caller sets its own.

Experiment (dev stage, no LLM)

Each run was deploy_sandbox (VM, 16 GiB) followed by install_agent (claude-code-cli), attributed to the agent-env unattributed test project.

Provider vCPU Passed install_agent time
modal_vm 1 4/4 37, 40, 47, 48 s
modal_vm 2 3/3 42, 42, 50 s
beta_scale 1 2/3* 137, 138 s
beta_scale 2 3/3 79, 81, 86 s

* The failed run lost its connection to the dev MongoDB from the laptop (no primary), not a CPU problem.

On Modal, 1 vCPU behaves the same as 2. On beta_scale it's a hard cap, so install takes about 1.7× longer. That's why this PR leaves beta_scale at 2.

Testing

  • New tst/unit/task_step/deploy_sandbox_default_cpu_test.py covers:
    • the per-provider defaults and the chain's largest-value rule;
    • an unsized step, built in code or loaded from a stored dict, leaving cpu to the provider;
    • keeping a stored cpu as written;
    • the value create_vm and create_container receive, for an unset and an explicit cpu.
  • deploy_sandbox_image_test.py: its mock providers declare DEPLOY_SANDBOX_CPU, as real providers inherit it.
  • deploy_sandbox / TTL step tests pass locally (176), and so does ruff check. The rest of tst/unit runs in CI; my local venv predates main's current dependencies (sail, Vercel), so some unrelated provider tests can't import locally.

🤖 Generated with Claude Code

A deploy_sandbox step that sets no cpu now takes its provider's
DEPLOY_SANDBOX_CPU: 1.0 on the Modal container and VM providers, where
cpu is a reservation the sandbox bursts above and Modal bills the higher
of reservation and use, and 2.0 everywhere else, as before. A chain
takes its providers' largest, so a fallback is never given less. Steps
that set cpu, including every stored step, keep their value.

Part of FD-4008.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +57 to +58
async def test_the_provider_is_asked_for_the_resolved_cpu(step_cpu, default_cpu, requested):
step = DeploySandboxTaskStep(id="s", version=1, sandbox_name="box", sandbox_mode="vm", sandbox_type="any",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Container CPU requests lack tests

The new CPU tests only use sandbox_mode="vm" and check create_vm. The changed create_container call has no CPU assertion, so these tests would miss a regression that leaves Modal containers requesting 2 CPUs. Extend the test to cover container mode for omitted and explicit cpu values.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tst/unit/task_step/deploy_sandbox_default_cpu_test.py
Line: 57-58

Comment:
**Container CPU requests lack tests**

The new CPU tests only use `sandbox_mode="vm"` and check `create_vm`. The changed `create_container` call has no CPU assertion, so these tests would miss a regression that leaves Modal containers requesting 2 CPUs. Extend the test to cover container mode for omitted and explicit `cpu` values.

---

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26a3877: the CPU test now runs both modes (create_vm and create_container) for an unset CPU on Modal, an unset CPU elsewhere, and an explicit CPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@canersoz-scaleai
canersoz-scaleai marked this pull request as draft October 16, 2026 17:18

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant