- 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 frommirage.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:
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:
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 alwaysPathSpec values inside the VFS; do not pass filesystem paths as raw strings.
File Structure
python/tests/ without creating __init__.py files in the test tree.
1. Config, Accessor, and Registry
Define a typed Pydantic config and keep secrets inSecretStr fields.
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)returningFileStat.
make_resolve_glob(readdir, cap) (mirage.utils.glob_walk), or use the CommandIO.resolve_glob property.
Use explicit types:
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.
write=True. Export the decorated functions as OPS from ops/<name>/__init__.py.
4. Commands
Build standard commands withCommandIO 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=TruesoMountMode.READremains a real boundary. - Declare injected parameters such as
stdin,index, andprefixexplicitly. - Use
FlagView(flags, spec=...)when the command itself must read a flag; never useflags.get(...). - Add a provision estimator when a useful estimate is possible. Otherwise the planner reports
precision=unknown.
COMMANDS from commands/builtin/<name>/__init__.py.
5. Resource Class
Import commands and ops at module scope, then register them in the constructor: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
LeaveSUPPORTS_SNAPSHOT=False unless the complete drift contract is implemented:
stat()returns a stableFileStat.fingerprint.- Every read record includes the fingerprint that produced those bytes.
- If the backend supports immutable revisions, reads consult
revision_for(path.virtual)and record the resolved revision.
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 underinteg/ and check Python/TypeScript parity before opening the PR.