Quickstart
Quickstart
Section titled “Quickstart”This tutorial walks through a complete sandbox lifecycle using grpcurl against a local
compute instance. By the end you will have created a sandbox, executed a Python command
inside it, and verified that the response carries a usage_event_id.
Prerequisites
Section titled “Prerequisites”grpcurlinstalled (brew install grpcurlorgo install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest)- A running compute server (see Operations for local bring-up)
- The
paper-board/protorepository checked out alongside compute, or access to the compiled.protodescriptors in the compute Docker image
1. Verify the server is up
Section titled “1. Verify the server is up”grpcurl -plaintext localhost:50051 grpc.health.v1.Health/CheckExpected response:
{ "status": "SERVING"}2. Create a sandbox
Section titled “2. Create a sandbox”A sandbox is a gVisor-backed pod + 5 GiB workspace PVC. Pass a tenant_id and
session_id that identify the calling tenant and the agent session:
grpcurl -plaintext \ -d '{ "tenant_id": "acme-tenant-uuid", "session_id": "session-uuid-001", "image": "ghcr.io/paper-board/sandbox-python:v0.1.0" }' \ localhost:50051 \ compute.v1.ComputeService/CreateSandboxExpected response:
{ "sandbox_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "SANDBOX_STATUS_STARTING", "created_at": "2026-05-24T10:00:00Z"}Record the sandbox_id — you need it for the next two calls.
The sandbox status is
STARTINGimmediately afterCreateSandboxreturns. TheExecCommandRPC blocks internally until the pod transitions toRunningbefore streaming output, so you do not need to pollDescribeSandboxbefore callingExecCommand.
3. Execute a command
Section titled “3. Execute a command”ExecCommand is a server-streaming RPC. grpcurl prints each event as it arrives.
Run a Python one-liner that also prints the sandbox hostname (confirms isolation):
grpcurl -plaintext \ -d '{ "sandbox_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "command": "python3", "args": ["-c", "import socket, os; print(socket.gethostname()); print(42 * 2)"], "working_dir": "/workspace" }' \ localhost:50051 \ compute.v1.ComputeService/ExecCommandExpected response (three streamed events):
{ "stdout": { "data": "c2FuZGJveC1mNDdhYzEwYgo=" }, "emitted_at": "..." }{ "stdout": { "data": "ODQK" }, "emitted_at": "..." }{ "completed": { "exit_code": 0, "wall_time": "0.412s", "usage": { "cpu_ms": 110, "memory_peak_bytes": 18874368, "disk_read_bytes": 4096, "disk_write_bytes": 0 } }, "emitted_at": "..."}stdout.data is base64-encoded. Decode to see the hostname and 84:
echo "c2FuZGJveC1mNDdhYzEwYgo=" | base64 -d # sandbox-f47ac10becho "ODQK" | base64 -d # 844. Assert usage_event_id
Section titled “4. Assert usage_event_id”The agents service writes a usage_events row after each ExecCommand. If you are
running the full stack (agents + compute), confirm the row was written:
SELECT id, event_type, cpu_ms, memory_peak_bytes, wall_time_msFROM agents.usage_eventsWHERE session_id = 'session-uuid-001'ORDER BY occurred_at DESCLIMIT 1;Expected columns:
| id | event_type | cpu_ms | memory_peak_bytes | wall_time_ms |
|---|---|---|---|---|
<uuid> | exec_command | 110 | 18874368 | 412 |
This id is the usage_event_id that will be referenced by the Phase 5 billing engine.
5. Write and read a workspace file
Section titled “5. Write and read a workspace file”# Write a result filegrpcurl -plaintext \ -d '{ "sandbox_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "path": "result.txt", "data": "aGVsbG8gd29ybGQK" }' \ localhost:50051 \ compute.v1.ComputeService/WriteFile
# Read it backgrpcurl -plaintext \ -d '{ "sandbox_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "path": "result.txt" }' \ localhost:50051 \ compute.v1.ComputeService/ReadFileWriteFile.data and ReadFile.data are base64-encoded. The example writes
hello world\n (base64: aGVsbG8gd29ybGQK).
6. Destroy the sandbox
Section titled “6. Destroy the sandbox”DestroySandbox deletes the sandbox pod and annotates the PVC for 24-hour soft-delete:
grpcurl -plaintext \ -d '{"sandbox_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"}' \ localhost:50051 \ compute.v1.ComputeService/DestroySandboxExpected response:
{ "sandbox_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "total_usage": { "cpu_ms": 110, "memory_peak_bytes": 18874368 }}The PVC (workspace-f47ac10b) is not deleted immediately. The GC CronJob removes it after
the compute.paper-board.io/delete-after annotation timestamp has passed (24 h by default).
Next steps
Section titled “Next steps”- Read the API Reference for full message shapes and error codes.
- See Operations for environment variables, Helm values, and the RuntimeClass setup required in production.
- Read the gVisor install guide for DaemonSet troubleshooting.