AgentENV Proxy Design and Usage
This document describes the per-node reverse proxy that forwards requests into individual sandboxes. For the distributed routing layer (gateway to scheduler to node), see System Architecture.
Scope
Each AgentENV node runs a reverse proxy that accepts requests on its API server and forwards them to sandbox services over the sandbox interaction network. In a multi-node deployment, the gateway resolves sandbox ownership through the scheduler and forwards data-plane requests to the owning node’s proxy surface.
Current entrypoints:
ANY /proxyANY /proxy/{*proxy_path}- Any otherwise unmatched path that carries sandbox routing headers
- Host-based sandbox proxy requests when
[sandbox_proxy].domainsis configured:{port}-{sandboxID}.{domain}
Implementation references:
src/api/proxy.rssrc/orchestrator/service.rssrc/orchestrator/proxy.rs
Routing Contract
Each proxied request must identify:
- Sandbox ID
- Target port inside the sandbox service plane
Accepted headers:
x-agentenv-sandbox-idx-agentenv-target-port- E2B-compatible aliases:
e2b-sandbox-ide2b-sandbox-port
Validation:
- Sandbox ID must be a valid UUID format.
- Target port must parse as
u16and be greater than0.
Host-based routing derives both fields from Host. The configured domain must
match exactly after lowercase normalization and optional trailing-dot removal.
Sandbox IDs in host routes must be valid UUIDs and the target port must fit in
u16.
Runtime Route Model
AgentENV keeps an in-memory runtime route table in orchestrator.
- Route key:
SandboxId - Route value:
ProxyTarget(currently host interaction IP) plus route metadata (version,updated_at)
Design rule:
- Only
Runningsandboxes publish runtime routes. - Non-running states rely on metadata fallback (not route-table state).
Lookup behavior (proxy_lookup_for):
- If runtime route exists:
Ready(target) - Else read metadata:
- no metadata:
NotFound - metadata state is
Running:RouteMissing - metadata state is
Paused:Paused { auto_resume } - other states:
Unavailable(state)
- no metadata:
This keeps hot-path reads lock-light and avoids reading sandbox instance internals in API request paths.
Paused Sandbox Auto-Resume
/proxy can auto-resume paused sandboxes when lifecycle policy enables it.
- Proxy never reads sandbox metadata directly.
- Orchestrator lookup returns
Paused { auto_resume }, and proxy decides behavior from that signal. - Auto-resume is attempted once per request.
- Resume timeout update uses
EnsureMinimum(5 minutes):- effective sandbox timeout is
max(existing_timeout, 5 minutes)
- effective sandbox timeout is
- Proxy waits up to:
- test builds: short unit-test timeout
- non-test runtime:
60s
Request outcomes for paused sandboxes:
auto_resume = false:410 Goneauto_resume = trueand resume succeeds, then route becomes ready: request is forwarded normallyauto_resume = truebut resume/lookup fails:502 Bad Gatewayauto_resume = truebut resume wait times out:504 Gateway Timeout
Lifecycle Hooks and Race Hardening
Route publication/removal is tied to lifecycle transitions.
- Create/Resume success path:
- Persist metadata to
Running - Publish runtime route only if the launching sandbox handle is still the current handle
- Persist metadata to
- Pause/Delete/Rollback paths:
- Atomically detach sandbox handle and runtime route before stop/finalization
Race protections:
- Late route publication from stale handles is blocked via pointer identity check.
- Handle detachment and route removal happen in one critical section to reduce stale-route visibility windows.
- Launch rollback supports both transitional-state rollback and running-state rollback paths.
HTTP Forwarding Semantics
Path and Query
/proxyforwards to upstream//proxy/{*proxy_path}forwards raw URI path suffix after/proxy- Header-routed fallback requests forward the original request path
- Host-based requests forward the original request path
- Query string is forwarded unchanged
Important details:
- Percent-encoded path segments are preserved.
- Repeated leading slashes are preserved.
- Example:
/proxy//apiforwards as//api
- Example:
- Host-based proxy routing runs before Axum route matching. When a configured sandbox proxy host is used, the request is data-plane traffic even if its path resembles a control-plane API.
Header Handling
Control-plane routing headers are stripped before forwarding upstream:
x-agentenv-sandbox-idx-agentenv-target-porte2b-sandbox-ide2b-sandbox-port
Hop-by-hop headers are stripped on both request and response paths, including:
- Standard hop-by-hop headers (
Connection,Upgrade,TE,Trailer,Transfer-Encoding,Proxy-Authenticate,Proxy-Authorization,Keep-Alive) - Any extra headers nominated by
Connection
Forwarded headers are injected:
x-forwarded-hostx-forwarded-protox-forwarded-methodx-forwarded-uri
Streaming
HTTP bodies are proxied as streams (request and response), including SSE and large uploads/downloads.
WebSocket Semantics
WebSocket upgrade is supported through the same /proxy endpoints.
- Client upgrade request is validated and forwarded upstream.
- Bidirectional frame bridging is established after successful upstream handshake.
- Selected subprotocol from upstream is propagated to the client.
Handshake failure behavior:
- If upstream rejects with an HTTP response (for example
401,403,404), status/body are forwarded as-is. - Transport or connection failures return
502 Bad Gateway. - Handshake timeout returns
504 Gateway Timeout.
Error Mapping
400 Bad Request- Missing/invalid sandbox routing header
- Missing/invalid target port header
- Invalid upstream URI construction
404 Not Found- Sandbox not found
410 Gone- Sandbox is paused and auto-resume is disabled
- Sandbox exists but is not proxyable in current state
502 Bad Gateway- Upstream transport/connect failure
- Sandbox is
Runningbut runtime route is missing (RouteMissing) - Paused sandbox auto-resume failed
504 Gateway Timeout- Paused sandbox auto-resume timed out
- Upstream response header timeout
- Upstream websocket handshake timeout
Usage Examples
HTTP
curl -i \
-H 'X-API-Key: test-key' \
-H 'x-agentenv-sandbox-id: <sandbox-uuid>' \
-H 'x-agentenv-target-port: 8080' \
'http://127.0.0.1:8000/proxy/health?full=true'
E2B-compatible headers
curl -i \
-H 'X-API-Key: test-key' \
-H 'e2b-sandbox-id: <sandbox-uuid>' \
-H 'e2b-sandbox-port: 8080' \
'http://127.0.0.1:8000/proxy/status'
WebSocket
Example (generic):
- URL:
ws://127.0.0.1:8000/proxy/ws/echo - Headers:
x-agentenv-sandbox-id: <sandbox-uuid>x-agentenv-target-port: <port>- API auth headers as required by your deployment
Host-based
curl -i \
-H 'X-API-Key: test-key' \
'http://8080-<sandbox-uuid>.sandbox.example.com/status'
Testing Notes
Relevant test coverage exists in:
src/api/proxy.rsunit tests (HTTP, SSE, large body, websocket, headers, path preservation, error mapping)src/orchestrator/service.rsunit tests (route publication/removal behavior and stale-handle guard)- Integration lifecycle tests in
tests/integration/orchestrator.rs - E2E proxy suite in
scripts/tests/e2e/suites/06_proxy.sh(header compatibility and paused auto-resume behavior)
For environment-backed integration validation, use repository-prescribed integration targets.