Skip to content

Commit e376010

Browse files
authored
Merge pull request #33 from diggerhq/patches
add patching on wake
2 parents 92e5938 + 58194ca commit e376010

File tree

13 files changed

+1199
-23
lines changed

13 files changed

+1199
-23
lines changed

docs/mint.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@
5757
"sdks/typescript/commands",
5858
"sdks/typescript/filesystem",
5959
"sdks/typescript/pty",
60-
"sdks/typescript/templates"
60+
"sdks/typescript/templates",
61+
"sdks/typescript/checkpoints",
62+
"sdks/typescript/patches"
6163
]
6264
},
6365
{
@@ -68,7 +70,9 @@
6870
"sdks/python/commands",
6971
"sdks/python/filesystem",
7072
"sdks/python/pty",
71-
"sdks/python/templates"
73+
"sdks/python/templates",
74+
"sdks/python/checkpoints",
75+
"sdks/python/patches"
7276
]
7377
}
7478
],

docs/sdks/python/checkpoints.mdx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: "Checkpoints"
3+
description: "Snapshot, fork, and restore sandboxes"
4+
---
5+
6+
Checkpoints capture a sandbox's full state (filesystem + memory). Fork new sandboxes from any checkpoint, or restore a sandbox to a previous point in time.
7+
8+
## Create a Checkpoint
9+
10+
```python
11+
checkpoint = await sandbox.create_checkpoint("my-checkpoint")
12+
# {"id": "uuid", "name": "my-checkpoint", "status": "processing"}
13+
```
14+
15+
### `await sandbox.create_checkpoint(name)`
16+
17+
<ParamField body="name" type="str" required>
18+
A name for this checkpoint.
19+
</ParamField>
20+
21+
**Returns:** `dict` — status transitions from `"processing"` to `"ready"` once the snapshot is uploaded.
22+
23+
## List Checkpoints
24+
25+
```python
26+
checkpoints = await sandbox.list_checkpoints()
27+
```
28+
29+
**Returns:** `list[dict]`
30+
31+
## Fork from a Checkpoint
32+
33+
Create a new sandbox from a checkpoint. The new sandbox boots with the checkpointed filesystem state.
34+
35+
```python
36+
fork = await Sandbox.create_from_checkpoint(
37+
checkpoint["id"],
38+
timeout=600,
39+
)
40+
41+
result = await fork.commands.run("cat /root/data.txt")
42+
```
43+
44+
### `Sandbox.create_from_checkpoint(checkpoint_id, **kwargs)`
45+
46+
<ParamField body="checkpoint_id" type="str" required>
47+
UUID of the checkpoint to fork from.
48+
</ParamField>
49+
<ParamField body="timeout" type="int" default="300">
50+
Sandbox TTL in seconds.
51+
</ParamField>
52+
<ParamField body="api_key" type="str | None" default="None">
53+
API key override.
54+
</ParamField>
55+
<ParamField body="api_url" type="str | None" default="None">
56+
API URL override.
57+
</ParamField>
58+
59+
**Returns:** `Sandbox`
60+
61+
## Restore a Checkpoint
62+
63+
Revert a running sandbox to a previous checkpoint. The VM reboots with the checkpointed drives.
64+
65+
```python
66+
await sandbox.restore_checkpoint(checkpoint["id"])
67+
```
68+
69+
**Returns:** `None`
70+
71+
## Delete a Checkpoint
72+
73+
```python
74+
await sandbox.delete_checkpoint(checkpoint["id"])
75+
```
76+
77+
**Returns:** `None`

