Files
List, read, write, upload and download — with an encoding you name rather than one that is guessed.
File access needs no input lease.
List
const listing = await machine.files.list("/home/agent");
for (const entry of listing.entries) console.log(entry.name, entry.type, entry.size);listing = machine.files.list("/home/agent")
for entry in listing["entries"]:
print(entry["name"], entry["type"], entry["size"])An entry’s type is file, dir, symlink or other.
Read
const text = await machine.files.read("/etc/hostname");
text.content;
const chunk = await machine.files.read("/var/log/app.log", { offset: 0, length: 4096 });
chunk.eof;
const raw = await machine.files.read("/tmp/logo.png", { encoding: "base64" });text = machine.files.read("/etc/hostname")
text["content"]
chunk = machine.files.read("/var/log/app.log", offset=0, length=4096)
chunk["eof"]
raw = machine.files.read("/tmp/logo.png", encoding="base64")Encoding is named, never guessed
A utf8 read of bytes that are not UTF-8 is an invalid_request telling you to ask for
base64. It is never silently decoded, because a decode-and-re-encode is exactly where a PNG
turns into a field of replacement characters — and the corruption surfaces later, somewhere else,
as a mystery.
Write
await machine.files.write("/home/agent/notes.md", "# notes\n");
await machine.files.write("/home/agent/notes.md", "more\n", { append: true });
await machine.files.write("/home/agent/run.sh", script, { mode: "0755" });machine.files.write("/home/agent/notes.md", "# notes\n")
machine.files.write("/home/agent/notes.md", "more\n", append=True)
machine.files.write("/home/agent/run.sh", script, mode="0755")encoding?"utf8" | "base64"
"utf8" | "base64""utf8"append?boolean
Append rather than replace.
booleanfalsecreateDirs?boolean
TypeScript `createDirs` / Python `create_dirs`. Create parent directories as needed.
booleantruemode?string
File mode, e.g. "0644".
stringUpload and download
await machine.files.upload("/home/agent/logo.png", bytes); // Uint8Array in
const png = await machine.files.download("/home/agent/logo.png"); // Uint8Array out
const big = await machine.files.download("/var/log/huge.log", { maxBytes: 256 * 1024 * 1024 });machine.files.upload("/home/agent/logo.png", png_bytes) # bytes in
png = machine.files.download("/home/agent/logo.png") # bytes out
big = machine.files.download("/var/log/huge.log", max_bytes=256 * 1024 * 1024)upload is write with base64, which is an exact round trip rather than a guess.
download does the chunking for you. The guest bounds an inline read, so a large file arrives as
several 4 MB ranges and is reassembled client-side. It ends on the guest’s own eof rather than a
size comparison, so a file that grows under the read does not truncate, and it stops at 64 MB
unless you raise the limit.
What is refused
The machine is your own computer and the microVM is the boundary, so the default readable area is everything. What is denied is the small set of paths that are not yours: the substrate’s guest credentials, the machine agent’s own, and the host’s control channel into the guest.
await machine.files.read("/etc/machine-agent/credentials.json");
// RasterError: permission_deniedmachine.files.read("/etc/machine-agent/credentials.json")
# RasterError: permission_deniedThe denylist is fixed and not configurable, and the check runs against every form the path takes — as written, each symlink hop, and the fully resolved result. See files for why each of those is necessary.
It does not sandbox the machine from itself: a process inside the machine still reads whatever it can. The denylist keeps the control plane’s own credentials out of the API surface.