> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mesa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Filesystem

> Understand Mesa's filesystem interface and choose between the POSIX mount and app mount

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](https://en.wikipedia.org/wiki/Filesystem_in_Userspace) 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](/content/mesafs/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](/content/mesafs/app-mount) for setup and usage.

## Choosing between the two

|                            | POSIX mount (FUSE)                        | App mount                                 |
| -------------------------- | ----------------------------------------- | ----------------------------------------- |
| **Setup**                  | Requires FUSE + sandbox                   | Only requires the Mesa SDK                |
| **Install dependencies**   | Yes                                       | No                                        |
| **Run arbitrary binaries** | Yes                                       | No (bash builtins + optional Python/JS)   |
| **Multi-process access**   | Yes                                       | Single process                            |
| **Environment**            | Any (Docker, VMs, local)                  | Node.js/Python backend                    |
| **Best for**               | Full dev environments, CI, sandbox agents | Lightweight 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**.

```text theme={null}
@  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`.

<CodeGroup>
  ```typescript TypeScript theme={null}
  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,
  });
  ```

  ```python Python theme={null}
  async with mesa.fs.mount(repos=["my-repo"]) as fs:
      # 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).
      change_id = await fs.changes.new("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=change_id,
      )
  ```

  ```bash CLI theme={null}
  # Fork a new change based on main's tip. The mount now edits the fork,
  # no bookmark follows it, and main stays put.
  mesa new main --repo acme/my-repo

  cd ~/.local/share/mesa/mnt/acme/my-repo

  # Do some writes...

  # Move the bookmark onto the change to "accept" the writes.
  mesa bookmark move main --repo acme/my-repo
  ```
</CodeGroup>

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).

<CodeGroup>
  ```typescript TypeScript theme={null}
  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 });
  ```

  ```python Python theme={null}
  async with mesa.fs.mount(repos=["my-repo"]) as fs:
      # Switch to the change `my-feature` points at. Writes modify that change rather than `main`.
      await fs.changes.edit("my-repo", bookmark="my-feature")

      # OR switch by id -- ex. a fresh `change.new` fork without a bookmark.
      await fs.changes.edit("my-repo", change_id=change_id)
  ```

  ```bash CLI theme={null}
  # Switch to the change `my-feature` points at. Writes modify that change rather than `main`.
  mesa edit my-feature --repo acme/my-repo

  # OR switch by id -- ex. a fresh `change.new` fork without a bookmark.
  mesa edit "$CHANGE_ID" --repo acme/my-repo
  ```
</CodeGroup>

See [Versioning](/content/concepts/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](/content/mesafs/advanced/realtime).
