2026 03 23 Sandbox Placement Router Design

Documentation for Plans

SandboxPlacementRouter — Multi-Server VM Routing

Session: 261 | Date: 2026-03-23 | Status: Implementation ready Predecessor: S260 (infrastructure setup, both servers healthy)


Problem

GoGrab has two Hetzner dedicated servers (AX102-U, AX42-U) but SelfHostedProvider is a single-server HTTP client. Only one server receives VMs. $248/mo of hardware at 50% utilization. No role-based routing, no capacity overflow, no sticky placement.

Solution

Two new files, four modified files:

New: sandbox-server-client.ts

Plain TypeScript class (not NestJS). One instance per server. Handles all HTTP communication with a single sandbox-orchestrator: VM lifecycle, screenshots, input, file ops, snapshots. Extracted from SelfHostedProvider patterns. Config via constructor, not env vars.

New: sandbox-placement-router.ts

NestJS service implementing SandboxProvider interface. Manages N SandboxServerClient instances. Core logic:

Modified: sandbox-provider.factory.ts

Priority 0: SANDBOX_SERVERS_JSON → PlacementRouter. Falls back to existing chain if not set.

Modified: sandbox-proxy.controller.ts

Replace direct SelfHostedProvider injection with SandboxPlacementRouter for writeFile/readFile/injectSshKey.

Modified: infrastructure.module.ts

Register and export SandboxPlacementRouter.

Modified: computer-use-loop.service.ts

Pass workloadType: 'executive' | 'customer' hint to getOrCreateSandbox().

Server Fleet

ID Roles Priority Max VMs
ax102-01 customer-sandbox, git-operations 1 60
ax42-01 executive-sandbox, customer-sandbox 2 30

Adding a New Server

  1. Run yarn sandbox:bootstrap --host [email protected] --domain new.gograb.space --api-key <key>
  2. Run yarn sandbox:deploy:host --host [email protected]
  3. Add JSON entry to SANDBOX_SERVERS_JSON
  4. Restart backend-ai-api

Zero code changes required.

Future: Persistent VMs per Tenant

Current placement map is in-memory, rebuilt on restart. When persistent VMs ship, upgrade to MongoDB-backed placement. The router's API doesn't change — only the storage layer.

Edge Cases