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

# mesa.fs()

> Define a layout, mount it as a Mesa virtual filesystem, or mint its token.

`mesa.fs(layout=..., ttl=..., authors=...)` builds a `LayoutDefinition`: a prepared path map bundled with `layout()`, `mount()`, and `token()`. Calling `.mount()` is an async context manager that yields a `MesaFileSystem`.

```python theme={null}
import os
from mesa_sdk import Mesa, repo

mesa = Mesa(private_key=os.environ["MESA_PRIVATE_KEY"])

async with mesa.fs(
    layout={"/workspace": repo("app", mode="rw", at={"bookmark": "main"})},
    authors=[{"name": "Mesa Bot", "email": "mesa-bot@example.com"}],
).mount() as fs:
    data = await fs.read("/workspace/README.md")
    print(data.decode())
```

Map `repos.list()` results into layout declarations when the repository set is dynamic:

```python theme={null}
result = await mesa.repos.list(
    tags={"$and": [{"environment": "prod"}, {"workload": {"$in": ["sync", "index"]}}]}
)

async with mesa.fs(
    layout={"/skills": [repo(r.name, mode="ro") for r in result.repos]},
    authors=[{"name": "Mesa Bot", "email": "mesa-bot@example.com"}],
).mount() as fs:
    ...
```

Private-key clients sign a short-lived, layout-scoped token locally for the mount. A client built from an access token forwards that token unchanged. Either way the mount keeps the token it started with for its whole lifetime; nothing refreshes it in the background.

See [Layouts](/content/mesafs/layouts) for nesting, structural rules, and running a layout mount in a sandbox.

## `mesa.fs(...)`

Synchronous. Builds the definition eagerly so invalid layouts raise at this call.

<ParamField path="layout" type="Mapping[str, Repo | Sequence[Repo]]" required>
  Map of absolute mount path (`/`-prefixed) to one `repo(...)` declaration or a sequence of them. The mount namespace is exactly this map.
</ParamField>

<ParamField path="ttl" type="int | None">
  Lifetime, in seconds, of every token the definition mints, through `token()` and under the hood in `mount()`. Private-key clients default to `900` and allow up to `14400`. Not accepted by access-token clients, whose token lifetime is already fixed.
</ParamField>

<ParamField path="authors" type="list[SigningKeyAuthor] | None">
  Commit authors, in order, with at least one entry. **Required** for private-key clients; rejected for access-token clients.
</ParamField>

### Definition members

<ResponseField name="layout()" type="Layout">
  The prepared layout. `str(definition.layout())` produces the JSON document `mesa mount --layout` reads.
</ResponseField>

<ResponseField name="mount(disk_cache=None)" type="async context manager -> MesaFileSystem">
  Mount the layout as the complete namespace. Accepts only runtime options (`disk_cache`). The mount's token lifetime is the definition's `ttl`.
</ResponseField>

<ResponseField name="token()" type="async -> TokenCreateResult">
  Mint the layout-scoped, least-privilege access token: repositories collected from every declaration and scoped by name; `["read"]` when every `mode` is `"ro"`, otherwise `["read", "write"]`. Access-token clients cannot mint another token and raise `InvalidOptionsError`.
</ResponseField>

`token()` validates the layout structurally before minting, so an invalid layout fails there with the same error the mount would report. Repository names resolve only at mount time — a layout naming a nonexistent repository still mints a token and fails when the mount resolves it.

```python theme={null}
definition = mesa.fs(
    layout={"/workspace": repo("app", mode="rw")},
    authors=[{"name": "Mesa Bot", "email": "mesa-bot@example.com"}],
    ttl=3600,
)

sandbox.write_file("layout.json", str(definition.layout()))
token = (await definition.token()).token
```

## `repo(selector, *, mode, ...)`

Declare one repository in a layout. Do not construct `Repo` mappings by hand.

```python theme={null}
repo("app", mode="rw", at={"bookmark": "main"})
repo({"name": "app"}, mode="ro", at={"change_id": "zyxwvutsrqponmlkzyxwvutsrqponmlk"})
repo(
    "app",
    mode="rw",
    branched_from={
        "bookmark": "main",
        "as": {"bookmark": "run-27", "describe": "initiate plan..."},
    },
)
```

<ParamField path="selector" type="str | RepoName" required>
  Repository name within the client's organization, as a string or `{"name": "..."}`.
