vfs
The vfs namespace — 76 functions.
globals/vfs/clearPlayShadow
vfs.clearPlayShadow() -> boolean
Forget the entire play-shadow set after a bulk promote or discard. Tracking only — never touches the bytes.
Returns boolean — Always true.
vfs.clearPlayShadow()
globals/vfs/copy
vfs.copy(src: string, dst: string) -> (boolean, 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.
Parameters
srcstring— Source absolute VFS path (file or directory).dststring— Destination absolute VFS path.
Returns (boolean, string?) — True on success; (false, errmsg) on failure.
vfs.copy("/zero/runtime/recordings/take1.mp4", "/zero/source/clips/take1.mp4")
globals/vfs/currentAuthor
vfs.currentAuthor() -> { id: string, name: string? }?
The agent this call is attributed to — the author a /source write
made right now would be recorded under in the play shadow. Nil when the
call carries no actor identity, which is the case for engine-authored
work and for a caller that presented no token. Compare its id against
vfs.playShadowAuthors() to separate your own pending edits from a
co-author's.
Returns { id: string, name: string? }? — { id, name } for the acting agent, or nil when unattributed.
local me = vfs.currentAuthor()
print(if me ~= nil then me.id else "unattributed")
globals/vfs/evict
vfs.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.
Parameters
pathstring— VFS path whose bytes should be evicted.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if MemFs bytes were dropped; false otherwise.
vfs.evict("/zero/source/textures/imported_big.png")
globals/vfs/exists
vfs.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.
Parameters
pathstring— VFS path to check.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if the path is known regardless of byte cache state.
assert(vfs.exists("@builtin/models/Cube"))
globals/vfs/isDirectory
vfs.isDirectory(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.
Parameters
pathstring— VFS path to classify.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if the path is a directory.
if vfs.isDirectory("/zero/source/Goblin.dynamicAsset") then print("folder asset") end
globals/vfs/isSaveExcluded
vfs.isSaveExcluded(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.
Parameters
pathstring— VFS path to classify.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True when the path is held back from world saves and sync.
if not vfs.isSaveExcluded(p) then table.insert(publishable, p) end
globals/vfs/list
vfs.list(path: string?) -> { VfsListEntry }
List entries in a VFS directory.
Parameters
pathstring(optional) — Directory path (defaults to/zero).
Returns { VfsListEntry } — Array of { name, isDirectory } tables.
for _, e in ipairs(vfs.list("/zero/source")) do print(e.name) end
globals/vfs/memResident
vfs.memResident() -> { { path: string, bytes: number, kind: string } }
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.
Returns { { path: string, bytes: number, kind: string } } — Array of { path, bytes, kind }; kind is "binary" for non-text content and "large-text" for oversized text. Empty when the VFS isn't up.
for _, e in ipairs(vfs.memResident()) do print(e.path, e.bytes, e.kind) end
globals/vfs/mkdir
vfs.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.
Parameters
pathstring— Directory path.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if the directory exists after the call. A creation the running play session refuses raises with the whole reason.
vfs.mkdir("/zero/source/scenes/")
globals/vfs/move
vfs.move(src: string, dst: string, opts: { quiet: boolean? }?) -> (boolean, string?)
Move a file from src to dst. By default fires the destination's
write side effects; pass opts.quiet = true to suppress them.
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.
Parameters
srcstring— Source absolute VFS path.dststring— Destination absolute VFS path.opts{ quiet: boolean? }(optional) — Optional{ quiet: boolean? }.
Returns (boolean, string?) — True on success; (false, errmsg) when the paths themselves refuse it. A move the running play session refuses raises with the whole reason instead.
vfs.move("/zero/source/a.luau", "/zero/source/b.luau")
globals/vfs/mutationSeq
vfs.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.
Returns number — Count of applied VFS mutations this session. Monotonic.
local at = vfs.mutationSeq()
vfs.write("/zero/source/tmp/note.txt", "hi")
repeat task.wait() until vfs.mutationSeq() > at
globals/vfs/pendingWrites
vfs.pendingWrites() -> { string }
List the /source paths with an in-flight local write the synced
manifest has not reflected yet — the read-your-writes frontier. A
just-written 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. Empty when fully synced.
Returns { string } — Array of VFS paths with pending (unconfirmed) local writes.
for _, p in ipairs(vfs.pendingWrites()) do print(p) end
globals/vfs/playShadowAuthors
vfs.playShadowAuthors() -> { [string]: { id: string, name: string? } }
The agent behind each currently-shadowed /source path: the ZeroMind
user id the write was attributed to, and the username to show for it.
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. A
path written with no actor identity carries no entry — it belongs to no
agent in particular, and stays settleable by any of them.
Returns { [string]: { id: string, name: string? } } — Map of shadowed path to { id, name }.
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
globals/vfs/playShadowPaths
vfs.playShadowPaths() -> { string }
List the /source paths edited during running play that are currently
held as copy-on-write SHADOWS (MemFs-only, on-disk original untouched) —
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.
Returns { string } — Array of normalized VFS paths currently shadowed.
for _, p in ipairs(vfs.playShadowPaths()) do print(p) end
globals/vfs/promotePlayShadow
vfs.promotePlayShadow(path: string) -> string
Promote a single play-shadow edit into a canonical write. Re-asserts the live overlay bytes through the full write pipeline with the play write lock released, then unmarks the path. The bytes stay in the engine end to end, so binary content promotes exactly. Takes ONE file path, and needs the write lock released, so run it inside a pause you take and hand back. A promotion that cannot happen raises with the reason: the path is not shadowed, the path is a folder covering shadowed edits, play is running, 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.
Parameters
pathstring— Shadowed VFS path to promote (one of vfs.playShadowPaths()).
Returns string — The path the call settled — it is no longer shadowed.
engine.paused = true
local promoted = vfs.promotePlayShadow("/zero/source/cover.jpg")
engine.paused = false
globals/vfs/read
vfs.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 called from a coroutine and the bytes
aren't locally cached, transparently yields the coroutine
while the lazy fetch runs.
Parameters
pathstring— VFS path.optsVfsOpts(optional) —{ root = "/source/" }.
Returns string? — File contents (binary-safe), or nil.
local src = vfs.read("@builtin/components/Camera.luau")
globals/vfs/readAsync
vfs.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.
Parameters
pathstring— VFS path.optsVfsOpts(optional) —{ root = "/source/" }.
Returns string — Promise ID — pass to task.await().
local data = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))
globals/vfs/reload
vfs.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.
Parameters
modulePathstring(optional) — Module identity to reload (omit to reload all).
Returns boolean — For a single identity, whether a module was cached under that name and has now been dropped — false says the name matched nothing. The no-arg form returns true.
vfs.reload("@mylib/utils.helpers")
globals/vfs/remove
vfs.remove(path: string, opts: VfsOpts?) -> (boolean, string?)
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.
Parameters
pathstring— VFS path to remove.optsVfsOpts(optional) —{ root = "/source/", recursive = false }.
Returns (boolean, string?) — True on success; (false, errmsg) when the path itself refuses 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.
vfs.remove("/zero/source/scratch.luau")
globals/vfs/revertPlayShadow
vfs.revertPlayShadow(path: string) -> string
Revert a single play-shadow edit: restore 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. Takes ONE file path, and needs the write lock released, so run it inside a pause you take and hand back. A revert that cannot happen raises with the reason, on the same terms as vfs.promotePlayShadow.
Parameters
pathstring— Shadowed VFS path to revert (one of vfs.playShadowPaths()).
Returns string — The path that is now reverted and no longer shadowed.
engine.paused = true
local reverted = vfs.revertPlayShadow("/zero/source/Foo.component/init.luau")
engine.paused = false
globals/vfs/unmarkPlayShadow
vfs.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.
Parameters
pathstring— VFS path to unmark.
Returns boolean — Always true.
vfs.unmarkPlayShadow("/zero/source/Foo.component/init.luau")
globals/vfs/unwatch
vfs.unwatch(watcherId: number) -> boolean
Remove a previously registered VFS watcher.
Parameters
watcherIdnumber— Watcher id returned byvfs.watch.
Returns boolean — True if the watcher was found and removed.
vfs.unwatch(id)
globals/vfs/watch
vfs.watch(path: string, callback: (string, string) -> ()) -> number
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.
Parameters
pathstring— Exact path, or folder path ending in/.callback(string, string) -> ()—(mutated_path, kind) -> (), kind"write"or"remove".
Returns number — Watcher id.
local id = vfs.watch("/zero/source/", function(path, kind) print(kind, path) end)
globals/vfs/write
vfs.write(path: string, content: string, opts: VfsOpts?) -> (boolean, string?)
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 with disk
source untouched, and is discarded on a guarded play-exit unless
accepted. 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.
Parameters
pathstring— VFS path to write to.contentstring— File content (binary-safe).optsVfsOpts(optional) —{ root = "/source/", overwrite = true, quiet = false, durable = false }.
Returns (boolean, string?) — True on success; on failure returns false + error message. A durable write raises instead of returning false.
vfs.write("/zero/source/notes.md", body)
vfs.write("/zero/source/game/Vent.component/init.luau", src, { durable = true })
modules/vfs/README
require("@builtin/modules/api/engine/vfs") -- vfs (also available as global 'vfs')
Virtual filesystem — read, write, list, watch. Public Luau surface over the __vfs Internal FFI namespace.
Usage: local vfs = require("@builtin/modules/api/engine/vfs") Also available as global: vfs
modules/vfs/clearPlayShadow
clearPlayShadow(): boolean
Forget the entire play-shadow set after a bulk promote or discard. Tracking only — never touches the bytes.
vfs.clearPlayShadow()
modules/vfs/copy
copy(src: string, dst: string): (boolean, 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.
Parameters
srcstring— Source absolute VFS path (file or directory).dststring— Destination absolute VFS path.
vfs.copy("/zero/runtime/recordings/take1.mp4", "/zero/source/clips/take1.mp4")
modules/vfs/currentAuthor
currentAuthor(): { id: string, name: string? }?
The agent this call is attributed to — the author a /source write
made right now would be recorded under in the play shadow. Nil when the
call carries no actor identity, which is the case for engine-authored
work and for a caller that presented no token. Compare its id against
vfs.playShadowAuthors() to separate your own pending edits from a
co-author's.
local me = vfs.currentAuthor()
print(if me ~= nil then me.id else "unattributed")
modules/vfs/evict
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.
Parameters
pathstring— VFS path whose bytes should be evicted.optsVfsOpts?(optional) —{ root = "/source/" }.
vfs.evict("/zero/source/textures/imported_big.png")
modules/vfs/exists
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.
Parameters
pathstring— VFS path to check.optsVfsOpts?(optional) —{ root = "/source/" }.
assert(vfs.exists("@builtin/models/Cube"))
modules/vfs/isDirectory
isDirectory(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.
Parameters
pathstring— VFS path to classify.optsVfsOpts?(optional) —{ root = "/source/" }.
if vfs.isDirectory("/zero/source/Goblin.dynamicAsset") then print("folder asset") end
modules/vfs/isSaveExcluded
isSaveExcluded(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.
Parameters
pathstring— VFS path to classify.optsVfsOpts?(optional) —{ root = "/source/" }.
if not vfs.isSaveExcluded(p) then table.insert(publishable, p) end
modules/vfs/list
list(path: string?): { VfsListEntry }
List entries in a VFS directory.
Parameters
pathstring?(optional) — Directory path (defaults to/zero).
for _, e in ipairs(vfs.list("/zero/source")) do print(e.name) end
modules/vfs/memResident
memResident(): { { path: string, bytes: number, kind: string } }
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.
for _, e in ipairs(vfs.memResident()) do print(e.path, e.bytes, e.kind) end
modules/vfs/mkdir
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.
Parameters
pathstring— Directory path.optsVfsOpts?(optional) —{ root = "/source/" }.
vfs.mkdir("/zero/source/scenes/")
modules/vfs/move
move(src: string, dst: string, opts: { quiet: boolean? }?): (boolean, string?)
Move a file from src to dst. By default fires the destination's
write side effects; pass opts.quiet = true to suppress them.
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.
Parameters
srcstring— Source absolute VFS path.dststring— Destination absolute VFS path.opts{ quiet: boolean? }?(optional) — Optional{ quiet: boolean? }.
vfs.move("/zero/source/a.luau", "/zero/source/b.luau")
modules/vfs/mutationSeq
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.
local at = vfs.mutationSeq()
vfs.write("/zero/source/tmp/note.txt", "hi")
repeat task.wait() until vfs.mutationSeq() > at
modules/vfs/pendingWrites
pendingWrites(): { string }
List the /source paths with an in-flight local write the synced
manifest has not reflected yet — the read-your-writes frontier. A
just-written 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. Empty when fully synced.
for _, p in ipairs(vfs.pendingWrites()) do print(p) end
modules/vfs/playShadowAuthors
playShadowAuthors(): { [string]: { id: string, name: string? } }
The agent behind each currently-shadowed /source path: the ZeroMind
user id the write was attributed to, and the username to show for it.
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. A
path written with no actor identity carries no entry — it belongs to no
agent in particular, and stays settleable by any of them.
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
modules/vfs/playShadowPaths
playShadowPaths(): { string }
List the /source paths edited during running play that are currently
held as copy-on-write SHADOWS (MemFs-only, on-disk original untouched) —
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.
for _, p in ipairs(vfs.playShadowPaths()) do print(p) end
modules/vfs/promotePlayShadow
promotePlayShadow(path: string): string
Promote a single play-shadow edit into a canonical write. Re-asserts the live overlay bytes through the full write pipeline with the play write lock released, then unmarks the path. The bytes stay in the engine end to end, so binary content promotes exactly. Takes ONE file path, and needs the write lock released, so run it inside a pause you take and hand back. A promotion that cannot happen raises with the reason: the path is not shadowed, the path is a folder covering shadowed edits, play is running, 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.
Parameters
pathstring— Shadowed VFS path to promote (one of vfs.playShadowPaths()).
engine.paused = true
local promoted = vfs.promotePlayShadow("/zero/source/cover.jpg")
engine.paused = false
modules/vfs/read
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 called from a coroutine and the bytes
aren't locally cached, transparently yields the coroutine
while the lazy fetch runs.
Parameters
pathstring— VFS path.optsVfsOpts?(optional) —{ root = "/source/" }.
local src = vfs.read("@builtin/components/Camera.luau")
modules/vfs/readAsync
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.
Parameters
pathstring— VFS path.optsVfsOpts?(optional) —{ root = "/source/" }.
local data = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))
modules/vfs/reload
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.
Parameters
modulePathstring?(optional) — Module identity to reload (omit to reload all).
vfs.reload("@mylib/utils.helpers")
modules/vfs/remove
remove(path: string, opts: VfsOpts?): (boolean, string?)
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.
Parameters
pathstring— VFS path to remove.optsVfsOpts?(optional) —{ root = "/source/", recursive = false }.
vfs.remove("/zero/source/scratch.luau")
modules/vfs/revertPlayShadow
revertPlayShadow(path: string): string
Revert a single play-shadow edit: restore 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. Takes ONE file path, and needs the write lock released, so run it inside a pause you take and hand back. A revert that cannot happen raises with the reason, on the same terms as vfs.promotePlayShadow.
Parameters
pathstring— Shadowed VFS path to revert (one of vfs.playShadowPaths()).
engine.paused = true
local reverted = vfs.revertPlayShadow("/zero/source/Foo.component/init.luau")
engine.paused = false
modules/vfs/unmarkPlayShadow
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.
Parameters
pathstring— VFS path to unmark.
vfs.unmarkPlayShadow("/zero/source/Foo.component/init.luau")
modules/vfs/unwatch
unwatch(watcherId: number): boolean
Remove a previously registered VFS watcher.
Parameters
watcherIdnumber— Watcher id returned byvfs.watch.
vfs.unwatch(id)
modules/vfs/watch
watch(path: string, callback: (string, string) -> ()): number
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.
Parameters
pathstring— Exact path, or folder path ending in/.callback(string, string) -> ()—(mutated_path, kind) -> (), kind"write"or"remove".
local id = vfs.watch("/zero/source/", function(path, kind) print(kind, path) end)
modules/vfs/write
write(path: string, content: string, opts: VfsOpts?): (boolean, string?)
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 with disk
source untouched, and is discarded on a guarded play-exit unless
accepted. 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.
Parameters
pathstring— VFS path to write to.contentstring— File content (binary-safe).optsVfsOpts?(optional) —{ root = "/source/", overwrite = true, quiet = false, durable = false }.
vfs.write("/zero/source/notes.md", body)
vfs.write("/zero/source/game/Vent.component/init.luau", src, { durable = true })
typed/builtin//modules/api/engine/vfs/vfs/clearPlayShadow
vfs.clearPlayShadow() -> boolean
Forget the entire play-shadow set after a bulk promote or discard. Tracking only — never touches the bytes.
Returns boolean — Always true.
vfs.clearPlayShadow()
typed/builtin//modules/api/engine/vfs/vfs/copy
vfs.copy(src: string, dst: string) -> (boolean, 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.
Parameters
srcstring— Source absolute VFS path (file or directory).dststring— Destination absolute VFS path.
Returns (boolean, string?) — True on success; (false, errmsg) on failure.
vfs.copy("/zero/runtime/recordings/take1.mp4", "/zero/source/clips/take1.mp4")
typed/builtin//modules/api/engine/vfs/vfs/currentAuthor
vfs.currentAuthor() -> { id: string, name: string? }?
The agent this call is attributed to — the author a /source write
made right now would be recorded under in the play shadow. Nil when the
call carries no actor identity, which is the case for engine-authored
work and for a caller that presented no token. Compare its id against
vfs.playShadowAuthors() to separate your own pending edits from a
co-author's.
Returns { id: string, name: string? }? — { id, name } for the acting agent, or nil when unattributed.
local me = vfs.currentAuthor()
print(if me ~= nil then me.id else "unattributed")
typed/builtin//modules/api/engine/vfs/vfs/evict
vfs.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.
Parameters
pathstring— VFS path whose bytes should be evicted.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if MemFs bytes were dropped; false otherwise.
vfs.evict("/zero/source/textures/imported_big.png")
typed/builtin//modules/api/engine/vfs/vfs/exists
vfs.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.
Parameters
pathstring— VFS path to check.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if the path is known regardless of byte cache state.
assert(vfs.exists("@builtin/models/Cube"))
typed/builtin//modules/api/engine/vfs/vfs/isDirectory
vfs.isDirectory(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.
Parameters
pathstring— VFS path to classify.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if the path is a directory.
if vfs.isDirectory("/zero/source/Goblin.dynamicAsset") then print("folder asset") end
typed/builtin//modules/api/engine/vfs/vfs/isSaveExcluded
vfs.isSaveExcluded(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.
Parameters
pathstring— VFS path to classify.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True when the path is held back from world saves and sync.
if not vfs.isSaveExcluded(p) then table.insert(publishable, p) end
typed/builtin//modules/api/engine/vfs/vfs/list
vfs.list(path: string?) -> { VfsListEntry }
List entries in a VFS directory.
typed/builtin//modules/api/engine/vfs/vfs/memResident
vfs.memResident() -> { { path: string, bytes: number, kind: string } }
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.
Returns { { path: string, bytes: number, kind: string } } — Array of { path, bytes, kind }; kind is "binary" for non-text content and "large-text" for oversized text. Empty when the VFS isn't up.
for _, e in ipairs(vfs.memResident()) do print(e.path, e.bytes, e.kind) end
typed/builtin//modules/api/engine/vfs/vfs/mkdir
vfs.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.
Parameters
pathstring— Directory path.optsVfsOpts(optional) —{ root = "/source/" }.
Returns boolean — True if the directory exists after the call. A creation the running play session refuses raises with the whole reason.
vfs.mkdir("/zero/source/scenes/")
typed/builtin//modules/api/engine/vfs/vfs/move
vfs.move(src: string, dst: string, opts: { quiet: boolean? }?) -> (boolean, string?)
Move a file from src to dst. By default fires the destination's
write side effects; pass opts.quiet = true to suppress them.
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.
Parameters
srcstring— Source absolute VFS path.dststring— Destination absolute VFS path.opts{ quiet: boolean? }(optional) — Optional{ quiet: boolean? }.
Returns (boolean, string?) — True on success; (false, errmsg) when the paths themselves refuse it. A move the running play session refuses raises with the whole reason instead.
vfs.move("/zero/source/a.luau", "/zero/source/b.luau")
typed/builtin//modules/api/engine/vfs/vfs/mutationSeq
vfs.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.
Returns number — Count of applied VFS mutations this session. Monotonic.
local at = vfs.mutationSeq()
vfs.write("/zero/source/tmp/note.txt", "hi")
repeat task.wait() until vfs.mutationSeq() > at
typed/builtin//modules/api/engine/vfs/vfs/pendingWrites
vfs.pendingWrites() -> { string }
List the /source paths with an in-flight local write the synced
manifest has not reflected yet — the read-your-writes frontier. A
just-written 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. Empty when fully synced.
Returns { string } — Array of VFS paths with pending (unconfirmed) local writes.
for _, p in ipairs(vfs.pendingWrites()) do print(p) end
typed/builtin//modules/api/engine/vfs/vfs/playShadowAuthors
vfs.playShadowAuthors() -> { [string]: { id: string, name: string? } }
The agent behind each currently-shadowed /source path: the ZeroMind
user id the write was attributed to, and the username to show for it.
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. A
path written with no actor identity carries no entry — it belongs to no
agent in particular, and stays settleable by any of them.
Returns { [string]: { id: string, name: string? } } — Map of shadowed path to { id, name }.
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
typed/builtin//modules/api/engine/vfs/vfs/playShadowPaths
vfs.playShadowPaths() -> { string }
List the /source paths edited during running play that are currently
held as copy-on-write SHADOWS (MemFs-only, on-disk original untouched) —
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.
Returns { string } — Array of normalized VFS paths currently shadowed.
for _, p in ipairs(vfs.playShadowPaths()) do print(p) end
typed/builtin//modules/api/engine/vfs/vfs/promotePlayShadow
vfs.promotePlayShadow(path: string) -> string
Promote a single play-shadow edit into a canonical write. Re-asserts the live overlay bytes through the full write pipeline with the play write lock released, then unmarks the path. The bytes stay in the engine end to end, so binary content promotes exactly. Takes ONE file path, and needs the write lock released, so run it inside a pause you take and hand back. A promotion that cannot happen raises with the reason: the path is not shadowed, the path is a folder covering shadowed edits, play is running, 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.
Parameters
pathstring— Shadowed VFS path to promote (one of vfs.playShadowPaths()).
Returns string — The path the call settled — it is no longer shadowed.
engine.paused = true
local promoted = vfs.promotePlayShadow("/zero/source/cover.jpg")
engine.paused = false
typed/builtin//modules/api/engine/vfs/vfs/read
vfs.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 called from a coroutine and the bytes
aren't locally cached, transparently yields the coroutine
while the lazy fetch runs.
typed/builtin//modules/api/engine/vfs/vfs/readAsync
vfs.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.
Parameters
pathstring— VFS path.optsVfsOpts(optional) —{ root = "/source/" }.
Returns string — Promise ID — pass to task.await().
local data = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))
typed/builtin//modules/api/engine/vfs/vfs/reload
vfs.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.
Parameters
modulePathstring(optional) — Module identity to reload (omit to reload all).
Returns boolean — For a single identity, whether a module was cached under that name and has now been dropped — false says the name matched nothing. The no-arg form returns true.
vfs.reload("@mylib/utils.helpers")
typed/builtin//modules/api/engine/vfs/vfs/remove
vfs.remove(path: string, opts: VfsOpts?) -> (boolean, string?)
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.
Parameters
pathstring— VFS path to remove.optsVfsOpts(optional) —{ root = "/source/", recursive = false }.
Returns (boolean, string?) — True on success; (false, errmsg) when the path itself refuses 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.
vfs.remove("/zero/source/scratch.luau")
typed/builtin//modules/api/engine/vfs/vfs/revertPlayShadow
vfs.revertPlayShadow(path: string) -> string
Revert a single play-shadow edit: restore 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. Takes ONE file path, and needs the write lock released, so run it inside a pause you take and hand back. A revert that cannot happen raises with the reason, on the same terms as vfs.promotePlayShadow.
Parameters
pathstring— Shadowed VFS path to revert (one of vfs.playShadowPaths()).
Returns string — The path that is now reverted and no longer shadowed.
engine.paused = true
local reverted = vfs.revertPlayShadow("/zero/source/Foo.component/init.luau")
engine.paused = false
typed/builtin//modules/api/engine/vfs/vfs/unmarkPlayShadow
vfs.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.
Parameters
pathstring— VFS path to unmark.
Returns boolean — Always true.
vfs.unmarkPlayShadow("/zero/source/Foo.component/init.luau")
typed/builtin//modules/api/engine/vfs/vfs/unwatch
vfs.unwatch(watcherId: number) -> boolean
Remove a previously registered VFS watcher.
Parameters
watcherIdnumber— Watcher id returned byvfs.watch.
Returns boolean — True if the watcher was found and removed.
vfs.unwatch(id)
typed/builtin//modules/api/engine/vfs/vfs/watch
vfs.watch(path: string, callback: (string, string) -> ()) -> number
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.
Parameters
pathstring— Exact path, or folder path ending in/.callback(string, string) -> ()—(mutated_path, kind) -> (), kind"write"or"remove".
Returns number — Watcher id.
local id = vfs.watch("/zero/source/", function(path, kind) print(kind, path) end)
typed/builtin//modules/api/engine/vfs/vfs/write
vfs.write(path: string, content: string, opts: VfsOpts?) -> (boolean, string?)
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 with disk
source untouched, and is discarded on a guarded play-exit unless
accepted. 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.
Parameters
pathstring— VFS path to write to.contentstring— File content (binary-safe).optsVfsOpts(optional) —{ root = "/source/", overwrite = true, quiet = false, durable = false }.
Returns (boolean, string?) — True on success; on failure returns false + error message. A durable write raises instead of returning false.
vfs.write("/zero/source/notes.md", body)
vfs.write("/zero/source/game/Vent.component/init.luau", src, { durable = true })