Skip to content

Paths and the WIT contract

Every interaction with omnifs is a filesystem operation on a path. There is no client library, no query language, no pagination cursor. The path encodes what you want; the host runtime resolves it; the bytes come back. This page explains exactly how that mapping works.

Take this path as a worked example:

/github/0xff-ai/omnifs/_issues/_open/2853/title
│ │ │ │ │ │ │
│ │ │ │ │ │ └── leaf file
│ │ │ │ │ └─────── object identifier
│ │ │ │ └──────────── filter (synthetic view)
│ │ │ └──────────────────── collection (synthetic view)
│ │ └──────────────────────────── repo
│ └──────────────────────────────────── owner
└──────────────────────────────────────────── provider mount

Each segment has a fixed role:

SegmentRoleExample
Provider mountFirst segment; names the WASM provider loaded by the hostgithub
OwnerAccount or organization scoping the resource0xff-ai
RepoRepository within that owneromnifs
CollectionSynthetic directory grouping a kind of object_issues
FilterSynthetic directory narrowing the collection_open
Object identifierThe specific object within the filtered set2853
Leaf fileA single readable field of that objecttitle

Not every path has all seven segments. ls /github/0xff-ai/omnifs resolves at the repo level and returns the top-level synthetic directories. ls /github/0xff-ai/omnifs/_issues/_open goes two levels deeper and returns issue identifiers.

Segments that begin with an underscore are synthetic directories. They do not correspond to objects that exist upstream; they are views the provider constructs.

_issues, _prs, and _actions are collections: they group objects of the same kind under one path prefix. _open, _all, and _running are filters: they narrow a collection to a subset defined by the provider’s logic. runs inside _actions is a sub-collection keyed by run ID.

The underscore convention signals this distinction. A path segment without an underscore is usually a real upstream identifier: an owner, a repo name, an issue number, a container name. A segment with a leading underscore is a view that the provider synthesizes from upstream data.

/github/{owner}/{repo}/_issues/_open/2853/title
↑ ↑ ↑
collection filter leaf
(synthetic) (synthetic) (real field)
/docker/containers/_running
filter (synthetic: only running containers)
/docker/containers/by-name/{name}/state
↑ ↑
sub-tree leaf (real field)

More path examples across the five live providers:

Terminal window
# GitHub
ls /github/0xff-ai/omnifs/_issues/_open
cat /github/0xff-ai/omnifs/_issues/_open/2853/body
cat /github/0xff-ai/omnifs/_prs/_open/17/diff
cat /github/0xff-ai/omnifs/_actions/runs/14209871234/status
# Docker
ls /docker/containers/_running
cat /docker/containers/by-name/postgres/state
cat /docker/containers/by-name/postgres/inspect.json
# Linear
ls /linear/teams/ENG/issues/_open
cat /linear/teams/ENG/issues/_open/ENG-1421/state
cat /linear/teams/ENG/issues/_open/ENG-1421/priority
# arXiv
ls /arxiv/papers/1706.03762
cat /arxiv/papers/1706.03762/metadata.json
# DNS
ls /dns/example.com
cat /dns/example.com/MX
cat /dns/@8.8.8.8/example.com/A

How shell commands map to the three read ops

Section titled “How shell commands map to the three read ops”

The host runtime exposes a FUSE mount. Every shell operation on that mount resolves to exactly one of three read operations defined in the WIT interface: lookup-child, list-children, or read-file.

Shell operationRead op invokedWhat it does
ls /path/to/dirlist-childrenReturns the names of entries directly under this path
cat /path/to/fileread-fileReturns the byte content of a leaf file
Traversal through intermediate segmentslookup-childVerifies a named child exists at this path segment

When you run cat /github/0xff-ai/omnifs/_issues/_open/2853/title, the host walks the path segment by segment. Each intermediate segment (github, 0xff-ai, omnifs, _issues, _open, 2853) resolves via lookup-child. The final segment (title) resolves via read-file.

When you run ls /github/0xff-ai/omnifs/_issues/_open, the host resolves each segment up to _open via lookup-child, then calls list-children on the resulting node to enumerate the issue identifiers beneath it.

The provider never sees the full path string. The host resolves the provider from the first segment, then feeds it one operation at a time as it descends. The provider only needs to answer three questions: “does this child exist here?”, “what children are here?”, and “what are the bytes at this leaf?”

The entire provider contract fits in one WIT interface. The package is omnifs:provider@1.0.0.

package omnifs:provider@1.0.0;
interface provider {
/// Verify that a named child exists under the given node.
/// Returns the child's node handle, or none if it does not exist.
lookup-child: func(node: node-id, name: string) -> option<node-id>;
/// Enumerate the direct children of a directory node.
/// Returns a list of (name, node-id) pairs.
list-children: func(node: node-id) -> result<list<dir-entry>, provider-error>;
/// Read the byte content of a leaf file node.
read-file: func(node: node-id, offset: u64, length: u64) -> result<list<u8>, provider-error>;
}
world provider-world {
export provider;
}

Three exported functions. No ambient I/O; the host grants the provider a capability handle for its upstream service at instantiation time. A provider that does not call out to the network (for example, a provider over a local socket or a static dataset) does not need any capability at all.

A single “resolve path” RPC would force the provider to re-parse and re-validate the full path on every request. Three separate ops let the host drive traversal incrementally. The host can short-circuit on a cache hit at any intermediate node, stopping the descent without calling the provider at all. It can also cache individual nodes (not just full paths), so a second cat on a different leaf of the same issue reuses the already-resolved intermediate nodes.

The three ops also map cleanly onto POSIX filesystem semantics: lookup-child is lookup(2), list-children is readdir(2), and read-file is read(2). This alignment is what makes every existing tool work without modification. grep, find, diff, tail, rsync all reduce to the same three ops at the provider boundary.

The host does more than route ops to the provider:

  • Cache. A capacity-bounded in-memory cache holds resolved nodes and file contents. Invalidation is event-driven (upstream webhooks or polling), not TTL-based. A node does not expire on a timer; it expires when the upstream signals a change.
  • Stable inodes. Object identity is stable across upstream renames. If a GitHub repo is renamed, the inode for its subtree does not change. Tools that track files by inode (editors, watchers) remain consistent.
  • Credential isolation. Credentials live in the host’s credential store, not in the provider binary. The provider receives a scoped capability handle; it cannot read the raw token. A compromised provider WASM module cannot exfiltrate credentials it was never given.

The path grammar and the three-op interface are two views of the same model. The path is how humans and tools express what they want. The three ops are how the host and provider negotiate the answer. Everything between a cat in your shell and bytes printed to stdout passes through exactly this interface, no matter which of the five live providers serves the request.

cat /github/0xff-ai/omnifs/_issues/_open/2853/title
└── FUSE → host runtime
├── lookup-child(root, "github") → github node
├── lookup-child(github, "0xff-ai") → owner node
├── lookup-child(owner, "omnifs") → repo node
├── lookup-child(repo, "_issues") → issues node
├── lookup-child(issues, "_open") → open-filter node
├── lookup-child(open, "2853") → issue node
└── read-file(issue, "title", 0, ...) → "Auth tokens expire mid-session\n"

Each lookup-child call may return immediately from cache. Only the final read-file (and any cache-miss lookup-child calls) reach the provider WASM component, and from there, one HTTPS request upstream.