Virtual file system
The sandboxed in-memory VFS and its file operations.
All file operations in a workflow run against a sandboxed, in-memory virtual file system (VFS) — never the host machine's filesystem. They are global functions, no imports needed.
Type definitions are in
runtime/src/tide.d.tsfor editor autocompletion.
How the VFS works
- Isolation. Each invocation has its own independent VFS; workflows cannot see each other's files or reach the host.
- Derived from the journal. There is no separate VFS snapshot file. On first
run the VFS starts empty and each
writeFile/removeFilerecords a journal entry; on replay it starts empty again and the write/remove entries re-apply in order to rebuild the exact same state. - Step snapshots. When a step begins, Tide takes an in-memory snapshot of the VFS. If the step fails, the VFS is restored from that snapshot — only an in-memory mechanism, never persisted.
- Paths. Paths are plain strings relative to the VFS root; intermediate directories are created on write. No drive letters, symlinks, or permissions.
The host filesystem is never touched — see Security for why.
readFile
readFile(path: string): Promise<string>Reads a file from the virtual filesystem.
Parameters:
path-- Path to the file in the VFS
Returns: The file contents as a UTF-8 string.
Throws: If the file does not exist.
Behavior:
- First run: Reads from the in-memory VFS and records the result in the journal
- Replay: Returns the journaled result without accessing the VFS
- Inside a step: Reads see the current in-memory VFS state, including writes made earlier in the same step. The read result is buffered as a journal entry and committed atomically when the step completes.
await writeFile("config.json", '{"key": "value"}');
const data = await readFile("config.json");
log(data); // '{"key": "value"}'readFileBytes
readFileBytes(path: string): Promise<Uint8Array>Reads binary data from the virtual filesystem.
Parameters:
path-- Path to the file in the VFS
Returns: The file contents as a Uint8Array.
Throws: If the file does not exist.
Behavior:
- First run: Reads from the in-memory VFS and records the result in the journal (stored as base64)
- Replay: Returns the journaled result without accessing the VFS
- Inside a step: Reads see the current in-memory VFS state, including writes made earlier in the same step. The read result is buffered as a journal entry and committed atomically when the step completes.
await writeFileBytes("image.png", pngData);
const bytes = await readFileBytes("image.png");
console.log(bytes instanceof Uint8Array); // truewriteFile
writeFile(path: string, contents: string): Promise<void>Writes a text file to the virtual filesystem.
Parameters:
path-- Path to the file in the VFScontents-- String contents to write
Returns: void
Behavior:
- Creates the file if it doesn't exist, overwrites if it does
- Mutates the in-memory VFS immediately (subsequent reads see the new content)
- Outside a step: The journal entry is committed to storage immediately
- Inside a step: The journal entry is buffered and committed atomically when the step completes. If the step fails, the VFS is rolled back to its pre-step state and the buffered entry is discarded.
- Replay: The VFS mutation is re-applied from the journal to rebuild state; the journaled result (
null) is returned
await writeFile("output.txt", "Hello, world!");
await writeFile("output.txt", "Overwritten!"); // Replaces the file
const contents = await readFile("output.txt");
console.log(contents); // "Overwritten!"writeFileBytes
writeFileBytes(path: string, contents: Uint8Array): Promise<void>Writes binary data to the virtual filesystem.
Parameters:
path-- Path to the file in the VFScontents-- Binary contents to write as aUint8Array
Returns: void
Behavior:
- Creates the file if it doesn't exist, overwrites if it does
- Mutates the in-memory VFS immediately (subsequent reads see the new content)
- Outside a step: The journal entry is committed to storage immediately
- Inside a step: The journal entry is buffered and committed atomically when the step completes. If the step fails, the VFS is rolled back to its pre-step state and the buffered entry is discarded.
- Replay: The VFS mutation is re-applied from the journal to rebuild state; the journaled result (
null) is returned
const data = new Uint8Array([0x89, 0x50, 0x4e, 0x47]); // PNG header
await writeFileBytes("image.png", data);
const readBack = await readFileBytes("image.png");
console.log(readBack[0]); // 137 (0x89)removeFile
removeFile(path: string): Promise<void>Removes a file from the virtual filesystem.
Parameters:
path-- Path to the file in the VFS
Returns: void
Behavior:
- Deletes the file from the in-memory VFS
- Does not throw if the file does not exist
- Outside a step: The journal entry is committed to storage immediately
- Inside a step: The journal entry is buffered and committed atomically when the step completes. If the step fails, the VFS is rolled back to its pre-step state and the removed file is restored.
- Replay: The VFS mutation is re-applied from the journal to rebuild state; the journaled result is returned
await writeFile("temp.txt", "temporary");
await removeFile("temp.txt");
// File no longer exists in VFSlistFiles
listFiles(path?: string): Promise<Array<{ name: string; isFile: boolean }>>Lists the contents of a directory in the virtual filesystem.
Parameters:
path-- (optional) Path to the directory in the VFS. Defaults to the VFS root when omitted.
Returns: An array of entries, each with:
name-- The name of the file or directoryisFile--trueif the entry is a file,falseif it is a directory
Behavior:
- First run: Reads the directory listing from the in-memory VFS and records the result in the journal
- Replay: Returns the journaled result for deterministic replay
- Inside a step: Reads see the current in-memory VFS state. The listing result is buffered as a journal entry and committed atomically when the step completes.
await writeFile("docs/readme.txt", "Hello");
await writeFile("docs/notes.txt", "World");
const entries = await listFiles("docs");
console.log(entries);
// [{ name: "readme.txt", isFile: true }, { name: "notes.txt", isFile: true }]
const root = await listFiles();
console.log(root);
// [{ name: "docs", isFile: false }]Journal Behavior Summary
All file operations are journaled. The table below summarizes what each operation stores and how it behaves during replay:
| Operation | Journal op | Result stored | VFS mutation on replay |
|---|---|---|---|
readFile | op_read_file | File contents (string) | No |
readFileBytes | op_read_file_bytes | File contents (base64) | No |
writeFile | op_write_file | null | Yes -- re-applies write |
writeFileBytes | op_write_file_bytes | null | Yes -- re-applies write |
removeFile | op_remove_file | null | Yes -- re-applies removal |
listFiles | op_list_files | Array of entries | No |
Read operations (readFile, readFileBytes, listFiles) return their journaled result on replay without touching the VFS. Write operations (writeFile, writeFileBytes, removeFile) re-apply their mutations to rebuild the in-memory VFS state during replay.
File Paths
VFS paths are simple strings. There is no concept of a working directory -- all paths are treated relative to the VFS root:
await writeFile("hello.txt", "data"); // /hello.txt
await writeFile("subdir/file.txt", "data"); // /subdir/file.txt
await readFile("hello.txt"); // reads /hello.txtIntermediate directories are created automatically when writing to a nested path. The VFS does not support absolute paths with drive letters, symbolic links, or file permissions.
Related
- Steps — atomic transactions with VFS rollback.
- Durable execution — how journaling and replay work.
- Security — why the VFS is sandboxed from the host.