docs/sdks/python/patches.mdx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: "Patches"
3+
description: "Rolling updates for forked sandboxes"
4+
---
5+
6+
Patches attach bash scripts to a checkpoint. When a sandbox boots from that checkpoint or wakes from hibernation, all pending patches run automatically in sequence order.
7+
8+
## Create a Patch
9+
10+
```python
11+
result = await Sandbox.create_checkpoint_patch(
12+
checkpoint["id"],
13+
script="pip install requests==2.31.0",
14+
description="Pin requests version",
15+
)
16+
17+
print(result["patch"]["sequence"]) # 1
18+
```
19+
20+
### `Sandbox.create_checkpoint_patch(checkpoint_id, script, **kwargs)`
21+
22+
<ParamField body="checkpoint_id" type="str" required>
23+
UUID of the checkpoint to patch.
24+
</ParamField>
25+
<ParamField body="script" type="str" required>
26+
Bash script to execute inside the sandbox.
27+
</ParamField>
28+
<ParamField body="description" type="str" default="''">
29+
Human-readable description.
30+
</ParamField>
31+
<ParamField body="api_key" type="str | None" default="None">
32+
API key override.
33+
</ParamField>
34+
<ParamField body="api_url" type="str | None" default="None">
35+
API URL override.
36+
</ParamField>
37+
38+
**Returns:** `dict`
39+
40+
## List Patches
41+
42+
```python
43+
patches = await Sandbox.list_checkpoint_patches(checkpoint["id"])
44+
# [{"id": "...", "sequence": 1, "script": "...", "description": "..."}, ...]
45+
```
46+
47+
### `Sandbox.list_checkpoint_patches(checkpoint_id, **kwargs)`
48+
49+
**Returns:** `list[dict]` — ordered by sequence number.
50+
51+
## Delete a Patch
52+
53+
Remove a bad or unwanted patch. Remaining patches continue to apply in sequence order.
54+
55+
```python
56+
await Sandbox.delete_checkpoint_patch(checkpoint["id"], patch["id"])
57+
```
58+
59+
### `Sandbox.delete_checkpoint_patch(checkpoint_id, patch_id, **kwargs)`
60+
61+
<ParamField body="checkpoint_id" type="str" required>
62+
UUID of the checkpoint.
63+
</ParamField>
64+
<ParamField body="patch_id" type="str" required>
65+
UUID of the patch to delete.
66+
</ParamField>
67+
68+
**Returns:** `None`
69+
70+
## How Patches Apply
71+
72+
| Event | Patches run? |
73+
| --- | --- |
74+
| `Sandbox.create_from_checkpoint()` | Yes — after boot |
75+
| `await sandbox.wake()` | Yes — after restore |
76+
| Sandbox already running | No — next wake/boot |
77+
78+
Patches run sequentially by sequence number. If a patch fails (non-zero exit), the chain stops. Progress is tracked per-sandbox, so the next wake retries from the last successful patch.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: "Checkpoints"
3+
description: "Snapshot, fork, and restore sandboxes"
4+
---
5+
6+
Checkpoints capture a sandbox's full state (filesystem + memory). Fork new sandboxes from any checkpoint, or restore a sandbox to a previous point in time.
7+
8+
## Create a Checkpoint
9+
10+
```typescript
11+
const checkpoint = await sandbox.createCheckpoint("my-checkpoint");
12+
// { id: "uuid", name: "my-checkpoint", status: "processing" }
13+
```
14+
15+
### `sandbox.createCheckpoint(name)`
16+
17+
<ParamField body="name" type="string" required>
18+
A name for this checkpoint.
19+
</ParamField>
20+
21+
**Returns:** `Promise<CheckpointInfo>` — status transitions from `"processing"` to `"ready"` once the snapshot is uploaded.
22+
23+
## List Checkpoints
24+
25+
```typescript
26+
const checkpoints = await sandbox.listCheckpoints();
27+
```
28+
29+
**Returns:** `Promise<CheckpointInfo[]>`
30+
31+
## Fork from a Checkpoint
32+
33+
Create a new sandbox from a checkpoint. The new sandbox boots with the checkpointed filesystem state.
34+
35+
```typescript
36+
const fork = await Sandbox.createFromCheckpoint(checkpoint.id, {
37+
timeout: 600,
38+
});
39+
40+
const result = await fork.commands.run("cat /root/data.txt");
41+
```
42+
43+
### `Sandbox.createFromCheckpoint(checkpointId, opts?)`
44+
45+
<ParamField body="checkpointId" type="string" required>
46+
UUID of the checkpoint to fork from.
47+
</ParamField>
48+
<ParamField body="opts" type="object" optional>
49+
<Expandable title="properties">
50+
<ParamField body="timeout" type="number" default="300">
51+
Sandbox TTL in seconds.
52+
</ParamField>
53+
<ParamField body="apiKey" type="string" optional>
54+
API key override.
55+
</ParamField>
56+
<ParamField body="apiUrl" type="string" optional>
57+
API URL override.
58+
</ParamField>
59+
</Expandable>
60+
</ParamField>
61+
62+
**Returns:** `Promise<Sandbox>`
63+
64+
## Restore a Checkpoint
65+
66+
Revert a running sandbox to a previous checkpoint. The VM reboots with the checkpointed drives.
67+
68+
```typescript
69+
await sandbox.restoreCheckpoint(checkpoint.id);
70+
```
71+
72+
**Returns:** `Promise<void>`
73+
74+
## Delete a Checkpoint
75+
76+
```typescript
77+
await sandbox.deleteCheckpoint(checkpoint.id);
78+
```
79+
80+
**Returns:** `Promise<void>`