</ParamField>

<ParamField path="mode" type="&#x22;rw&#x22; | &#x22;ro&#x22;" required>
  Presentation mode. Always required — there is no default. `"ro"` rejects writes with `EROFS`.
</ParamField>

<ParamField path="at" type="Mapping[str, object] | None">
  Pin an existing revision: `{"bookmark": ...}` or `{"change_id": ...}`. Mutually exclusive with `branched_from`. When neither `at` nor `branched_from` is set, the repository's default bookmark is used.
</ParamField>

<ParamField path="branched_from" type="Mapping[str, object] | None">
  Fork a new empty descendant from a parent tip at cold open and check it out. Requires `mode="rw"`. Shape: `{"bookmark"|"change_id": ..., "as": {"bookmark"?: str, "describe"?: str}}`. Omit `as` (or `as.bookmark`) for an anonymous tip. Parent bookmark is not moved. Mutually exclusive with `at`.
</ParamField>

<ParamField path="alias" type="str | None">
  Directory-name override. Only valid when the declaration is an element of a sequence value in the layout.
</ParamField>

<ParamField path="sub_paths" type="Mapping[str, Repo | Sequence[Repo]] | None">
  Nested mounts declared beneath this repository's path. Keys are relative paths.
</ParamField>

## Runtime mount options

Non-token options accepted by `definition.mount(...)`.

### DiskCacheConfig

```python theme={null}
from mesa_sdk import DiskCacheConfig

async with mesa.fs(
    layout={"/workspace": repo("app", mode="rw")},
    authors=[{"name": "Mesa Bot"}],
).mount(
    disk_cache=DiskCacheConfig(path="/tmp/mesa-cache", max_size_bytes=500_000_000),
) as fs:
    ...
```

<ParamField path="DiskCacheConfig.path" type="str" required>
  Directory for the on-disk cache.
</ParamField>

<ParamField path="DiskCacheConfig.max_size_bytes" type="int | None">
  Optional cache size cap. When omitted, the native extension auto-sizes the budget against system resources.
</ParamField>

## Paths

Mounted paths are whatever the layout declares:

```text theme={null}
/workspace/README.md
/workspace/src/main.py
/skills/code-review/SKILL.md
```

## Response

`definition.mount()` yields a `MesaFileSystem`.

## MesaFileSystem

The yielded `fs` object exposes async file I/O, metadata, traversal, mutation, Bash, and mounted-repo version-control helpers.

```python theme={null}
async with mesa.fs(
    layout={"/workspace": repo("app", mode="rw")},
    authors=[{"name": "Mesa Bot"}],
).mount() as fs:
    await fs.mkdir("/workspace/src", recursive=True)
    await fs.write("/workspace/src/main.py", b"print('hello')\n")
    data = await fs.read("/workspace/src/main.py")
```

### Byte I/O

<ParamField path="read(path)" type="async -> bytes">
  Read a file as bytes.
</ParamField>

<ParamField path="write(path, content)" type="async -> None">
  Replace file contents, creating the file if missing. Parent directories must already exist.
</ParamField>

<ParamField path="append(path, content)" type="async -> None">
  Append bytes to a file, creating it if missing.
</ParamField>

<ParamField path="exists(path)" type="async -> bool">
  Return whether a path exists. Follows symlinks.
</ParamField>

### Metadata and traversal

<ParamField path="stat(path)" type="async -> FsStat">
  Return metadata for a path, following symlinks.
</ParamField>

<ParamField path="lstat(path)" type="async -> FsStat">
  Return metadata for a path without following symlinks.
</ParamField>

<ParamField path="readdir(path)" type="async -> list[str]">
  Return entry names in a directory. Sort client-side if you need deterministic ordering.
</ParamField>

<ParamField path="realpath(path)" type="async -> str">
  Resolve symlinks and `..` segments to a canonical path.
</ParamField>

<ParamField path="readlink(path)" type="async -> str">
  Return the target of a symlink.
</ParamField>

<ParamField path="resolve_path(base, path)" type="str">
  Join and normalize a path against a base path without touching the filesystem.
</ParamField>

### Mutations

<ParamField path="mkdir(path, recursive=None)" type="async -> None">
  Create a directory. With `recursive=True`, create missing parents and do nothing when the path already exists as a directory.
