Freestyle Docs

Freestyle / Docs

Files

Read and write VM files, stream large transfers, and browse paused workspaces without starting compute.

Every VM exposes its guest filesystem at vm.fs. Reads and writes carry raw bytes, so there is nothing to encode and no size limit short of 16 GiB per file — the same call that writes a config file uploads a model checkpoint.

Read And Write

await vm.fs.writeFile("/etc/app.conf", "debug = true\n");

const config = await vm.fs.readTextFile("/etc/app.conf");
const bytes = await vm.fs.readFile("/usr/share/logo.png");

writeFile takes a string, a Uint8Array, or a Blob. Writes are atomic: the file is verified before it replaces the target, so a reader inside the VM sees either the old file or the new one — never a half-written one. Integrity is checked with a sha256 the client computes and the guest re-verifies, so a corrupted transfer fails instead of landing.

Read Files While Paused

Keep workspaces paused between tasks and still let users browse code, inspect logs, or download finished artifacts. Freestyle reads the saved filesystem directly, so read-only file access does not resume the VM or start compute.

await vm.pause();

const config = await vm.fs.readTextFile("/etc/app.conf");
const entries = await vm.fs.readDir("/etc");
// The VM stays paused after both calls.

readFile, readTextFile, readFileStream, readDir, stat, and exists all work while paused, including ranged reads. The same calls also work on stopped VMs with saved disk state. No special endpoint or option is needed.

Reads return the saved filesystem as of the pause. Writes, directory creation, and removal wake the VM automatically to apply the change. See VM Lifecycle for more on pausing and starting VMs.

Large Files

Nothing special is required for a big file. Pass a Blob and the SDK reads it a chunk at a time, so the file is never held in memory whole:

import { openAsBlob } from "node:fs";

await vm.fs.writeFile("/data/model.bin", await openAsBlob("model.bin"), {
  onProgress: ({ completedBytes, totalBytes }) => {
    process.stdout.write(`\r${Math.floor((completedBytes / totalBytes) * 100)}%`);
  },
});

Above 8 MiB the SDK switches transports on its own: instead of one request, the file goes as a sequence of chunks, each retried independently. A dropped connection costs one chunk rather than the whole upload, which is what makes a multi-gigabyte transfer over an ordinary internet link finish. The file still appears at its path atomically, at the end.

Streaming Reads

readFile buffers; readFileStream does not. Use it to pipe a large file somewhere without holding it in memory:

import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";

const bytes = await vm.fs.readFileStream("/var/log/build.log");
await pipeline(Readable.fromWeb(bytes), createWriteStream("build.log"));

Reads are also ranged, which is how you tail a log or resume an interrupted download:

const { size } = await vm.fs.stat("/var/log/build.log");
const tail = await vm.fs.readTextFile("/var/log/build.log", {
  offset: Math.max(0, size - 4096),
});

Directories

await vm.fs.mkdir("/data/checkpoints");
const entries = await vm.fs.readDir("/data");
const info = await vm.fs.stat("/data/model.bin");
const there = await vm.fs.exists("/data/model.bin");
await vm.fs.remove("/data/checkpoints");

mkdir creates parents. remove deletes a file or a directory tree.

From The CLI

freestyle vm fs write <vmId> /data/model.bin ./model.bin
freestyle vm fs read <vmId> /var/log/build.log --out build.log
freestyle vm scp ./model.bin <vmId>:/data/model.bin
freestyle vm scp ./dataset <vmId>:/data/dataset
freestyle vm fs ls <vmId> /data
esc