Skip to content

Commit 439afd1

Browse files
authored
Merge pull request #130 from smol-machines/binbin-fix-workspace-and-run-timeout
fix: agents.md updates, test updates for correctness, fix bug with workspace symlink
2 parents 6b7c4f6 + 301dcc4 commit 439afd1

File tree

4 files changed

+50
-5
lines changed

4 files changed

+50
-5
lines changed

AGENTS.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ smolvm machine create myvm --ssh-agent --net
3333
# Pack into portable executable
3434
smolvm pack create --image python:3.12-alpine -o ./my-python
3535
./my-python run -- python3 -c "print('hello')"
36+
37+
# Create machine from packed artifact (fast start, no pull)
38+
smolvm machine create my-vm --from ./my-python.smolmachine
39+
smolvm machine start --name my-vm
40+
smolvm machine exec --name my-vm -- pip install requests
3641
```
3742

3843
## When to Use What
@@ -43,6 +48,7 @@ smolvm pack create --image python:3.12-alpine -o ./my-python
4348
| Interactive shell | `smolvm machine run --net -it --image IMAGE -- /bin/sh` |
4449
| Persistent dev environment | `machine create``machine start``machine exec` |
4550
| Ship software as a binary | `smolvm pack create --image IMAGE -o OUTPUT` |
51+
| Fast persistent machine from packed artifact | `machine create NAME --from FILE.smolmachine` |
4652
| Use git/ssh with private keys safely | Add `--ssh-agent` to run or create |
4753
| Minimal VM without image | `smolvm machine run -s Smolfile` (bare VM) |
4854
| Declarative VM config | Create a Smolfile, use `--smolfile`/`-s` flag |
@@ -52,7 +58,9 @@ smolvm pack create --image python:3.12-alpine -o ./my-python
5258
- **`machine run`** — ephemeral. All changes are discarded when the command exits.
5359
- **`machine exec`** — persistent. Filesystem changes (package installs, config edits) persist across exec sessions for the same machine, whether bare or image-based. Changes are stored in an overlay on the machine's storage disk.
5460
- **`machine stop` + `start`** — changes persist across restarts. The persistent overlay is remounted preserving previous changes.
55-
- **`pack run`** / **`pack exec`** — ephemeral. Each exec starts fresh from the packed image.
61+
- **`pack run`** — ephemeral. Each run starts fresh from the packed image.
62+
- **`pack start` + `exec`** — daemon mode. `/workspace` persists across exec sessions and stop/start. Container overlay resets per exec (package installs don't persist — use `/workspace` for durable data).
63+
- **`machine create --from .smolmachine`** — creates a persistent named machine from a packed artifact. Boots from pre-extracted layers (~250ms, no image pull). Full `machine exec` persistence — package installs, file writes all survive across exec and stop/start.
5664

5765
## CLI Structure
5866

@@ -62,6 +70,7 @@ All commands use named flags (no positional args except `machine create NAME` an
6270
smolvm machine run --image IMAGE [-- COMMAND] # ephemeral
6371
smolvm machine exec --name NAME [-- COMMAND] # run in existing VM
6472
smolvm machine create NAME [OPTIONS] # create persistent
73+
smolvm machine create NAME --from FILE.smolmachine # from packed artifact
6574
smolvm machine start [--name NAME] # start (default: "default")
6675
smolvm machine stop [--name NAME] # stop
6776
smolvm machine delete NAME [-f] # delete
@@ -232,9 +241,10 @@ through the container's overlay filesystem so both commands see the
232241
same files.
233242

234243
**`/workspace` shared directory:** Every machine has a `/workspace`
235-
directory that is shared between the VM and the container. It persists
236-
across `exec` sessions and is a good default location for scripts,
237-
data, and results:
244+
directory — bare VMs, image-based VMs, and machines created from
245+
`.smolmachine` artifacts. It persists across `exec` sessions and
246+
across `stop`/`start` cycles. It's a good default location for
247+
scripts, data, and results:
238248

239249
```bash
240250
# Typical agent workflow: copy code in, execute, extract results
@@ -321,6 +331,15 @@ The packed binary runs as a normal executable:
321331
./my-app stop # stop daemon
322332
```
323333

334+
Alternatively, create a named machine from the `.smolmachine` for full lifecycle management:
335+
```bash
336+
smolvm machine create my-vm --from my-app.smolmachine
337+
smolvm machine start --name my-vm # ~250ms boot, no image pull
338+
smolvm machine exec --name my-vm -- pip install x # fully persistent
339+
smolvm machine stop --name my-vm
340+
smolvm machine ls # shows my-vm
341+
```
342+
324343
The `.smolmachine` manifest includes registry-oriented metadata:
325344
- `host_platform` — host OS+arch this machine runs on (e.g., `darwin/arm64`), distinct from `platform` which is the guest
326345
- `created` — RFC 3339 timestamp of when the machine was packed

crates/smolvm-agent/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ fn main() {
168168
// before this point, ensure_storage_mounted() handles the mount on demand.
169169
ensure_storage_mounted();
170170

171+
// Create /workspace symlink for bare VMs. Image-based VMs get /workspace
172+
// via a bind mount in the container spec, but bare VMs run directly in the
173+
// VM rootfs where /workspace doesn't exist. The symlink makes /workspace
174+
// available in both modes.
175+
{
176+
let workspace_link = std::path::Path::new("/workspace");
177+
let workspace_target = std::path::Path::new("/storage/workspace");
178+
if !workspace_link.exists() && workspace_target.exists() {
179+
let _ = std::os::unix::fs::symlink(workspace_target, workspace_link);
180+
}
181+
}
182+
171183
// Initialize packed layers support (if SMOLVM_PACKED_LAYERS env var is set)
172184
let t0 = uptime_ms();
173185
if let Some(packed_dir) = storage::get_packed_layers_dir() {

src/cli/machine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ impl RunCmd {
665665
)?
666666
} else {
667667
let (exit_code, stdout, stderr) =
668-
client.vm_exec(command, env, params.workdir.clone(), None)?;
668+
client.vm_exec(command, env, params.workdir.clone(), self.timeout)?;
669669
if !stdout.is_empty() {
670670
print!("{}", stdout);
671671
}

tests/test_machine.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,19 @@ test_machine_run_timeout() {
963963
[[ "$output" == *"timed out"* ]] || [[ "$output" == *"Killed"* ]] || [[ $? -ne 0 ]]
964964
}
965965

966+
# Regression: /workspace must exist on bare VMs (not just image-based).
967+
test_bare_vm_workspace() {
968+
ensure_machine_running
969+
local output
970+
output=$($SMOLVM machine exec -- ls -d /workspace 2>&1)
971+
[[ "$output" == *"/workspace"* ]] || { echo "FAIL: /workspace missing on bare VM"; return 1; }
972+
973+
# Write and read back
974+
$SMOLVM machine exec -- sh -c 'echo ws-bare > /workspace/bare.txt' 2>&1 || return 1
975+
output=$($SMOLVM machine exec -- cat /workspace/bare.txt 2>&1)
976+
[[ "$output" == *"ws-bare"* ]]
977+
}
978+
966979
test_machine_run_pipeline() {
967980
local output
968981
output=$($SMOLVM machine run --net --image alpine:latest -- sh -c "echo 'hello world' | wc -w" 2>&1)
@@ -1085,6 +1098,7 @@ run_test "Machine run: volume readonly" test_machine_run_volume_readonly || true
10851098
run_test "Machine run: workdir" test_machine_run_workdir || true
10861099
run_test "Machine run: detached" test_machine_run_detached || true
10871100
run_test "Machine run: timeout" test_machine_run_timeout || true
1101+
run_test "Bare VM: /workspace exists" test_bare_vm_workspace || true
10881102
run_test "Machine images" test_machine_images || true
10891103
run_test "Machine prune --dry-run" test_machine_prune_dry_run || true
10901104

0 commit comments

Comments
 (0)