vfs
Public Luau surface over the `__vfs` Internal FFI namespace — the engine's virtual filesystem.
vfs Module
Public Luau surface over the __vfs Internal FFI namespace — the
engine's virtual filesystem.
Purpose
Wrap the raw __vfs.* FFI namespace in a typed Luau table that gets
auto-injected as _G.vfs via the prelude. The standard read / write /
move / remove / mkdir / list / exists / readAsync / reload / evict
surface, plus trusted-only watch / unwatch for the bootstrap VM.
vfs.read yields on a byte-cache miss: the module's last step installs
the vfs_async_read fallback over the synchronous read, so every VM that
holds this namespace, the trusted VM included, reads a path through the
same two steps and gets the same bytes for it.
Usage
-- Read / write
local src = vfs.read("@builtin/components/Camera.luau")
vfs.write("/zero/source/notes.md", body)
-- What a write landed: where the bytes went, and what the static passes
-- read in the Luau among them
local ok, report = vfs.write("/zero/source/game/Vent.component/init.luau", code)
if report.diagnostics then
for _, d in report.diagnostics do
print(d.severity, d.path, d.line, d.message)
end
end
if not report.durable then
log.warn(report.warning .. " " .. report.playShadow)
end
-- Listing
for _, e in ipairs(vfs.list("/zero/source")) do
print(e.isDirectory and "[d] " or " ", e.name)
end
-- Async fetch
local png = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))
-- Reclaim RAM after processing a big binary
vfs.evict("/zero/source/textures/imported_huge.png")
-- Reload a module after editing its source on disk
if vfs.reload("@mylib/utils.helpers") then
local m = require("@mylib/utils.helpers") -- sees the new source
end
vfs.reload(identity) answers whether a module was cached under that name
and has now been dropped. A false says the name matched nothing — either a
misspelled identity or a module this VM never required — so the next
require() returns whatever it would have returned anyway.
A module that caches state derived from OTHER files keeps serving the old
value when those files are rewritten, since only its own source is watched.
Reload it by identity to make the next require() recompute.
Exports
vfs.read(path, opts?) -> string?vfs.write(path, content, opts?) -> (boolean, WriteReport | string)vfs.move(src, dst, opts?) -> (boolean, WriteReport | string)vfs.copy(src, dst) -> (boolean, WriteReport | string)vfs.remove(path, opts?) -> (boolean, string?)vfs.mkdir(path, opts?) -> booleanvfs.list(path?) -> { VfsListEntry }vfs.exists(path, opts?) -> booleanvfs.readAsync(path, opts?) -> promiseIdvfs.reload(modulePath?) -> booleanvfs.evict(path, opts?) -> booleanvfs.mutationSeq() -> number- Trusted-only:
vfs.watch(path, callback) -> number/vfs.unwatch(watcherId) -> boolean.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2global vfs global-types module vfs Virtual filesystem — read, write, list, watch. Public Luau surface over the `__vfs` Internal FFI namespace. require modules/api/engine/vfs about A write to authored `/source` WHILE PLAY IS RUNNING lands on the play shadow: the bytes are live in the session immediately, and a guarded play-exit puts the pre-play copy back unless they were kept. Where the bound world syncs the path, the write is carried to it as it lands, so a restart or crash before that exit keeps it; where nothing syncs the path, a restart ends it. `vfs.durability(path)` answers where any write's bytes went: `durable`, whether a restart keeps them in `survivesRestart`, the state in `warning`, the routes out in `playShadow`, and which of the paths you asked about the shadow holds in `shadowed`. `vfs.playShadowPaths()` lists everything the session is holding, and `vfs.playShadowAuthors()` says whose each one is. Three routes keep a write, differing in what they cost the session: vfs.promotePlayShadow(path) -- or a list of paths: THOSE -- paths onto canonical source, -- play keeps running, the rest -- of the set untouched vfs.write(path, bytes, { durable = true }) -- the write skips the -- shadow, nothing to promote tools.use("sceneAuthoring", "changes") -- then acceptChanges: reaches -- ENTITY changes too, the -- session keeps running `vfs.revertPlayShadow` takes the same path or list and does the opposite. Promote the whole SET the asset landed, not the path you passed: a path carrying an asset-type suffix names the asset, so one `.component` write shadows its entry file, its README and its metadata together, and a play-exit refuses over whichever of them are left behind. Read that set out of `vfs.playShadowPaths()`, filtered to the paths under the asset. The `core/vfs` guide has the model and what each route costs.
bumpVersion(path: string) → void
Bump a path's content-version counter so any assetType behavior that memoized a parse of that file (keyed by `content_version.get`) rebuilds on its next read — SYNCHRONOUSLY, in the same tick as the write, which the deferred asset-change `onChange` dispatch can't guarantee. Lazy-required so this module (installed early in the prelude) carries no load-order dependency on it; the module has no dependencies of its own, so the first call resolves cleanly.
| arg | type | description |
|---|---|---|
| path | string |
bumpCopied(src: string, dst: string) → void
Bump every path a copy of `src` landed under `dst`. A leaf lands one path; a folder lands one per descendant, at the same relative position under `dst`. The walk follows the SOURCE, so the bumped set is exactly the set whose bytes the copy replaced — a file already sitting beside the destination keeps its counter, and the work is bounded by what was copied.
| arg | type | description |
|---|---|---|
| src | string | |
| dst | string |
read(path: string, opts: VfsOpts?) → string
Read a file from the virtual filesystem. Binary-safe. Returns file contents as a string, or nil if the file is not known. Relative paths resolve under `opts.root` (default `/source/`). When the bytes aren't locally cached and the calling code can yield (`coroutine.isyieldable()`: a task, `awake`, `start`, `update`, `onEnable`, `onOwnerChanged`, `onSyncReceived`, a replicated event's handler), the read waits while the lazy fetch runs and answers the bytes. Code that cannot yield (`onDisable`, `onDestroy`, `onPropertyChanged`, a received synced-function call, a `vfs.watch` callback, a module's top level while `require` runs it, a metamethod, a `table.sort` comparator) cannot wait: a read there of bytes still on their way (a file `vfs.exists` knows, a render surface being captured) raises `VFS_READ_CANNOT_WAIT`, naming the path and where to read it.
| arg | type | description |
|---|---|---|
| path | string | VFS path. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
local src = vfs.read("@builtin/components/Camera.luau")readBounded(path: string, maxBytes: number) →
Read a file from the virtual filesystem, at most `maxBytes` long. Binary-safe, and read the way `vfs.read` reads it. A file longer than `maxBytes` answers `nil, "too_large"`; where the layer holding it states its length (a file held in memory, a synced or staged file) its bytes are never read. Relative paths resolve under `/source/`. Where the calling code can yield, a file whose bytes are not cached is fetched and held to the same bound; where it cannot, a known file whose bytes have not arrived raises `VFS_READ_CANNOT_WAIT`, as `vfs.read` does. file longer than `maxBytes`.
| arg | type | description |
|---|---|---|
| path | string | VFS path. |
| maxBytes | number | The most bytes the read answers, a whole number 0 or more. |
examples
local raw, why = vfs.readBounded("/zero/source/notes.json", 65536)write(path: string, content: string, opts: VfsOpts?) →
Write content to a file. Binary-safe. Overwrites existing files by default — pass `opts.overwrite = false` to refuse to clobber. While play is running a `/source` write lands on the play shadow: it succeeds and reads back, live in the session, and is undone by a guarded play-exit unless accepted; the report's `survivesRestart` says whether a restart keeps it. Scratch under `/source/tmp/` writes through untouched. Pass `opts.durable = true` to say these bytes ARE the source: the write reaches canonical `/source` with play still running and the session still in play, hot-reloading the modules and components that read it, so the edit is observed running in the same play session with nothing left to promote. A durable write RAISES with the reason when the bytes cannot become canonical source. went, with `survivesRestart` / `warning` / `playShadow` / `shadowed` beside it when the play shadow took them, `diagnostics` carries what the static passes read in the Luau it landed, and `contentRequirements` names what the asset it landed in must still say before the world publishes. It is the answer the MCP `write_file` tool gives for the same bytes at the same path. On failure returns false + error message; a durable write raises instead of returning false.
| arg | type | description |
|---|---|---|
| path | string | VFS path to write to. |
| content | string | File content (binary-safe). |
| opts | VfsOpts? | `{ root = "/source/", overwrite = true, quiet = false, durable = false }`. |
examples
vfs.write("/zero/source/notes.md", body)local ok, report = vfs.write("/zero/source/game/Vent.component/init.luau", src)if report.diagnostics then for _, d in report.diagnostics do print(d.severity, d.path, d.line, d.message) end end
if report.contentRequirements then for _, r in report.contentRequirements do print(r.path, r.detail) end end
if not report.durable then log.warn(report.warning .. " " .. report.playShadow) end
vfs.write("/zero/source/game/Vent.component/init.luau", src, { durable = true })move(src: string, dst: string, opts: { quiet: boolean? }?) →
Move a file from `src` to `dst`. By default fires the destination's write side effects; pass `opts.quiet = true` to suppress them. A quiet move still returns the destination's report and still posts the resolve rule the publish gate reads, which is what `vfs.write` does with the same option: a publish takes no override, so the finding is stated at the line that carries it. While play is running a move takes the source away, so authored `/source` content that predates play is refused and the refusal RAISES with the reason; content this play session created moves and stays tracked on the play shadow. destination, so it reports what `vfs.write` reports for the same bytes at that path: where they went, and what the static passes read in the Luau among them, per landed leaf when the move took a folder. (false, errmsg) when the paths themselves refuse it. A move the running play session refuses raises with the whole reason instead.
| arg | type | description |
|---|---|---|
| src | string | Source absolute VFS path. |
| dst | string | Destination absolute VFS path. |
| opts | { quiet: boolean? }? | Optional `{ quiet: boolean? }`. |
examples
vfs.move("/zero/source/a.luau", "/zero/source/b.luau")local ok, report = vfs.move(mod .. "/init.luau", comp .. "/init.luau")
copy(src: string, dst: string) →
Copy a file OR directory from `src` to `dst`, `cp -r` style. A directory recurses — every descendant is replicated at the same relative path under `dst`, `.refs` sidecars included. `.meta` sidecars are minted fresh, so a copy is a distinct asset with its own identity. Both paths are absolute. The source may live in any layer (writable, library mount, builtin, runtime-generated); the destination must be a writable route. destination, so it reports what `vfs.write` reports for the same bytes at that path: where they went, and what the static passes read in the Luau among them, per landed leaf when the copy took a folder. A module that is advisory where it sat reads as a script error once it sits on a component path, and the copy is what put it there. (false, errmsg) on failure.
| arg | type | description |
|---|---|---|
| src | string | Source absolute VFS path (file or directory). |
| dst | string | Destination absolute VFS path. |
examples
vfs.copy("/zero/runtime/recordings/take1.mp4", "/zero/source/clips/take1.mp4")local ok, report = vfs.copy(mod, "/zero/source/game/Vent.component")
remove(path: string, opts: VfsOpts?) →
Remove a file. Refuses to remove directories unless `opts.recursive = true`. Refuses protected system roots. While play is running, authored `/source` content that predates play is refused and the refusal RAISES with the reason; content this play session created is removable, a folder included. Pass `opts.durable = true` to say the removal IS a change to the source: it takes the route edit mode takes with play still running and the session still in play, the counterpart of `vfs.write(path, bytes, { durable = true })` for taking a path away rather than replacing its bytes. Every other refusal stands in either terms — a protected root, a read-only route, a plain directory without `recursive`. the removal. A removal the running play session refuses raises with the whole reason instead, so a `pcall` around the call reads it — and the lock answers ahead of whether the path is there, so a locked `/source` path raises whether or not it holds anything.
| arg | type | description |
|---|---|---|
| path | string | VFS path to remove. |
| opts | VfsOpts? | `{ root = "/source/", recursive = false, durable = false }`. |
examples
vfs.remove("/zero/source/scratch.luau")mkdir(path: string, opts: VfsOpts?) → boolean
Create a directory. `mkdir -p` semantics — idempotent. Errors if a file already exists at the same path. While play is running an authored `/source` directory waits for the lock to lift and the refusal RAISES with the reason; writing a file under the path creates it as part of that write, and scratch under `/source/tmp/` creates as in edit mode. running play session refuses raises with the whole reason.
| arg | type | description |
|---|---|---|
| path | string | Directory path. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
vfs.mkdir("/zero/source/scenes/")list(path: string?) →
List entries in a VFS directory.
| arg | type | description |
|---|---|---|
| path | string? | Directory path (defaults to `/zero`). |
examples
for _, e in ipairs(vfs.list("/zero/source")) do print(e.name) endisDirectory(path: string, opts: VfsOpts?) → boolean
Is ONE path a directory? Answers from reality — the writable layer's children, a resolver-served folder listing, an explicit empty-directory marker — so a loose file whose extension collides with an assetType name (`notes.json`) reads as the file it is while a real `<name>.<type>/` folder reads as a folder. Costs the same whatever the containing folder holds; use `vfs.list` when you want every entry's kind, this when you hold one path.
| arg | type | description |
|---|---|---|
| path | string | VFS path to classify. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
if vfs.isDirectory("/zero/source/Goblin.dynamicAsset") then print("folder asset") endisSaveExcluded(path: string, opts: VfsOpts?) → boolean
Does this path hold content the machine keeps to itself? `/source/tmp/` is session scratch and `/source/local/` is this machine's own durable content — each directory itself included, and everything under it. Both are writable, hot-reloadable and enumerable like the rest of `/source/`; what separates them is where they stop. The engine filters them out of every world save and every peer broadcast, so they reach no world, carry no manifest row there, and a staging verb handed one refuses it by name. The match reads a whole path segment, so `/source/tmpfoo/` is ordinary content. Ask here whenever your code has to agree with what a world can hold.
| arg | type | description |
|---|---|---|
| path | string | VFS path to classify. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
if not vfs.isSaveExcluded(p) then table.insert(publishable, p) end
installedFrom(path: string, opts: VfsOpts?) → InstalledFrom
Where the asset holding `path` comes from, when the library carries it by reference. The library's source then holds an install record naming the asset, the world that published it and the commit its bytes are, and the engine serves the asset's files from those. `path` is the asset's own folder or anything inside it. Nil for a path whose bytes the library or the world holds itself.
| arg | type | description |
|---|---|---|
| path | string | VFS path of the asset or of anything inside it. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
local from = vfs.installedFrom(ref.path) if from then print(from.world, from.commit) end
movedReferenceNames(path: string?, opts: VfsOpts?) →
The world sources that spell a reference by a name its target no longer has. A source records, beside each name it uses, the guid of the asset the name reached; when that asset later sits at another path (it was moved, or the engine's library files it elsewhere), the reference keeps resolving through the guid and a read serves the current name, while the stored text keeps the old one. This lists those sources and changes nothing. Writing an entry's `currentText` back with `vfs.write(source, currentText, { verbatim = true })` brings the stored spelling up to date without changing what any reference resolves to. Only sources the world authors are considered: the engine library and `/source/deps/` keep the text they were published with. when none does.
| arg | type | description |
|---|---|---|
| path | string? | One source to ask about; every source the world authors when absent. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
for _, s in ipairs(vfs.movedReferenceNames()) do print(s.source, #s.names) end
exists(path: string, opts: VfsOpts?) → boolean
Is the path known to the VFS? Checks the Stage-1 metadata (`.meta` sidecar / ManifestView) — NOT "are the bytes locally cached?". Use `vfs.read(path) ~= nil` to confirm bytes are reachable.
| arg | type | description |
|---|---|---|
| path | string | VFS path to check. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
assert(vfs.exists("@builtin/models/Cube"))contentAddress(path: string, opts: VfsOpts?) → string
The content address (sha-256 hex) of the bytes `vfs.read(path)` returns, for a path whose bytes the blob store holds, answered synchronously and without the bytes. Two answers that agree name the same bytes, and a write that changes them changes it. Nil for a path whose bytes are held in memory (text under the inline size threshold, a play shadow, a kept read), which a synchronous `vfs.read` already returns, and for a path nothing is stored at. Keep it beside a copy of a file you read to tell, from a caller that cannot yield, whether the copy is still what the path holds.
| arg | type | description |
|---|---|---|
| path | string | VFS path. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
local address = vfs.contentAddress("/zero/source/scenes/main.scene/scene.json")readAsync(path: string, opts: VfsOpts?) → string
Asynchronous binary-safe read. Returns a promise ID that resolves to the file contents. Useful for reading render textures from the main thread without blocking.
| arg | type | description |
|---|---|---|
| path | string | VFS path. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
local data = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))reload(modulePath: string?) → boolean
Clear entries from the `require()` cache so the next `require(name)` re-runs the module's source. Pass a single module identity to drop only that entry; call with no arguments to drop every cached module of the world's own. A module of the built-in library runs once for the process — its body registers into registries that outlive it — and stands through a reload with no argument. The next `require` runs the source the module's file holds when the reload is called, whichever write brought those bytes there, a `quiet` one included. module of the world's own). that name and has now been dropped — `false` says the name matched nothing. The no-arg form returns true.
| arg | type | description |
|---|---|---|
| modulePath | string? | Module identity to reload (omit to reload every |
examples
vfs.reload("@mylib/utils.helpers")evict(path: string, opts: VfsOpts?) → boolean
Drop the in-memory bytes for `path` from the writable MemFs layer without removing the asset. Use after processing large binaries to reclaim RAM.
| arg | type | description |
|---|---|---|
| path | string | VFS path whose bytes should be evicted. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
vfs.evict("/zero/source/textures/imported_big.png")memResident( ) →
List the MemFs entries that are NOT resident-by-default — the writable in-memory layer's binary blobs and its large text files (text at or above the inline-text size threshold). These are the bytes `vfs.evict` can reclaim: the ones kept in RAM rather than left to fall through to the on-disk BlobStore cache. Small text (resident by default) is omitted. The audit counterpart to `vfs.evict` and to reading with `{ keep = true }` — use it to see what encoded bytes are held in RAM, and why. content and `"large-text"` for oversized text. Empty when the VFS isn't up.
examples
for _, e in ipairs(vfs.memResident()) do print(e.path, e.bytes, e.kind) end
durability(paths: string | { string }, opts: VfsOpts?) →
Did that write save to disk, and if not how is it kept? What became of the bytes just written at `paths` — one path, or an array of them answered as ONE write. `durable` is true when they are where the call filed them, and false when the play shadow took any of them: live in this session and undone by a guarded play-exit unless kept. `durable` is present whatever the answer is, so its absence is never a reading. A non-durable answer carries `survivesRestart` (true when a restart of this engine keeps the bytes, because the bound world syncs them as they land; false when a restart ends them), `warning` (the state and what a restart does with it, for a reader scanning values rather than checking a field), `playShadow` (the routes to disk and what each costs a session other people are running in) and `shadowed` (which of the given paths the shadow has, or under `pending` would take). This is the answer, off the same shadow set and in the same words, that the `write_file` / `edit_file` / `capture` tools attach to their own results and that `asset.create` reports as its second return value. Ask it here at any other site that lands files, so every write surface states where the bytes went in one set of terms. write. A path resolves the way `vfs.write` resolves its own — absolute or `@`-rooted as it stands, a bare one under `/source/` — so the answer is about the file that write landed. A value that is not a path — an AssetRef, a record, a number, a string with nothing in it — raises rather than being answered off the empty set it reads as; a list with no entries names nothing and answers durable. the same question of a write that has NOT landed yet: where bytes written at these paths would go, read off the same play-lock verdict that will decide it. That is what a surface arranging a write it files later reports from — `av.record` opens a take here and the film is written when the take ends — because until those bytes land the shadow is holding nothing under the path and the answer would be durable at the one moment the caller can still act on it. The fields and the words are the same either way; the state names the tense it is true in. present exactly when `durable` is false.
| arg | type | description |
|---|---|---|
| paths | string | { string } | One VFS path, or an array of paths answered together as one |
| opts | VfsOpts? | `{ root = "/source/", pending = false }`. `pending = true` asks |
examples
local ok = vfs.write(path, body)
local d = vfs.durability(path)
if not d.durable then log.warn(d.warning .. " " .. d.playShadow) end
local ahead = vfs.durability("/zero/source/clips/take.mp4", { pending = true })playShadowPaths( ) →
Every source write made during play that is not durable yet — the set to keep or discard before leaving play. Lists the `/source` paths edited during running play that the play shadow holds, each beside the pre-play copy a guarded discard puts back: the universal play shadow-copy set. These are the in-play edits persist promotes over the originals on confirm, or drops on a guarded discard. Empty outside play or when nothing was edited.
examples
for _, p in ipairs(vfs.playShadowPaths()) do print(p) end
playShadowAuthors( ) →
The caller behind each currently-shadowed `/source` path: the id the write was attributed to, the username to show for it, and the ZeroMind account it authenticated as. Several agents drive one engine at once and every one of their in-play source edits sits in the same shadow set, so this is how a review, a refusal or a verdict tells one agent's pending work from another's. `id` names the calling session where the call carried one, so two agent sessions on a machine linked to one ZeroMind account are two authors. A path written by a caller the engine could not name carries no entry, because it belongs to nobody in particular, and stays settleable by anyone.
examples
local mine = vfs.currentAuthor()
for path, who in pairs(vfs.playShadowAuthors()) do
if mine == nil or who.id ~= mine.id then print(path, "belongs to", who.name) end
end
currentAuthor( ) →
The caller this call is attributed to: the author a `/source` write made right now would be recorded under in the play shadow. `id` names the calling session where the transport attested one, so two agent sessions driving one engine read as two callers even when one linked ZeroMind account minted both their tokens. `stable` is the key the caller had before the world's roster named it, which a rename leaves where it was: the key the caller's staging area and the writer its dirty rows record are kept under. `account` is that shared account. Nil when the engine can name no caller at all, which is the case for engine-authored work and for a caller that presented neither a session nor a token. Compare its `id` against `vfs.playShadowAuthors()` to separate your own pending edits from a co-author's.
examples
local me = vfs.currentAuthor()
print(if me ~= nil then me.id else "unattributed")
pendingWrites( ) →
List the `/source` paths with a local write or removal the synced manifest has not reflected yet: everything this client still owes the server. A just-written or just-removed file appears here until its upload round-trips and the synced dirty state catches up; `world.vcsStatus` unions these so a fresh edit reads back as dirty immediately, and `world.syncStatus().owed_paths` reports the same list. Empty when fully synced.
examples
for _, p in ipairs(vfs.pendingWrites()) do print(p) end
mutationSeq( ) → number
Lifetime count of VFS mutations the engine has APPLIED — the drain's clock. A write queues its side effects (an asset's content reload, the assetType's `onChange`, a component or scene registration) and a later frame runs them; this number advances as each one completes. Read it, write, then poll for a larger value to learn the queue has moved past the point you wrote at — instead of waiting a guessed number of frames. It counts every mutation kind, so it answers about the pipeline rather than about one file; `asset.reloadSeq(ref)` is the per-asset reading.
examples
local at = vfs.mutationSeq()
vfs.write("/zero/source/tmp/note.txt", "hi")repeat task.wait() until vfs.mutationSeq() > at
folderHashWalks( ) → number
Lifetime count of category-folder checksum walks — the part of a write's cost that grows with how much content already exists. A folder's checksum is the tree hash over the descendants that describe the asset, so deriving one reads every key under it, and a write re-derives it for each enclosing category folder whose bytes that write reaches. Content the checksum is made of therefore moves this once per enclosing folder per write; bytes that reach no checksum — a managed sidecar, a scene's autosave overlay — move it by nothing. Read it around a burst to learn what that burst cost in walks, a number that reads the same on a loaded machine and a quiet one.
examples
local at = vfs.folderHashWalks()
vfs.write("/zero/source/scenes/probe.scene/scene_dirty/entities/e1.json", "{}")print(vfs.folderHashWalks() - at)
claimWalks( ) → number
Lifetime count of walks over every asset the world offers: the part of an enumeration's cost that grows with how many assets exist. `asset.list` reads a snapshot of every registered claim, built once for each change to what the world offers and shared by every list after it; `asset.categories` reads the category registry and walks nothing. Read it around a burst to learn what that burst cost in walks, a number that reads the same on a loaded machine and a quiet one.
examples
local at = vfs.claimWalks()
vfs.write("/zero/source/probe.module/init.luau", "return {}")asset.list("module")print(vfs.claimWalks() - at)
unmarkPlayShadow(path: string) → boolean
Forget a single play-shadow path after it has been promoted (saved over source) or discarded. Tracking only — never touches the bytes.
| arg | type | description |
|---|---|---|
| path | string | VFS path to unmark. |
examples
vfs.unmarkPlayShadow("/zero/source/Foo.component/init.luau")clearPlayShadow( ) → boolean
Forget the entire play-shadow set after a bulk promote or discard. Tracking only — never touches the bytes.
examples
vfs.clearPlayShadow()
discardPlayShadow( ) →
Throw away every play-shadow edit: the discard a play-exit takes when the play session's work is not kept. Each path reverts to its pre-play copy, as vfs.revertPlayShadow does. A path whose pre-play copy cannot be put back yet (the world's blob, still being pulled; the world's manifest row, read once the manifest has taken the update it is applying; or a copy whose write back is refused) leaves the play shadow all the same and is OWED: it holds the play bytes until the copy lands and its write is taken, and the copy is put back then, unless the path has changed in the meantime. Every owed path is returned, posted as a notice and logged by name. Calling it again asks for every owed restore again, pulling a copy whose pull failed. restore has not landed. Empty when every path reverted.
examples
for _, owed in ipairs(vfs.discardPlayShadow()) do
print(owed.path, owed.reason)
end
playDiscardsOwed( ) →
The play edits a discard threw away whose pre-play copy has not been put back yet. Each of these paths holds the play session's bytes until its restore lands, which happens on its own once the copy arrives and its write is taken. A path that changes first keeps its new content and leaves this list. landed.
examples
for _, owed in ipairs(vfs.playDiscardsOwed()) do
print(owed.path, owed.reason)
end
promotePlayShadow(path: string | { string }) → string |
KEEP a source write made while play was running — the call that saves a play-mode edit onto disk and makes it durable. Promotes play-shadow edits into canonical writes: re-asserts the live overlay bytes through the full write pipeline, then unmarks each path. The bytes stay in the engine end to end, so binary content promotes exactly. It answers while play is RUNNING and leaves the mode, the clock and every shadow entry it did not name exactly where they stood. Takes ONE path, or an ARRAY of them — a single folder-asset write shadows the entry file, the README and the metadata together, so a slice is tens of paths, and naming them keeps the call to the caller's own work on an engine other sessions are running in. Every named path is attempted; one that refuses does not stop the ones after it. A promotion that cannot happen raises with the reason: the path is not shadowed, the path is a folder covering shadowed edits, the workspace is read-only, or the write-through failed. A shadow entry whose bytes are gone is dropped as promoted, so the path comes back with nothing written for it. vfs.playShadowPaths()). array went in — none of them are shadowed any more.
| arg | type | description |
|---|---|---|
| path | string | { string } | A shadowed VFS path to promote, or an array of them (from |
examples
local promoted = vfs.promotePlayShadow("/zero/source/cover.jpg")local mine = {}for _, p in ipairs(vfs.playShadowPaths()) do
if string.find(p, "/zero/source/mine/", 1, true) == 1 then table.insert(mine, p) end
end
local settled = vfs.promotePlayShadow(mine)
revertPlayShadow(path: string | { string }) → string |
DISCARD a source write made while play was running, keeping nothing — the opposite of promoting it. Reverts play-shadow edits: restores the pre-play copy captured at the first play-mode write (the last edit-mode state, unstaged edits included) into the live slot — or remove the file when it did not exist at that moment — then unmark the path. Hot-reload picks the original back up, so the running session actually reverts. It answers while play is RUNNING and takes ONE path or an ARRAY of them, on the same terms as vfs.promotePlayShadow. A revert that cannot happen raises with the reason. vfs.playShadowPaths()). an array went in — none of them are shadowed any more.
| arg | type | description |
|---|---|---|
| path | string | { string } | A shadowed VFS path to revert, or an array of them (from |
examples
local reverted = vfs.revertPlayShadow("/zero/source/Foo.component/init.luau")local dropped = vfs.revertPlayShadow({ "/zero/source/a.md", "/zero/source/b.md" })watch(path: string, callback: (string, string, string) →
Register a callback that fires when a VFS path is written or removed. Two match modes: exact, or folder/prefix (key ends with `/`, and fires for any descendant). The callback runs in the VM that registered it. Returns a watcher id for `vfs.unwatch`. `"remove"`; origin `"local"` for a change this engine made (through `vfs.*`, the shell or any other surface) and `"remote"` for one that arrived from another participant in the world.
| arg | type | description |
|---|---|---|
| path | string | Exact path, or folder path ending in `/`. |
| callback | (string, string, string | `(mutated_path, kind, origin) -> ()`, kind `"write"` or |
examples
local id = vfs.watch("/zero/source/", function(path, kind, origin) print(kind, origin, path) end)unwatch(watcherId: number) → boolean
Remove a previously registered VFS watcher.
| arg | type | description |
|---|---|---|
| watcherId | number | Watcher id returned by `vfs.watch`. |
examples
vfs.unwatch(id)
open(path: string, opts: VfsOpts?) → PluginHandle
A reader of the file at `path`, to pass a plugin in a call. The plugin reads it by range and reaches nothing else through it; it is refused, raising the reason, where this script could not read. A reader whose file leaves its path answers the plugin that the asset is gone.
| arg | type | description |
|---|---|---|
| path | string | The file to read. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
local weights = vfs.open(asset.containing(__FILE__).path .. "/weights.gguf")
openDir(path: string, opts: VfsOpts?) → PluginHandle
A reader of the folder at `path` and everything under it, to pass a plugin in a call. The plugin lists it and opens readers of its files, by paths relative to the folder that cannot leave it.
| arg | type | description |
|---|---|---|
| path | string | The folder to read. |
| opts | VfsOpts? | `{ root = "/source/" }`. |
examples
local model = vfs.openDir(asset.containing(__FILE__).path .. "/model")
sink(path: string, opts: SinkOpts) → PluginHandle
A sink for the file at `path`, to pass a plugin in a call. The plugin writes it from empty, up to `opts.maxBytes`, and the file lands when the plugin closes the sink or drops it. Only the world's own files take a sink.
| arg | type | description |
|---|---|---|
| path | string | The file to write. |
| opts | SinkOpts | `{ maxBytes = <bytes> }`, required. |
examples
local out = vfs.sink("/source/imported/sphere.zvol", { maxBytes = 64 * 1024 * 1024 })dirSink(path: string, opts: DirSinkOpts) → PluginHandle
A sink for new files under the folder at `path`, subfolders included, to pass a plugin in a call. The plugin makes whatever files it Only the world's own files take a sink.
| arg | type | description |
|---|---|---|
| path | string | The folder to write under. |
| opts | DirSinkOpts | `{ maxFiles = <n>, maxBytes = <bytes>, extensions = { "png" }? }`. |
examples
local frames = vfs.dirSink("/source/frames", { maxFiles = 240, maxBytes = 200 * 1024 * 1024, extensions = { "png" } })PluginHandle = anySinkOpts = { maxBytes: number, root: string? }DirSinkOpts = { maxFiles: number, maxBytes: number, extensions: { string }?, root: string? }VfsOpts = {VfsListEntry = { name: string, isDirectory: boolean, path: string }Durability = {WriteDiagnostic = {WriteContentRequirement = {WriteReport = {InstalledFrom = {MovedReferenceName = {MovedReferenceSource = {Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.