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 inside a run's sandbox pod. For the platform-owned Build & Test live-preview path, AgentHost also fronts the app with a pod-local TCP forwarder so the Gateway always targets a pod-IP- reachable port.

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 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 and returns preview_url + keepalive_url; it does not API-probe podIP:{target_port}. 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 preview sessions for the run. Liveness is the policy-safe existence of a bound pod with the preview-run label, not an API-side TCP probe.

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

Readiness and liveness model

SandboxPreviewService.StartPreviewAsync is orchestration-only: it resolves the bound pod, patches labels, creates the ClusterIP Service, and creates the HTTPRoute. It deliberately does not connect from the API pod to podIP:{target_port} because sandbox-allow-preview-ingress admits TCP 3000-9000 only from the preview Gateway's data-plane pods (SandboxPreviewService.cs:134, k8s/base/networkpolicy-sandbox.yaml). For platform live-preview, readiness is the AgentHost in-pod observation: log hints are tried first, then the pod's own /proc/net/tcp and /proc/net/tcp6 tables are parsed for new listening sockets, the app responds on its real port, the in-pod TcpPortForwarder listens on 0.0.0.0:{publicPort}, and AgentHost verifies the forwarder public port before registration (apps/Agentweaver.AgentHost/PreviewRunner.cs:262, :610). The tcp6 table matters for Node's default IPv6-any binds. After registration, the real end-to-end check is opening the returned Gateway hostname (preview_url). ListForRunAsync uses a label-selector pod-existence check as its liveness proxy, because the same NetworkPolicy makes an API-side TCP liveness probe invalid (SandboxPreviewService.cs:399, :768).

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 platform-owned Build & Test step can use the same preview surface. Its canned prompt tells the agent to build, run all tests, start the web/service preview server after tests pass, observe the actual bound port, verify it, and call start_preview(port=PORT) with that port (BuildTestTurnExecutor.cs:10). During coordinator assembly in pod-per-run mode, Build & Test runs in a dedicated AgentHost pod bound to the coordinator run id and configured with the detached integration worktree as workingDirectory (CollectiveAssemblyPipeline.cs:155, KubernetesSandboxExecutor.cs:423). start_preview therefore provisions the preview HTTPRoute to that run-bound pod, keeping the assembled preview reachable during human review. Preview pod resolution accepts both retained claim naming conventions for the run — agent-{runId} for AgentHost pod-per-run claims and run-{runId} for retained command-sandbox claims — before returning 409 for "no bound pod" (SandboxClaimConventions.cs:28, SandboxPreviewService.cs:432).

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 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 (local 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. For manual previews this is the user-requested app port. For platform live-preview this is the forwarder's pod-IP-reachable public port; the app's real port is observed separately by AgentHost.
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 and 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/base/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 and the AgentHost forwarder public-port scan.
Sandbox:Preview:AllowedPortMax9000Highest target_port a preview may expose (inclusive). Mirrors the NetworkPolicy range and the AgentHost forwarder public-port scan.
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 port-forward fallback.
500Unexpected failure provisioning the preview (or kubectl failed to start the fallback tunnel).

Platform live-preview failures are emitted as sandbox.preview_failed events rather than API status codes. Discovery/observe reasons include no_listening_port_discovered, process_exited:exit={code}, and observe_error; forwarder-specific reasons include bound_unreachable (the app was reachable on loopback, but the forwarder's public pod-IP port failed health check) and no_public_port_available (no free public port in the allowed 3000-9000 range). See Decoupled live-preview provisioning — Reference.

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
Live-preview pod-local TCP forwarderapps/Agentweaver.AgentHost/TcpPortForwarder.cs
Live-preview runner /proc port discovery, observation, and forwarder lifecycleapps/Agentweaver.AgentHost/PreviewRunner.cs
Deterministic live-preview stepapps/Agentweaver.Api/Coordinator/Preview/PreviewStep.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
Build & Test preview activation promptpackages/Agentweaver.AgentRuntime/Workflow/BuildTestTurnExecutor.cs
DTO fieldsapps/web/src/api/types.ts
API clientapps/web/src/api/client.ts

See also