</ParamField>

<ParamField path="rm(path, recursive=None, force=None)" type="async -> None">
  Remove a file or directory. Use `recursive=True` for non-empty directories and `force=True` to ignore missing paths.
</ParamField>

<ParamField path="cp(src, dest, recursive=None)" type="async -> None">
  Copy a file or directory. Use `recursive=True` for directories.
</ParamField>

<ParamField path="mv(src, dest)" type="async -> None">
  Move or rename a file or directory.
</ParamField>

<ParamField path="chmod(path, mode)" type="async -> None">
  Set permission bits, such as `0o755`.
</ParamField>

<ParamField path="symlink(target, link)" type="async -> None">
  Create a symlink. Relative targets are stored verbatim and resolve against the parent of `link` at read time.
</ParamField>

<ParamField path="utimes(path, atime_ms, mtime_ms)" type="async -> None">
  Set access and modification times. Values are milliseconds since the Unix epoch, not seconds.
</ParamField>

<ParamField path="link(existing, new)" type="None">
  Hard links are not supported and this method raises `NotImplementedError`.
</ParamField>

### Subscriptions

MesaFS reads and writes are realtime by default. Use subscriptions only when your process needs an event stream that identifies which paths changed, such as to refetch data and rerender a frontend.

<ParamField path="subscribe(handler)" type="MesaFileSystemSubscription">
  Subscribe to filesystem invalidation events. The handler is called after the changed state is visible through this filesystem instance.
</ParamField>

```python theme={null}
async def on_change(event):
    if not event.recursive:
        content = await fs.read(event.path)
        print(event.path, content.decode())

subscription = fs.subscribe(on_change)
await subscription.unsubscribe()
```

<ParamField path="handler" type="Callable[[WatchEvent], None | Awaitable[None]]">
  Callback invoked for each filesystem invalidation.
</ParamField>

<ResponseField name="WatchEvent.path" type="str">
  Absolute MesaFS path that changed, such as `/workspace/src/index.py`.
</ResponseField>

<ResponseField name="WatchEvent.recursive" type="bool">
  Whether descendants of `path` may have changed. Refresh any cached directory or subtree state below `path` when this is `True`.
</ResponseField>

<ResponseField name="MesaFileSystemSubscription.unsubscribe()" type="async -> None">
  Stop receiving events and close the underlying watcher.
</ResponseField>

### FsStat

`stat(...)` and `lstat(...)` return `FsStat`.

<ResponseField name="is_file" type="bool">
  Whether the path is a regular file.
</ResponseField>

<ResponseField name="is_directory" type="bool">
  Whether the path is a directory.
</ResponseField>

<ResponseField name="is_symbolic_link" type="bool">
  Whether the path is a symlink. This is `False` from `stat(...)` when the target exists because `stat` follows symlinks.
</ResponseField>

<ResponseField name="mode" type="int">
  POSIX mode bits.
</ResponseField>

<ResponseField name="size" type="int">
  Size in bytes.
</ResponseField>

<ResponseField name="mtime_ms" type="float">
  Modification time in milliseconds since the Unix epoch.
</ResponseField>

### Related filesystem methods

| Method namespace    | Reference                                              |
| ------------------- | ------------------------------------------------------ |
| `fs.bash(...)`      | [fs.bash()](/content/reference/py/fs-bash)             |
| `fs.changes`        | [fs.changes](/content/reference/py/fs-changes)         |
| `fs.bookmarks`      | [fs.bookmarks](/content/reference/py/fs-bookmarks)     |
| `fs.subscribe(...)` | [Advanced Realtime](/content/mesafs/advanced/realtime) |

## Errors

Raises `InvalidOptionsError` for a missing or empty layout, a `ttl` outside the credential's range (private-key `1..14400`, API-key `1..86400`), missing or rejected `authors`, or invalid `mode`. A layout that breaks the [structural rules](/content/mesafs/layouts#declaration-fields) raises at the `mesa.fs(...)` call rather than at mount. Token signing or VCS connection failures can raise `ApiError` subclasses or connection errors.

## Multiprocessing

MesaFS is not fork-safe. If you use `multiprocessing`, set the start method to `spawn` or `forkserver` before creating Mesa objects.

```python theme={null}
import multiprocessing

multiprocessing.set_start_method("spawn")
```
