Sandbox browser preview
Sometimes an agent starts a server inside its sandbox pod — a dev server, a freshly built web app, an API it stood up, a debug endpoint — and you want to actually open it in your browser and look. Because each run executes in its own isolated pod, that server isn't reachable by default. The sandbox browser preview is the supported way to reach it: a live preview served at a unique HTTPS URL that routes straight to the run's own pod, scoped to that one run.
This page walks through the user experience. For the API see the reference; for how the proxy works under the hood see the deep dive.
When the Preview button is available
A Preview Sandbox button appears in the coordinator run page when both are true:
- the run is using the Kubernetes sandbox (the run's
sandbox.selectedevent reports backendkubernetes-sandbox-claim), and - a preview lifecycle state has been recorded or an existing preview session is available.
Kubernetes placement alone does not make the header button appear. The current UI does not hide it solely because the run is terminal: a retained preview can still be inspected. An operator API request can also start a post-run preview if the required pod is still bound; agent/platform publication remains run-bound.
Step by step
- Click Preview. The Sandbox Preview dialog opens.
- Pick a port. Enter the port your app is listening on inside the sandbox pod. The Target port (inside sandbox) field defaults to
3000. It must be within the allowed preview range (3000–9000 by default); other values are rejected. - Start. Click Start. The app calls
POST /api/runs/{runId}/sandbox/port-forwardwith that port. The API resolves the run's bound pod from cluster state and provisions a per-preview HTTPRoute and ClusterIP Service wiring the shared preview gateway to your pod. - See it become active. The dialog confirms "Preview active for port {target_port} on pod {pod_name}" and shows the session id.
- When a
preview_urlis returned (the normal case in-cluster), the dialog embeds the live preview in an iframe (withreferrerPolicy="no-referrer") and offers an Open preview button that opens it in a new tab. - On a local/dev backend where the gateway path is off, the dialog notes that no proxied preview URL was returned.
- When a
- Keepalive is bounded. While the run page has an active session with a keepalive URL, it sends keepalive about every 60 seconds. This is not gated on the dialog being open. Expiry is bounded by the original maximum lifetime; watching does not extend the hard cap.
- Stop when done. Click Stop to tear it down (
DELETEon that session). Close just dismisses the dialog.
Agent-initiated preview (with your approval)
An agent can also open a preview for you, mid-run, when it has just started a server and wants to show you the result — without you opening the dialog or typing a port. The agent calls a start_preview(port) tool; instead of exposing the server silently, that request raises a human-in-the-loop approval on the run timeline:
- A "the agent wants to expose a preview server on port N" approval card appears (the same kind of card used for the agent's URL-fetch requests). Approve it and the agent gets back the live
preview_url; the preview behaves exactly like one you started yourself (same URL, same auto-expiry, same Stop). - The approval remains visible in the notification badge, a persistent toast, and the run timeline. The timeline card shows when it expires.
- If you don't approve within the project's configured window (24 hours by default), the request lapses. Choose Retry approval on the expired card or preview status to create a fresh approval request. Agentweaver reuses the healthy server process instead of restarting the run or executing the preview command again.
- The agent can only ever request a preview for its own run — the run is bound server-side, so a
start_previewcall can't reach another run's pod.
Operators running automated demos can set SANDBOX_PREVIEW_AUTO_APPROVE=true (or the per-run auto-approve-tools option) to grant these requests automatically. Project owners can set the manual approval expiry and preview lifetime to 1–1440 minutes in Project settings → Sandbox policy; both default to 24 hours. Preview lifetime is a single expiration and hard-cap setting. The deployment setting Sandbox:Preview:ApprovalTimeoutMinutes (or SANDBOX_PREVIEW_APPROVAL_TIMEOUT_MINUTES) is retained as a 24-hour-default fallback for legacy runs that are not associated with a project. In normal use the approval stays in your hands.
Build & Test preview
Workflows that include the platform-owned Build & Test step can also produce a browser preview. After an approved or request-changes Build & Test verdict, the platform attempts to start the app/service, discover its actual port, and register a preview URL through the approval flow. Declined verdicts skip it; preview failure does not block human review. It no longer injects PORT=3000 or --port; apps use their framework default or honor process.env.PORT if they already support it. Port discovery is dependency-free: AgentHost reads app log hints and the sandbox pod's /proc/net/tcp plus /proc/net/tcp6 socket tables, so it works even when the image lacks ss and when Node binds IPv6-any (::). Before registration, AgentHost fronts the observed app port with a pod-local TCP forwarder on an allowed public port (3000-9000), so even a loopback-only app is reachable from the Gateway.
The preview backend resolves either the run's AgentHost claim or a retained command-sandbox claim, so previews work whether the server was started in the agent-{runId} pod-per-run sandbox or the run-{runId} Build & Test command sandbox (apps/Agentweaver.Api/Sandbox/SandboxClaimConventions.cs:28, apps/Agentweaver.Api/Sandbox/Preview/SandboxPreviewService.cs:432).
What to expect
- Kubernetes-only. The preview routes into the run's own sandbox pod. On local/dev runs there is no claim pod, so the button doesn't appear — the same "this is a cluster feature" boundary as the pod pill.
- A real, public HTTPS URL. Unlike the older loopback design, the preview is reachable at
https://{token}-preview.{ZoneSuffix}. The URL is an unguessable capability link (128-bit token): anyone with the URL can open it, so don't share it. It is short-lived and auto-expires. - Scoped to this run's pod. A preview reaches only the run's own sandbox pod, never another run's. Keepalive and stop verify the token actually belongs to the run before acting.
- Auto-expiry. Project lifetime defaults to 1440 minutes (24 hours) and can be set to 1–1440 minutes. Initial expiry and maximum lifetime are both calculated from that same setting at creation; keepalive cannot extend the hard cap. Expiry, explicit Stop, or pod loss ends availability. The default retention policy permits a preview to outlive its run only while the required pod/process resources remain available. The route state is durable across API restarts.
- Platform previews handle loopback binds. The Build & Test live-preview path runs the app and forwarder inside the sandbox pod and registers the forwarder's
0.0.0.0public port, so apps that only bind127.0.0.1can still be previewed. Manual previews still expose the port you enter directly, so prefer all-interface binds there. - Gateway is the reachability test. The API does not probe the sandbox pod directly. Platform previews first check readiness in-pod; registration then waits for publication through the generated HTTPS Gateway URL. Opening that returned URL exercises the same path.
- Failure is actionable, not blocking. If the app exits, no listening port appears, observe hits an unexpected error, or the forwarder cannot make the app reachable, you see Preview unavailable with a reason such as
process_exited:exit={code},no_listening_port_discovered,observe_error,bound_unreachable, orno_public_port_available; review can continue.
Related reading
- Sandbox browser preview — Reference — routes, DTO fields, config, status codes.
- Sandbox browser preview — Deep Dive — the reverse proxy, lifecycle, and cleanup.
- Live-preview provisioning — the Build & Test preview review flow.
- Sandbox pod execution experience — the pod pill and the pod-per-run model.
- Runs, board & live inspection — where embedded run inspection lives.
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Preview readiness follows the public path |
| takeaway | Provision the route, then probe its exact HTTPS URL; object creation alone is not ready. |
| group-title0 | CONTROL: PROVISION + PROBE |
| group-title1 | GATEWAY DATA PATH |
| Preview API | Preview API |
| Preview API | Resolve bound SandboxClaim |
| Preview API | Patch run selector on pod |
| Preview API | Create Service + HTTPRoute |
| Preview API | State from cluster, not cache |
| Publication probe | Publication probe |
| Publication probe | Exact generated HTTPS URL |
| Publication probe | Wait for managed DNS |
| Publication probe | Check Gateway + application |
| Publication probe | Only then return ready |
| Browser preview | Browser preview |
| Browser preview | Open the returned URL |
| Browser preview | Run-scoped capability host |
| Browser preview | Keepalive via API |
| Browser preview | Iframe: no-referrer |
| Preview Gateway | Preview Gateway |
| Preview Gateway | Separate shared Gateway |
| Preview Gateway | HTTPS host match |
| Preview Gateway | HTTPRoute selects Service |
| Preview Gateway | Not API port-forward |
| ClusterIP Service | ClusterIP Service |
| ClusterIP Service | Per-preview target selector |
| ClusterIP Service | Service :80 → public port |
| ClusterIP Service | Routes to bound sandbox pod |
| ClusterIP Service | Allowed ports 3000–9000 |
| Sandbox preview app | Sandbox preview app |
| Sandbox preview app | AgentHost pod-local path |
| Sandbox preview app | Live preview: TCP forwarder |
| Sandbox preview app | 0.0.0.0 → loopback app |
| Sandbox preview app | Manual: chosen target port |
| relation-0 | 1 after create |
| relation-1 | 2 ready URL |
| relation-2 | 3 HTTPS probe |
| relation-3 | 4 HTTPS |
| relation-4 | 5 route |
| relation-5 | 6 public port |
| assurance | No API → pod TCP readiness probe. Publication failure rolls back; DNS convergence has a bounded retry window. |
| assurance-0-label | Public readiness |
| assurance-0-fact | Probe the exact generated HTTPS URL. |
| assurance-0-source | SandboxPreviewService.cs |
| assurance-1-label | Rollback on failure |
| assurance-1-fact | Unpublish failed preview resources. |
| assurance-1-source | SandboxPreviewPublicationTests.cs |
| assurance-2-label | Separate ingress |
| assurance-2-fact | DNS managed externally, not by API. |
| assurance-2-source | gateway-preview.yaml |
| n0 | Patch run selector on pod; Create Service + HTTPRoute |
| n1 | Wait for managed DNS; Check Gateway + application |
| n2 | Run-scoped capability host; Keepalive via API |
| n3 | HTTPS host match; HTTPRoute selects Service |
| n4 | Service :80 → public port; Routes to bound sandbox pod |
| n5 | Live preview: TCP forwarder; 0.0.0.0 → loopback app |
| groups | CONTROL: PROVISION + PROBE; GATEWAY DATA PATH |
