Skip to main content
Mesa’s virtual filesystem lets tools interact with repository files directly — no full clone required. Instead of pulling an entire repo to disk, Mesa fetches data on demand and presents it through a standard filesystem interface. Reads and writes go through Mesa’s API transparently, so any tool that works with files can work with Mesa repositories. There are two ways to use the virtual filesystem: POSIX mount (FUSE) and app mount. Both give you access to the same underlying Mesa filesystem — the difference is how it’s exposed to your tools.

POSIX Mount

The POSIX mount uses FUSE to mount repositories as real directories on the host. Every process on the machine — editors, language servers, build systems, agents — sees standard files and directories at a mount path like ~/.local/share/mesa/mnt/my-org/my-repo. Use a POSIX mount when:
  • You’re working inside a sandboxed environment (isolated Docker container, Daytona micro-VM, etc.)
  • You need multiple concurrent processes to access files through native filesystem APIs (ex. multi-processes architures, build systems, compilers, language servers)
  • Your agent needs to install external dependencies (npm install, pip install, etc.) or run arbitrary code
See POSIX Mount for setup and usage.

App Mount

The app mount runs entirely in-process via the Python and TypeScript SDKs. Your application gets a Mesa filesystem handle, and can either call filesystem methods (readFile, writeFile, readdir, etc.) directly or use the Mesa-provided emulated bash tool to execute shell commands (ls, cat, grep, cp, etc.) against Mesa repositories — no FUSE, no sandbox required. Use an app mount when:
  • You have a TS/Python agent running in your own backend and don’t want to manage a sandbox
  • Your agent only needs to read/write files and run shell commands (no dependency installs or arbitrary binaries)
  • You want the simplest possible setup — just npm install @mesadev/sdk and go
  • You’re building with frameworks like Vercel AI SDK, Mastra, or Langchain and want to add a bash tool
See App Mount for setup and usage.

Choosing between the two

POSIX mount (FUSE)App mount
SetupRequires FUSE + sandboxOnly requires the Mesa SDK
Install dependenciesYesNo
Run arbitrary binariesYesNo (bash builtins + optional Python/JS)
Multi-process accessYesSingle process
EnvironmentAny (Docker, VMs, local)Node.js/Python backend
Best forFull dev environments, CI, sandbox agentsLightweight agents, multi-tenant backends
Both modes support read and write operations and use the same caching and prefetching under the hood.

Writing to a mount

Whatever revision you mount, typically a bookmark like main, or a specific change, that is the change your writes edit, in place.
@  qzvqqupx   main       ← mounted here
○  ovknlmro
◆  root
A write through the mount amends qzvqqupx. What you mounted is what you modified. Any two writers looking at the same change will see one another’s modifications in real time. For example, if two different machines run mesa mount on the same main bookmark, these two machines will have identical views of the files. To isolate edits, you can give each its own change with mesa new. The one exception is an empty repository: there is no change to edit yet, so the first write creates the repo’s initial change and the default bookmark picks it up.

Approvals and branching

You may want a flow where a human or agent “drafts” some modifications without touching your canonical state. A typical case might be an agent producing work that a human later approves. In this case, to avoid prematurely modifying your canonical documents, you should explicitly “branch” by creating a new change before writing. When using the fs.change.new method, providing a bookmark argument tells Mesa which base change to branch from; it does not move the bookmark to the new change. After calling fs.change.new, Mesa will switch onto the new change, meaning writes land there. Bookmarks stay put until you move them with mesa.bookmarks.move.
const fs = await mesa.fs.mount({
  repos: [{ name: "my-repo", bookmark: "main" }],
});

// Fork a new change off the `main` change. Writes on the mount now modify the fork.
// The forked change is unnamed (has no bookmark attached to it).
const result = await fs.change.new({ repo: "my-repo", bookmark: "main", message: "implement new feature" });

// Do some writes...

// Move the bookmark to the new change to "accept" the writes.
await mesa.bookmarks.move({
  repo: "my-repo",
  bookmark: "main",
  change_id: result.changeOid,
});
For mounts that should never write at all, mount read-only: writes are rejected with EROFS before they can touch any change.

Switching changes

Switch the mounted repo onto a different existing change at any time. The common case is switching to a change with some draft work. After calling fs.change.edit, writes now modify the change you switched onto. To specify the change that you want to switch onto, you can call fs.change.edit with either a bookmark or a changeId (in case the change doesn’t have an attached bookmark).
const fs = await mesa.fs.mount({
  repos: [{ name: "my-repo", bookmark: "main" }],
});

// Switch to the change `my-feature` points at. Writes modify that change rather than `main`.
await fs.change.edit({ repo: "my-repo", bookmark: "my-feature" });

// OR switch by id -- ex. a fresh `change.new` fork without a bookmark.
await fs.change.edit({ repo: "my-repo", changeId: result.changeOid });
See Versioning for more on how changes and bookmarks work in Mesa.

Realtime

MesaFS reads and writes are realtime by default. Mounts on the same change can see each other’s edits, even when they are running on different hosts. The realtime boundary is the active change, not the repository as a whole. Use separate changes when writers should work in isolation, and use the same change when you want collaborative reads and writes. For more details, see Realtime.