Capability: runtime
Implement a sandbox runtime — boot a box that runs Spunto's agent, keep its /workspace across stops, and say what state it is in.
runtime is where sandboxes run. Spunto Cloud, Google Cloud and the machines an organization brings itself are each a runtime app; so is anything you implement here. A member picks among the runtimes their organization installed when they create a sandbox.
The contract is small on purpose. A provider owns the lifecycle of a box: boot it, stop it, start it again, delete it, and say what state it's in.
It does not carry the terminal, command execution, the IDE or port forwarding. Every box runs Spunto's agent, which dials back to the platform over an outbound connection and carries all of that itself — whatever the provider. Your boxes need no inbound port open to the internet.
What a box must do:
- Run the image it is given, with the environment it is given.
- Run the
bootstrapcommand as its main process. It fetches and starts the agent (curlorwget, andsh, must be in the image). - Reach
SPUNTO_API_URLover HTTPS, outbound. - Keep
/workspaceacrossstopandstart.
| Operation | Required | Unlocks |
|---|---|---|
create | Yes | Creating a sandbox |
start | Yes | Starting a stopped sandbox |
stop | Yes | Stopping one, /workspace kept |
destroy | Yes | Deleting one |
describe | Yes | Knowing its state |
listMachineTypes | A size picker, with prices | |
listSizes | A simpler size picker, when there is one kind of machine | |
listLocations | Choosing where a box runs (a region, a machine) | |
features | Telling members what a box here can and can't do | |
pause | Pause: a stop that keeps the box's memory, processes included | |
fork | Fork: a copy of a running box, memory included | |
grow | Growing a box's disks | |
logs, build, buildStatus, snapshot… | Starting boxes from a prebuilt project image instead of building in place |
Every operation's input carries a handle — what create returned — except create itself and the listings.
// input
{
"sandboxId": "sbx_8f2c…",
"name": "fix-login",
"image": "mcr.microsoft.com/devcontainers/typescript-node:22",
"size": "small",
"machineType": "small",
"location": "europe-west1-b",
"needs": { "dind": false },
"volumes": [{ "name": "workspace", "path": "/workspace" }],
"bootstrap": "if command -v curl …; fi",
"env": {
"SPUNTO_API_URL": "https://spunto.net",
"SPUNTO_SANDBOX_ID": "sbx_8f2c…",
"SPUNTO_SANDBOX_TOKEN": "…"
},
"labels": {
"spunto.sandboxId": "sbx_8f2c…",
"spunto.organizationId": "org_…",
"spunto.projectId": "prj_…",
"spunto.userId": "usr_…"
}
}
// result
{ "handle": "vm-8f2c" }- Answer within 10 seconds. Accept the request, start booting in the background, and return the handle. The agent's connection is how Spunto learns the box is up;
describeis how it learns anything else. - Be idempotent on
sandboxId. Ifcreateis called for a sandbox you already have a box for, return that box's handle. Spunto retriescreatewhen your app was unreachable or timed out, and a box booted twice is a box billed twice. envcontains a secret (SPUNTO_SANDBOX_TOKEN): the box's identity towards the platform. Pass it to the box and nowhere else — not in logs, not in labels.labelsare ids and links, never secrets: keep them with the box (instance labels, container labels) for whoever operates the machines. Ignore them if you have nowhere to put them.size,machineTypeandlocationare ids your app gave in its own listings.machineTypewins oversize.needs.dind: the project needs Docker inside the box. Refuse if you can't provide it rather than boot a box that will fail later.- Optional in the result:
idePort, if the IDE cannot listen on 8080 in your boxes.
// input
{ "handle": "vm-8f2c", "sandboxId": "sbx_8f2c…" }
// result
{ "ok": true }stopkeeps/workspaceand nothing else: the nextstartboots the box again. Billing should stop with it.stopmay answer before the box is fully down —describethen says"transition": "stopping"until it is.destroydeletes the box and its storage, and is idempotent: destroying a box that is already gone succeeds.
// input
{ "handle": "vm-8f2c", "sandboxId": "sbx_8f2c…" }
// result
{ "state": "running" }state | |
|---|---|
creating | Booting |
running | Up — the agent may not have connected yet |
stopped | Stopped, /workspace kept |
missing | You have no box for this handle |
error | It failed — say why in detail |
unreachable | You can't tell right now (the machine is offline). Nothing should be concluded from it |
Optional fields: detail (one line, shown to the member), transition (stopping, pausing) while a stop or a pause is under way, ram (saved / none) for a provider that implements pause.
What a member picks a box as — a size on a kind of machine.
// result
{
"machineTypes": [
{ "id": "small", "label": "Small", "size": "small", "cpus": 2, "memoryGb": 4, "diskGb": 30, "pricePerHour": 0.04 }
]
}The id comes back to create as machineType. pricePerHour is shown as is; leave it out if you don't bill by the hour.
What your boxes guarantee, shown to members so they know what they're picking.
// result
{
"dind": true,
"suspendKeepsDisk": true,
"suspendKeepsProcesses": false,
"location": "Your own machines, in Frankfurt",
"billing": "A stopped box costs nothing"
}Most runtimes need a credential — an API key, a cluster token. Declare "auth": "apiToken" and serve a form on your installPath where an OAuth app would put its consent screen: verify the launch token, ask for the credential, check it against your backend, then stage and redirect exactly as in Installing. A runtime that needs nothing ("auth": "none") can stage and redirect straight away.