Skip to content

Sandbox browser preview — Reference

Terse reference for the sandbox browser preview API: the routes that start, keep alive, stop, and list a live HTTPS preview of a server an agent started inside its run's sandbox pod.

When Sandbox:Preview:Enabled=true (the default in AKS deployments), POST …/port-forward provisions a Gateway-direct reverse proxy — a per-preview HTTPRoute → per-run ClusterIP Service → the run's sandbox pod — and returns a public preview_url plus a keepalive_url. When disabled (local dev), the same route falls back to the legacy kubectl port-forward and returns a loopback local_port instead. Every call verifies the run exists and the caller owns it (404/403). Source: SandboxEndpoints.cs, SandboxPreviewService.cs.

Routes

Method & pathBodyReturnsNotes
POST /api/runs/{runId}/sandbox/port-forward{ "target_port": <3000..9000> }PortForwardSessionDtoStarts a preview. Preview path: provisions Service + HTTPRoute, returns preview_url + keepalive_url. target_port must be within AllowedPortMin..AllowedPortMax. Human/operator-initiated (owner-only).
POST /api/runs/{runId}/sandbox/preview{ "target_port": <3000..9000> }PortForwardSessionDtoAgent-initiated variant of the start route. Two caller surfaces hit it: the in-sandbox start_preview(port) agent tool and the start_preview(run_id, port) MCP tool on agentweaver-mcp (RunTools.cs). Routes through a human-in-the-loop approval gate (AgentPreviewGate) before running the same preview-start path. Authorized for the run's owner OR its own agent callback (SandboxEndpoints.cs:57).
POST /api/runs/{runId}/sandbox/preview/{token}/keepalive{ token, kept_alive: true }Bumps the preview's idle expiry to now + IdleTimeoutMinutes. Preview path only. Verifies the token's HTTPRoute carries the matching run before bumping.
DELETE /api/runs/{runId}/sandbox/port-forward/{sessionId}{ session_id, stopped: true }Explicit stop. For the preview path sessionId is the capability token; deletes the HTTPRoute then the Service. Verifies run↔token first.
GET /api/runs/{runId}/sandbox/port-forwardPortForwardSessionDto[]Lists active legacy port-forward sessions for the run.

The relative keepalive_url returned by POST …/port-forward is /api/runs/{runId}/sandbox/preview/{token}/keepalive (SandboxEndpoints.cs:70).

Agent-initiated preview (start_preview)

A running agent can expose a server it started without a human typing a port in the UI, via the start_preview tool. The model calls start_preview(port: int); the tool POSTs { "target_port": <port> } to POST /api/runs/{runId}/sandbox/preview and returns the resulting preview_url string back to the agent. The tool is run-scoped: the runId is captured server-side in the tool closure (AgentweaverApiTools.cs:245), so the model supplies only the port and can never target another run.

The request routes through a human-in-the-loop approval gate before any preview is provisioned (AgentPreviewGate.RequestApprovalAsync, AgentPreviewGate.cs:85):

  • If an auto-approve source is on (see below) the request is auto-granted immediately.
  • Otherwise a tool.approval_required event is emitted onto the run timeline (AgentPreviewGate.cs:103) and the call suspends until an operator grants it via POST /api/runs/{runId}/tool-approvals (with the emitted request_id) or the 5-minute window times out.

StartPreviewRequest (SandboxEndpoints.cs:311) uses the snake_case DTO convention — the wire field is target_port via [JsonPropertyName("target_port")], unlike the legacy PortForwardRequest which binds camelCase targetPort.

Auto-approve sources

Any one being true auto-grants the preview (production default is human-gated):

SourceWhereDefault
Sandbox:Preview:AutoApprove config / env SANDBOX_PREVIEW_AUTO_APPROVEAgentPreviewGate.cs:125false
Per-run AutoApproveTools operator optionIRunOptionsStore.Get(runId)false
An existing run/always-scoped allow policy on the shared gateIToolApprovalGate.IsAutoApprovednone

The env var SANDBOX_PREVIEW_AUTO_APPROVE is read directly (not via the ASP.NET __ hierarchy separator), so the exact name works as an environment variable. It exists so an automated demo can run the preview flow end-to-end unattended; leave it false in production.

The relative keepalive_url example below is for the operator route.

PortForwardSessionDto

From apps/web/src/api/types.ts:1169.

