Skip to main content
A resource maps an external system to Mirage’s filesystem operations and shell commands. 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.
  • resolve_glob(accessor, paths, index) returning resolved PathSpec values.
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.