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.
The Mesa Python SDK lets you mount repositories as a virtual filesystem. Read
files, write files, run shell commands — without cloning anything to disk.
Everything is async. The SDK connects to Mesa’s VCS backend over the network,
so you always work with the latest data.
For installation and client setup, see the Python SDK overview.
Mounting a Filesystem
Use mesa.fs.mount() to mount one or more repos. It’s an async context manager
that handles setup and teardown automatically:
from mesa_sdk import Mesa
async with Mesa() as mesa:
async with mesa.fs.mount(repos=["my-repo"]) as fs:
data = await fs.read("/my-repo/README.md")
print(data.decode())
Under the hood, mount():
- Mints a scoped API key with the minimum required permissions
- Connects to the Mesa VCS backend
- Yields a
MesaFileSystem you can use for I/O
When the async with block exits, the scoped key is automatically revoked. It
also carries a 1-hour server-side TTL, so even if revocation fails, it expires
on its own.
Mount options
from mesa_sdk import RepoConfig, DiskCacheConfig
async with mesa.fs.mount(
repos=[
"simple-repo", # default bookmark
RepoConfig("pinned-repo", bookmark="staging"), # specific bookmark
RepoConfig("frozen-repo", change_id="abc123"), # specific change
],
mode="rw", # or "ro" for read-only
disk_cache=DiskCacheConfig("/tmp/mesa-cache"), # optional on-disk cache
) as fs:
...
| Parameter | Type | Default | Description |
|---|
repos | list[str | RepoConfig] | required | Repos to mount. Pass a name or a RepoConfig for pinning. |
mode | "ro" | "rw" | "rw" | Access mode. Read-only mounts can’t write. |
disk_cache | DiskCacheConfig | None | None | On-disk cache location and optional size cap. |
Paths
Every path starts with a leading slash and the repo name:
/my-repo/src/main.py
/my-repo/README.md
Reading Files
# Raw bytes
raw = await fs.read("/my-repo/image.png")
# Text — decode the bytes yourself
text = (await fs.read("/my-repo/README.md")).decode()
There is no read_text method. The SDK returns raw bytes so you control
the encoding. Decode with .decode() or .decode("utf-8").
Writing Files
# Create or overwrite a file
await fs.write("/my-repo/output.txt", b"Hello!")
# Append to a file (creates it if missing)
await fs.append("/my-repo/log.txt", b"new line\n")
Parent directories must exist. Create them first if needed:
await fs.mkdir("/my-repo/src/utils", recursive=True)
await fs.write("/my-repo/src/utils/helpers.py", b"def greet(): ...")
Checking Existence
if await fs.exists("/my-repo/config.yaml"):
config = await fs.read("/my-repo/config.yaml")
Listing Directories
entries = await fs.readdir("/my-repo/src")
# ["main.py", "utils.py", "tests"]
Order is not guaranteed. Sort the result if you need deterministic output.
stat returns an FsStat object with metadata about a file or directory:
info = await fs.stat("/my-repo/src/main.py")
print(info.size) # bytes
print(info.is_file) # True
print(info.is_directory) # False
print(info.mode) # e.g. 0o100644
print(info.mtime_ms) # milliseconds since epoch
Use lstat to inspect a symlink itself (without following it):
link_info = await fs.lstat("/my-repo/latest")
print(link_info.is_symbolic_link) # True
| Field | Type | Description |
|---|
is_file | bool | Regular file |
is_directory | bool | Directory |
is_symbolic_link | bool | Symlink (always False from stat) |
mode | int | Permission bits |
size | int | Size in bytes |
mtime_ms | float | Modification time (milliseconds since epoch) |
File Operations
# Copy a file
await fs.cp("/my-repo/a.txt", "/my-repo/b.txt")
# Copy a directory
await fs.cp("/my-repo/src", "/my-repo/src-backup", recursive=True)
# Move / rename
await fs.mv("/my-repo/old.txt", "/my-repo/new.txt")
# Delete a file
await fs.rm("/my-repo/temp.txt")
# Delete a directory (recursive required)
await fs.rm("/my-repo/build", recursive=True)
# Delete if exists (no error when missing)
await fs.rm("/my-repo/maybe.txt", force=True)
# Create directories
await fs.mkdir("/my-repo/a/b/c", recursive=True)
Symlinks
# Create a symlink
await fs.symlink("../shared/config.yaml", "/my-repo/config.yaml")
# Read the target
target = await fs.readlink("/my-repo/config.yaml")
# Resolve through all symlinks to an absolute path
real = await fs.realpath("/my-repo/config.yaml")
Hard links are not supported. Calling fs.link() raises NotImplementedError.
Permissions and Timestamps
# Set permission bits
await fs.chmod("/my-repo/script.sh", 0o755)
# Set access and modification time
import time
now_ms = time.time() * 1000
await fs.utimes("/my-repo/file.txt", atime_ms=now_ms, mtime_ms=now_ms)
utimes takes milliseconds since the Unix epoch, not seconds.
Multiply time.time() by 1000.
Running Shell Commands
fs.bash() creates a Bash interpreter that runs commands against the mounted
filesystem. No host shell is spawned — it’s fully isolated.
bash = fs.bash(cwd="/my-repo", env={"CI": "true"})
result = await bash.exec("grep -r TODO src/ | wc -l")
print(result.stdout.decode().strip())
Bash options
| Parameter | Type | Default | Description |
|---|
env | dict[str, str] | None | empty | Environment variables. Host env is not inherited. |
cwd | str | None | "/" | Working directory inside the mount. |
timeout_ms | int | None | 30000 | Per-exec wall-clock timeout in milliseconds. |
ExecResult
bash.exec() returns an ExecResult:
| Field | Type | Description |
|---|
stdout | bytes | Standard output |
stderr | bytes | Standard error |
exit_code | int | Exit code (0 = success) |
Pipelines
Shell pipes, redirects, and multi-line scripts all work:
result = await bash.exec("""
find /my-repo -name '*.py' | \
xargs grep -l 'TODO' | \
wc -l
""")
todo_count = int(result.stdout.strip())
Binary files
If you cat a binary (non-UTF-8) file, the command fails with a non-zero exit
code and stderr tells you to use read instead. This is intentional —
use fs.read() for binary data.
Changes
Changes are Mesa’s equivalent of commits. The filesystem exposes change
operations through fs.changes:
# Create a new change forked from a bookmark
change_id = await fs.changes.new("my-repo", bookmark="main")
# Make edits on this change
await fs.write("/my-repo/feature.py", b"def new_feature(): ...")
# Fork from an existing change
child_id = await fs.changes.new("my-repo", change_id=change_id)
# Check out an existing change (doesn't create a new one)
await fs.changes.edit("my-repo", change_id=change_id)
# List recent changes
changes = await fs.changes.list("my-repo", limit=10)
for ch in changes:
print(f"change={ch.change_id} commit={ch.commit_oid}")
| Method | Returns | Description |
|---|
new(repo, bookmark=, change_id=) | str | Create a new change, returns its hex ID |
edit(repo, bookmark=, change_id=) | str | Check out an existing change |
list(repo, limit=50) | list[ChangeInfo] | List recent changes |
Bookmarks
Bookmarks are named references — like git branches. The filesystem exposes
read and create operations:
# List all bookmarks
bookmarks = await fs.bookmarks.list("my-repo")
# ["main", "staging", "feature-x"]
# Create a bookmark at the current change's commit
await fs.bookmarks.create("my-repo", "my-feature")
For merging and moving bookmarks, use the REST API:
mesa.bookmarks.merge(...) and mesa.bookmarks.move(...).
Disk Caching
By default the mount uses an in-memory cache only. For long-running processes
(CI pipelines, agents, notebooks), enable on-disk caching:
from mesa_sdk import DiskCacheConfig
async with mesa.fs.mount(
repos=["my-repo"],
disk_cache=DiskCacheConfig("/tmp/mesa-cache", max_size_bytes=500_000_000),
) as fs:
# First read: fetched from network, cached to disk
# Subsequent reads: served from cache
data = await fs.read("/my-repo/large-dataset.csv")
Error Handling
Filesystem operations raise Python’s built-in exceptions — not MesaError:
try:
await fs.read("/my-repo/missing.txt")
except FileNotFoundError:
print("File doesn't exist")
try:
await fs.read("/my-repo/src")
except IsADirectoryError:
print("That's a directory, use readdir()")
try:
await fs.write("/my-repo/file.txt", b"data")
except PermissionError:
print("Mount is read-only")
| Exception | When |
|---|
FileNotFoundError | Path does not exist |
FileExistsError | Already exists (e.g. duplicate bookmark) |
IsADirectoryError | Expected a file, got a directory |
NotADirectoryError | Expected a directory, got a file |
PermissionError | Write on a read-only mount |
OSError | General I/O failure |
NotImplementedError | link() — hard links not supported |
Multiprocessing
The filesystem is not fork-safe. If you use multiprocessing, set the
start method before creating any Mesa objects:
import multiprocessing
multiprocessing.set_start_method("spawn") # or "forkserver"
Complete Example
A script that creates a change, writes files, runs tests, and prints the result:
import asyncio
from mesa_sdk import Mesa
async def main():
async with Mesa() as mesa:
async with mesa.fs.mount(repos=["my-app"]) as fs:
# Create a change to work on
change_id = await fs.changes.new("my-app", bookmark="main")
# Write a new module
await fs.mkdir("/my-app/src", recursive=True)
await fs.write("/my-app/src/greet.py", b"""\
def greet(name: str) -> str:
return f"Hello, {name}!"
""")
# Run tests
bash = fs.bash(cwd="/my-app", env={"PYTHONPATH": "/my-app"})
result = await bash.exec("python -c 'from src.greet import greet; print(greet(\"World\"))'")
print(result.stdout.decode())
if result.exit_code == 0:
print(f"Done. Change ID: {change_id}")
asyncio.run(main())
API Reference
MesaFileSystem
| Method | Signature | Returns |
|---|
read | (path) | bytes |
write | (path, content) | None |
append | (path, content) | None |
exists | (path) | bool |
stat | (path) | FsStat |
lstat | (path) | FsStat |
readdir | (path) | list[str] |
realpath | (path) | str |
readlink | (path) | str |
resolve_path | (base, path) | str |
mkdir | (path, *, recursive=) | None |
rm | (path, *, recursive=, force=) | None |
cp | (src, dest, *, recursive=) | None |
mv | (src, dest) | None |
chmod | (path, mode) | None |
symlink | (target, link) | None |
utimes | (path, atime_ms, mtime_ms) | None |
bash | (*, env=, cwd=, timeout_ms=) | Bash |
All methods except resolve_path and bash are async.
Bash
| Method | Signature | Returns |
|---|
exec | (commands) | ExecResult |
exec is async.
Config Types
| Type | Constructor |
|---|
RepoConfig | (name, *, bookmark=None, change_id=None) |
DiskCacheConfig | (path, *, max_size_bytes=None) |