Skip to main content
A resource maps an external system to Mirage’s filesystem operations and shell commands. There are two paths:
  • Ship your own backend — a single Python file in your own project or package, built on mirage.sdk. No Mirage fork, no edits to Mirage source.
  • Contribute a builtin — the four-layer layout inside the Mirage repo, mirrored in TypeScript.

Ship Your Own Backend (SDK)

Everything an out-of-tree backend needs is exported from mirage.sdk — that module is the stable public contract. Write the core functions over your data source, put them on a CommandIO, and GenericResource wires the full generic command set (ls, cat, grep, find, head, wc, …) plus glob resolution:
Mount it like any builtin: Workspace({"/jira/": make_jira_resource(cfg)}). The escape hatches mirror what builtins use: optional CommandIO fields unlock more surface (write enables the byte-mutation family; find/du_total/du_all become native fast paths; is_dir_name hints virtual directories), overrides= suppresses a generic command you replace, and commands=[...] adds bespoke @command verbs. VFS/FUSE ops are derived from the same table automatically (make_generic_ops under the hood): read/readdir/stat plus whatever mutations the table carries. Pass ops=[...] only for irregular handlers (they shadow same-named derived ops), or auto_ops=False to opt out. To make the backend constructible by name (workspace YAML, snapshots, the daemon), register it:
or ship it as a normal package with an entry point — discovered automatically at registry-build time:
The entry point resolves to the resource class; declare a CONFIG_CLS class attribute when the constructor takes a typed config. See examples/python/other/custom_resource.py for a complete runnable backend in one file.

Contribute a Builtin Resource

Builtins live inside the Mirage repo with the four-layer layout below. Keep the core I/O layer independent from command parsing, and check whether the same resource or behavior should also be added to TypeScript. Use a recent resource such as Dify or Databricks Volume as the structural reference. Paths are always PathSpec values inside the VFS; do not pass filesystem paths as raw strings.

File Structure

Add matching tests under python/tests/ without creating __init__.py files in the test tree.

1. Config, Accessor, and Registry

Define a typed Pydantic config and keep secrets in SecretStr fields.
Create an Accessor that owns the client or transport. Add the resource name to ResourceName, export the config/resource from resource/<name>/__init__.py, and add a lazy ResourceEntry to mirage/resource/registry.py. The registry entry is required for YAML, snapshots, and the daemon to construct the resource. Keep every import at module scope. If that creates a cycle, change the dependency direction instead of adding a function-local import.

2. Core VFS Operations

Implement only the operations the backend supports. Read-only API resources usually start with:
  • readdir(accessor, path, index) returning child names.
  • read_bytes(accessor, path, index) returning bytes.
  • stat(accessor, path, index) returning FileStat.
Glob resolution is not a per-backend file: bind it from readdir with make_resolve_glob(readdir, cap) (mirage.utils.glob_walk), or use the CommandIO.resolve_glob property. Use explicit types:
Add write, append, create, unlink, rename, or directory operations only when the backend has matching semantics. I/O should remain async-native.

3. Ops Layer

Ops are thin typed adapters from the workspace dispatcher to core functions. Declare dispatcher-injected arguments explicitly and keep **flags: object opaque.
Mark mutation ops with write=True. Export the decorated functions as OPS from ops/<name>/__init__.py.

4. Commands

Build standard commands with CommandIO and make_generic_commands; the generic command owns flag interpretation. Backend wrappers should only connect glob resolution and I/O functions. For a resource-specific command:
  • Use the shared command spec.
  • Mark every mutation with write=True so MountMode.READ remains a real boundary.
  • Declare injected parameters such as stdin, index, and prefix explicitly.
  • Use FlagView(flags, spec=...) when the command itself must read a flag; never use flags.get(...).
  • Add a provision estimator when a useful estimate is possible. Otherwise the planner reports precision=unknown.
Export the final list as COMMANDS from commands/builtin/<name>/__init__.py.

5. Resource Class

Import commands and ops at module scope, then register them in the constructor:
Set caches_reads=True only for stable, read-mostly content. Implement get_state() with credentials redacted and close any network clients in close().

6. Snapshot Support

Leave SUPPORTS_SNAPSHOT=False unless the complete drift contract is implemented:
  1. stat() returns a stable FileStat.fingerprint.
  2. Every read record includes the fingerprint that produced those bytes.
  3. If the backend supports immutable revisions, reads consult revision_for(path.virtual) and record the resolved revision.
Setting the flag without recording fingerprints does not provide drift detection.

7. Verification

Add tests for config validation, path layout, every VFS op, command behavior, read-only enforcement, state redaction, and cleanup. For major features, add or update integration coverage under integ/ and check Python/TypeScript parity before opening the PR.