FieldTypeMeaning
session_idstringSession identifier. In the preview path this is the capability token; used as {sessionId} to stop the preview.
local_portnumberLoopback port on the API host (legacy fallback only). In the preview path this is 0 — the preview is a public URL, not a loopback.
target_portnumberPort inside the sandbox pod being exposed.
pod_namestringBound sandbox pod the preview targets (resolved from the run's SandboxClaim status).
started_atstringISO timestamp of when the preview started.
preview_url / previewUrlstring | nullPublic HTTPS capability URL https://{token}-preview.{ZoneSuffix} (preview path). The web UI embeds it in a no-referrer iframe and offers Open preview.
keepalive_url / keepaliveUrlstring | nullRelative URL the frontend pings ~every 60 s to keep the preview alive (preview path).

Configuration

Bound from the Sandbox:Preview section into SandboxPreviewOptions.cs.

Config keyDefaultMeaning
Sandbox:Preview:Enabledtrue (AKS) / false (local dev)Master switch. When true the API provisions Gateway-direct HTTPRoute+Service objects and returns a preview_url. When false the Gateway path and reaper are no-ops; the legacy kubectl port-forward is used instead. Enabled by default in AKS deployments via Sandbox__Preview__Enabled=true.
Sandbox:Preview:ZoneSuffix"" (set by deploy)Managed aksapp.io zone; the preview host is {token}-preview.{ZoneSuffix}. Supplied by the AKS deploy script. Production value: 6a41f26c75d5cf00019ef7d7.westus2.staging.aksapp.io.
Sandbox:Preview:GatewayNameagentweaver-preview-gatewayShared Gateway the per-preview HTTPRoute attaches to. Applied from k8s/gateway-preview.yaml.
Sandbox:Preview:GatewayNamespaceagentweaverNamespace of the shared preview Gateway.
Sandbox:Preview:NamespaceagentweaverNamespace where the per-preview Service / HTTPRoute / pod live.
Sandbox:Preview:IdleTimeoutMinutes30Sliding idle TTL; a preview not kept alive within this window is reaped.
Sandbox:Preview:MaxLifetimeHours8Hard cap; a preview is always reaped after this, regardless of keepalive.
Sandbox:Preview:KeepAfterRuntrueRetain the preview after the run completes / pod is released; only the reaper or an explicit stop removes it.
Sandbox:Preview:AllowedPortMin3000Lowest target_port a preview may expose (inclusive). Mirrors the NetworkPolicy range.
Sandbox:Preview:AllowedPortMax9000Highest target_port a preview may expose (inclusive).
Sandbox:Preview:AutoApprove (env SANDBOX_PREVIEW_AUTO_APPROVE)falseWhen true, the agent-initiated start_preview approval gate auto-grants without an operator. Read in AgentPreviewGate.cs:125. Keep false in production.

Status codes

CodeWhen
200 OKPreview started (POST), kept alive (keepalive), stopped (DELETE), or listed (GET).
400 Bad Requesttarget_port outside 1..65535, outside AllowedPortMin..AllowedPortMax (preview path), or runId not parseable.
403 ForbiddenCaller does not own the run (operator route); or — on the agent route — the caller is neither the owner nor the run's own agent callback, or the agent-preview approval was denied / timed out at the HITL gate.
404 Not FoundRun does not exist; or (keepalive/DELETE, preview path) the token's HTTPRoute does not carry the matching run (run↔token binding).
409 ConflictNo bound sandbox pod for the run (the SandboxClaim is missing or not yet Bound), or the Gateway preview is not enabled on the keepalive path.
429 Too Many RequestsA session cap was hit on the legacy port-forward fallback.
500Unexpected failure provisioning the preview (or kubectl failed to start the legacy tunnel).

Example

http
POST /api/runs/run_01HXYZ/sandbox/port-forward
Content-Type: application/json

{ "target_port": 3000 }
json
{
  "session_id": "swift-falcon-amber-k7m2q9x4n8b3r6t5w1z0c2d4f7",
  "local_port": 0,
  "target_port": 3000,
  "pod_name": "agent-pod-worker-7",
  "started_at": "2026-06-28T09:20:07Z",
  "preview_url": "https://swift-falcon-amber-k7m2q9x4n8b3r6t5w1z0c2d4f7-preview.preview.cluster.westus2.aksapp.io",
  "keepalive_url": "/api/runs/run_01HXYZ/sandbox/preview/swift-falcon-amber-k7m2q9x4n8b3r6t5w1z0c2d4f7/keepalive"
}
http
DELETE /api/runs/run_01HXYZ/sandbox/port-forward/swift-falcon-amber-k7m2q9x4n8b3r6t5w1z0c2d4f7
json
{ "session_id": "swift-falcon-amber-k7m2q9x4n8b3r6t5w1z0c2d4f7", "stopped": true }

Source

ConcernFile
Endpoints (start / agent-start / keepalive / stop / list)apps/Agentweaver.Api/Endpoints/SandboxEndpoints.cs
Agent-initiated approval gate (HITL + auto-approve)apps/Agentweaver.Api/Sandbox/Preview/AgentPreviewGate.cs
start_preview agent tool (run-scoped HTTP callback)packages/Agentweaver.AgentRuntime/AgentweaverApiTools.cs
Owner-or-agent-callback authorizationapps/Agentweaver.Api/Endpoints/EndpointHelpers.cs
Preview provisioning, keepalive, stop, reapapps/Agentweaver.Api/Sandbox/Preview/SandboxPreviewService.cs
Config defaults & port-range checkapps/Agentweaver.Api/Sandbox/Preview/SandboxPreviewOptions.cs
Capability tokenapps/Agentweaver.Api/Sandbox/Preview/PreviewToken.cs
SandboxClaim CRD coordinates + bound-pod parsingapps/Agentweaver.Api/Sandbox/SandboxClaimConventions.cs
DTO fieldsapps/web/src/api/types.ts
API clientapps/web/src/api/client.ts

See also