docs/sdks/typescript/patches.mdx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
title: "Patches"
3+
description: "Rolling updates for forked sandboxes"
4+
---
5+
6+
Patches attach bash scripts to a checkpoint. When a sandbox boots from that checkpoint or wakes from hibernation, all pending patches run automatically in sequence order.
7+
8+
## Create a Patch
9+
10+
```typescript
11+
const result = await Sandbox.createCheckpointPatch(checkpoint.id, {
12+
script: "npm install lodash@4.17.21",
13+
description: "Pin lodash version",
14+
});
15+
16+
console.log(result.patch.sequence); // 1
17+
```
18+
19+
### `Sandbox.createCheckpointPatch(checkpointId, opts)`
20+
21+
<ParamField body="checkpointId" type="string" required>
22+
UUID of the checkpoint to patch.
23+
</ParamField>
24+
<ParamField body="opts" type="object" required>
25+
<Expandable title="properties">
26+
<ParamField body="script" type="string" required>
27+
Bash script to execute inside the sandbox.
28+
</ParamField>
29+
<ParamField body="description" type="string" optional>
30+
Human-readable description.
31+
</ParamField>
32+
<ParamField body="apiKey" type="string" optional>
33+
API key override.
34+
</ParamField>
35+
<ParamField body="apiUrl" type="string" optional>
36+
API URL override.
37+
</ParamField>
38+
</Expandable>
39+
</ParamField>
40+
41+
**Returns:** `Promise<PatchResult>`
42+
43+
## List Patches
44+
45+
```typescript
46+
const patches = await Sandbox.listCheckpointPatches(checkpoint.id);
47+
// [{ id: "...", sequence: 1, script: "...", description: "..." }, ...]
48+
```
49+
50+
### `Sandbox.listCheckpointPatches(checkpointId, opts?)`
51+
52+
**Returns:** `Promise<PatchInfo[]>` — ordered by sequence number.
53+
54+
## Delete a Patch
55+
56+
Remove a bad or unwanted patch. Remaining patches continue to apply in sequence order.
57+
58+
```typescript
59+
await Sandbox.deleteCheckpointPatch(checkpoint.id, patch.id);
60+
```
61+
62+
### `Sandbox.deleteCheckpointPatch(checkpointId, patchId, opts?)`
63+
64+
<ParamField body="checkpointId" type="string" required>
65+
UUID of the checkpoint.
66+
</ParamField>
67+
<ParamField body="patchId" type="string" required>
68+
UUID of the patch to delete.
69+
</ParamField>
70+
71+
**Returns:** `Promise<void>`
72+
73+
## How Patches Apply
74+
75+
| Event | Patches run? |
76+
| --- | --- |
77+
| `Sandbox.createFromCheckpoint()` | Yes — after boot |
78+
| `sandbox.wake()` | Yes — after restore |
79+
| Sandbox already running | No — next wake/boot |
80+
81+
Patches run sequentially by sequence number. If a patch fails (non-zero exit), the chain stops. Progress is tracked per-sandbox, so the next wake retries from the last successful patch.

internal/api/router.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ func NewServer(mgr sandbox.Manager, ptyMgr *sandbox.PTYManager, apiKey string, o
142142
api.POST("/sandboxes/from-checkpoint/:checkpointId", s.createFromCheckpoint)
143143
api.DELETE("/sandboxes/:id/checkpoints/:checkpointId", s.deleteCheckpoint)
144144

145+
// Checkpoint patches
146+
api.POST("/sandboxes/checkpoints/:checkpointId/patches", s.createCheckpointPatch)
147+
api.GET("/sandboxes/checkpoints/:checkpointId/patches", s.listCheckpointPatches)
148+
api.DELETE("/sandboxes/checkpoints/:checkpointId/patches/:patchId", s.deleteCheckpointPatch)
149+
145150
// Preview URLs (on-demand port-based)
146151
api.POST("/sandboxes/:id/preview", s.createPreviewURL)
147152
api.GET("/sandboxes/:id/preview", s.listPreviewURLs)

0 commit comments

Comments
 (0)